+/*
+ * Strip carriage returns from content.
+ */
+static int
+strip_crs (CT ct, int *message_mods) {
+ char *charset = content_charset (ct);
+ int status = OK;
+
+ /* Only strip carriage returns if content is ASCII or another
+ charset that has the same readily recognizable CR followed by a
+ LF. We can include UTF-8 here because if the high-order bit of
+ a UTF-8 byte is 0, then it must be a single-byte ASCII
+ character. */
+ if (! strcasecmp (charset, "US-ASCII") ||
+ ! strcasecmp (charset, "UTF-8") ||
+ ! strncasecmp (charset, "ISO-8859-", 9) ||
+ ! strncasecmp (charset, "WINDOWS-12", 10)) {
+ char **file = NULL;
+ FILE **fp = NULL;
+ size_t begin;
+ size_t end;
+ int has_crs = 0;
+ int opened_input_file = 0;
+
+ if (ct->c_cefile.ce_file) {
+ file = &ct->c_cefile.ce_file;
+ fp = &ct->c_cefile.ce_fp;
+ begin = end = 0;
+ } else if (ct->c_file) {
+ file = &ct->c_file;
+ fp = &ct->c_fp;
+ begin = (size_t) ct->c_begin;
+ end = (size_t) ct->c_end;
+ } /* else don't know where the content is */
+
+ if (file && *file && fp) {
+ if (! *fp) {
+ if ((*fp = fopen (*file, "r")) == NULL) {
+ advise (*file, "unable to open for reading");
+ status = NOTOK;
+ } else {
+ opened_input_file = 1;
+ }
+ }
+ }
+
+ if (fp && *fp) {
+ char buffer[BUFSIZ];
+ size_t bytes_read;
+ size_t bytes_to_read =
+ end > 0 && end > begin ? end - begin : sizeof buffer;
+
+ fseeko (*fp, begin, SEEK_SET);
+ while ((bytes_read = fread (buffer, 1,
+ min (bytes_to_read, sizeof buffer),
+ *fp)) > 0) {
+ /* Look for CR followed by a LF. This is supposed to
+ be text so there should be LF's. If not, don't
+ modify the content. */
+ char *cp;
+ size_t i;
+ int last_char_was_cr = 0;
+
+ if (end > 0) { bytes_to_read -= bytes_read; }
+
+ for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
+ if (*cp == '\n' && last_char_was_cr) {
+ has_crs = 1;
+ break;
+ }
+
+ last_char_was_cr = *cp == '\r' ? 1 : 0;
+ }
+ }
+
+ if (has_crs) {
+ int fd;
+ char *stripped_content_file;
+ char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL);
+
+ if (tempfile == NULL) {
+ adios (NULL, "unable to create temporary file in %s",
+ get_temp_dir());
+ }
+ stripped_content_file = add (tempfile, NULL);
+
+ /* Strip each CR before a LF from the content. */
+ fseeko (*fp, begin, SEEK_SET);
+ while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) >
+ 0) {
+ char *cp;
+ size_t i;
+ int last_char_was_cr = 0;
+
+ for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
+ if (*cp == '\r') {
+ last_char_was_cr = 1;
+ } else if (last_char_was_cr) {
+ if (*cp != '\n') {
+ if (write (fd, "\r", 1) < 0) {
+ advise (tempfile, "CR write");
+ }
+ }
+ if (write (fd, cp, 1) < 0) {
+ advise (tempfile, "write");
+ }
+ last_char_was_cr = 0;
+ } else {
+ if (write (fd, cp, 1) < 0) {
+ advise (tempfile, "write");
+ }
+ last_char_was_cr = 0;
+ }
+ }
+ }
+
+ if (close (fd)) {
+ admonish (NULL, "unable to write temporary file %s",
+ stripped_content_file);
+ (void) m_unlink (stripped_content_file);
+ status = NOTOK;
+ } else {
+ /* Replace the decoded file with the converted one. */
+ if (ct->c_cefile.ce_file && ct->c_cefile.ce_unlink)
+ (void) m_unlink (ct->c_cefile.ce_file);
+
+ mh_xfree(ct->c_cefile.ce_file);
+ ct->c_cefile.ce_file = stripped_content_file;
+ ct->c_cefile.ce_unlink = 1;
+
+ ++*message_mods;
+ if (verbosw) {
+ report (NULL, ct->c_partno,
+ begin == 0 && end == 0 ? "" : *file,
+ "stripped CRs");
+ }
+ }
+ }
+
+ if (opened_input_file) {
+ fclose (*fp);
+ *fp = NULL;
+ }
+ }
+ }
+
+ free (charset);
+
+ return status;
+}
+
+
+/*
+ * Add/update, if necessary, the message C-T-E, based on the least restrictive
+ * of the part C-T-E's.
+ */
+static void
+update_cte (CT ct) {
+ const int least_restrictive_enc = least_restrictive_encoding (ct);
+
+ if (least_restrictive_enc != CE_UNKNOWN &&
+ least_restrictive_enc != CE_7BIT) {
+ char *cte = concat (" ", ce_str (least_restrictive_enc), "\n", NULL);
+ HF hf;
+ int found_cte = 0;
+
+ /* Update/add Content-Transfer-Encoding header field. */
+ for (hf = ct->c_first_hf; hf; hf = hf->next) {
+ if (! strcasecmp (ENCODING_FIELD, hf->name)) {
+ found_cte = 1;
+ free (hf->value);
+ hf->value = cte;
+ }
+ }
+ if (! found_cte) {
+ add_header (ct, add (ENCODING_FIELD, NULL), cte);
+ }
+ }
+}
+
+
+/*
+ * Find the least restrictive encoding (7bit, 8bit, binary) of the parts
+ * within a message.
+ */
+static int
+least_restrictive_encoding (CT ct) {
+ int encoding = CE_UNKNOWN;
+
+ switch (ct->c_type) {
+ case CT_MULTIPART: {
+ struct multipart *m = (struct multipart *) ct->c_ctparams;
+ struct part *part;
+
+ for (part = m->mp_parts; part; part = part->mp_next) {
+ const int part_encoding =
+ least_restrictive_encoding (part->mp_part);
+
+ if (less_restrictive (encoding, part_encoding)) {
+ encoding = part_encoding;
+ }
+ }
+ break;
+ }
+
+ case CT_MESSAGE:
+ if (ct->c_subtype == MESSAGE_EXTERNAL) {
+ struct exbody *e = (struct exbody *) ct->c_ctparams;
+ const int part_encoding =
+ least_restrictive_encoding (e->eb_content);
+
+ if (less_restrictive (encoding, part_encoding)) {
+ encoding = part_encoding;
+ }
+ }
+ break;
+
+ default: {
+ if (less_restrictive (encoding, ct->c_encoding)) {
+ encoding = ct->c_encoding;
+ }
+ }}
+
+ return encoding;
+}
+
+
+/*
+ * Return whether the second encoding is less restrictive than the first, where
+ * "less restrictive" is in the sense used by RFC 2045 Secs. 6.1 and 6.4. So,
+ * CE_BINARY is less restrictive than CE_8BIT and
+ * CE_8BIT is less restrictive than CE_7BIT.
+ */
+static int
+less_restrictive (int encoding, int second_encoding) {
+ switch (second_encoding) {
+ case CE_BINARY:
+ return encoding != CE_BINARY;
+ case CE_8BIT:
+ return encoding != CE_BINARY && encoding != CE_8BIT;
+ case CE_7BIT:
+ return encoding != CE_BINARY && encoding != CE_8BIT &&
+ encoding != CE_7BIT;
+ default :
+ return 0;
+ }
+}
+
+
+/*
+ * Convert character set of each part.
+ */