]> diplodocus.org Git - nmh/blobdiff - uip/mhparse.c
Make sure we include space in the list of characters we need to encode in
[nmh] / uip / mhparse.c
index 25e2a411a6619d31468083fc808d58e579e9360b..c381bb8583f298a066de0609069ef994ddf8e61e 100644 (file)
@@ -109,7 +109,7 @@ void free_encoding (CT, int);
  * static prototypes
  */
 static CT get_content (FILE *, char *, int);
-static int get_comment (const char *, CI, char **, int);
+static int get_comment (const char *, const char *, char **, char **);
 
 static int InitGeneric (CT);
 static int InitText (CT);
@@ -136,6 +136,9 @@ static int get_leftover_mp_content (CT, int);
 static int InitURL (CT);
 static int openURL (CT, char **);
 static size_t param_len(PM, int, size_t, int *);
+static size_t encode_param(PM, char *, size_t, size_t, size_t, int);
+static size_t normal_param(PM, char *, size_t, size_t, size_t);
+static int get_dispo (char *, CT, int);
 
 struct str2init str2cts[] = {
     { "application", CT_APPLICATION, InitApplication },
@@ -378,7 +381,7 @@ get_content (FILE *in, char *file, int toplevel)
                fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
 
            if (*cp == '('  &&
-                get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK)
+                get_comment (ct->c_file, VRSN_FIELD, &cp, NULL) == NOTOK)
                goto out;
 
            for (dp = cp; istoken (*dp); dp++)
@@ -488,7 +491,7 @@ get_content (FILE *in, char *file, int toplevel)
                fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
 
            if (*cp == '('  &&
-                get_comment (ct->c_file, &ct->c_ctinfo, &cp, 0) == NOTOK) {
+                get_comment (ct->c_file, MD5_FIELD, &cp, NULL) == NOTOK) {
                free (ep);
                goto out;
            }
@@ -511,7 +514,8 @@ get_content (FILE *in, char *file, int toplevel)
        }
        else if (!strcasecmp (hp->name, DISPO_FIELD)) {
        /* Get Content-Disposition field */
-           ct->c_dispo = add (hp->value, ct->c_dispo);
+           if (get_dispo(hp->value, ct, 0) == NOTOK)
+               goto out;
        }
 
 next_header:
@@ -677,14 +681,12 @@ extract_name_value (char *name_suffix, char *value) {
 int
 get_ctinfo (char *cp, CT ct, int magic)
 {
-    int        i;
     char *dp;
     char c;
     CI ci;
     int status;
 
     ci = &ct->c_ctinfo;
-    i = strlen (invo_name) + 2;
 
     /* store copy of Content-Type line */
     cp = ct->c_ctline = add (cp, NULL);
@@ -705,7 +707,8 @@ get_ctinfo (char *cp, CT ct, int magic)
     if (debugsw)
        fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
 
-    if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+    if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+                                  &ci->ci_comment) == NOTOK)
        return NOTOK;
 
     for (dp = cp; istoken (*dp); dp++)
@@ -728,7 +731,8 @@ get_ctinfo (char *cp, CT ct, int magic)
     while (isspace ((unsigned char) *cp))
        cp++;
 
-    if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+    if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+                                  &ci->ci_comment) == NOTOK)
        return NOTOK;
 
     if (*cp != '/') {
@@ -741,7 +745,8 @@ get_ctinfo (char *cp, CT ct, int magic)
     while (isspace ((unsigned char) *cp))
        cp++;
 
-    if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+    if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+                                  &ci->ci_comment) == NOTOK)
        return NOTOK;
 
     for (dp = cp; istoken (*dp); dp++)
@@ -766,11 +771,14 @@ magic_skip:
     while (isspace ((unsigned char) *cp))
        cp++;
 
-    if (*cp == '(' && get_comment (ct->c_file, &ct->c_ctinfo, &cp, 1) == NOTOK)
+    if (*cp == '(' && get_comment (ct->c_file, TYPE_FIELD, &cp,
+                                  &ci->ci_comment) == NOTOK)
        return NOTOK;
 
