[PATCH] Split off MIME handling primitives and make them public
Guido Berhoerster <[email protected]> Sat, 25 Jul 2015 15:47:43 +0200
| Newsgroups | gmane.network.slrn.user |
|---|---|
| Message-ID | <20150725134743.GD21857@hal> |
This splits off MIME handling primitives into mimelib.sl and makes them
public so that alternative MIME handling macros can make use of them.
---
macros/mime.sl | 303 +++-------------------------------------------------
macros/mimelib.sl | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 325 insertions(+), 289 deletions(-)
create mode 100644 macros/mimelib.sl
diff --git a/macros/mime.sl b/macros/mime.sl
index 3d58547..2e01fff 100644
--- a/macros/mime.sl
+++ b/macros/mime.sl
@@ -36,212 +36,9 @@
autoload ("mailcap_lookup_entry", "mailcap");
-private variable Mime_Save_Dir = make_home_filename ("");
-private variable Mime_Save_Charset = get_charset ("display");
-
-define mime_set_save_charset (charset)
-{
- Mime_Save_Charset = charset;
-}
-
-private define set_header_key (hash, name, value)
-{
- hash[strlow(name)] = struct
- {
- name = name,
- value = strtrim (value),
- };
-}
-
-private define get_header_key (hash, name, lowercase)
-{
- try
- {
- variable h = hash[strlow (name)];
- return h.value;
- }
- catch AnyError;
- return "";
-}
-
-private define merge_headers (a, b)
-{
- variable c = Assoc_Type[Struct_Type];
- variable value;
-
- foreach value (a) using ("values")
- set_header_key (c, value.name, value.value);
- foreach value (b) using ("values")
- set_header_key (c, value.name, value.value);
- return c;
-}
-
-private define split_article (art)
-{
- variable ofs = is_substrbytes (art, "\n\n");
- if (ofs == 0)
- throw DataError, "Unable to find the header separator";
-
- variable header = substrbytes (art, 1, ofs-1);
- (header,) = strreplace (header, "\n ", " ", strbytelen(header));
- (header,) = strreplace (header, "\n\t", " ", strbytelen(header));
- header = strchop (header, '\n', 0);
-
- variable hash = Assoc_Type[Struct_Type];
- _for (0, length (header)-1, 1)
- {
- variable i = ();
- variable fields = strchop (header[i], ':', 0);
- set_header_key (hash, fields[0], strjoin (fields[[1:]], ":"));
- }
- variable body = substrbytes (art, ofs+2, -1);
- return hash, body;
-}
-
-
-private define parse_subkeyword (key, word)
-{
- variable val = string_matches (key, `\C` + word + ` *= *"\([^"]+\)"`);
- if (val == NULL)
- val = string_matches (key, `\C` + word + ` *= *\([^; ]+\)`);
- if (val == NULL)
- return val;
-
- return val[1];
-}
-
-private define get_multipart_boundary (header)
-{
- variable ct = get_header_key (header, "Content-Type", 0);
- if (ct == "")
- return NULL;
-
- ifnot (is_substr (strlow (ct), "multipart/"))
- return NULL;
-
- variable boundary = parse_subkeyword (ct, "boundary");
- if (boundary == NULL)
- return NULL;
-
- return boundary;
-}
-
-% The idea here is to represent an article as a list of mime objects
-% in the form of a tree. For a non-multipart article, there is only
-% one node. For a multipart message, there will be a linked list of
-% nodes, one for each subpart. If the subpart is a multipart, a new
-% subtree will begin. For example, here is an article with a
-% two-multiparts, with the second contained in the first.
-%
-% article
-% / \
-% /\
-%
-private variable Mime_Node_Type = struct
-{
- mimetype, % lowercase type/subtype, from content-type
- disposition, % content-disposition header
- content_type, % full content-type header
- header, % assoc array of header keywords
- list, % non-null list of nodes if multipart
- message, charset, encoding % non-multipart decoded message
-};
-
-private define parse_mime ();
-private define parse_multipart (node, body)
-{
- variable boundary = get_multipart_boundary (node.header);
- if (boundary == NULL)
- return;
-
- boundary = "--" + boundary;
- variable
- blen = strbytelen (boundary),
- boundary_end = boundary + "--", blen_end = blen + 2;
-
- node.list = {};
-
- body = strchop (body, '\n', 0);
- variable i = 0, imax = length(body);
- while (i < imax)
- {
- if (strnbytecmp (body[i], boundary, blen))
- {
- i++;
- continue;
- }
-
- if (0 == strnbytecmp (body[i], boundary_end, blen_end))
- break;
-
- i++;
- variable i0 = i;
- if (i0 == imax)
- break;
-
- while (i < imax)
- {
- if (strnbytecmp (body[i], boundary, blen))
- {
- i++;
- continue;
- }
- break;
- }
- variable new_node = parse_mime (strjoin (body[[i0:i-1]], "\n"));
- if (new_node != NULL)
- list_append (node.list, new_node);
- }
-}
-
-private define extract_mimetype (content_type)
-{
- return strlow (strtrim (strchop (content_type, ';', 0)[0]));
-}
-
-private define parse_mime (art)
-{
- variable header, body;
- (header, body) = split_article (art);
-
- variable node = @Mime_Node_Type;
- node.content_type = get_header_key (header, "Content-Type", 1);
- node.disposition = get_header_key (header, "Content-Disposition", 0);
- node.header = header;
- node.mimetype = extract_mimetype (node.content_type);
-
- if (is_substr (node.mimetype, "multipart/"))
- {
- parse_multipart (node, body);
- return node;
- }
-
- node.message = body;
-
- variable encoding = get_header_key (header, "Content-Transfer-Encoding", 1);
- encoding = strlow (encoding);
- if (is_substr (encoding, "base64"))
- node.encoding = "base64";
- else if (is_substr (encoding, "quoted-printable"))
- node.encoding = "quoted-printable";
-
- node.charset = parse_subkeyword (node.content_type, "charset");
+require ("mimelib")
- return node;
-}
-
-private define flatten_node_tree (node, leaves); % recursive
-private define flatten_node_tree (node, leaves)
-{
- if (node.list == NULL)
- {
- list_append (leaves, node);
- return;
- }
-
- foreach node (node.list)
- flatten_node_tree (node, leaves);
-}
+private variable Mime_Save_Dir = make_home_filename ("");
% Search for the first node whose type matches one in the types list.
private define find_first_matching_leaf (leaves, types)
@@ -263,19 +60,6 @@ private variable Mime_Object_List = NULL;
private variable Mime_MessageID = NULL;
private variable Mime_Article_Headers = NULL;
-% Returns NULL if the message is not Mime Encoded, otherwise it
-% returns the value of the Content-Type header.
-private define is_mime_message ()
-{
- variable h = extract_article_header ("Mime-Version");
- if ((h == NULL) || (h == ""))
- return NULL;
-
- h = extract_article_header ("Content-Type");
- if (h == "") h = NULL;
- return h;
-}
-
% This function returns the top-level headers in the message
private define process_mime_message ()
{
@@ -287,10 +71,10 @@ private define process_mime_message ()
Mime_Object_List = NULL;
variable art = raw_article_as_string ();
- variable nodes = parse_mime (art);
+ variable nodes = mime_parse_mime (art);
variable leaf, leaves = {};
- flatten_node_tree (nodes, leaves);
+ mime_flatten_node_tree (nodes, leaves);
Mime_MessageID = msgid;
Mime_Object_List = leaves;
@@ -300,7 +84,7 @@ private define process_mime_message ()
private define replace_article_with_mime_obj (obj)
{
% Replace some of the headers in the raw article by subpart headers
- variable header = merge_headers (Mime_Article_Headers, obj.header);
+ variable header = mime_merge_headers (Mime_Article_Headers, obj.header);
variable value, art = "";
foreach value (header) using ("values")
@@ -311,28 +95,6 @@ private define replace_article_with_mime_obj (obj)
replace_cooked_article (art, 1);
}
-private define is_attachment (node)
-{
- return is_substrbytes (strlow (node.disposition), "attachment");
-}
-
-private define is_text (node)
-{
- return is_substrbytes (node.mimetype, "text/");
-}
-
-private define get_mime_filename (node)
-{
- variable file = parse_subkeyword (node.disposition, "filename");
- if (file != NULL)
- return file;
- file = parse_subkeyword (node.content_type, "name");
- if (file != NULL)
- return file;
-
- return "";
-}
-
private define format_type (type, width)
{
if (0 == strnbytecmp (type, "application", 11))
@@ -345,43 +107,6 @@ private define format_type (type, width)
return type;
}
-private define convert_mime_object (obj)
-{
- variable str = obj.message;
- if (str == "")
- return str;
-
- if (obj.encoding == "base64")
- str = decode_base64_string (str);
- else if (obj.encoding == "quoted-printable")
- str = decode_qp_string (str);
-
- variable charset = obj.charset;
- if ((charset != NULL) && (charset != "")
- && (Mime_Save_Charset != NULL)
- && (strlow(charset) != strlow(Mime_Save_Charset)))
- {
- str = charset_convert_string (str, charset, Mime_Save_Charset, 0);
- }
- return str;
-}
-
-private define save_mime_object (obj, fp)
-{
- if (typeof (fp) == String_Type)
- {
- variable file = fp;
- fp = fopen (file, "w");
- if (fp == NULL)
- throw OpenError, "Could not open $file for writing"$;
- }
-
- variable str = convert_mime_object (obj);
-
- () = fwrite (str, fp);
- () = fflush (fp);
-}
-
private define make_safe_filename (file)
{
return strtrans (file, "-+_/.%@{}:A-Za-z0-9", "_");
@@ -402,7 +127,7 @@ private define view_mime_object (obj)
if (mc == NULL)
throw NotImplementedError, "No viewer for $type available"$;
- variable str = convert_mime_object (obj);
+ variable str = mime_convert_mime_object (obj);
variable e;
try (e)
@@ -426,7 +151,7 @@ private define view_mime_object (obj)
define mime_browse ()
{
- if (NULL == is_mime_message ())
+ if (NULL == mime_is_mime_message ())
return;
process_mime_message ();
@@ -436,12 +161,12 @@ define mime_browse ()
list_append (descriptions, "View full message with all parts");
foreach node (Mime_Object_List)
{
- variable filename = get_mime_filename (node);
+ variable filename = mime_get_mime_filename (node);
filename = path_basename (rfc1522_decode_string (filename));
list_append (filenames, filename);
variable attachment = "";
variable charset = node.charset; if (charset == NULL) charset = "";
- if (is_attachment (node)) attachment = "[attachment]";
+ if (mime_is_attachment (node)) attachment = "[attachment]";
list_append (descriptions,
sprintf ("%-16s |%12s| %s%S",
format_type (node.mimetype, 16),
@@ -485,7 +210,7 @@ define mime_browse ()
if (n == -1)
return;
}
- save_mime_object (node, filename);
+ mime_save_mime_object (node, filename);
Mime_Save_Dir = path_dirname (filename);
break;
}
@@ -510,12 +235,12 @@ define mime_browse ()
define mime_process_multipart ()
{
- variable h = is_mime_message ();
+ variable h = mime_is_mime_message ();
if (h == NULL)
return;
- variable mimetype = extract_mimetype (h);
+ variable mimetype = mime_extract_mimetype (h);
if (0 == is_substrbytes (mimetype, "multipart/"))
{
@@ -537,9 +262,9 @@ define mime_process_multipart ()
num_text = 0, num_html = 0, num_plain = 0;
foreach node (Mime_Object_List)
{
- if (is_attachment (node))
+ if (mime_is_attachment (node))
num_attchments++;
- else if (is_text (node))
+ else if (mime_is_text (node))
{
num_text++;
num_html += (0 != is_substrbytes (node.mimetype, "/html"));
diff --git a/macros/mimelib.sl b/macros/mimelib.sl
new file mode 100644
index 0000000..5245473
--- /dev/null
+++ b/macros/mimelib.sl
@@ -0,0 +1,311 @@
+% The routines in this file parse a multipart MIME message.
+% Copyright (C) 2012 John E. Davis <[email protected]>
+%
+% This may be distributed under the terms of the GNU General Public
+% License. See the file COPYING for more information.
+%
+% Public functions in this file:
+%
+% mime_set_save_charset (charset)
+% mime_get_save_charset ()
+% mime_set_header_key (hash, name, value)
+% mime_get_header_key (hash, name, lowercase)
+% mime_merge_headers (a, b)
+% mime_split_article (art)
+% mime_parse_subkeyword (key, word)
+% mime_extract_mimetype (content_type)
+% mime_parse_mime (art)
+% mime_flatten_node_tree (node, leaves)
+% mime_is_mime_message ()
+% mime_is_attachment (node)
+% mime_is_text (node)
+% mime_get_mime_filename (node)
+% mime_convert_mime_object (obj)
+% mime_save_mime_object (obj, fp)
+%
+% Thes functions constitute the building blocks for MIME message handling such
+% as the implementation in mime.sl.
+
+private variable Mime_Save_Charset = get_charset ("display");
+
+define mime_set_save_charset (charset)
+{
+ Mime_Save_Charset = charset;
+}
+
+define mime_get_save_charset ()
+{
+ return Mime_Save_Charset;
+}
+
+define mime_set_header_key (hash, name, value)
+{
+ hash[strlow(name)] = struct
+ {
+ name = name,
+ value = strtrim (value),
+ };
+}
+
+define mime_get_header_key (hash, name, lowercase)
+{
+ try
+ {
+ variable h = hash[strlow (name)];
+ return h.value;
+ }
+ catch AnyError;
+ return "";
+}
+
+define mime_merge_headers (a, b)
+{
+ variable c = Assoc_Type[Struct_Type];
+ variable value;
+
+ foreach value (a) using ("values")
+ mime_set_header_key (c, value.name, value.value);
+ foreach value (b) using ("values")
+ mime_set_header_key (c, value.name, value.value);
+ return c;
+}
+
+define mime_split_article (art)
+{
+ variable ofs = is_substrbytes (art, "\n\n");
+ if (ofs == 0)
+ throw DataError, "Unable to find the header separator";
+
+ variable header = substrbytes (art, 1, ofs-1);
+ (header,) = strreplace (header, "\n ", " ", strbytelen(header));
+ (header,) = strreplace (header, "\n\t", " ", strbytelen(header));
+ header = strchop (header, '\n', 0);
+
+ variable hash = Assoc_Type[Struct_Type];
+ _for (0, length (header)-1, 1)
+ {
+ variable i = ();
+ variable fields = strchop (header[i], ':', 0);
+ mime_set_header_key (hash, fields[0], strjoin (fields[[1:]], ":"));
+ }
+ variable body = substrbytes (art, ofs+2, -1);
+ return hash, body;
+}
+
+
+define mime_parse_subkeyword (key, word)
+{
+ variable val = string_matches (key, `\C` + word + ` *= *"\([^"]+\)"`);
+ if (val == NULL)
+ val = string_matches (key, `\C` + word + ` *= *\([^; ]+\)`);
+ if (val == NULL)
+ return val;
+
+ return val[1];
+}
+
+private define get_multipart_boundary (header)
+{
+ variable ct = mime_get_header_key (header, "Content-Type", 0);
+ if (ct == "")
+ return NULL;
+
+ ifnot (is_substr (strlow (ct), "multipart/"))
+ return NULL;
+
+ variable boundary = mime_parse_subkeyword (ct, "boundary");
+ if (boundary == NULL)
+ return NULL;
+
+ return boundary;
+}
+
+% The idea here is to represent an article as a list of mime objects
+% in the form of a tree. For a non-multipart article, there is only
+% one node. For a multipart message, there will be a linked list of
+% nodes, one for each subpart. If the subpart is a multipart, a new
+% subtree will begin. For example, here is an article with a
+% two-multiparts, with the second contained in the first.
+%
+% article
+% / \
+% /\
+%
+private variable Mime_Node_Type = struct
+{
+ mimetype, % lowercase type/subtype, from content-type
+ disposition, % content-disposition header
+ content_type, % full content-type header
+ header, % assoc array of header keywords
+ list, % non-null list of nodes if multipart
+ message, charset, encoding % non-multipart decoded message
+};
+
+define mime_parse_mime ();
+private define parse_multipart (node, body)
+{
+ variable boundary = get_multipart_boundary (node.header);
+ if (boundary == NULL)
+ return;
+
+ boundary = "--" + boundary;
+ variable
+ blen = strbytelen (boundary),
+ boundary_end = boundary + "--", blen_end = blen + 2;
+
+ node.list = {};
+
+ body = strchop (body, '\n', 0);
+ variable i = 0, imax = length(body);
+ while (i < imax)
+ {
+ if (strnbytecmp (body[i], boundary, blen))
+ {
+ i++;
+ continue;
+ }
+
+ if (0 == strnbytecmp (body[i], boundary_end, blen_end))
+ break;
+
+ i++;
+ variable i0 = i;
+ if (i0 == imax)
+ break;
+
+ while (i < imax)
+ {
+ if (strnbytecmp (body[i], boundary, blen))
+ {
+ i++;
+ continue;
+ }
+ break;
+ }
+ variable new_node = mime_parse_mime (strjoin (body[[i0:i-1]], "\n"));
+ if (new_node != NULL)
+ list_append (node.list, new_node);
+ }
+}
+
+define mime_extract_mimetype (content_type)
+{
+ return strlow (strtrim (strchop (content_type, ';', 0)[0]));
+}
+
+define mime_parse_mime (art)
+{
+ variable header, body;
+ (header, body) = mime_split_article (art);
+
+ variable node = @Mime_Node_Type;
+ node.content_type = mime_get_header_key (header, "Content-Type", 1);
+ node.disposition = mime_get_header_key (header, "Content-Disposition", 0);
+ node.header = header;
+ node.mimetype = mime_extract_mimetype (node.content_type);
+
+ if (is_substr (node.mimetype, "multipart/"))
+ {
+ parse_multipart (node, body);
+ return node;
+ }
+
+ node.message = body;
+
+ variable encoding = mime_get_header_key (header, "Content-Transfer-Encoding", 1);
+ encoding = strlow (encoding);
+ if (is_substr (encoding, "base64"))
+ node.encoding = "base64";
+ else if (is_substr (encoding, "quoted-printable"))
+ node.encoding = "quoted-printable";
+
+ node.charset = mime_parse_subkeyword (node.content_type, "charset");
+
+ return node;
+}
+
+define mime_flatten_node_tree (node, leaves); % recursive
+define mime_flatten_node_tree (node, leaves)
+{
+ if (node.list == NULL)
+ {
+ list_append (leaves, node);
+ return;
+ }
+
+ foreach node (node.list)
+ mime_flatten_node_tree (node, leaves);
+}
+
+% Returns NULL if the message is not Mime Encoded, otherwise it
+% returns the value of the Content-Type header.
+define mime_is_mime_message ()
+{
+ variable h = extract_article_header ("Mime-Version");
+ if ((h == NULL) || (h == ""))
+ return NULL;
+
+ h = extract_article_header ("Content-Type");
+ if (h == "") h = NULL;
+ return h;
+}
+
+define mime_is_attachment (node)
+{
+ return is_substrbytes (strlow (node.disposition), "attachment");
+}
+
+define mime_is_text (node)
+{
+ return is_substrbytes (node.mimetype, "text/");
+}
+
+define mime_get_mime_filename (node)
+{
+ variable file = mime_parse_subkeyword (node.disposition, "filename");
+ if (file != NULL)
+ return file;
+ file = mime_parse_subkeyword (node.content_type, "name");
+ if (file != NULL)
+ return file;
+
+ return "";
+}
+
+define mime_convert_mime_object (obj)
+{
+ variable str = obj.message;
+ if (str == "")
+ return str;
+
+ if (obj.encoding == "base64")
+ str = decode_base64_string (str);
+ else if (obj.encoding == "quoted-printable")
+ str = decode_qp_string (str);
+
+ variable charset = obj.charset;
+ if ((charset != NULL) && (charset != "")
+ && (Mime_Save_Charset != NULL)
+ && (strlow(charset) != strlow(Mime_Save_Charset)))
+ {
+ str = charset_convert_string (str, charset, Mime_Save_Charset, 0);
+ }
+ return str;
+}
+
+define mime_save_mime_object (obj, fp)
+{
+ if (typeof (fp) == String_Type)
+ {
+ variable file = fp;
+ fp = fopen (file, "w");
+ if (fp == NULL)
+ throw OpenError, "Could not open $file for writing"$;
+ }
+
+ variable str = mime_convert_mime_object (obj);
+
+ () = fwrite (str, fp);
+ () = fflush (fp);
+}
+
--
2.1.4
------------------------------------------------------------------------------