+
+int
+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;
+
+ cp++;
+ while (isspace ((unsigned char) *cp))
+ cp++;
+
+ if (*cp == '(' &&
+ get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
+ return NOTOK;
+ }
+
+ if (*cp == 0) {
+ advise (NULL,
+ "extraneous trailing ';' in message %s's %s: "
+ "parameter list",
+ filename, fieldname);
+ return DONE;
+ }
+
+ /* down case the attribute name */
+ for (dp = cp; istoken ((unsigned char) *dp); dp++)
+ if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
+ *dp = tolower ((unsigned char) *dp);
+
+ for (up = dp; isspace ((unsigned char) *dp);)
+ dp++;
+ if (dp == cp || *dp != '=') {
+ advise (NULL,
+ "invalid parameter in message %s's %s: "
+ "field\n%*sparameter %s (error detected at offset %d)",
+ filename, fieldname, strlen(invo_name) + 2, "",cp, dp - cp);
+ return NOTOK;
+ }
+
+ 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. */
+
+ vp = pm->pm_name + (dp - cp);
+
+ if (*dp == '"') {
+ for (cp = ++dp, dp = vp;;) {
+ switch (c = *cp++) {
+ case '\0':
+bad_quote:
+ advise (NULL,
+ "invalid quoted-string in message %s's %s: "
+ "field\n%*s(parameter %s)",
+ filename, fieldname, strlen(invo_name) + 2, "",
+ pm->pm_name);
+ return NOTOK;
+
+ case '\\':
+ *dp++ = c;
+ if ((c = *cp++) == '\0')
+ goto bad_quote;
+ /* else fall... */
+
+ default:
+ *dp++ = c;
+ continue;
+
+ case '"':
+ *dp = '\0';
+ break;
+ }
+ break;
+ }
+ } else {
+ for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
+ 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, fieldname, strlen(invo_name) + 2, "",
+ pm->pm_name);
+ return NOTOK;
+ }
+
+ while (isspace ((unsigned char) *cp))
+ cp++;
+
+ if (*cp == '(' &&
+ get_comment (filename, fieldname, &cp, commentp) == NOTOK) {
+ return NOTOK;
+ }
+
+ if (*param_head == NULL) {
+ *param_head = pm;
+ *param_tail = pm;
+ } else {
+ (*param_tail)->pm_next = pm;
+ *param_tail = pm;
+ }
+ }
+
+ *header_attrp = cp;
+ return OK;
+}
+
+/*
+ * Create a string based on a list of output parameters. Assume that this
+ * parameter string will be appended to an existing header, so start out
+ * with the separator (;). Perform RFC 2231 encoding when necessary.
+ */
+
+char *
+output_params(size_t initialwidth, PM params, int *offsetout)
+{
+ char *paramout = NULL;
+ char line[CPERLIN * 2], *q;
+ int curlen, index, eightbit, encode, i;
+ size_t valoff;
+
+ while (params != NULL) {
+ encode = 0;
+ index = 0;
+ valoff = 0;
+ q = line;
+
+ if (strlen(params->pm_name) > CPERLIN) {
+ advise(NULL, "Parameter name \"%s\" is too long", params->pm_name);
+ if (paramout)
+ free(paramout);
+ return NULL;
+ }
+
+ curlen = param_len(params, index, valoff, &eightbit);
+
+ /*
+ * 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.
+ */
+
+ while (curlen + 8 > CPERLIN - 1) {
+ int curvallen = strlen(params->pm_value + valoff) -
+ (curlen + 8 - (CPERLIN - 1));
+
+ *q++ = ';';
+ *q++ = '\n';
+ *q++ = '\t';
+
+ /*
+ * curvallen holds how many characters we take from this
+ * current value. Make sure it's at least 1.
+ */
+
+ if (curvallen < 1)
+ curvallen = 1;
+
+ /*
+ * At this point we're definitely continuing the line, so
+ * be sure to include the parameter name and section 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
+ * the character set and encode the first section. Otherwise
+ * only encode if the section we're on contains an 8bit character.
+ */
+
+ if (eightbit && index == 0)
+ encode = 1;
+ else if (eightbit && contains8bit(params->pm_value + valoff,
+ params->pm_value + valoff + curvallen))
+ encode = 1;
+
+ /*
+ * 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;
+ }
+
+ if (offsetout)
+ *offsetout = initialwidth;
+
+ return paramout;
+}
+
+/*
+ * Calculate the size of a parameter. Include any necessary encoding.
+ * Start the length computation from where "offset" is marked.
+ */
+
+static size_t
+param_len(PM pm, int index, size_t valueoff, int *eightbit)
+{
+ char *start = pm->pm_value + valueoff, *p;
+ size_t len = 0;
+
+ /*
+ * Add up the length. First, start with the parameter name, and include
+ * the equal sign.
+ */
+
+ len += strlen(pm->pm_name) + 1;
+
+ /*
+ * Scan the parameter value. If we find an 8-bit character, then
+ * we need to compute the locale name for the length.
+ */
+
+ *eightbit = contains8bit(start, NULL);
+
+ /*
+ * If we've got 8-bit character, put the locale on the front (if we're
+ * doing part 0. Also compute the length of the string based on the
+ * encoding we need to do.
+ */
+
+ 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(pm->pm_charset) + strlen(pm->pm_lang) + 2;
+ } else {
+ /*
+ * We know we definitely need to include an index.
+ * This will get the length wrong if we have more than 99
+ * sections. I can live with that.
+ */
+ len += 2; /* *<N> */
+ if (index > 9)
+ len++;
+ }
+ for (p = start; *p != '\0'; p++) {
+ if (isparamencode(*p))
+ len += 3;
+ else
+ len++;
+ }
+ } else {
+ /*
+ * Calculate the string length, but add room for quoting \
+ * and " if necessary. Also account for quotes at beginning
+ * and end.
+ */
+ for (p = start; *p != '\0'; p++) {
+ switch (*p) {
+ case '"':
+ case '\\':
+ len++;
+ /* FALL THROUGH */
+ default:
+ len++;
+ }
+ }
+
+ len += 2;
+ }
+
+ return len;
+}
+
+/*
+ * Output an encoded parameter string.
+ */
+
+static size_t
+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, *p;
+
+ /*
+ * First, output the marker for an encoded string.
+ */
+
+ *output++ = '*';
+ *output++ = '=';
+ outlen += 2;
+
+ /*
+ * 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().
+ */
+
+ 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;
+}