/*====================================================================== FILE: sspm.c Parse Mime CREATOR: eric 25 June 2000 (C) COPYRIGHT 2000, Eric Busboom http://www.softwarestudio.org This library is free software; you can redistribute it and/or modify it under the terms of either: The LGPL as published by the Free Software Foundation, version 2.1, available at: http://www.gnu.org/licenses/lgpl-2.1.html Or: The Mozilla Public License Version 2.0. You may obtain a copy of the License at http://www.mozilla.org/MPL/ This library is free software; you can redistribute it and/or modify it under the terms of either: The LGPL as published by the Free Software Foundation, version 2.1, available at: http://www.gnu.org/licenses/lgpl-2.1.html Or: The Mozilla Public License Version 2.0. You may obtain a copy of the License at http://www.mozilla.org/MPL/ The Initial Developer of the Original Code is Eric Busboom ======================================================================*/ #ifdef HAVE_CONFIG_H #include #endif #include "sspm.h" #include #include #include #define TMP_BUF_SIZE 1024 enum line_type { EMPTY, BLANK, MIME_HEADER, MAIL_HEADER, HEADER_CONTINUATION, BOUNDARY, TERMINATING_BOUNDARY, UNKNOWN_TYPE }; enum mime_state { UNKNOWN_STATE, IN_HEADER, END_OF_HEADER, IN_BODY, OPENING_PART, END_OF_PART, TERMINAL_END_OF_PART, END_OF_INPUT }; struct mime_impl { struct sspm_part *parts; size_t max_parts; int part_no; int level; const struct sspm_action_map *actions; char *(*get_string) (char *s, size_t size, void *data); void *get_string_data; char temp[TMP_BUF_SIZE]; enum mime_state state; }; static void *sspm_make_multipart_part(struct mime_impl *impl, struct sspm_header *header); static void *sspm_make_multipart_subpart(struct mime_impl *impl, struct sspm_header *parent_header); static struct major_content_type_map { enum sspm_major_type type; const char *str; } major_content_type_map[] = { {SSPM_MULTIPART_MAJOR_TYPE, "multipart"}, {SSPM_TEXT_MAJOR_TYPE, "text"}, {SSPM_TEXT_MAJOR_TYPE, "text"}, {SSPM_IMAGE_MAJOR_TYPE, "image"}, {SSPM_AUDIO_MAJOR_TYPE, "audio"}, {SSPM_VIDEO_MAJOR_TYPE, "video"}, {SSPM_APPLICATION_MAJOR_TYPE, "application"}, {SSPM_MULTIPART_MAJOR_TYPE, "multipart"}, {SSPM_MESSAGE_MAJOR_TYPE, "message"}, {SSPM_UNKNOWN_MAJOR_TYPE, ""} }; static struct minor_content_type_map { enum sspm_minor_type type; const char *str; } minor_content_type_map[] = { {SSPM_ANY_MINOR_TYPE, "*"}, {SSPM_PLAIN_MINOR_TYPE, "plain"}, {SSPM_RFC822_MINOR_TYPE, "rfc822"}, {SSPM_DIGEST_MINOR_TYPE, "digest"}, {SSPM_CALENDAR_MINOR_TYPE, "calendar"}, {SSPM_MIXED_MINOR_TYPE, "mixed"}, {SSPM_RELATED_MINOR_TYPE, "related"}, {SSPM_ALTERNATIVE_MINOR_TYPE, "alternative"}, {SSPM_PARALLEL_MINOR_TYPE, "parallel"}, {SSPM_UNKNOWN_MINOR_TYPE, ""} }; static struct encoding_map { enum sspm_encoding encoding; const char *str; } sspm_encoding_map[] = { {SSPM_NO_ENCODING, ""}, {SSPM_QUOTED_PRINTABLE_ENCODING, "quoted-printable"}, {SSPM_8BIT_ENCODING, "8bit"}, {SSPM_7BIT_ENCODING, "7bit"}, {SSPM_BINARY_ENCODING, "binary"}, {SSPM_BASE64_ENCODING, "base64"}, {SSPM_UNKNOWN_ENCODING, ""} }; static char *sspm_strdup(const char *str) { char *s; s = strdup(str); return s; } static char *sspm_get_parameter(const char *line, const char *parameter) { char *p, *s, *q; static char name[1024]; /* Find where the parameter name is in the line */ p = strstr(line, parameter); if (p == 0) { return 0; } /* skip over the parameter name, the '=' and any blank spaces */ p += strlen(parameter); while (*p == ' ' || *p == '=') { p++; } /*now find the next semicolon */ s = strchr(p, ';'); /* Strip of leading quote */ q = strchr(p, '\"'); if (q != 0) { p = q + 1; } if (s != 0) { strncpy(name, p, (size_t) s - (size_t) p); } else { strncpy(name, p, sizeof(name) - 1); name[sizeof(name) - 1] = '\0'; } /* Strip off trailing quote, if it exists */ q = strrchr(name, '\"'); if (q != 0) { *q = '\0'; } return name; } static char *sspm_property_name(const char *line) { static char name[1024]; char *c = strchr(line, ':'); if (c != 0) { strncpy(name, line, (size_t) c - (size_t) line); name[(size_t) c - (size_t) line] = '\0'; return name; } else { return 0; } } static char *sspm_value(char *line) { static char value[1024]; char *c, *s, *p; /* Find the first colon and the next semicolon */ value[0] = 0; c = strchr(line, ':'); if (!c) { return value; } s = strchr(c, ';'); /* Skip the colon */ c++; if (s == 0) { s = c + strlen(line); } for (p = value; c != s; c++) { if (*c != ' ' && *c != '\n') { *(p++) = *c; } } *p = '\0'; return value; } static const char *mime_headers[] = { "Content-Type", "Content-Transfer-Encoding", "Content-Disposition", "Content-Id", "Mime-Version", 0 }; static void *sspm_default_new_part(void) { return 0; } static void sspm_default_add_line(void *part, struct sspm_header *header, const char *line, size_t size) { _unused(part); _unused(header); _unused(line); _unused(size); } static void *sspm_default_end_part(void *part) { _unused(part); return 0; } static void sspm_default_free_part(void *part) { _unused(part); } static struct sspm_action_map sspm_action_map[] = { {SSPM_UNKNOWN_MAJOR_TYPE, SSPM_UNKNOWN_MINOR_TYPE, sspm_default_new_part, sspm_default_add_line, sspm_default_end_part, sspm_default_free_part}, }; static int sspm_is_mime_header(char *line) { char *name = sspm_property_name(line); int i; if (name == 0) { return 0; } for (i = 0; mime_headers[i] != 0; i++) { if (strcasecmp(name, mime_headers[i]) == 0) { return 1; } } return 0; } static int sspm_is_mail_header(char *line) { char *name = sspm_property_name(line); if (name != 0) { return 1; } return 0; } static int sspm_is_blank(char *line) { char *p; char c = 0; for (p = line; *p != 0; p++) { if (!(*p == ' ' || *p == '\t' || *p == '\n')) { c++; } } if (c == 0) { return 1; } return 0; } static int sspm_is_continuation_line(char *line) { if (line[0] == ' ' || line[0] == '\t') { return 1; } return 0; } static int sspm_is_mime_boundary(char *line) { if (line[0] == '-' && line[1] == '-') { return 1; } return 0; } static int sspm_is_mime_terminating_boundary(char *line) { if (sspm_is_mime_boundary(line) && strstr(line, "--\n")) { return 1; } return 0; } static enum line_type get_line_type(char *line) { if (line == 0) { return EMPTY; } else if (sspm_is_blank(line)) { return BLANK; } else if (sspm_is_mime_header(line)) { return MIME_HEADER; } else if (sspm_is_mail_header(line)) { return MAIL_HEADER; } else if (sspm_is_continuation_line(line)) { return HEADER_CONTINUATION; } else if (sspm_is_mime_terminating_boundary(line)) { return TERMINATING_BOUNDARY; } else if (sspm_is_mime_boundary(line)) { return BOUNDARY; } else { return UNKNOWN_TYPE; } } static struct sspm_action_map get_action(struct mime_impl *impl, enum sspm_major_type major, enum sspm_minor_type minor) { size_t i; size_t len; /* Read caller suppled action map */ if (impl->actions != 0) { for (i = 0; impl->actions[i].major != SSPM_UNKNOWN_MAJOR_TYPE; i++) { if ((major == impl->actions[i].major && minor == impl->actions[i].minor) || (major == impl->actions[i].major && minor == SSPM_ANY_MINOR_TYPE)) { return impl->actions[i]; } } } /* Else, read default action map */ len = sizeof(sspm_action_map) / sizeof(sspm_action_map[0]); for (i = 0; i < len && sspm_action_map[i].major != SSPM_UNKNOWN_MAJOR_TYPE; i++) { if ((major == sspm_action_map[i].major && minor == sspm_action_map[i].minor) || (major == sspm_action_map[i].major && minor == SSPM_ANY_MINOR_TYPE)) { return sspm_action_map[i]; } } assert(i < len); /*should return before now */ return sspm_action_map[0]; } static char *sspm_lowercase(char *str) { char *p = 0; char *new; if (str == 0) { return 0; } new = sspm_strdup(str); for (p = new; *p != 0; p++) { *p = tolower((int)*p); } return new; } static enum sspm_major_type sspm_find_major_content_type(char *type) { int i; char *ltype = sspm_lowercase(type); for (i = 0; major_content_type_map[i].type != SSPM_UNKNOWN_MAJOR_TYPE; i++) { if (strncmp(ltype, major_content_type_map[i].str, strlen(major_content_type_map[i].str)) == 0) { free(ltype); return major_content_type_map[i].type; } } free(ltype); return major_content_type_map[i].type; /* Should return SSPM_UNKNOWN_MINOR_TYPE */ } static enum sspm_minor_type sspm_find_minor_content_type(char *type) { int i; char *ltype = sspm_lowercase(type); char *p = strchr(ltype, '/'); if (p == 0) { free(ltype); return SSPM_UNKNOWN_MINOR_TYPE; } p++; /* Skip the '/' */ for (i = 0; minor_content_type_map[i].type != SSPM_UNKNOWN_MINOR_TYPE; i++) { if (strncmp(p, minor_content_type_map[i].str, strlen(minor_content_type_map[i].str)) == 0) { free(ltype); return minor_content_type_map[i].type; } } free(ltype); return minor_content_type_map[i].type; /* Should return SSPM_UNKNOWN_MINOR_TYPE */ } const char *sspm_major_type_string(enum sspm_major_type type) { int i; for (i = 0; major_content_type_map[i].type != SSPM_UNKNOWN_MAJOR_TYPE; i++) { if (type == major_content_type_map[i].type) { return major_content_type_map[i].str; } } return major_content_type_map[i].str; /* Should return SSPM_UNKNOWN_MINOR_TYPE */ } const char *sspm_minor_type_string(enum sspm_minor_type type) { int i; for (i = 0; minor_content_type_map[i].type != SSPM_UNKNOWN_MINOR_TYPE; i++) { if (type == minor_content_type_map[i].type) { return minor_content_type_map[i].str; } } return minor_content_type_map[i].str; /* Should return SSPM_UNKNOWN_MINOR_TYPE */ } const char *sspm_encoding_string(enum sspm_encoding type) { int i; for (i = 0; sspm_encoding_map[i].encoding != SSPM_UNKNOWN_ENCODING; i++) { if (type == sspm_encoding_map[i].encoding) { return sspm_encoding_map[i].str; } } return sspm_encoding_map[i].str; /* Should return SSPM_UNKNOWN_MINOR_TYPE */ } /* Interpret a header line and add its data to the header structure. */ static void sspm_build_header(struct sspm_header *header, char *line) { char *prop; char *val; val = sspm_strdup(sspm_value(line)); prop = sspm_strdup(sspm_property_name(line)); if (strcasecmp(prop, "Content-Type") == 0) { /* Create a new mime_header, fill in content-type and possibly boundary */ char *boundary = sspm_get_parameter(line, "boundary"); header->def = 0; header->major = sspm_find_major_content_type(val); header->minor = sspm_find_minor_content_type(val); if (header->minor == SSPM_UNKNOWN_MINOR_TYPE) { char *p = strchr(val, '/'); if (p != 0) { p++; /* Skip the '/' */ header->minor_text = sspm_strdup(p); } else { /* Error, malformed content type */ header->minor_text = sspm_strdup("unknown"); } } if (boundary != 0) { header->boundary = sspm_strdup(boundary); } } else if (strcasecmp(prop, "Content-Transfer-Encoding") == 0) { char *encoding = sspm_value(line); char *lencoding = sspm_lowercase(encoding); if (strcasecmp(lencoding, "base64") == 0) { header->encoding = SSPM_BASE64_ENCODING; } else if (strcasecmp(lencoding, "quoted-printable") == 0) { header->encoding = SSPM_QUOTED_PRINTABLE_ENCODING; } else if (strcasecmp(lencoding, "binary") == 0) { header->encoding = SSPM_BINARY_ENCODING; } else if (strcasecmp(lencoding, "7bit") == 0) { header->encoding = SSPM_7BIT_ENCODING; } else if (strcasecmp(lencoding, "8bit") == 0) { header->encoding = SSPM_8BIT_ENCODING; } else { header->encoding = SSPM_UNKNOWN_ENCODING; } free(lencoding); header->def = 0; } else if (strcasecmp(prop, "Content-Id") == 0) { char *cid = sspm_value(line); header->content_id = sspm_strdup(cid); header->def = 0; } free(val); free(prop); } static char *sspm_get_next_line(struct mime_impl *impl) { char *s; s = impl->get_string(impl->temp, TMP_BUF_SIZE, impl->get_string_data); if (s == 0) { impl->state = END_OF_INPUT; } return s; } static void sspm_store_part(struct mime_impl *impl, struct sspm_header header, int level, void *part, size_t size) { impl->parts[impl->part_no].header = header; impl->parts[impl->part_no].level = level; impl->parts[impl->part_no].data = part; impl->parts[impl->part_no].data_size = size; impl->part_no++; } static void sspm_set_error(struct sspm_header *header, enum sspm_error error, char *message) { header->error = error; if (header->error_text != 0) { free(header->error_text); } header->def = 0; if (message != 0) { header->error_text = sspm_strdup(message); } else { header->error_text = 0; } } static void sspm_read_header(struct mime_impl *impl, struct sspm_header *header) { #define MAX_HEADER_LINES 25 char *buf; char header_lines[MAX_HEADER_LINES][TMP_BUF_SIZE]; /* HACK, hard limits TODO */ int current_line = -1; int end = 0; memset(header_lines, 0, sizeof(header_lines)); memset(header, 0, sizeof(struct sspm_header)); /* Set up default header */ header->def = 1; header->major = SSPM_TEXT_MAJOR_TYPE; header->minor = SSPM_PLAIN_MINOR_TYPE; header->error = SSPM_NO_ERROR; header->error_text = 0; /* Read all of the lines into memory */ while (current_line < (MAX_HEADER_LINES - 2) && (end == 0) && ((buf = sspm_get_next_line(impl)) != 0)) { enum line_type line_type = get_line_type(buf); switch (line_type) { case BLANK:{ end = 1; impl->state = END_OF_HEADER; break; } case MAIL_HEADER: case MIME_HEADER:{ impl->state = IN_HEADER; current_line++; assert(strlen(buf) < TMP_BUF_SIZE); strncpy(header_lines[current_line], buf, TMP_BUF_SIZE); header_lines[current_line][TMP_BUF_SIZE - 1] = '\0'; break; } case HEADER_CONTINUATION:{ char *last_line; char *buf_start; if (current_line < 0) { /* This is not really a continuation line, since we have not see any header line yet */ sspm_set_error(header, SSPM_MALFORMED_HEADER_ERROR, buf); return; } last_line = header_lines[current_line]; impl->state = IN_HEADER; /* skip over the spaces in buf start, and remove the new line at the end of the lat line */ if (last_line[strlen(last_line) - 1] == '\n') { last_line[strlen(last_line) - 1] = '\0'; } buf_start = buf; while (*buf_start == ' ' || *buf_start == '\t') { buf_start++; } assert(strlen(buf_start) + strlen(last_line) < TMP_BUF_SIZE); strncat(last_line, buf_start, TMP_BUF_SIZE - strlen(last_line) - 1); break; } default:{ sspm_set_error(header, SSPM_MALFORMED_HEADER_ERROR, buf); return; } } } for (current_line = 0; current_line < MAX_HEADER_LINES && header_lines[current_line][0] != 0; current_line++) { sspm_build_header(header, header_lines[current_line]); } } static void sspm_make_part(struct mime_impl *impl, struct sspm_header *header, struct sspm_header *parent_header, void **end_part, size_t *size) { /* For a single part type, read to the boundary, if there is a boundary. Otherwise, read until the end of input. This routine assumes that the caller has read the header and has left the input at the first blank line */ char *line; void *part; int end = 0; struct sspm_action_map action = get_action(impl, header->major, header->minor); *size = 0; part = action.new_part(); impl->state = IN_BODY; while (end == 0 && (line = sspm_get_next_line(impl)) != 0) { if (sspm_is_mime_boundary(line)) { /* If there is a boundary, then this must be a multipart part, so there must be a parent_header. */ if (parent_header == 0) { char *boundary; end = 1; *end_part = 0; sspm_set_error(header, SSPM_UNEXPECTED_BOUNDARY_ERROR, line); /* Read until the paired terminating boundary */ if ((boundary = (char *)malloc(strlen(line) + 5)) == 0) { fprintf(stderr, "Out of memory"); abort(); } strcpy(boundary, line); strcat(boundary, "--"); while ((line = sspm_get_next_line(impl)) != 0) { /*printf("Error: %s\n",line); */ if (strcmp(boundary, line) == 0) { break; } } free(boundary); break; } if (strcmp((line + 2), parent_header->boundary) == 0) { *end_part = action.end_part(part); if (sspm_is_mime_boundary(line)) { impl->state = END_OF_PART; } else if (sspm_is_mime_terminating_boundary(line)) { impl->state = TERMINAL_END_OF_PART; } end = 1; } else { /* Error, this is not the correct terminating boundary */ /* read and discard until we get the right boundary. */ char *boundary; char msg[256]; snprintf(msg, 256, "Expected: %s--. Got: %s", parent_header->boundary, line); sspm_set_error(parent_header, SSPM_WRONG_BOUNDARY_ERROR, msg); /* Read until the paired terminating boundary */ if ((boundary = (char *)malloc(strlen(line) + 5)) == 0) { fprintf(stderr, "Out of memory"); abort(); } strcpy(boundary, line); strcat(boundary, "--"); while ((line = sspm_get_next_line(impl)) != 0) { if (strcmp(boundary, line) == 0) { break; } } free(boundary); } } else { char *data = 0; char *rtrn = 0; *size = strlen(line); data = (char *)malloc(*size + 2); assert(data != 0); if (header->encoding == SSPM_BASE64_ENCODING) { rtrn = decode_base64(data, line, size); } else if (header->encoding == SSPM_QUOTED_PRINTABLE_ENCODING) { rtrn = decode_quoted_printable(data, line, size); } if (rtrn == 0) { strcpy(data, line); } /* add a end-of-string after the data, just in case binary data from decode64 gets passed to a tring handling routine in add_line */ data[*size + 1] = '\0'; action.add_line(part, header, data, *size); free(data); } } if (end == 0) { /* End the part if the input is exhausted */ *end_part = action.end_part(part); } return; } static void *sspm_make_multipart_part(struct mime_impl *impl, struct sspm_header *header) { void *part = 0; /* Now descend a level into each of the children of this part */ impl->level++; /* Now we are working on the CHILD */ memset(&(impl->parts[impl->part_no]), 0, sizeof(struct sspm_part)); do { part = sspm_make_multipart_subpart(impl, header); if (part == 0) { /* Clean up the part in progress */ impl->parts[impl->part_no].header.major = SSPM_NO_MAJOR_TYPE; impl->parts[impl->part_no].header.minor = SSPM_NO_MINOR_TYPE; } } while (get_line_type(impl->temp) != TERMINATING_BOUNDARY && impl->state != END_OF_INPUT); impl->level--; return 0; } static void *sspm_make_multipart_subpart(struct mime_impl *impl, struct sspm_header *parent_header) { struct sspm_header header; char *line; void *part; size_t size; if (parent_header->boundary == 0) { /* Error. Multipart headers must have a boundary */ sspm_set_error(parent_header, SSPM_NO_BOUNDARY_ERROR, 0); /* read all of the reamining lines */ while ((line = sspm_get_next_line(impl)) != 0) { } return 0; } /* Step 1: Read the opening boundary */ if (get_line_type(impl->temp) != BOUNDARY) { while ((line = sspm_get_next_line(impl)) != 0) { if (sspm_is_mime_boundary(line)) { assert(parent_header != 0); /* Check if it is the right boundary */ if (!sspm_is_mime_terminating_boundary(line) && strcmp((line + 2), parent_header->boundary) == 0) { /* The +2 in strncmp skips over the leading "--" */ break; } else { /* Got the wrong boundary, so read and discard until we get the right boundary. */ char *boundary; char msg[256]; snprintf(msg, 256, "Expected: %s. Got: %s", parent_header->boundary, line); sspm_set_error(parent_header, SSPM_WRONG_BOUNDARY_ERROR, msg); /* Read until the paired terminating boundary */ if ((boundary = (char *)malloc(strlen(line) + 5)) == 0) { fprintf(stderr, "Out of memory"); abort(); } strcpy(boundary, line); strcat(boundary, "--"); while ((line = sspm_get_next_line(impl)) != 0) { if (strcmp(boundary, line) == 0) { break; } } free(boundary); return 0; } } } } /* Step 2: Get the part header */ sspm_read_header(impl, &header); /* If the header is still listed as default, there was probably an error */ if (header.def == 1 && header.error != SSPM_NO_ERROR) { sspm_set_error(&header, SSPM_NO_HEADER_ERROR, 0); return 0; } if (header.error != SSPM_NO_ERROR) { sspm_store_part(impl, header, impl->level, 0, 0); return 0; } /* Step 3: read the body */ if (header.major == SSPM_MULTIPART_MAJOR_TYPE) { struct sspm_header *child_header; child_header = &(impl->parts[impl->part_no].header); /* Store the multipart part */ sspm_store_part(impl, header, impl->level, 0, 0); /* now get all of the sub-parts */ part = sspm_make_multipart_part(impl, child_header); if (get_line_type(impl->temp) != TERMINATING_BOUNDARY) { sspm_set_error(child_header, SSPM_NO_BOUNDARY_ERROR, impl->temp); return 0; } (void)sspm_get_next_line(impl); /* Step past the terminating boundary */ } else { sspm_make_part(impl, &header, parent_header, &part, &size); memset(&(impl->parts[impl->part_no]), 0, sizeof(struct sspm_part)); sspm_store_part(impl, header, impl->level, part, size); } return part; } /* Root routine for parsing mime entries*/ int sspm_parse_mime(struct sspm_part *parts, size_t max_parts, const struct sspm_action_map *actions, char *(*get_string) (char *s, size_t size, void *data), void *get_string_data, struct sspm_header *first_header) { struct mime_impl impl; struct sspm_header header; int i; _unused(first_header); /* Initialize all of the data */ memset(&impl, 0, sizeof(struct mime_impl)); memset(&header, 0, sizeof(struct sspm_header)); for (i = 0; i < (int)max_parts; i++) { parts[i].header.major = SSPM_NO_MAJOR_TYPE; parts[i].header.minor = SSPM_NO_MINOR_TYPE; } impl.parts = parts; impl.max_parts = max_parts; impl.part_no = 0; impl.actions = actions; impl.get_string = get_string; impl.get_string_data = get_string_data; /* Read the header of the message. This will be the email header, unless first_header is specified. But ( HACK) that var is not currently being used */ sspm_read_header(&impl, &header); if (header.major == SSPM_MULTIPART_MAJOR_TYPE) { struct sspm_header *child_header; child_header = &(impl.parts[impl.part_no].header); sspm_store_part(&impl, header, impl.level, 0, 0); (void)sspm_make_multipart_part(&impl, child_header); } else { void *part; size_t size; sspm_make_part(&impl, &header, 0, &part, &size); memset(&(impl.parts[impl.part_no]), 0, sizeof(struct sspm_part)); sspm_store_part(&impl, header, impl.level, part, size); } return 0; } static void sspm_free_header(struct sspm_header *header) { if (header->boundary != 0) { free(header->boundary); } if (header->minor_text != 0) { free(header->minor_text); } if (header->charset != 0) { free(header->charset); } if (header->filename != 0) { free(header->filename); } if (header->content_id != 0) { free(header->content_id); } if (header->error_text != 0) { free(header->error_text); } } void sspm_free_parts(struct sspm_part *parts, size_t max_parts) { int i; for (i = 0; i < (int)max_parts && parts[i].header.major != SSPM_NO_MAJOR_TYPE; i++) { sspm_free_header(&(parts[i].header)); } } /*********************************************************************** The remaining code is beased on code from the mimelite distribution, which has the following notice: | Authorship: | Copyright (c) 1994 Gisle Hannemyr. | Permission is granted to hack, make and distribute copies of this | program as long as this copyright notice is not removed. | Flames, bug reports, comments and improvements to: | snail: Gisle Hannemyr, Brageveien 3A, 0452 Oslo, Norway | email: Inet: gisle@oslonett.no The code is heavily modified by Eric Busboom. ***********************************************************************/ char *decode_quoted_printable(char *dest, char *src, size_t *size) { int cc; size_t i = 0; while (*src != 0 && i < *size) { if (*src == '=') { src++; if (!*src) { break; } /* remove soft line breaks */ if ((*src == '\n') || (*src == '\r')) { src++; if ((*src == '\n') || (*src == '\r')) { src++; } continue; } cc = isdigit((int)*src) ? (*src - '0') : (*src - 55); cc *= 0x10; src++; if (!*src) { break; } cc += isdigit((int)*src) ? (*src - '0') : (*src - 55); *dest = cc; } else { *dest = *src; } dest++; src++; i++; } *dest = '\0'; *size = i; return (dest); } char *decode_base64(char *dest, char *src, size_t *size) { int cc = 0; char buf[4] = { 0, 0, 0, 0 }; int p = 0; int valid_data = 0; size_t size_out = 0; while (*src && p < (int)*size && (cc != -1)) { /* convert a character into the Base64 alphabet */ cc = *src++; if ((cc >= 'A') && (cc <= 'Z')) { cc = cc - 'A'; } else if ((cc >= 'a') && (cc <= 'z')) { cc = cc - 'a' + 26; } else if ((cc >= '0') && (cc <= '9')) { cc = cc - '0' + 52; } else if (cc == '/') { cc = 63; } else if (cc == '+') { cc = 62; } else { cc = -1; } assert(cc < 64); /* If we've reached the end, fill the remaining slots in the bucket and do a final conversion */ if (cc == -1) { if (valid_data == 0) { return 0; } while (p % 4 != 3) { p++; buf[p % 4] = 0; } } else { buf[p % 4] = cc; size_out++; valid_data = 1; } /* When we have 4 base64 letters, convert them into three bytes */ if (p % 4 == 3) { *dest++ = (buf[0] << 2) | ((buf[1] & 0x30) >> 4); *dest++ = ((buf[1] & 0x0F) << 4) | ((buf[2] & 0x3C) >> 2); *dest++ = ((buf[2] & 0x03) << 6) | (buf[3] & 0x3F); memset(buf, 0, 4); } p++; } /* Calculate the size of the converted data */ *size = ((size_t) (size_out / 4)) * 3; if (size_out % 4 == 2) { *size += 1; } if (size_out % 4 == 3) { *size += 2; } return (dest); } /*********************************************************************** Routines to output MIME **********************************************************************/ struct sspm_buffer { char *buffer; char *pos; size_t buf_size; int line_pos; }; static void sspm_append_string(struct sspm_buffer *buf, const char *string); static void sspm_append_hex(struct sspm_buffer *buf, char ch) { char tmp[4]; snprintf(tmp, sizeof(tmp), "=%02X", (unsigned int)ch); sspm_append_string(buf, tmp); } /* a copy of icalmemory_append_char */ static void sspm_append_char(struct sspm_buffer *buf, char ch) { char *new_buf; char *new_pos; size_t data_length, final_length; data_length = (size_t) buf->pos - (size_t) buf->buffer; final_length = data_length + 2; if (final_length > (size_t) buf->buf_size) { buf->buf_size = (buf->buf_size) * 2 + final_length + 1; new_buf = realloc(buf->buffer, buf->buf_size); new_pos = (void *)((size_t) new_buf + data_length); buf->pos = new_pos; buf->buffer = new_buf; } *(buf->pos) = ch; buf->pos += 1; *(buf->pos) = 0; } /* A copy of icalmemory_append_string */ void sspm_append_string(struct sspm_buffer *buf, const char *string) { char *new_buf; char *new_pos; size_t data_length, final_length, string_length; string_length = strlen(string); data_length = (size_t) buf->pos - (size_t) buf->buffer; final_length = data_length + string_length; if (final_length >= (size_t) buf->buf_size) { buf->buf_size = (buf->buf_size) * 2 + final_length; new_buf = realloc(buf->buffer, buf->buf_size); new_pos = (void *)((size_t) new_buf + data_length); buf->pos = new_pos; buf->buffer = new_buf; } strcpy(buf->pos, string); buf->pos += string_length; } static int sspm_is_printable(char c) { return (c >= 33) && (c <= 126) && (c != '='); } static void sspm_encode_quoted_printable(struct sspm_buffer *buf, char *data) { char *p; int lpos = 0; for (p = data; *p != 0; p++) { if (sspm_is_printable(*p)) { /* plain characters can represent themselves */ /* RFC2045 Rule #2 */ sspm_append_char(buf, *p); lpos++; } else if (*p == '\t' || *p == ' ') { /* For tabs and spaces, only encode if they appear at the end of the line */ /* RFC2045 Rule #3 */ char n = *(p + 1); if (n == '\n' || n == '\r') { sspm_append_hex(buf, *p); lpos += 3; } else { sspm_append_char(buf, *p); lpos++; } } else if (*p == '\n' || *p == '\r') { sspm_append_char(buf, *p); lpos = 0; } else { /* All others need to be encoded */ sspm_append_hex(buf, *p); lpos += 3; } /* Add line breaks */ if (lpos > 72) { lpos = 0; sspm_append_string(buf, "=\n"); } } } static char BaseTable[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; static void sspm_write_base64(struct sspm_buffer *buf, char *inbuf, int size) { char outbuf[4]; int i; outbuf[0] = outbuf[1] = outbuf[2] = outbuf[3] = 65; switch (size) { case 4: outbuf[3] = inbuf[2] & 0x3F; /* falls through */ case 3: outbuf[2] = ((inbuf[1] & 0x0F) << 2) | ((inbuf[2] & 0xC0) >> 6); /* falls through */ case 2: outbuf[0] = (inbuf[0] & 0xFC) >> 2; outbuf[1] = ((inbuf[0] & 0x03) << 4) | ((inbuf[1] & 0xF0) >> 4); break; default: assert(0); } for (i = 0; i < 4; i++) { if (outbuf[i] == 65) { sspm_append_char(buf, '='); } else { sspm_append_char(buf, BaseTable[(int)outbuf[i]]); } } } static void sspm_encode_base64(struct sspm_buffer *buf, char *data, size_t size) { char *p; char inbuf[3]; int i = 0; int first = 1; int lpos = 0; _unused(size); inbuf[0] = inbuf[1] = inbuf[2] = 0; for (p = data; *p != 0; p++) { if (i % 3 == 0 && first == 0) { sspm_write_base64(buf, inbuf, 4); lpos += 4; inbuf[0] = inbuf[1] = inbuf[2] = 0; } assert(lpos % 4 == 0); if (lpos == 72) { sspm_append_string(buf, "\n"); lpos = 0; } inbuf[i % 3] = *p; i++; first = 0; } /* If the inbuf was not exactly filled on the last byte, we need to spit out the odd bytes that did get in -- either one or two. This will result in an output of two bytes and '==' or three bytes and '=', respectively */ if (i % 3 == 1 && first == 0) { sspm_write_base64(buf, inbuf, 2); } else if (i % 3 == 2 && first == 0) { sspm_write_base64(buf, inbuf, 3); } } static void sspm_write_header(struct sspm_buffer *buf, struct sspm_header *header) { int i; char temp[TMP_BUF_SIZE]; const char *major; const char *minor; /* Content-type */ major = sspm_major_type_string(header->major); minor = sspm_minor_type_string(header->minor); if (header->minor == SSPM_UNKNOWN_MINOR_TYPE) { assert(header->minor_text != 0); minor = header->minor_text; } snprintf(temp, sizeof(temp), "Content-Type: %s/%s", major, minor); sspm_append_string(buf, temp); if (header->boundary != 0) { snprintf(temp, sizeof(temp), ";boundary=\"%s\"", header->boundary); sspm_append_string(buf, temp); } /* Append any content type parameters */ if (header->content_type_params != 0) { for (i = 0; *(header->content_type_params[i]) != 0; i++) { snprintf(temp, sizeof(temp), "%s", header->content_type_params[i]); sspm_append_char(buf, ';'); sspm_append_string(buf, temp); } } sspm_append_char(buf, '\n'); /*Content-Transfer-Encoding */ if (header->encoding != SSPM_UNKNOWN_ENCODING && header->encoding != SSPM_NO_ENCODING) { snprintf(temp, sizeof(temp), "Content-Transfer-Encoding: %s\n", sspm_encoding_string(header->encoding)); } sspm_append_char(buf, '\n'); } static void sspm_write_part(struct sspm_buffer *buf, struct sspm_part *part, int *part_num) { _unused(part_num); /* Write header */ sspm_write_header(buf, &(part->header)); /* Write part data */ if (part->data == 0) { return; } if (part->header.encoding == SSPM_BASE64_ENCODING) { assert(part->data_size != 0); sspm_encode_base64(buf, part->data, part->data_size); } else if (part->header.encoding == SSPM_QUOTED_PRINTABLE_ENCODING) { sspm_encode_quoted_printable(buf, part->data); } else { sspm_append_string(buf, part->data); } sspm_append_string(buf, "\n\n"); } static void sspm_write_multipart_part(struct sspm_buffer *buf, struct sspm_part *parts, int *part_num) { int parent_level, level; struct sspm_header *header = &(parts[*part_num].header); /* Write the header for the multipart part */ sspm_write_header(buf, header); parent_level = parts[*part_num].level; (*part_num)++; level = parts[*part_num].level; while (parts[*part_num].header.major != SSPM_NO_MAJOR_TYPE && level == parent_level + 1) { assert(header->boundary != NULL); sspm_append_string(buf, header->boundary); sspm_append_char(buf, '\n'); if (parts[*part_num].header.major == SSPM_MULTIPART_MAJOR_TYPE) { sspm_write_multipart_part(buf, parts, part_num); } else { sspm_write_part(buf, &(parts[*part_num]), part_num); } (*part_num)++; level = parts[*part_num].level; } sspm_append_string(buf, "\n\n--"); sspm_append_string(buf, header->boundary); sspm_append_string(buf, "\n"); (*part_num)--; /* undo last, spurious, increment */ } int sspm_write_mime(struct sspm_part *parts, size_t num_parts, char **output_string, const char *header) { struct sspm_buffer buf; int part_num = 0; size_t slen; _unused(num_parts); buf.buffer = malloc(4096); buf.buffer[0] = '\0'; buf.pos = buf.buffer; buf.buf_size = 10; buf.line_pos = 0; /* write caller's header */ if (header != 0) { sspm_append_string(&buf, header); } slen = strlen(buf.buffer); if (slen > 0 && buf.buffer[slen - 1] != '\n') { sspm_append_char(&buf, '\n'); } /* write mime-version header */ sspm_append_string(&buf, "Mime-Version: 1.0\n"); /* End of header */ /* Write body parts */ while (parts[part_num].header.major != SSPM_NO_MAJOR_TYPE) { if (parts[part_num].header.major == SSPM_MULTIPART_MAJOR_TYPE) { sspm_write_multipart_part(&buf, parts, &part_num); } else { sspm_write_part(&buf, &(parts[part_num]), &part_num); } part_num++; } *output_string = buf.buffer; return 0; }