From 73b2dc8b3bc23384206270fbe49bbc0227880594 Mon Sep 17 00:00:00 2001 From: Dany LE Date: Tue, 10 Jan 2023 00:34:20 +0100 Subject: [PATCH] Add binding to some basic mail API --- Notes.md | 104 +++++++++++++++++++++++++++++++++++++ limap.c | 152 ++++++++++++++++++++++++++++++++++++++++++++----------- test.lua | 14 ++--- 3 files changed, 233 insertions(+), 37 deletions(-) create mode 100644 Notes.md diff --git a/Notes.md b/Notes.md new file mode 100644 index 0000000..b95d696 --- /dev/null +++ b/Notes.md @@ -0,0 +1,104 @@ +# How to identify partno string + +imap-fetchbody() will decode attached email messages inline with the rest of the email parts, however the way it works when handling attached email messages is inconsistent with the main email message. + +With an email message that only has a text body and does not have any mime attachments, imap-fetchbody() will return the following for each requested part number: + +(empty) - Entire message +0 - Message header +1 - Body text + +With an email message that is a multi-part message in MIME format, and contains the message text in plain text and HTML, and has a file.ext attachment, imap-fetchbody() will return something like the following for each requested part number: + +(empty) - Entire message +0 - Message header +1 - MULTIPART/ALTERNATIVE +1.1 - TEXT/PLAIN +1.2 - TEXT/HTML +2 - file.ext + +Now if you attach the above email to an email with the message text in plain text and HTML, imap_fetchbody() will use this type of part number system: + +(empty) - Entire message +0 - Message header +1 - MULTIPART/ALTERNATIVE +1.1 - TEXT/PLAIN +1.2 - TEXT/HTML +2 - MESSAGE/RFC822 (entire attached message) +2.0 - Attached message header +2.1 - TEXT/PLAIN +2.2 - TEXT/HTML +2.3 - file.ext + +Note that the file.ext is on the same level now as the plain text and HTML, and that there is no way to access the MULTIPART/ALTERNATIVE in the attached message. + +Here is a modified version of some of the code from previous posts that will build an easily accessible array that includes accessible attached message parts and the message body if there aren't multipart mimes. The $structure variable is the output of the imap_fetchstructure() function. The returned $part_array has the field 'part_number' which contains the part number to be fed directly into the imap_fetchbody() function. + +parts) > 0) { // There some sub parts + foreach ($structure->parts as $count => $part) { + add_part_to_array($part, $prefix.($count+1), $part_array); + } + }else{ // Email does not have a seperate mime attachment for text + $part_array[] = array('part_number' => $prefix.'1', 'part_object' => $obj); + } + return $part_array; +} +// Sub function for create_part_array(). Only called by create_part_array() and itself. +function add_part_to_array($obj, $partno, & $part_array) { + $part_array[] = array('part_number' => $partno, 'part_object' => $obj); + if ($obj->type == 2) { // Check to see if the part is an attached email message, as in the RFC-822 type + //print_r($obj); + if (sizeof($obj->parts) > 0) { // Check to see if the email has parts + foreach ($obj->parts as $count => $part) { + // Iterate here again to compensate for the broken way that imap_fetchbody() handles attachments + if (sizeof($part->parts) > 0) { + foreach ($part->parts as $count2 => $part2) { + add_part_to_array($part2, $partno.".".($count2+1), $part_array); + } + }else{ // Attached email does not have a seperate mime attachment for text + $part_array[] = array('part_number' => $partno.'.'.($count+1), 'part_object' => $obj); + } + } + }else{ // Not sure if this is possible + $part_array[] = array('part_number' => $prefix.'.1', 'part_object' => $obj); + } + }else{ // If there are more sub-parts, expand them out. + if (sizeof($obj->parts) > 0) { + foreach ($obj->parts as $count => $p) { + add_part_to_array($p, $partno.".".($count+1), $part_array); + } + } + } +} +?> + +# body type + +```c +#define TYPETEXT 0 /* unformatted text */ +#define TYPEMULTIPART 1 /* multiple part */ +#define TYPEMESSAGE 2 /* encapsulated message */ +#define TYPEAPPLICATION 3 /* application data */ +#define TYPEAUDIO 4 /* audio */ +#define TYPEIMAGE 5 /* static image */ +#define TYPEVIDEO 6 /* video */ +#define TYPEMODEL 7 /* model */ +#define TYPEOTHER 8 /* unknown */ +#define TYPEMAX 15 /* maximum type code */ +``` + +# stream flags + +```lua +local OP_DEBUG = 0x1 +local OP_READONLY = 0x2 +local OP_ANONYMOUS = 0x4 --/* anonymous open of newsgroup */ +local OP_SHORTCACHE = 0x8 --/* short (elt-only) caching */ +local OP_SILENT = 0x10 --/* don't pass up events (internal use) */ +local OP_PROTOTYPE = 0x20 --/* return driver prototype */ +local OP_HALFOPEN = 0x40 --/* half-open (IMAP connect but no select) */ +local OP_EXPUNGE= 0x80 --/* silently expunge recycle stream */ +``` \ No newline at end of file diff --git a/limap.c b/limap.c index 694d172..5378633 100644 --- a/limap.c +++ b/limap.c @@ -308,9 +308,15 @@ static int l_mail_open(lua_State *L) { const char* mailbox = luaL_checkstring(L,1); long flag = 0; - if( lua_isnumber(L,2)) + if( lua_toboolean(L,2)) { - flag = flag | (long)luaL_checknumber(L,2); + /*half open*/ + flag = flag | OP_HALFOPEN; + } + if( lua_toboolean(L,3)) + { + /*debug*/ + flag = flag | OP_DEBUG; } MAILSTREAM *stream = mail_open (NIL,(char*)mailbox,flag); if(!stream) @@ -548,9 +554,15 @@ static int l_reopen(lua_State *L) MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); const char* mailbox = luaL_checkstring(L,2); long flag = 0; - if( lua_isnumber(L,3)) + if( lua_toboolean(L,3)) { - flag = flag | (long)luaL_checknumber(L,3); + /*half open*/ + flag = flag | OP_HALFOPEN; + } + if( lua_toboolean(L,4)) + { + /*debug*/ + flag = flag | OP_DEBUG; } if(!stream) { @@ -604,16 +616,16 @@ static int l_mail_get_header(lua_State* L) /* now run through properties that are only going to be returned from a server, not text headers */ - add_property_string(L, "Recent", cache->recent ? (cache->seen ? "R": "N") : " "); - add_property_string(L, "Unseen", (cache->recent | cache->seen) ? " " : "U"); - add_property_string(L, "Flagged", cache->flagged ? "F" : " "); - add_property_string(L, "Answered", cache->answered ? "A" : " "); - add_property_string(L, "Deleted", cache->deleted ? "D" : " "); - add_property_string(L, "Draft", cache->draft ? "X" : " "); - add_property_long(L, "Msgno", cache->msgno); + add_property_string(L, "recent", cache->recent ? (cache->seen ? "R": "N") : " "); + add_property_string(L, "unseen", (cache->recent | cache->seen) ? " " : "U"); + add_property_string(L, "flagged", cache->flagged ? "F" : " "); + add_property_string(L, "answered", cache->answered ? "A" : " "); + add_property_string(L, "deleted", cache->deleted ? "D" : " "); + add_property_string(L, "draft", cache->draft ? "X" : " "); + add_property_long(L, "msgno", cache->msgno); mail_date(tmp, cache); - add_property_string(L, "MailDate", tmp); - add_property_long(L, "Size", cache->rfc822_size); + add_property_string(L, "mail_date", tmp); + add_property_long(L, "size", cache->rfc822_size); add_property_long(L, "udate", mail_longdate(cache)); if (en->from) { @@ -710,10 +722,63 @@ static void _imap_add_body(lua_State* L, BODY *body) if (body->type <= TYPEMAX) { add_property_long(L, "type", body->type); + + switch(body->type) + { + case TYPETEXT: /* unformatted text */ + add_property_string(L, "type_text", "TEXT"); + break; + case TYPEMULTIPART: /* multiple part */ + add_property_string(L, "type_text", "MULTIPART"); + break; + case TYPEMESSAGE: /* encapsulated message */ + add_property_string(L, "type_text", "MESSAGE"); + break; + case TYPEAPPLICATION: /* application data */ + add_property_string(L, "type_text", "APPLICATION"); + break; + case TYPEAUDIO: /* audio */ + add_property_string(L, "type_text", "AUDIO"); + break; + case TYPEIMAGE: /* static image */ + add_property_string(L, "type_text", "IMAGE"); + break; + case TYPEVIDEO: /* video */ + add_property_string(L, "type_text", "VIDEO"); + break; + case TYPEMODEL: /* model */ + add_property_string(L, "type_text", "MODEL"); + break; + default: + add_property_string(L, "type_text", "OTHER"); + break; + } } if (body->encoding <= ENCMAX) { add_property_long(L, "encoding", body->encoding); + + switch(body->encoding) + { + case ENC7BIT: /* 7 bit SMTP semantic data */ + add_property_string(L, "encoding_text", "7BIT"); + break; + case ENC8BIT: /* 8 bit SMTP semantic data */ + add_property_string(L, "encoding_text", "8BIT"); + break; + case ENCBINARY: /* 8 bit binary data */ + add_property_string(L, "encoding_text", "BINARY"); + break; + case ENCBASE64: /* base-64 encoded data */ + add_property_string(L, "encoding_text", "BASE64"); + break; + case ENCQUOTEDPRINTABLE: /* human-readable 8-as-7 bit data */ + add_property_string(L, "encoding_text", "QUOTEDPRINTABLE"); + break; + default: + add_property_string(L, "encoding_text", "OTHER"); + break; + } } if (body->subtype) { @@ -751,14 +816,16 @@ static void _imap_add_body(lua_State* L, BODY *body) dpar = body->disposition.parameter; lua_pushstring(L, "dparameters"); lua_newtable(L); - count = 1; + //count = 1; do { - lua_pushnumber(L, count); - lua_newtable(L); - add_property_string(L, "attribute", dpar->attribute); - add_property_string(L, "value", dpar->value); - lua_settable(L,-3); - count++; + //lua_pushnumber(L, count); + //lua_newtable(L); + if(dpar->attribute && dpar->value) + { + add_property_string(L, dpar->attribute, dpar->value); + } + //lua_settable(L,-3); + //count++; } while ((dpar = dpar->next)); lua_settable(L, -3); } @@ -766,19 +833,16 @@ static void _imap_add_body(lua_State* L, BODY *body) if ((par = body->parameter)) { lua_pushstring(L, "parameters"); lua_newtable(L); - count = 1; + //count = 1; do { - lua_pushnumber(L, count); - lua_newtable(L); - if (par->attribute) { - add_property_string(L, "attribute", par->attribute); - } - if (par->value) { - add_property_string(L, "value", par->value); + //lua_pushnumber(L, count); + //lua_newtable(L); + if (par->attribute && par->value) { + add_property_string(L, par->attribute, par->value); } - lua_settable(L,-3); - count++; + //lua_settable(L,-3); + //count++; } while ((par = par->next)); lua_settable(L, -3); } @@ -833,6 +897,33 @@ static int l_get_structure(lua_State* L) return 1; } +static int l_get_body_part(lua_State* L) +{ + MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); + if(!stream) + { + lua_pushnil(L); + return 1; + } + const int msgno = luaL_checknumber(L,2); + const char* part_name = luaL_checkstring(L,3); + unsigned int flags = 0; + if(lua_toboolean(L,3)) + { + flags |= FT_PEEK; + } + char *body; + unsigned long len; + body = mail_fetchbody_full(stream, msgno, (char*)part_name, &len, flags); + + if (!body) { + lua_pushnil(L); + return 1; + } + lua_pushstring(L, body); + return 1; +} + static const struct luaL_Reg _lib [] = { {"open", l_mail_open}, {"close", l_mail_close}, @@ -847,6 +938,7 @@ static const struct luaL_Reg _lib [] = { {"rfc822_parse_header", l_rfc822_parse_header}, {"get_raw_body", l_get_raw_body}, {"get_structure", l_get_structure}, + {"get_body_part", l_get_body_part}, {NULL,NULL} }; diff --git a/test.lua b/test.lua index 1e9d6b7..ad9d12b 100644 --- a/test.lua +++ b/test.lua @@ -9,9 +9,9 @@ local OP_EXPUNGE= 0x80 --/* silently expunge recycle stream */ local imap = require("limap") -local passwd = os.getenv("IMAP_PWD") or "" +local passwd = os.getenv("IMAP_PWD") or "!x$@n9ph" local url = string.format("{iohub.dev:143/imap/tls-sslv23/novalidate-cert/user=mrsang;%s}",passwd); -local handle, box = imap.open(url,OP_HALFOPEN|OP_DEBUG) +local handle, box = imap.open(url,true, true) -- half open and debug on local r,v = false,nil; @@ -37,7 +37,7 @@ if(handle) then print(k1,v1) end end - r, box = imap.reopen(handle,box:gsub("[^}]*$","INBOX"), OP_DEBUG) + r, box = imap.reopen(handle,box:gsub("[^}]*$","INBOX"), false, true) -- half open off, debug on if r then print("new box opened", box) else @@ -59,13 +59,13 @@ if(handle) then dump(headers) end print("===============================") - local header = imap.get_header(handle, nmail-3) + local header = imap.get_header(handle, nmail) dump(header) print("===============================") - local raw_body = imap.get_raw_body(handle, nmail-3, true) -- peak the message without set it to seen - print(raw_body) + --local raw_body = imap.get_raw_body(handle, nmail, true) -- peak the message without set it to seen + --print(raw_body) print("===============================") - local structure = imap.get_structure(handle, nmail-3) + local structure = imap.get_structure(handle, nmail) dump(structure) imap.close(handle)