-    if (parse_header_attrs (ct->c_file, i, &cp, ci, &status) == NOTOK) {
-       return status;
+    if ((status = parse_header_attrs (ct->c_file, TYPE_FIELD, &cp,
+                                     &ci->ci_first_pm, &ci->ci_last_pm,
+                                     &ci->ci_comment)) != OK) {
+       return status == NOTOK ? NOTOK : OK;
     }
 
     /*
@@ -829,7 +837,7 @@ magic_skip:
      * Get any {Content-Disposition} given in buffer.
      */
     if (magic && *cp == '{') {
-        ct->c_dispo = ++cp;
+        ++cp;
        for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
            if (*dp == '}')
                break;
@@ -841,10 +849,10 @@ magic_skip:
        
        c = *dp;
        *dp = '\0';
-       if (*ct->c_dispo)
-           ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
-       else
-           ct->c_dispo = NULL;
+
+       if (get_dispo(cp, ct, 1) != OK)
+           return NOTOK;
+
        *dp++ = c;
        cp = dp;
 
@@ -915,16 +923,90 @@ magic_skip:
         }
        else
            advise (NULL,
-                   "extraneous information in message %s's %s: field\n%*.*s(%s)",
-                    ct->c_file, TYPE_FIELD, i, i, "", cp);
+                   "extraneous information in message %s's %s: field\n%*s(%s)",
+                    ct->c_file, TYPE_FIELD, strlen(invo_name) + 2, "", cp);
+    }
+
+    return OK;
+}
+
+
+/*
+ * Parse out a Content-Disposition header.  A lot of this is cribbed from
+ * get_ctinfo().
+ */
+static int
+get_dispo (char *cp, CT ct, int buildflag)
+{
+    char *dp, *dispoheader;
+    char c;
+    int status;
+
+    /*
+     * Save the whole copy of the Content-Disposition header, unless we're
+     * processing a mhbuild directive.  A NULL c_dispo will be a flag to
+     * mhbuild that the disposition header needs to be generated at that
+     * time.
+     */
+
+    dispoheader = cp = add(cp, NULL);
+
+    while (isspace ((unsigned char) *cp))      /* trim leading spaces */
+       cp++;
+
+    /* change newlines to spaces */
+    for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
+       *dp++ = ' ';
+
+    /* trim trailing spaces */
+    for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
+       if (!isspace ((unsigned char) *dp))
+           break;
+    *++dp = '\0';
+
+    if (debugsw)
+       fprintf (stderr, "%s: %s\n", DISPO_FIELD, cp);
+
+    if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) ==
+                                                       NOTOK) {
+       free(dispoheader);
+       return NOTOK;
+    }
+
+    for (dp = cp; istoken (*dp); dp++)
+       continue;
+    c = *dp, *dp = '\0';
+    ct->c_dispo_type = add (cp, NULL); /* store disposition type */
+    *dp = c, cp = dp;
+
+    if (*cp == '(' && get_comment (ct->c_file, DISPO_FIELD, &cp, NULL) == NOTOK)
+       return NOTOK;
+
+    if ((status = parse_header_attrs (ct->c_file, DISPO_FIELD, &cp,
+                                     &ct->c_dispo_first, &ct->c_dispo_last,
+                                     NULL)) != OK) {
+       if (status == NOTOK) {
+           free(dispoheader);
+           return NOTOK;
+       }
+    } else if (*cp) {
+       advise (NULL,
+               "extraneous information in message %s's %s: field\n%*s(%s)",
+                    ct->c_file, DISPO_FIELD, strlen(invo_name) + 2, "", cp);
     }
 
+    if (buildflag)
+       free(dispoheader);
+    else
+       ct->c_dispo = dispoheader;
+
     return OK;
 }
 
 
 static int
-get_comment (const char *filename, CI ci, char **ap, int istype)
+get_comment (const char *filename, const char *fieldname, char **ap,
+            char **commentp)
 {
     int i;
     char *bp, *cp;
@@ -939,7 +1021,7 @@ get_comment (const char *filename, CI ci, char **ap, int istype)
        case '\0':
 invalid:
        advise (NULL, "invalid comment in message %s's %s: field",
-               filename, istype ? TYPE_FIELD : VRSN_FIELD);
+               filename, fieldname);
        return NOTOK;
 
        case '\\':
@@ -966,12 +1048,12 @@ invalid:
     }
     *bp = '\0';
 
