1 //Written in the D programming language 2 /* 3 * MIME entity writing utility 4 * 5 * Copyright (C) 2013 Jaypha 6 * 7 * Distributed under the Boost Software License, Version 1.0. 8 * (See http://www.boost.org/LICENSE_1_0.txt) 9 * 10 * Authors: Jason den Dulk 11 */ 12 13 module jaypha.inet.mime.writing; 14 15 public import jaypha.inet.mime.header; 16 public import jaypha.inet.mime.content_type; 17 import jaypha.inet.imf.writing; 18 19 import std.base64; 20 import std.algorithm; 21 22 import jaypha.types; 23 import jaypha.range; 24 import jaypha.rnd; 25 26 // Use this struct specifically for writing. 27 // To read MIME entities, use jaypha.inet.mime.reading.mimEntityReader 28 29 // A MIME entity has two parts, the headers and the body. The headers are 30 // provided by MimeHeader. The body can be either flat content (type "Single") 31 // or a list of MIME entities (type "Multi"). MIME entities that are multiple 32 // are identified by a mime-type of "mulitpart". 33 34 // Serialisation is performed by copy(), which writes the entity to the given 35 // output range, performing any needed formatting and encoding. 36 37 // The definition of MIME entities are convered by several RFC documents. See 38 // rfc.txt for a list of these documents. 39 40 struct MimeEntity 41 { 42 enum Type { Single, Multi }; 43 44 Type entityType; 45 46 MimeHeader[] headers; 47 string encoding = "8bit"; 48 string encoded; 49 50 string boundary; 51 union Content 52 { 53 ByteArray bod; // Cannot use 'body' as it is a reserved word. 54 MimeEntity[] bodParts; // For the sake of consistency. 55 } 56 Content content; 57 58 //------------------------------------------------------- 59 60 this(MimeContentType contentType, bool wrap = false) 61 { 62 if (contentType.type.startsWith("multipart")) 63 { 64 entityType = Type.Multi; 65 boundary = rndString(20); 66 contentType.parameters["boundary"] = boundary; 67 } 68 else 69 entityType = Type.Single; 70 71 headers = [ contentType.toMimeHeader(wrap) ]; 72 } 73 74 //------------------------------------------------------- 75 76 @property string asString() 77 { 78 auto a = appender!string(); 79 copy(a); 80 return a.data; 81 } 82 83 //------------------------------------------------------- 84 85 void copy(R)(R range) if (isOutputRange!(R,ByteArray)) 86 { 87 headers ~= MimeHeader("Content-Transfer-Encoding",encoding); 88 foreach (h;headers) 89 range.put(cast(ByteArray)h.toString()); 90 range.put(cast(ByteArray)MimeEoln); 91 92 if (entityType == Type.Multi) 93 { 94 foreach (bodPart; content.bodParts) 95 { 96 range.put(cast(ByteArray)"--"); 97 range.put(cast(ByteArray)boundary); 98 range.put(cast(ByteArray)MimeEoln); 99 bodPart.copy(range); 100 } 101 range.put(cast(ByteArray)"--"); 102 range.put(cast(ByteArray)boundary); 103 range.put(cast(ByteArray)"--"); 104 range.put(cast(ByteArray)MimeEoln); 105 } 106 else 107 { 108 switch (encoding) 109 { 110 case "base64": 111 byChunk(cast(ByteArray)Base64.encode(content.bod),ImfRecLineLength).join(cast(ubyte[])MimeEoln).copy(range); 112 break; 113 case "8bit": 114 case "7bit": 115 case "binary": 116 content.bod.copy(range); 117 range.put(cast(ByteArray)MimeEoln); 118 break; 119 case "quoted-printable": 120 content.bod.copy(range); // TODO 121 break; 122 default: 123 throw new Exception("Unknown encoding"); 124 } 125 } 126 } 127 } 128 129 /+ 130 ByteArray quotePrintableEncode(ByteArray input) 131 { 132 // Replace non printables 133 $input = preg_replace('/([^\x20\x21-\x3C\x3E-\x7E\x0A\x0D])/e', 'sprintf("=%02X", ord("\1"))', $input); 134 $inputLen = strlen($input); 135 $outLines = array(); 136 $output = ''; 137 138 $lines = preg_split('/\r?\n/', $input); 139 140 // Walk through each line 141 for ($i=0; $i<count($lines); $i++) { 142 // Is line too long ? 143 if (strlen($lines[$i]) > $lineMax) { 144 $outLines[] = substr($lines[$i], 0, $lineMax - 1) . "="; // \r\n Gets added when lines are imploded 145 $lines[$i] = substr($lines[$i], $lineMax - 1); 146 $i--; // Ensure this line gets redone as we just changed it 147 } else { 148 $outLines[] = $lines[$i]; 149 } 150 } 151 152 // Convert trailing whitespace 153 $output = preg_replace('/(\x20+)$/me', 'str_replace(" ", "=20", "\1")', $outLines); 154 155 return implode("\r\n", $output); 156 } 157 158 +/