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.contenttype;
17 
18 import jaypha.inet.mime.quotedprintable;
19 import jaypha.inet.imf.writing;
20 
21 import std.base64;
22 import std.algorithm;
23 
24 import jaypha.types;
25 import jaypha.range;
26 import jaypha.rnd;
27 
28 // Use this struct specifically for writing.
29 // To read MIME entities, use jaypha.inet.mime.reading.mimEntityReader
30 
31 // A MIME entity has two parts, the headers and the body. The headers are
32 // provided by MimeHeader. The body can be either flat content (type "Single")
33 // or a list of MIME entities (type "Multi"). MIME entities that are multiple
34 // are identified by a mime-type of "mulitpart".
35 
36 // Serialisation is performed by copy(), which writes the entity to the given
37 // output range, performing any needed formatting and encoding.
38 
39 // The definition of MIME entities are convered by several RFC documents. See
40 // documentation for a list of these documents.
41 
42 struct MimeEntity
43 {
44   enum Type { Single, Multi };
45 
46   Type entityType;
47 
48   MimeHeader[] headers;
49   string encoding = "8bit";
50   string encoded;
51 
52   string boundary;
53   union Content
54   {
55     ByteArray bod; // Cannot use 'body' as it is a reserved word.
56     MimeEntity[] bodParts; // For the sake of consistency.
57   }
58   Content content;
59 
60   //-------------------------------------------------------
61 
62   this(MimeContentType contentType, bool asImf = false)
63   {
64     if (contentType.mimeType.startsWith("multipart"))
65     {
66       entityType = Type.Multi;
67       boundary = rndString(20);
68       contentType.parameters["boundary"] = boundary;
69     }
70     else
71       entityType = Type.Single;
72 
73     headers = [ contentType.toMimeHeader(asImf) ];
74   }
75 
76   //-------------------------------------------------------
77 
78   @property string asString()
79   {
80     auto a = appender!string();
81     copy(a);
82     return a.data;
83   }
84 
85   //-------------------------------------------------------
86 
87   void copy(R)(R range) if (isOutputRange!(R,ByteArray))
88   {
89     headers ~= MimeHeader("Content-Transfer-Encoding",encoding);
90     foreach (h;headers)
91       range.put(cast(ByteArray)h.asString);
92     range.put(cast(ByteArray)MimeEoln);
93 
94     if (entityType == Type.Multi)
95     {
96       foreach (bodPart; content.bodParts)
97       {
98         range.put(cast(ByteArray)"--");
99         range.put(cast(ByteArray)boundary);
100         range.put(cast(ByteArray)MimeEoln);
101         bodPart.copy(range);
102       }
103       range.put(cast(ByteArray)"--");
104       range.put(cast(ByteArray)boundary);
105       range.put(cast(ByteArray)"--");
106       range.put(cast(ByteArray)MimeEoln);
107     }
108     else
109     {
110       switch (encoding)
111       {
112         case "base64":
113           byChunk(cast(ByteArray)Base64.encode(content.bod),ImfRecLineLength).join(cast(ubyte[])MimeEoln).copy(range);
114           break;
115         case "8bit":
116         case "7bit":
117         case "binary":
118           content.bod.copy(range);
119           range.put(cast(ByteArray)MimeEoln);
120           break;
121         case "quoted-printable":
122           range.put(cast(ByteArray)quotedPrintableEncode(content.bod));
123           break;
124         default:
125           throw new Exception("Unsupported encoding");
126       }
127     }
128   }
129 }