-    if (istype) {
-       if ((dp = ci->ci_comment)) {
-           ci->ci_comment = concat (dp, " ", buffer, NULL);
+    if (commentp) {
+       if ((dp = *commentp)) {
+           *commentp = concat (dp, " ", buffer, NULL);
            free (dp);
        } else {
-           ci->ci_comment = add (buffer, NULL);
+           *commentp = add (buffer, NULL);
        }
     }
 
@@ -3225,32 +3307,22 @@ get_ce_method (const char *method) {
 }
 
 int
-parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci,
-                    int *status) {
-    char **attr = ci->ci_attrs;
+parse_header_attrs (const char *filename, const char *fieldname,
+                   char **header_attrp, PM *param_head, PM *param_tail,
+                   char **commentp)
+{
     char *cp = *header_attrp;
+    PM pm;
 
     while (*cp == ';') {
        char *dp, *vp, *up, c;
 
-        /* Relies on knowledge of this declaration:
-         *   char *ci_attrs[NPARMS + 2];
-         */
-       if (attr >= ci->ci_attrs + sizeof ci->ci_attrs/sizeof (char *) - 2) {
-           advise (NULL,
-                   "too many parameters in message %s's %s: field (%d max)",
-                   filename, TYPE_FIELD, NPARMS);
-           *status = NOTOK;
-           return NOTOK;
-       }
-
        cp++;
        while (isspace ((unsigned char) *cp))
            cp++;
 
        if (*cp == '('  &&
-            get_comment (filename, ci, &cp, 1) == NOTOK) {
-           *status = NOTOK;
+            get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
            return NOTOK;
         }
 
@@ -3258,9 +3330,8 @@ parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci,
            advise (NULL,
                    "extraneous trailing ';' in message %s's %s: "
                     "parameter list",
-                   filename, TYPE_FIELD);
-           *status = OK;
-           return NOTOK;
+                   filename, fieldname);
+           return DONE;
        }
 
        /* down case the attribute name */
@@ -3273,19 +3344,23 @@ parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci,
        if (dp == cp || *dp != '=') {
            advise (NULL,
                    "invalid parameter in message %s's %s: "
-                    "field\n%*.*sparameter %s (error detected at offset %d)",
-                   filename, TYPE_FIELD, len, len, "", cp, dp - cp);
-           *status = NOTOK;
+                    "field\n%*sparameter %s (error detected at offset %d)",
+                   filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp);
            return NOTOK;
        }
 
-       vp = (*attr = add (cp, NULL)) + (up - cp);
+       pm = mh_xmalloc(sizeof(*pm));
+       memset(pm, 0, sizeof(*pm));
+
+       /* This is all mega-bozo and needs cleanup */
+       vp = (pm->pm_name = add (cp, NULL)) + (up - cp);
        *vp = '\0';
        for (dp++; isspace ((unsigned char) *dp);)
            dp++;
 
        /* Now store the attribute value. */
-       ci->ci_values[attr - ci->ci_attrs] = vp = *attr + (dp - cp);
+
+       vp = pm->pm_name + (dp - cp);
 
        if (*dp == '"') {
            for (cp = ++dp, dp = vp;;) {
@@ -3294,9 +3369,9 @@ parse_header_attrs (const char *filename, int len, char **header_attrp, CI ci,
 bad_quote:
                        advise (NULL,
                                "invalid quoted-string in message %s's %s: "
-                                "field\n%*.*s(parameter %s)",
-                               filename, TYPE_FIELD, len, len, "", *attr);
-                       *status = NOTOK;
+                                "field\n%*s(parameter %s)",
+                               filename, fieldname, strlen(invo_name) + 2, "",
+                               pm->pm_name);
                        return NOTOK;
 
                    case '\\':
@@ -3320,12 +3395,13 @@ bad_quote:
                continue;
            *dp = '\0';
        }
+       pm->pm_value = getcpy(vp); 
        if (!*vp) {
            advise (NULL,
                    "invalid parameter in message %s's %s: "
-                    "field\n%*.*s(parameter %s)",
-                   filename, TYPE_FIELD, len, len, "", *attr);
-           *status = NOTOK;
+                    "field\n%*s(parameter %s)",
+                   filename, fieldname, strlen(invo_name) + 2, "",
+                   pm->pm_name);
            return NOTOK;
        }
 
@@ -3333,12 +3409,17 @@ bad_quote:
            cp++;
 
        if (*cp == '('  &&
-            get_comment (filename, ci, &cp, 1) == NOTOK) {
-           *status = NOTOK;
+            get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
            return NOTOK;
         }
 
-        ++attr;
+       if (*param_head == NULL) {
+           *param_head = pm;
+           *param_tail = pm;
+       } else {
+           (*param_tail)->pm_next = pm;
+           *param_tail = pm;
+       }
     }
 
     *header_attrp = cp;
