pike.git / lib / modules / MIME.pmod / module.pmod

version» Context lines:

pike.git/lib/modules/MIME.pmod/module.pmod:1:   // -*- Pike -*-   //   // RFC1521 functionality for Pike   //   // Marcus Comstedt 1996-1999       - //! RFC1521, the @b{Multipurpose Internet Mail Extensions@} memo, defines a + //! @rfc{1521@}, the @b{Multipurpose Internet Mail Extensions@} memo, defines a   //! structure which is the base for all messages read and written by   //! modern mail and news programs. It is also partly the base for the - //! HTTP protocol. Just like RFC822, MIME declares that a message should + //! HTTP protocol. Just like @rfc{822@}, MIME declares that a message should   //! consist of two entities, the headers and the body. In addition, the   //! following properties are given to these two entities:   //!   //! @dl   //! @item Headers   //! @ul   //! @item   //! A MIME-Version header must be present to signal MIME compatibility   //! @item   //! A Content-Type header should be present to describe the nature of
pike.git/lib/modules/MIME.pmod/module.pmod:65:   //! during parsing of @[MIME.Message]s.   protected class StringRange   {    string data;    int start; // Inclusive.    int end; // Exclusive.    protected void create(string|StringRange s, int start, int end)    {    if (start == end) {    data = ""; -  this_program::start = this_program::end = 0; +  this::start = this::end = 0;    return;    }    if (start < 0) start = 0;    if (end < 0) end = 0;    if (objectp(s)) {    start += s->start;    if (start > s->end) start = s->end;    end += s->start;    if (end > s->end) end = s->end;    s = s->data;    }    if ((end - start)*16 < sizeof(s)) {    s = s[start..end-1];    end -= start;    start = 0;    }    data = s; -  this_program::start = start; -  this_program::end = end; +  this::start = start; +  this::end = end;    }    protected int _sizeof()    {    return end-start;    }    protected string|StringRange `[..](int low, int ltype, int high, int htype)    {    int len = end - start;    if (ltype == Pike.INDEX_FROM_END) {    low = len - (low + 1);
pike.git/lib/modules/MIME.pmod/module.pmod:106:    high += 1;    if (htype == Pike.INDEX_FROM_END) {    high = len - high;    } else if (htype == Pike.OPEN_BOUND) {    high = len;    }    if (low < 0) low = 0;    if (high < 0) high = 0;    if (low > len) low = len;    if (high > len) high = len; -  if (!low && (high == len)) return this_object(); +  if (!low && (high == len)) return this;    if ((high - low) < 65536) return data[start+low..start+high-1]; -  return StringRange(this_object(), low, high); +  return StringRange(this, low, high);    }    protected int `[](int pos)    {    int npos = pos;    if (npos < 0) {    npos += end;    if (npos < start) {    error("Index out of range [-%d..%d]\n", 1 + end-start, end-start);    }    } else {    npos += start;    if (npos >= end) {    error("Index out of range [-%d..%d]\n", 1 + end-start, end-start);    }    }    return data[npos];    }    protected mixed cast(string type)    { -  switch(type) { -  case "string": +  if( type == "string" )    return data[start..end-1]; -  case "object": -  return this_object(); -  default: -  error("StringRange: Unsupported cast to %s.\n", type); +  return UNDEFINED;    } -  } +     protected int _search(string frag, int|void pos)    {    if (pos < 0)    error("Start must be greater or equal to zero.\n");    int npos = pos + start;    if (npos > end)    error("Start must not be greater than the length of the string.\n");    if ((npos + sizeof(frag)) > end) return -1;    npos = search(data, frag, npos);    if (npos < 0) return npos;    if ((npos + sizeof(frag)) > end) return -1;    return npos - start;    }    protected string _sprintf(int c)    {    if (c == 'O')    return sprintf("StringRange(%d bytes[%d..%d] %O)",    data && sizeof(data), start, end-1, data && data[..40]); -  return (string)this_object(); +  return (string)this;    }   }    - #if (__REAL_VERSION__ < 7.8) || ((__REAL_VERSION__) < 7.9 && (__REAL_BUILD__ < 413)) - // Compat with some older Pikes... + private string boundary_prefix;    - // Support has_prefix on objects. - protected int(0..1) has_prefix(string|object s, string prefix) + //! Set a message boundary prefix. The @[MIME.generate_boundary()] will use this + //! prefix when creating a boundary string. + //! + //! @throws + //! An error is thrown if @[boundary_prefix] doesn't adhere to @rfc{1521@}. + //! + //! @seealso + //! @[MIME.generate_boundary()], @[MIME.get_boundary_prefix()] + //! + //! @param boundary_prefix + //! This must adhere to @rfc{1521@} and can not be longer than 56 characters. + void set_boundary_prefix(string(8bit) boundary_prefix)   { -  if (!objectp(s)) return predef::has_prefix(s, prefix); -  for(int i = 0; i < sizeof(prefix); i++) { -  if (s[i] != prefix[i]) return 0; +  // 5 upto 14 chars is randomly added to the boundary so the prefix must not +  // risking overflowing the max-length of 70 chars +  if (boundary_prefix && (sizeof(boundary_prefix) + 14) > 70) { +  error("Too long boundary prefix. The boundary prefix can not be longer " +  "than 56 characters.\n");    } -  return 1; +  +  sscanf(boundary_prefix, "%*s%[^0-9a-zA-Z'()+_,./:=?-]", string illegal); +  +  if (illegal && sizeof(illegal)) { +  error("An illegal character (%q) was found in the boundary prefix.\n", +  illegal);    }    - #endif +  this::boundary_prefix = boundary_prefix; + }    -  + //! Returns the @tt{boundary_prefix@} set via @[set_boundary_prefix()]. + //! + //! @seealso + //! @[MIME.set_boundary_prefix()], @[MIME.Message.setboundary()] + string(8bit) get_boundary_prefix() + { +  return boundary_prefix; + } +    //! This function will create a string that can be used as a separator string - //! for multipart messages. The generated string is guaranteed not to appear + //! for multipart messages. If a boundary prefix has been set + //! using @[MIME.set_boundary_prefix()], the generated string will be prefixed + //! with the boundary prefix. + //! + //! The generated string is guaranteed not to appear   //! in @tt{base64@}, @tt{quoted-printable@}, or @tt{x-uue@} encoded data.   //! It is also unlikely to appear in normal text. This function is used by   //! the cast method of the @tt{Message@} class if no boundary string is   //! specified.   //! -  + //! @seealso + //! @[MIME.set_boundary_prefix()] + //!   string generate_boundary( )   { -  +  if (boundary_prefix) { +  return boundary_prefix + random( 1000000000 ); +  }    return "'ThIs-RaNdOm-StRiNg-/=_."+random( 1000000000 )+":";   }      //! Extract raw data from an encoded string suitable for transport between   //! systems.   //!   //! The encoding can be any of   //! @string   //! @value "7bit"   //! @value "8bit"
pike.git/lib/modules/MIME.pmod/module.pmod:268:    case "7bit":    case "8bit":    case "binary":    return data;    default:    error("Unknown transfer encoding %s.\n", encoding);    }   }      //! Extracts the textual content and character set from an @i{encoded word@} - //! as specified by RFC1522. The result is an array where the first element - //! is the raw text, and the second element the name of the character set. - //! If the input string is not an encoded word, the result is still an array, - //! but the char set element will be set to 0. + //! as specified by @rfc{1522@}/@rfc{2047@}. The result is an array where the + //! first element is the raw text, and the second element the name of the + //! character set. If the input string is not an encoded word, the result is + //! still an array, but the char set element will be set to 0.   //!   //! @note   //! Note that this function can only be applied to individual encoded words.   //!   //! @seealso   //! @[MIME.encode_word()]   //!   array(string) decode_word( string word )   {    string charset, encoding, encoded_text;
pike.git/lib/modules/MIME.pmod/module.pmod:301:    break;    default:    error( "Invalid rfc1522 encoding %s.\n", encoding );    }    return ({ decode( replace( encoded_text, "_", " " ), encoding ),    lower_case( charset ) });    } else    return ({ word, 0 });   }    - //! Create an @i{encoded word@} as specified in RFC1522 from an array + //! Create an @i{encoded word@} as specified in @rfc{1522@} from an array   //! containing a raw text string and a char set name.   //!   //! The text will be transfer encoded according to the encoding argument,   //! which can be either @expr{"base64"@} or @expr{"quoted-printable"@}   //! (or either @expr{"b"@} or @expr{"q"@} for short).   //!   //! If either the second element of the array (the char set name), or   //! the encoding argument is 0, the raw text is returned as is.   //!   //! @seealso
pike.git/lib/modules/MIME.pmod/module.pmod:402:    } else {    res += ({ word });    }    }    return res;   }      //! Like @[MIME.decode_words_text()], but the extracted strings are   //! also remapped from their specified character encoding into UNICODE,   //! and then pasted together. The result is thus a string in the original - //! text format, without RFC1522 escapes, and with all characters in UNICODE + //! text format, without @rfc{1522@} escapes, and with all characters in UNICODE   //! encoding.   //!   //! @seealso   //! @[MIME.decode_words_tokenized_remapped]   //!   string decode_words_text_remapped( string txt )   { -  return Array.map(decode_words_text(txt), remap)*""; +  return map(decode_words_text(txt), remap)*"";   }      //! Tokenizes a header value just like @[MIME.tokenize()], but also   //! converts encoded words using @[MIME.decode_word()]. The result is   //! an array where each element is either an @expr{int@} representing   //! a special character, or an @expr{array@} as returned by   //! @[decode_word()] representing an atom or a quoted string.   //!   //! @seealso   //! @[MIME.decode_words_tokenized_labled]   //! @[MIME.decode_words_tokenized_remapped]   //! @[MIME.decode_words_text]   //!   array(array(string)|int) decode_words_tokenized( string phrase, int|void flags )   { -  return Array.map(tokenize(phrase, flags), +  return map(tokenize(phrase, flags),    lambda(string|int item) {    return intp(item)? item : decode_word(item);    });   }      //! Like @[MIME.decode_words_tokenized()], but the extracted atoms are   //! also remapped from their specified character encoding into UNICODE.   //! The result is thus identical to that of @[MIME.tokenize()], but - //! without RFC1522 escapes, and with all characters in UNICODE encoding. + //! without @rfc{1522@} escapes, and with all characters in UNICODE encoding.   //!   //! @seealso   //! @[MIME.decode_words_tokenized_labled_remapped]   //! @[MIME.decode_words_text_remapped]   //!   array(string|int) decode_words_tokenized_remapped( string phrase,    int|void flags )   { -  return Array.map(decode_words_tokenized(phrase, flags), +  return map(decode_words_tokenized(phrase, flags),    lambda(array(string)|int item) {    return intp(item)? item : remap(item);    });   }      //! Tokenizes and labels a header value just like @[MIME.tokenize_labled()],   //! but also converts encoded words using @[MIME.decode_word()]. The result   //! is an array where each element is an array of two or more elements, the   //! first being the label. The rest of the array depends on the label:   //!
pike.git/lib/modules/MIME.pmod/module.pmod:476:   //! One additional element, containing an array as returned by   //! @[MIME.decode_words_text()].   //! @endstring   //!   //! @seealso   //! @[MIME.decode_words_tokenized_labled_remapped]   //!   array(array(string|int|array(array(string))))   decode_words_tokenized_labled( string phrase, int|void flags )   { -  return Array.map( tokenize_labled( phrase, flags ), +  return map( tokenize_labled( phrase, flags ),    lambda(array(string|int) item) {    switch(item[0]) {    case "encoded-word":    return ({ "word", @decode_word(item[1]) });    case "word":    return item + ({ 0 });    case "comment":    return ({ "comment", decode_words_text(item[1]) });    default:    return item;    }    });   }      //! Like @[MIME.decode_words_tokenized_labled()], but the extracted words are   //! also remapped from their specified character encoding into UNICODE.   //! The result is identical to that of @[MIME.tokenize_labled()], but - //! without RFC1522 escapes, and with all characters in UNICODE encoding. + //! without @rfc{1522@} escapes, and with all characters in UNICODE encoding.   //!   array(array(string|int))   decode_words_tokenized_labled_remapped(string phrase, int|void flags)   { -  return Array.map(decode_words_tokenized_labled(phrase, flags), +  return map(decode_words_tokenized_labled(phrase, flags),    lambda(array(string|int|array(array(string|int))) item) {    switch(item[0]) {    case "word":    return ({ "word", remap(item[1..]) });    case "comment": -  return ({ "comment", Array.map(item[1], remap)*"" }); +  return ({ "comment", map(item[1], remap)*"" });    default:    return item;    }    });   }      //! The inverse of @[decode_words_text()], this function accepts   //! an array of strings or pairs of strings which will each be encoded   //! by @[encode_word()], after which they are all pasted together.   //!
pike.git/lib/modules/MIME.pmod/module.pmod:607:   //! @param encoding   //! Either @expr{"base64"@} or @expr{"quoted-printable"@}   //! (or either @expr{"b"@} or @expr{"q"@} for short).   //!   //! @seealso   //! @[MIME.encode_words_quoted_remapped()]   //! @[MIME.encode_words_quoted_labled()]   //!   string encode_words_quoted(array(array(string)|int) phrase, string encoding)   { -  return quote(Array.map(phrase, lambda(array(string)|int item) { +  return quote(map(phrase, lambda(array(string)|int item) {    return intp(item)? item :    encode_word(item, encoding);    }));   }      //! The inverse of @[decode_words_tokenized_remapped()], this functions   //! accepts an array equivalent to the argument of @[quote()], but also   //! performs on demand word encoding in the same way as   //! @[encode_words_text_remapped()].   //!
pike.git/lib/modules/MIME.pmod/module.pmod:653:   //! Either @expr{"base64"@} or @expr{"quoted-printable"@}   //! (or either @expr{"b"@} or @expr{"q"@} for short).   //!   //! @seealso   //! @[MIME.encode_words_quoted()]   //! @[MIME.encode_words_quoted_labled_remapped()]   //!   string encode_words_quoted_labled(array(array(string|int|array(string|array(string)))) phrase, string encoding)   {    return -  quote_labled(Array.map(phrase, +  quote_labled(map(phrase,    lambda(array(string|int|array(string)) item) {    switch(item[0]) {    case "word":    if(sizeof(item)>2 && item[2])    return ({    "encoded-word",    encode_word(item[1..], encoding) });    else    return item;    case "comment":
pike.git/lib/modules/MIME.pmod/module.pmod:713:    replacement,    repcb) });    default:    return item;    }    }));   }      //! Provide a reasonable default for the subtype field.   //! - //! Some pre-RFC1521 mailers provide only a type and no subtype in the + //! Some pre-@rfc{1521@} mailers provide only a type and no subtype in the   //! Content-Type header field. This function can be used to obtain a   //! reasonable default subtype given the type of a message. (This is done   //! automatically by the @[MIME.Message] class.)   //!   //! Currently, the function uses the following guesses:   //! @string   //! @value "text"   //! @expr{"plain"@}   //! @value "message"   //! @expr{"rfc822"@}
pike.git/lib/modules/MIME.pmod/module.pmod:759:   //! This means that the body is returned as is, with any transfer-encoding   //! intact.   //!   //! It is possible to call this function with just the header part   //! of a message, in which case an empty body will be returned.   //!   //! The result is returned in the form of an array containing two elements.   //! The first element is a mapping containing the headers found. The second   //! element is a string containing the body.   //! - //! Headers that occurr multiple times will have their contents NUL separated, + //! Headers that occur multiple times will have their contents NUL separated,   //! unless @[use_multiple] has been specified, in which case the contents will   //! be arrays.   //! -  + //! @note + //! Some headers (eg Subject) may include @rfc{1522@}/@rfc{2047@} encoded words. To + //! decode these, see @[decode_words_text] and @[decode_words_tokenized] and + //! their friends. + //!   array(mapping(string:string|array(string))|string|StringRange)    parse_headers(string|StringRange message, void|int(1..1) use_multiple)   {    string head, header, hname, hcontents;    string|StringRange body;    int mesgsep;    if (has_prefix(message, "\r\n") || has_prefix(message, "\n")) {    // No headers.    return ({ ([]), message[1 + (message[0] == '\r')..] });    } else {
pike.git/lib/modules/MIME.pmod/module.pmod:840:    //! @value "content-transfer-encoding"    //! @endstring    //! The contents of these fields can be accessed and/or modified through    //! a set of variables and methods available for this purpose.    //!    //! @seealso    //! @[type], @[subtype], @[charset], @[boundary], @[transfer_encoding],    //! @[params], @[disposition], @[disp_params], @[setencoding()],    //! @[setparam()], @[setdisp_param()], @[setcharset()], @[setboundary()]    //! +  //! @note +  //! Some headers (eg Subject) may include @rfc{1522@}/@rfc{2047@} encoded words. To +  //! decode these, see @[decode_words_text] and @[decode_words_tokenized] and +  //! their friends. +  //!    mapping(string:string) headers;       //! If the message is of type @tt{multipart@}, this is an array    //! containing one Message object for each part of the message.    //! If the message is not a multipart, this field is @expr{0@} (zero).    //!    //! @seealso    //! @[type], @[boundary]    //!    array(object) body_parts;
pike.git/lib/modules/MIME.pmod/module.pmod:1183:    //! Casting the message object to a string will yield a byte stream suitable    //! for transmitting the message over protocols such as ESMTP and NNTP.    //!    //! The body will be encoded using the current transfer encoding, and    //! subparts of a multipart will be collected recursively. If the message    //! is a multipart and no boundary string has been set, one will be    //! generated using @[generate_boundary()].    //!    //! @seealso    //! @[create()] -  string cast( string dest_type ) +  protected string cast( string dest_type )    {    string data;    object body_part;       if (dest_type != "string") -  error( "Can't cast Message to %s.\n", dest_type); +  return UNDEFINED;       data = getencoded( );       if (body_parts) {       if (!boundary) {    if (type != "multipart") {    type = "multipart";    subtype = "mixed";    }
pike.git/lib/modules/MIME.pmod/module.pmod:1508:    if (terminator) break;    }    string epilogue = data[start..];    if (!decoded_data) {    if (guess) {    decoded_data = epilogue;    epilogue = "";    } else    error("boundary missing from multipart-body\n");    } -  if ((epilogue != "") && !guess) { +  if (epilogue != "" && epilogue != "\n" && epilogue != "\r\n" && !guess) {    error("multipart message improperly terminated (%O%s)\n",    epilogue[..200],    sizeof(epilogue) > 201 ? "[...]" : "");    }    body_parts = map(parts, this_program, 0, 0, guess);    }    if((hdrs || parts) && !decoded_data) {    decoded_data = (parts?    "This is a multi-part message in MIME format.\r\n":    "");    }    }       protected string _sprintf(int c)    {    if (c == 'O')    return sprintf("Message(%O)", disp_params); -  return (string)this_object(); +  return (string)this;    }   }      //! This function will attempt to reassemble a fragmented message from its   //! parts.   //!   //! The array @[collection] should contain @[MIME.Message] objects forming   //! a complete set of parts for a single fragmented message.   //! The order of the messages in the array is not important, but every part   //! must exist at least once.
pike.git/lib/modules/MIME.pmod/module.pmod:1596:    }    }       if(!total)    return -1;       if(got == total && maxgot == total) {    mapping(string:string) enclosing_headers = parts[1]->headers;       object reconstructed = -  Message(`+(@Array.map(sort(indices(parts)), +  Message(`+(@map(sort(indices(parts)),    lambda(int i, mapping(int:object) parts){    return parts[i]->getencoded();    }, parts)));    foreach(indices(reconstructed->headers), string h) {    if(h != "message-id" && h != "encrypted" && h != "mime-version" &&    h != "subject" && (sizeof(h)<8 || h[0..7] != "content-"))    m_delete(reconstructed->headers, h);    }    foreach(indices(enclosing_headers), string h) {    if(h != "message-id" && h != "encrypted" && h != "mime-version" &&    h != "subject" && (sizeof(h)<8 || h[0..7] != "content-"))    reconstructed->headers[h] = enclosing_headers[h];    }    return reconstructed;    } else return (maxgot>total? -1 : total-got);   }