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 +/