From: Ken Hornstein Date: Tue, 12 Mar 2013 18:22:49 +0000 (-0400) Subject: Add support for RFC-2017, message/external-body content which contains URLs. X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/9d211736c4ff802eef68d2264feacbe001f83e61?ds=inline;hp=--cc Add support for RFC-2017, message/external-body content which contains URLs. --- 9d211736c4ff802eef68d2264feacbe001f83e61 diff --git a/config/config.c b/config/config.c index d29f8a2e..4024d305 100644 --- a/config/config.c +++ b/config/config.c @@ -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 url access command */ +char *nmhaccessurl = "nmh-access-url"; + char *mhlibdir = NMHLIBDIR; char *mhetcdir = NMHETCDIR; diff --git a/docs/pending-release-notes b/docs/pending-release-notes index 97bdf02d..94a62af3 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -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 +- mhshow/mhstore now have support for RFC-2017 (access-type=url) for + external message bodies. ---------------------------- OBSOLETE/DEPRECATED FEATURES diff --git a/h/mh.h b/h/mh.h index b8e56442..daf47c02 100644 --- 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 *nmhaccessurl; extern char *nmhstorage; extern char *nmhcache; extern char *nmhprivcache; diff --git a/h/mhparse.h b/h/mhparse.h index d7bdb344..57bdabd3 100644 --- a/h/mhparse.h +++ b/h/mhparse.h @@ -231,6 +231,7 @@ struct exbody { char *eb_server; char *eb_subject; char *eb_body; + char *eb_url; }; /* diff --git a/man/mhbuild.man b/man/mhbuild.man index a2ce2054..05ed1d32 100644 --- a/man/mhbuild.man +++ b/man/mhbuild.man @@ -255,7 +255,7 @@ These parameters are of the form: .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 @@ -265,9 +265,24 @@ size= number of octets server= mailbox subject= subject to send body= command to send for retrieval +url= URL of content .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 @@ -680,6 +695,8 @@ line ::= "##" text EOL .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 ' diff --git a/man/mhshow.man b/man/mhshow.man index b2bc6f66..352bcfca 100644 --- a/man/mhshow.man +++ b/man/mhshow.man @@ -436,6 +436,8 @@ ftp local-file .IP \(bu 4 mail-server +.IP \(bu 4 +url .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 +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 @@ -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 +^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-~^Template for environment to render character sets diff --git a/man/mhstore.man b/man/mhstore.man index 35696494..ccf1ee9c 100644 --- a/man/mhstore.man +++ b/man/mhstore.man @@ -395,6 +395,8 @@ ftp local-file .IP \(bu 4 mail-server +.IP \(bu 4 +url .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 +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 @@ -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 +^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 diff --git a/uip/mhbuildsbr.c b/uip/mhbuildsbr.c index d2d033f1..8bac6d37 100644 --- a/uip/mhbuildsbr.c +++ b/uip/mhbuildsbr.c @@ -54,6 +54,12 @@ pid_t xpid = 0; static char prefix[] = "----- =_aaaaaaaaaa"; +/* + * Maximum size of URL token in message/external-body + */ + +#define MAXURLTOKEN 40 + /* mhmisc.c */ void content_error (char *, CT, char *, ...); @@ -1360,7 +1366,7 @@ scan_content (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; @@ -1402,9 +1408,8 @@ build_headers (CT ct) 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 @@ -1417,6 +1422,42 @@ build_headers (CT ct) 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); diff --git a/uip/mhfree.c b/uip/mhfree.c index 83ed5366..cf9e548e 100644 --- a/uip/mhfree.c +++ b/uip/mhfree.c @@ -246,6 +246,8 @@ free_external (CT ct) 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; diff --git a/uip/mhparse.c b/uip/mhparse.c index 62034555..4a70610e 100644 --- a/uip/mhparse.c +++ b/uip/mhparse.c @@ -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 InitURL (CT); +static int openURL (CT, char **); 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 }, + { "url", 0, InitURL }, { NULL, 0, NULL } }; @@ -1438,6 +1441,7 @@ invalid_param: 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; @@ -1510,6 +1514,7 @@ params_external (CT ct, int composing) 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; @@ -1565,6 +1570,23 @@ params_external (CT ct, int composing) 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; @@ -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) {