+ /*
+ * Both of these functions do a NUL termination
+ */
+
+ if (encode)
+ i = encode_param(params, q, sizeof(line) - (q - line),
+ numchars, valoff, index);
+ else
+ i = normal_param(params, q, sizeof(line) - (q - line),
+ numchars, valoff);
+
+ if (i == 0) {
+ mh_xfree(paramout);
+ return NULL;
+ }
+
+ valoff += numchars;
+ index++;
+ curlen = param_len(params, index, valoff, &encode, &cont,
+ &numchars);
+ 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 (encode)
+ 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) {
+ mh_xfree(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.
+ *
+ * Arguments include
+ *
+ * pm - The parameter being output
+ * index - If continuing the parameter, the index of the section
+ * we're on.
+ * valueoff - The current offset into the parameter value that we're
+ * working on (previous sections have consumed valueoff bytes).
+ * encode - Set if we should perform encoding on this parameter section
+ * (given that we're consuming bytesfit bytes).
+ * cont - Set if the remaining data in value will not fit on a single
+ * line and will need to be continued.
+ * bytesfit - The number of bytes that we can consume from the parameter
+ * value and still fit on a completely new line. The
+ * calculation assumes the new line starts with a tab,
+ * includes the parameter name and any encoding, and fits
+ * within CPERLIN bytes. Will always be at least 1.
+ */
+
+static size_t
+param_len(PM pm, int index, size_t valueoff, int *encode, int *cont,
+ size_t *bytesfit)
+{
+ char *start = pm->pm_value + valueoff, *p, indexchar[32];
+ size_t len = 0, fit = 0;
+ int fitlimit = 0, eightbit, maxfit;
+
+ *encode = 0;
+
+ /*
+ * Add up the length. First, start with the parameter name.
+ */
+
+ len = strlen(pm->pm_name);
+
+ /*
+ * Scan the parameter value and see if we need to do encoding for this
+ * section.
+ */
+
+ eightbit = contains8bit(start, NULL);
+
+ /*
+ * Determine if we need to encode this section. Encoding is necessary if:
+ *
+ * - There are any 8-bit characters at all and we're on the first
+ * section.
+ * - There are 8-bit characters within N bytes of our section start.
+ * N is calculated based on the number of bytes it would take to
+ * reach CPERLIN. Specifically:
+ * 8 (starting tab) +
+ * strlen(param name) +
+ * 4 ('* for section marker, '=', opening/closing '"')
+ * strlen (index)
+ * is the number of bytes used by everything that isn't part of the
+ * value. So that gets subtracted from CPERLIN.
+ */
+
+ snprintf(indexchar, sizeof(indexchar), "%d", index);
+ maxfit = CPERLIN - (12 + len + strlen(indexchar));
+ if ((eightbit && index == 0) || contains8bit(start, start + maxfit)) {
+ *encode = 1;
+ }
+
+ len++; /* Add in equal sign */
+
+ if (*encode) {
+ /*
+ * We're using maxfit as a marker for how many characters we can
+ * fit into the line. Bump it by two because we're not using quotes
+ * when encoding.
+ */
+
+ maxfit += 2;
+
+ /*
+ * If we don't have a charset or language tag in this parameter,
+ * add them now.
+ */
+
+ if (! pm->pm_charset) {
+ pm->pm_charset = mh_xstrdup(write_charset_8bit());
+ if (strcasecmp(pm->pm_charset, "US-ASCII") == 0)
+ adios(NULL, "8-bit characters in parameter \"%s\", but "
+ "local character set is US-ASCII", pm->pm_name);
+ }
+ if (! pm->pm_lang)
+ pm->pm_lang = mh_xstrdup(""); /* Default to a blank lang tag */
+
+ len++; /* For the encoding marker */
+ maxfit--;
+ if (index == 0) {
+ int enclen = strlen(pm->pm_charset) + strlen(pm->pm_lang) + 2;
+ len += enclen;
+ maxfit-= enclen;
+ } else {
+ /*
+ * We know we definitely need to include an index. maxfit already
+ * includes the section marker.
+ */
+ len += strlen(indexchar);
+ }
+ for (p = start; *p != '\0'; p++) {
+ if (isparamencode(*p)) {
+ len += 3;
+ maxfit -= 3;
+ } else {
+ len++;
+ maxfit--;
+ }
+ /*
+ * Just so there's no confusion: maxfit is counting OUTPUT
+ * characters (post-encoding). fit is counting INPUT characters.
+ */
+ if (! fitlimit && maxfit >= 0)
+ fit++;
+ else if (! fitlimit)
+ fitlimit++;
+ }