]> diplodocus.org Git - nmh/commitdiff
Add support for RFC-2017, message/external-body content which contains URLs.
authorKen Hornstein <kenh@pobox.com>
Tue, 12 Mar 2013 18:22:49 +0000 (14:22 -0400)
committerKen Hornstein <kenh@pobox.com>
Tue, 12 Mar 2013 18:23:33 +0000 (14:23 -0400)
config/config.c
docs/pending-release-notes
h/mh.h
h/mhparse.h
man/mhbuild.man
man/mhshow.man
man/mhstore.man
uip/mhbuildsbr.c
uip/mhfree.c
uip/mhparse.c

index d29f8a2ec5d2791af8910cce204d3f97249c8fcd..4024d305cbe9c86ac82203701d5be3dc3c9543c1 100644 (file)
@@ -138,6 +138,9 @@ char *nmhprivcache = "nmh-private-cache";
 /* profile entry for external ftp access command */
 char *nmhaccessftp = "nmh-access-ftp";
 
 /* profile entry for external ftp access command */
 char *nmhaccessftp = "nmh-access-ftp";
 
+/* profile entry for external url access command */
+char *nmhaccessurl = "nmh-access-url";
+
 char *mhlibdir = NMHLIBDIR;
 char *mhetcdir = NMHETCDIR;
 
 char *mhlibdir = NMHLIBDIR;
 char *mhetcdir = NMHETCDIR;
 
index 97bdf02d2f06dcbb4afbc0d1ae3420e8615deda9..94a62af35fc54ff58a07218ca2a7dd10b19ceda8 100644 (file)
@@ -42,6 +42,8 @@ NEW FEATURES
   contain spaces and shell metacharacters.  If found, such entries will
   either be space-splitted or processed by /bin/sh.
 - A new program, fmttest(1) is included to help debug format files
   contain spaces and shell metacharacters.  If found, such entries will
   either be space-splitted or processed by /bin/sh.
 - A new program, fmttest(1) is included to help debug format files
+- mhshow/mhstore now have support for RFC-2017 (access-type=url) for
+  external message bodies.
 
 ----------------------------
 OBSOLETE/DEPRECATED FEATURES
 
 ----------------------------
 OBSOLETE/DEPRECATED FEATURES
diff --git a/h/mh.h b/h/mh.h
index b8e5644257b5fd9da5ca561b1593980e7e1a89d6..daf47c02d042498a26ff93311808154e67322c14 100644 (file)
--- a/h/mh.h
+++ b/h/mh.h
@@ -387,6 +387,7 @@ extern char *moreproc;
 extern char *msgprot;
 extern char *mshproc;
 extern char *nmhaccessftp;
 extern char *msgprot;
 extern char *mshproc;
 extern char *nmhaccessftp;
+extern char *nmhaccessurl;
 extern char *nmhstorage;
 extern char *nmhcache;
 extern char *nmhprivcache;
 extern char *nmhstorage;
 extern char *nmhcache;
 extern char *nmhprivcache;