@@ -3356,7 +3437,7 @@ output_params(size_t initialwidth, PM params, int *offsetout)
 {
     char *paramout = NULL;
     char line[CPERLIN * 2], *q;
-    int curlen, index, eightbit, encode;
+    int curlen, index, eightbit, encode, i;
     size_t valoff;
 
     while (params != NULL) {
@@ -3375,25 +3456,18 @@ output_params(size_t initialwidth, PM params, int *offsetout)
        curlen = param_len(params, index, valoff, &eightbit);
 
        /*
-        * If this won't fit on the line, start a new one.  Save room in
-        * case we need a semicolon on the end
+        * Loop until we get a parameter that fits within a line.  We
+        * assume new lines start with a tab, so check our overflow based
+        * on that.
         */
 
-       if (initialwidth + curlen > CPERLIN - 1) {
-           paramout = add(";\n\t", paramout);
-           initialwidth = 8;
-       } else {
-           paramout = add("; ", paramout);
-           initialwidth += 2;
-       }
-
-       /*
-        * Loop until we get a parameter that fits within a line.
-        */
-
-       while (initialwidth + curlen > CPERLIN - 1) {
+       while (curlen + 8 > CPERLIN - 1) {
            int curvallen = strlen(params->pm_value + valoff) -
-                               (initialwidth + curlen - (CPERLIN - 1));
+                               (curlen + 8 - (CPERLIN - 1));
+
+           *q++ = ';';
+           *q++ = '\n';
+           *q++ = '\t';
 
            /*
             * curvallen holds how many characters we take from this
@@ -3408,7 +3482,8 @@ output_params(size_t initialwidth, PM params, int *offsetout)
             * be sure to include the parameter name and section index.
             */
 
-           q += snprintf(line, sizeof(line), "%s*%d", params->pm_name, index);
+           q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
+                         params->pm_name, index);
 
            /*
             * If eightbit was set and we're on index 0, we need to include
@@ -3422,9 +3497,89 @@ output_params(size_t initialwidth, PM params, int *offsetout)
                                        params->pm_value + valoff + curvallen))
                encode = 1;
 
-           if (encode) {
-               if (index == 0) {
-                   q +
+           /*
+            * Both of these functions do a NUL termination
+            */
+
+           if (encode)
+               i = encode_param(params, q, sizeof(line) - (q - line),
+                                curvallen, valoff, index);
+           else
+               i = normal_param(params, q, sizeof(line) - (q - line),
+                                curvallen, valoff);
+
+           if (i == 0) {
+               if (paramout)
+                   free(paramout);
+               return NULL;
+           }
+
+           valoff += curvallen;
+           index++;
+           curlen = param_len(params, index, valoff, &eightbit);
+           q = line;
+
+           /*
+            * "line" starts with a ;\n\t, so that doesn't count against
+            * the length.  But add 8 since it starts with a tab; that's
+            * how we end up with 5.
+            */
+
+           initialwidth = strlen(line) + 5;
+
+           /*
+            * At this point the line should be built, so add it to our
+            * current output buffer.
+            */
+
+           paramout = add(line, paramout);
+       }
+
+       /*
+        * If this won't fit on the line, start a new one.  Save room in
+        * case we need a semicolon on the end
+        */
+
+       if (initialwidth + curlen > CPERLIN - 1) {
+           *q++ = ';';
+           *q++ = '\n';
+           *q++ = '\t';
+           initialwidth = 8;
+       } else {
+           *q++ = ';';
+           *q++ = ' ';
+           initialwidth += 2;
+       }
+
+       /*
+        * At this point, we're either finishing a contined parameter, or
+        * we're working on a new one.
+        */
+
+       if (index > 0) {
+           q += snprintf(q, sizeof(line) - (q - line), "%s*%d",
+                         params->pm_name, index);
+       } else {
+           strncpy(q, params->pm_name, sizeof(line) - (q - line));
+           q += strlen(q);
+       }
+
+       if (eightbit)
+           i = encode_param(params, q, sizeof(line) - (q - line),
+                            strlen(params->pm_value + valoff), valoff, index);
+       else
+           i = normal_param(params, q, sizeof(line) - (q - line),
+                            strlen(params->pm_value + valoff), valoff);
+
+       if (i == 0) {
+           if (paramout)
+               free(paramout);
+           return NULL;
+       }
+
+       paramout = add(line, paramout);
+       initialwidth += strlen(line);
+
        params = params->pm_next;
     }
 
@@ -3466,9 +3621,19 @@ param_len(PM pm, int index, size_t valueoff, int *eightbit)
      */
 
     if (*eightbit) {
+       /*
+        * If we don't have a charset or language tag in this parameter,
+        * add them now.
+        */
+
+       if (! pm->pm_charset)
+           pm->pm_charset = getcpy(write_charset_8bit());
+       if (! pm->pm_lang)
+           pm->pm_lang = getcpy(NULL); /* Default to a blank lang tag */
+
        len++;          /* For the encoding we need to do */
        if (index == 0) {
-           len += strlen(write_charset_8bit()) + 2;    /* Plus extra '' */
+           len += strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
        } else {
            /*
             * We know we definitely need to include an index.
@@ -3494,7 +3659,7 @@ param_len(PM pm, int index, size_t valueoff, int *eightbit)
        for (p = start; *p != '\0'; p++) {
            switch (*p) {
            case '"':
-           case '\':
+           case '\\':
                len++;
            /* FALL THROUGH */
            default:
@@ -3513,11 +3678,11 @@ param_len(PM pm, int index, size_t valueoff, int *eightbit)
  */
 
 static size_t
-encoded_param(char *output, size_t len, const char *value, size_t valuelen,
-             int index)
+encode_param(PM pm, char *output, size_t len, size_t valuelen,
+             size_t valueoff, int index)
 {
     size_t outlen = 0, n;
-    char *endptr = output + len;
+    char *endptr = output + len, *p;
 
     /*
      * First, output the marker for an encoded string.
@@ -3528,16 +3693,113 @@ encoded_param(char *output, size_t len, const char *value, size_t valuelen,
     outlen += 2;
 
     /*
-     * If the index is 0, output the character set.
+     * If the index is 0, output the character set and language tag.
+     * If theses were NULL, they should have already been filled in
+     * by param_len().
      */
 
-    n = snprintf(output, len - outlen, "%s''", write_charset_8bit());
-
-    output += n;
-    outlen += n;
+    if (index == 0) {
+       n = snprintf(output, len - outlen, "%s'%s'", pm->pm_charset,
+                    pm->pm_lang);
+       output += n;
+       outlen += n;
+       if (output > endptr) {
+           advise(NULL, "Internal error: parameter buffer overflow");
+           return 0;
+       }
+    }
 
     /*
      * Copy over the value, encoding if necessary
      */
 
-    
+    p = pm->pm_value + valueoff;
+    while (valuelen-- > 0) {
+       if (isparamencode(*p)) {
+           n = snprintf(output, len - outlen, "%%%02X", (unsigned char) *p++);
+           output += n;
+           outlen += n;
+       } else {
+           *output++ = *p++;
+           outlen++;
+       }
+       if (output > endptr) {
+           advise(NULL, "Internal error: parameter buffer overflow");
+           return 0;
+       }
+    }
+
+    *output = '\0';
+
+    return outlen;
+}
+
+/*
+ * Output a "normal" parameter, without encoding.  Be sure to escape
+ * quotes and backslashes if necessary.
+ */
+
+static size_t
+normal_param(PM pm, char *output, size_t len, size_t valuelen,
+            size_t valueoff)
+{
+    size_t outlen = 0;
+    char *endptr = output + len, *p;
+
+    *output++ = '=';
+    *output++ = '"';
+    outlen += 2;
+
+    p = pm->pm_value + valueoff;
+
+    while (valuelen-- > 0) {
+       switch (*p) {
+       case '\\':
+       case '"':
+           *output++ = '\\';
+           outlen++;
+       default:
+           *output++ = *p++;
+           outlen++;
+       }
+       if (output > endptr) {
+           advise(NULL, "Internal error: parameter buffer overflow");
+           return 0;
+       }
+    }
+
+    if (output - 2 > endptr) {
+       advise(NULL, "Internal error: parameter buffer overflow");
+       return 0;
+    }
+
+    *output++ = '"';
+    *output++ = '\0';
+
+    return outlen + 1;
+}
+
+/*
+ * Add a parameter to the parameter linked list
+ */
+
+PM
+add_param(PM *first, PM *last, const char *name, const char *value)
+{
+    PM pm = mh_xmalloc(sizeof(*pm));
+
+    memset(pm, 0, sizeof(*pm));
+
+    pm->pm_name = getcpy(name);
+    pm->pm_value = getcpy(value);
+
+    if (*first) {
+       (*last)->pm_next = pm;
+       *last = pm;
+    } else {
+       *first = pm;
+       *last = pm;
+    }
+
+    return pm;
+}