/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* MIME parsing and structure management functions. */ #include "mod_mbox.h" /* Decode a multipart (or not) email. In order to support multiple * levels of MIME parts, this function is recursive. */ mbox_mime_message_t *mbox_mime_decode_multipart(apr_pool_t *p, char *body, char *ct, mbox_cte_e cte, char *boundary) { mbox_mime_message_t *mail; char *tmp = NULL, *k = NULL, *end_bound = NULL; char *headers_bound = NULL; if (!body) { return NULL; } /* Locate the end of part headers */ if (!ct) { headers_bound = ap_strstr(body, "\n\n"); if (!headers_bound) { return NULL; } } else { headers_bound = body; } /* If no Content-Type is provided, it means that we are parsing a sub-part of the multipart message. The Content-Type header should then be the first line of the part. If not, use text/plain as default for the sub-part. */ tmp = ap_strstr(body, "Content-Type: "); if (!ct && (!tmp || tmp > headers_bound)) { ct = "text/plain"; } mail = apr_pcalloc(p, sizeof(mbox_mime_message_t)); /* If no Content-Type is given, we have to look for it. */ if (!ct) { tmp += sizeof("Content-Type: ") - 1; k = strchr(tmp, ';'); /* Isolate the Content-Type string (between 'Content-Type: ' and ';' or end of line */ if (k && k < headers_bound) { *k = 0; } else { k = tmp; while (*k) { if (isspace(*k)) { *k = 0; break; } *k++; } } /* Copy the Content-Type and reset *k */ mail->content_type = apr_pstrdup(p, tmp); *k = ';'; /* If available, get MIME part name */ tmp = ap_strstr(body, "name="); if (tmp && tmp < headers_bound) { char c = '\0'; tmp += sizeof("name=") - 1; k = tmp; while (*k) { if (isspace(*k) || *k == ';') { c = *k; *k = 0; break; } *k++; } /* Check for double quotes */ if ((*tmp == '"') && (tmp[strlen(tmp) - 1] == '"')) { mail->content_name = apr_pstrndup(p, tmp + 1, strlen(tmp) - 2); } else { mail->content_name = apr_pstrdup(p, tmp); } *k = c; } } else { mail->content_type = ct; } /* Now we have a Content-Type. Look for other useful header information */ /* Check Content-Disposition if the match is within the headers */ tmp = ap_strstr(body, "Content-Disposition: "); if (tmp && tmp < headers_bound) { char c = '\0'; tmp += sizeof("Content-Disposition: ") - 1; k = tmp; while (*k) { if (isspace(*k) || *k == ';') { c = *k; *k = 0; break; } *k++; } /* Copy the Content-Disposition and reset *k */ mail->content_disposition = apr_pstrdup(p, tmp); *k = c; } else { mail->content_disposition = apr_pstrdup(p, "inline"); } /* Check Content-Transfer-Encoding, if needed */ if (cte == CTE_NONE) { tmp = ap_strstr(body, "Content-Transfer-Encoding: "); if (tmp && tmp < headers_bound) { char c = '\0'; tmp += sizeof("Content-Transfer-Encoding: ") - 1; k = tmp; while (*k) { if (isspace(*k) || *k == ';') { c = *k; *k = 0; break; } *k++; } /* Copy the Content-Disposition and reset *k */ mail->cte = mbox_parse_cte_header(tmp); *k = c; } } else { mail->cte = cte; } /* Now we have all the headers we need. Start processing the body. If the Content-Type was given at call time, the body starts where it's given. Otherwise it's after the headers (first new empty line) */ if (ct) { mail->body = body; } else { mail->body = ap_strstr(body, "\n\n"); if (mail->body != NULL) { mail->body += 2; } } /* If the mail is a multipart message, search for the boundary, and process its sub parts by recursive calls. */ if (strncmp(mail->content_type, "multipart/", strlen("multipart/")) == 0) { int end = 0, count = 0; char *search, *bound, *boundary_line; /* If the boundary was not given, we must look for it in the headers */ if (!boundary) { tmp = ap_strstr(body, "boundary=\""); if (!tmp) { return NULL; } tmp += sizeof("boundary=\"") - 1; k = tmp; while (*k) { if (*k == '"') { *k = 0; break; } *k++; } mail->boundary = apr_pstrdup(p, tmp); *k = '"'; } /* Otherwise, the boundary is as given to us */ else { mail->boundary = boundary; } /* Now we have our boundary string. We must : look for it once (begining of MIME part) and then look for the end boundary : --boundary-- to mark the end of the MIME part. In order to handle empty boundaries, we'll look for the boundary plus the \n. */ boundary_line = apr_pstrcat(p, "--", mail->boundary, NULL); /* The start boundary */ bound = ap_strstr(mail->body, boundary_line); if (!bound) { return NULL; } /* The end boudary */ end_bound = apr_psprintf(p, "--%s--", mail->boundary); tmp = ap_strstr(mail->body, end_bound); if (!tmp) { return NULL; } *tmp = 0; /* Set the search begining to the line after the start boundary. */ search = bound + strlen(boundary_line) + 1; /* While the MIME part is not finished, go through all sub parts */ while (!end) { char *inbound; inbound = ap_strstr(search, boundary_line); if (inbound) { *inbound = 0; } /* Allocate a new pointer for the sub part, and parse it. */ mail->sub = realloc(mail->sub, ++count * sizeof(struct mimemsg *)); mail->sub[count - 1] = mbox_mime_decode_multipart(p, search, NULL, CTE_NONE, NULL); /* If the boudary is found again, it means we have another MIME part in the same multipart message. Set the new search begining to the line after this new start boundary */ if (inbound) { *inbound = '-'; search = inbound + strlen(boundary_line) + 1; } /* Otherwise, the MIME part is finished. */ else { mail->sub_count = count; end = 1; } } /* Finally reset the end-body pointer. */ // *tmp = '-'; } /* If the parsed body is not multipart or is a MIME part, the body length is the length of the body string (no surprise here). If it's a MIME part, its correct length will be set after the call to mbox_mime_decode_multipart just a dozen lines above. */ else { if (mail->body != NULL) { mail->body_len = strlen(mail->body); } else { mail->body_len = 0; } } return mail; } /* Decode a MIME part body, according to its CTE. */ char *mbox_mime_decode_body(apr_pool_t *p, mbox_cte_e cte, char *body, apr_size_t len, apr_size_t *ret_len) { char *new_body; /* Failsafe : in case of body == NULL or len == 0, apr_pstrndup will not allocate anything, not even one byte for the '\0' */ if (!body || !len) { return NULL; } new_body = apr_pstrndup(p, body, len); if (cte == CTE_BASE64) { len = mbox_cte_decode_b64(new_body); if (ret_len) *ret_len = len; } else if (cte == CTE_QP) { len = mbox_cte_decode_qp(new_body); if (ret_len) *ret_len = len; } new_body[len] = 0; return new_body; } /* This function returns the relevant MIME part from a message. For * the moment, it just returns the first text/ MIME part available. */ char *mbox_mime_get_body(apr_pool_t *p, mbox_mime_message_t *m) { int i; /* If the message structure or the message body is empty, just return NULL */ if (!m || !m->body) { return MBOX_FETCH_ERROR_STR; } if (strncasecmp(m->content_type, "text/", strlen("text/")) == 0) { char *new_body; new_body = mbox_mime_decode_body(p, m->cte, m->body, m->body_len, NULL); if (!new_body) { return MBOX_FETCH_ERROR_STR; } m->body_len = mbox_cte_escape_html(p, new_body, m->body_len, &(m->body)); return apr_pstrndup(p, m->body, m->body_len); } if (!m->sub) { return MBOX_FETCH_ERROR_STR; } for (i = 0; i < m->sub_count; i++) { return mbox_mime_get_body(p, m->sub[i]); } return MBOX_FETCH_ERROR_STR; } /* Display an XHTML MIME structure */ void mbox_mime_display_static_structure(request_rec *r, mbox_mime_message_t *m, char *link) { int i; if (!m) { return; } ap_rputs("
  • ", r); if (m->body_len) { ap_rprintf(r, "", link); } if (m->content_name) { ap_rprintf(r, "%s", m->content_name); } else { ap_rprintf(r, "Unnamed %s", m->content_type); } if (m->body_len) { ap_rputs("", r); } ap_rprintf(r, " (%s, %s, %" APR_SIZE_T_FMT " bytes)
  • \n", m->content_disposition, mbox_cte_to_char(m->cte), m->body_len); if (!m->sub) { return; } for (i = 0; i < m->sub_count; i++) { ap_rputs("\n", r); } } /* Display an XML MIME structure */ void mbox_mime_display_xml_structure(request_rec *r, mbox_mime_message_t *m, char *link) { int i; if (!m) { return; } if (m->content_name) { ap_rprintf(r, "\n", m->content_name, m->content_disposition, mbox_cte_to_char(m->cte), m->body_len, link); } else { ap_rprintf(r, "\n", m->content_type, m->content_disposition, mbox_cte_to_char(m->cte), m->body_len, link); } if (!m->sub) { return; } ap_rputs("\n", r); for (i = 0; i < m->sub_count; i++) { if (link[strlen(link) - 1] == '/') { link[strlen(link) - 1] = 0; } mbox_mime_display_xml_structure(r, m->sub[i], apr_psprintf(r->pool, "%s/%d", link, i + 1)); } ap_rputs("\n", r); }