index d7bdb3449f8e42e1725a7cfa4e083e11217cdffd..57bdabd396e9419504bb3e03e33477e7815d53e7 100644 (file)
@@ -231,6 +231,7 @@ struct exbody {
     char *eb_server;
     char *eb_subject;
     char *eb_body;
     char *eb_server;
     char *eb_subject;
     char *eb_body;
+    char *eb_url;
 };
 
 /*
 };
 
 /*
index a2ce2054c685e842d543a646401c4dfdbc2df092..05ed1d320ffbd2dc2f84ef643ebb6122e4eaecf5 100644 (file)
@@ -255,7 +255,7 @@ These parameters are of the form:
 .RS 5
 .nf
 .ta \w'access-type=  'u
 .RS 5
 .nf
 .ta \w'access-type=  'u
-access-type=   usually \fIanon-ftp\fR or \fImail-server\fR
+access-type=   usually \fIanon-ftp\fR, \fImail-server\fR, or \fIurl\fR
 name=  filename
 permission=    read-only or read-write
 site=  hostname
 name=  filename
 permission=    read-only or read-write
 site=  hostname
@@ -265,9 +265,24 @@ size=      number of octets
 server=        mailbox
 subject=       subject to send
 body=  command to send for retrieval
 server=        mailbox
 subject=       subject to send
 body=  command to send for retrieval
+url=   URL of content
 .fi
 .RE
 .PP
 .fi
 .RE
 .PP
+A mimimum \*(lqexternal\-type\*(rq directive for the
+.B url
+.I access\-type
+would be as follows:
+.PP
+.RS 3
+.nf
+#@application/octet-stream [] access-type=url; \\
+  url="http://download.savannah.gnu.org/releases/nmh/nmh-1.5.tar.gz"
+.fi
+.RE
+.PP
+Any long URLs will be wrapped according to RFC\-2017 rules.
+.PP
 The \*(lqmessage\*(rq directive (#forw) is used to specify a message or
 group of messages to include.  You may optionally specify the name of
 the folder and which messages are to be forwarded.  If a folder is not
 The \*(lqmessage\*(rq directive (#forw) is used to specify a message or
 group of messages to include.  You may optionally specify the name of
 the folder and which messages are to be forwarded.  If a folder is not
@@ -680,6 +695,8 @@ line         ::=     "##" text EOL
 .PP
 .I "Multipurpose Internet Mail Extensions (MIME) Part Five: Conformance Criteria and Examples"
 (RFC\-2049)
 .PP
 .I "Multipurpose Internet Mail Extensions (MIME) Part Five: Conformance Criteria and Examples"
 (RFC\-2049)
+.I "Definition of the URL MIME External-Body Access-Type"
+(RRC\-2017)
 .SH DEFAULTS
 .nf
 .RB ` \-headers '
 .SH DEFAULTS
 .nf
 .RB ` \-headers '
index b2bc6f66a97018e7d324e9c7a44efbf075dae82c..352bcfca5e62529b733dcea033ab4669055d497a 100644 (file)
@@ -436,6 +436,8 @@ ftp
 local-file
 .IP \(bu 4
 mail-server
 local-file
 .IP \(bu 4
 mail-server
+.IP \(bu 4
+url
 .PP
 For the \*(lqanon-ftp\*(rq and \*(lqftp\*(rq access types,
 .B mhshow
 .PP
 For the \*(lqanon-ftp\*(rq and \*(lqftp\*(rq access types,
 .B mhshow
@@ -464,6 +466,14 @@ local filename
 .PP
 The program should terminate with an exit status of zero if the
 retrieval is successful, and a non-zero exit status otherwise.
 .PP
 The program should terminate with an exit status of zero if the
 retrieval is successful, and a non-zero exit status otherwise.
+.PP
+For the \*(lqurl\*(rq access\-type, 
+.B mhshow
+will look for the \*(lqnmh-access-url\*(rq
+profile entry.  See
+.IR mhstore (1)
+for more details.
+.PP
 .SS "The Content Cache"
 When
 .B mhshow
 .SS "The Content Cache"
 When
 .B mhshow
@@ -569,6 +579,7 @@ installation.
 ^Unseen\-Sequence:~^To name sequences denoting unseen messages
 ^mhlproc:~^Default program to display message headers
 ^nmh-access-ftp:~^Program to retrieve contents via FTP
 ^Unseen\-Sequence:~^To name sequences denoting unseen messages
 ^mhlproc:~^Default program to display message headers
 ^nmh-access-ftp:~^Program to retrieve contents via FTP
+^nmh-access-url:~^Program to retrieve contents via HTTP
 ^nmh-cache~^Public directory to store cached external contents
 ^nmh-private-cache~^Personal directory to store cached external contents
 ^mhshow-charset-<charset>~^Template for environment to render character sets
 ^nmh-cache~^Public directory to store cached external contents
 ^nmh-private-cache~^Personal directory to store cached external contents
 ^mhshow-charset-<charset>~^Template for environment to render character sets
index 356964942f03b7edf3eac552be6e5b89bf0d3323..ccf1ee9c3639cf88cda9bc39f10d1c0fb6e8767e 100644 (file)
@@ -395,6 +395,8 @@ ftp
 local-file
 .IP \(bu 4
 mail-server
 local-file
 .IP \(bu 4
 mail-server
+.IP \(bu 4
+url
 .PP
 For the \*(lqanon-ftp\*(rq and \*(lqftp\*(rq access types,
 .B mhstore
 .PP
 For the \*(lqanon-ftp\*(rq and \*(lqftp\*(rq access types,
 .B mhstore
@@ -422,6 +424,19 @@ local filename
 .PP
 The program should terminate with an exit status of zero if the
 retrieval is successful, and a non-zero exit status otherwise.
 .PP
 The program should terminate with an exit status of zero if the
 retrieval is successful, and a non-zero exit status otherwise.
+.PP
+For the \*(lqurl\*(rq access types,
+.B mhstore
+will look for the \*(lqnmh-access-url\*(rq profile entry, e.g.,
+.PP
+.RS 5
+nmh-access-url: curl -l
+.RE
+.PP
+to determine the program to use to perform the HTTP retrieval.  This program
+is invoked with one argument: the URL of the content to retrieve.  The program
+should write the content to standard out, and should terminate with a status of zero if the retrieval is successful and a non\-zero exit status otherwise.
+.PP
 .SS "The Content Cache"
 When
 .B mhstore
 .SS "The Content Cache"
 When
 .B mhstore
@@ -514,6 +529,7 @@ installation.
 ^Path:~^To determine the user's nmh directory
 ^Current\-Folder:~^To find the default current folder
 ^nmh-access-ftp:~^Program to retrieve contents via FTP
 ^Path:~^To determine the user's nmh directory
 ^Current\-Folder:~^To find the default current folder
 ^nmh-access-ftp:~^Program to retrieve contents via FTP
+^nmh-access-url:~^Program to retrieve contents via HTTP
 ^nmh-cache~^Public directory to store cached external contents
 ^nmh-private-cache~^Personal directory to store cached external contents
 ^nmh-storage~^Directory to store contents
 ^nmh-cache~^Public directory to store cached external contents
 ^nmh-private-cache~^Personal directory to store cached external contents
 ^nmh-storage~^Directory to store contents
index d2d033f1047c4d85502537e34bb6c5b132bc62bc..8bac6d37c477c41c67a449fbe997bd4bc026ae8d 100644 (file)
@@ -54,6 +54,12 @@ pid_t xpid = 0;
 
 static char prefix[] = "----- =_aaaaaaaaaa";
 
 
 static char prefix[] = "----- =_aaaaaaaaaa";
 
+/*
+ * Maximum size of URL token in message/external-body
+ */
+
+#define MAXURLTOKEN 40
+
 
 /* mhmisc.c */
 void content_error (char *, CT, char *, ...);
 
 /* mhmisc.c */
 void content_error (char *, CT, char *, ...);
