/* 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;
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
extern char *msgprot;
extern char *mshproc;
extern char *nmhaccessftp;
+extern char *nmhaccessurl;
extern char *nmhstorage;
extern char *nmhcache;
extern char *nmhprivcache;
char *eb_server;
char *eb_subject;
char *eb_body;
+ char *eb_url;
};
/*
.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
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
.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 '
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
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
^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
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
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
^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
static char prefix[] = "----- =_aaaaaaaaaa";
+/*
+ * Maximum size of URL token in message/external-body
+ */
+
+#define MAXURLTOKEN 40
+
/* mhmisc.c */
void content_error (char *, CT, char *, ...);
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;
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
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);
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;
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 },
{ "ftp", 0, InitFTP },
{ "local-file", 0, InitFile },
{ "mail-server", 0, InitMail },
+ { "url", 0, InitURL },
{ NULL, 0, NULL }
};
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;
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;
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;
}
+/*
+ * 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)
{