@@ -1360,7 +1366,7 @@ scan_content (CT ct)
 static int
 build_headers (CT ct)
 {
 static int
 build_headers (CT ct)
 {
-    int        cc, mailbody, len;
+    int        cc, mailbody, extbody, len;
     char **ap, **ep;
     char *np, *vp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
     char **ap, **ep;
     char *np, *vp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
@@ -1402,9 +1408,8 @@ build_headers (CT ct)
     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
                + strlen (ci->ci_subtype) + 3;
 
     len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
                + strlen (ci->ci_subtype) + 3;
 
-    mailbody = ct->c_type == CT_MESSAGE
-       && ct->c_subtype == MESSAGE_EXTERNAL
-       && ((struct exbody *) ct->c_ctparams)->eb_body;
+    extbody = ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_EXTERNAL;
+    mailbody = extbody && ((struct exbody *) ct->c_ctparams)->eb_body;
 
     /*
      * Append the attribute/value pairs to
 
     /*
      * Append the attribute/value pairs to
@@ -1417,6 +1422,42 @@ build_headers (CT ct)
        vp = add (";", vp);
        len++;
 
        vp = add (";", vp);
        len++;
 
+       /*
+        * According to RFC 2017, if we have a URL longer than 40 characters
+        * we have to break it across multiple lines
+        */
+
+       if (extbody && mh_strcasecmp (*ap, "url") == 0) {
+           char *value = *ep;
+
+           /* 7 here refers to " url=\"\"" */
+           if (len + 1 + (cc = (min(MAXURLTOKEN, strlen(value)) + 7)) >=
+                                                               CPERLIN) {
+               vp = add ("\n\t", vp);
+               len = 8;
+           } else {
+               vp = add (" ", vp);
+               len++;
+           }
+
+           vp = add ("url=\"", vp);
+           len += 5;
+
+           while (strlen(value) > MAXURLTOKEN) {
+               strncpy(buffer, value, MAXURLTOKEN);
+               buffer[MAXURLTOKEN] = '\0';
+               vp = add (buffer, vp);
+               vp = add ("\n\t", vp);
+               value += MAXURLTOKEN;
+               len = 8;
+           }
+
+           vp = add (value, vp);
+           vp = add ("\"", vp);
+           len += strlen(value) + 1;
+           continue;
+       }
+
        snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
        if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
            vp = add ("\n\t", vp);
        snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
        if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
            vp = add ("\n\t", vp);
index 83ed53666e5135145f3184a590c168fa905431ad..cf9e548e1fc0ed19bcca2be6a3cb5ff98bf90635 100644 (file)
@@ -246,6 +246,8 @@ free_external (CT ct)
     free_content (e->eb_content);
     if (e->eb_body)
        free (e->eb_body);
     free_content (e->eb_content);
     if (e->eb_body)
        free (e->eb_body);
+    if (e->eb_url)
+       free (e->eb_url);
 
     free ((char *) e);
     ct->c_ctparams = NULL;
 
     free ((char *) e);
     ct->c_ctparams = NULL;
index 62034555ee0a819ca00a77cd90592aee732020f2..4a70610e0f9c4ae0dda2299c59c99b9144919c2e 100644 (file)
@@ -128,6 +128,8 @@ static int InitMail (CT);
 static int openMail (CT, char **);
 static int readDigest (CT, char *);
 static int get_leftover_mp_content (CT, int);
 static int openMail (CT, char **);
 static int readDigest (CT, char *);
 static int get_leftover_mp_content (CT, int);
+static int InitURL (CT);
+static int openURL (CT, char **);
 
 struct str2init str2cts[] = {
     { "application", CT_APPLICATION, InitApplication },
 
 struct str2init str2cts[] = {
     { "application", CT_APPLICATION, InitApplication },
@@ -162,6 +164,7 @@ struct str2init str2methods[] = {
     { "ftp",         0,        InitFTP },
     { "local-file",  0,        InitFile },
     { "mail-server", 0,        InitMail },
     { "ftp",         0,        InitFTP },
     { "local-file",  0,        InitFile },
     { "mail-server", 0,        InitMail },
+    { "url",         0, InitURL },
     { NULL,          0, NULL }
 };
 
     { NULL,          0, NULL }
 };
 
@@ -1438,6 +1441,7 @@ invalid_param:
                e->eb_parent = ct;
                e->eb_content = p;
                p->c_ctexbody = e;
                e->eb_parent = ct;
                e->eb_content = p;
                p->c_ctexbody = e;
+               p->c_ceopenfnx = NULL;
                if ((exresult = params_external (ct, 0)) != NOTOK
                        && p->c_ceopenfnx == openMail) {
                    int cc, size;
                if ((exresult = params_external (ct, 0)) != NOTOK
                        && p->c_ceopenfnx == openMail) {
                    int cc, size;
@@ -1510,6 +1514,7 @@ params_external (CT ct, int composing)
     struct exbody *e = (struct exbody *) ct->c_ctparams;
     CI ci = &ct->c_ctinfo;
 
     struct exbody *e = (struct exbody *) ct->c_ctparams;
     CI ci = &ct->c_ctinfo;
 
+    ct->c_ceopenfnx = NULL;
     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
        if (!mh_strcasecmp (*ap, "access-type")) {
            struct str2init *s2i;
     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
        if (!mh_strcasecmp (*ap, "access-type")) {
            struct str2init *s2i;
@@ -1565,6 +1570,23 @@ params_external (CT ct, int composing)
            e->eb_subject = *ep;
            continue;
        }
            e->eb_subject = *ep;
            continue;
        }
+       if (!mh_strcasecmp (*ap, "url")) {
+           /*
+            * According to RFC 2017, we have to remove all whitespace from
+            * the URL
+            */
+
+           char *u, *p = *ep;
+           e->eb_url = u = mh_xmalloc(strlen(*ep) + 1);
+
+           for (; *p != '\0'; p++) {
+               if (! isspace((unsigned char) *p))
+                   *u++ = *p;
+           }
+
+           *u = '\0';
+           continue;
+       }
        if (composing && !mh_strcasecmp (*ap, "body")) {
            e->eb_body = getcpy (*ep);
            continue;
        if (composing && !mh_strcasecmp (*ap, "body")) {
            e->eb_body = getcpy (*ep);
            continue;
@@ -2799,6 +2821,141 @@ openMail (CT ct, char **file)
 }
 
 
 }
 
 
+/*
+ * URL
+ */
+
+static int
+InitURL (CT ct)
+{
+    return init_encoding (ct, openURL);
+}
+
+
+static int
+openURL (CT ct, char **file)
+{
+    struct exbody *e = ct->c_ctexbody;
+    CE ce = ct->c_cefile;
+    char *urlprog, *program;
+    char buffer[BUFSIZ], cachefile[BUFSIZ];
+    int fd, caching, cachetype;
+    struct msgs_array args = { 0, 0, NULL};
+    pid_t child_id;
+
+    if ((urlprog = context_find(nmhaccessurl)) && *urlprog == '\0')
+       urlprog = NULL;
+
+    if (! urlprog) {
+       content_error(NULL, ct, "No entry for nmh-access-url in profile");
+       return NOTOK;
+    }
+
+    switch (openExternal(e->eb_parent, e->eb_content, ce, file, &fd)) {
+       case NOTOK:
+           return NOTOK;
+
+       case OK:
+           break;
+
+       case DONE:
+           return fd;
+    }
+
+    if (!e->eb_url) {
+       content_error(NULL, ct, "missing url parameter");
+       return NOTOK;
+    }
+
+    if (xpid) {
+       if (xpid < 0)
+           xpid = -xpid;
+       pidcheck (pidwait (xpid, NOTOK));
+       xpid = 0;
+    }
+
+    ce->ce_unlink = (*file == NULL);
+    caching = 0;
+    cachefile[0] = '\0';
+
+    if (find_cache(NULL, wcachesw, &cachetype, e->eb_content->c_id,
+                  cachefile, sizeof(cachefile)) != NOTOK) {
+       if (*file == NULL) {
+           ce->ce_unlink = 0;
+           caching = 1;
+       }
+    }
+
+    if (*file)
+       ce->ce_file = add(*file, NULL);
+    else if (caching)
+       ce->ce_file = add(cachefile, NULL);
+    else
+       ce->ce_file = add(m_mktemp(tmp, NULL, NULL), NULL);
+
+    if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
+       content_error(ce->ce_file, ct, "unable to fopen for read/writing");
+       return NOTOK;
+    }
+
+    switch (child_id = fork()) {
+    case NOTOK:
+       adios ("fork", "unable to");
+       /* NOTREACHED */
+
+    case OK:
+       argsplit_msgarg(&args, urlprog, &program);
+       app_msgarg(&args, e->eb_url);
+       app_msgarg(&args, NULL);
+       dup2(fileno(ce->ce_fp), 1);
+       close(fileno(ce->ce_fp));
+       execvp(program, args.msgs);
+       fprintf(stderr, "Unable to exec ");
+       perror(program);
+       _exit(-1);
+       /* NOTREACHED */
+
+    default:
+       if (pidXwait(child_id, NULL)) {
+           ce->ce_unlink = 1;
+           return NOTOK;
+       }
+    }
+
+    if (cachefile[0]) {
+       if (caching)
+           chmod(cachefile, cachetype ? m_gmprot() : 0444);
+       else {
+           int mask;
+           FILE *fp;
+
+           mask = umask (cachetype ? ~m_gmprot() : 0222);
+           if ((fp = fopen(cachefile, "w"))) {
+               int cc;
+               FILE *gp = ce->ce_fp;
+
+               fseeko(gp, 0, SEEK_SET);
+
+               while ((cc = fread(buffer, sizeof(*buffer),
+                                  sizeof(buffer), gp)) > 0)
+                   fwrite(buffer, sizeof(*buffer), cc, fp);
+
+               fflush(fp);
+
+               if (ferror(gp)) {
+                   admonish(ce->ce_file, "error reading");
+                   unlink(cachefile);
+               }
+           }
+           umask(mask);
+       }
+    }
+
+    fseeko(ce->ce_fp, 0, SEEK_SET);
+    *file = ce->ce_file;
+    return fd;
+}
+
 static int
 readDigest (CT ct, char *cp)
 {
 static int
 readDigest (CT ct, char *cp)
 {