1 /**
2  * Header parsing
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.net.ietf.headerparse;
15 
16 import std.string;
17 import std.array;
18 
19 import ae.net.ietf.headers;
20 import ae.sys.data;
21 import ae.utils.text;
22 
23 /**
24  * Check if the passed data contains a full set of headers
25  * (that is, contain a \r\n\r\n sequence), and if so -
26  * parses them, removes them from data (so that it contains
27  * only the message body), and returns true; otherwise
28  * returns false.
29  * The header string data is duplicated to the managed
30  * heap.
31  */
32 /// ditto
33 bool parseHeaders(ref Data[] data, out Headers headers)
34 {
35 	string dummy;
36 	return parseHeadersImpl!false(data, dummy, headers);
37 }
38 
39 /// As above, but treat the first line differently, and
40 /// return it in firstLine.
41 bool parseHeaders(ref Data[] data, out string firstLine, out Headers headers)
42 {
43 	return parseHeadersImpl!true(data, firstLine, headers);
44 }
45 
46 /// Parse headers from the given string.
47 Headers parseHeaders(string headerData)
48 {
49 	string firstLine; // unused
50 	return parseHeadersImpl!false(headerData, firstLine);
51 }
52 
53 private:
54 
55 bool parseHeadersImpl(bool FIRST_LINE)(ref Data[] data, out string firstLine, out Headers headers)
56 {
57 	if (!data.length)
58 		return false;
59 
60 	static const DELIM1 = "\r\n\r\n";
61 	static const DELIM2 = "\n\n";
62 
63 	size_t startFrom = 0;
64 	string delim;
65 searchAgain:
66 	string data0 = cast(string)data[0].contents;
67 	sizediff_t headersEnd;
68 	delim = DELIM1; headersEnd = data0[startFrom..$].indexOf(delim);
69 	if (headersEnd < 0)
70 	{
71 		delim = DELIM2; headersEnd = data0[startFrom..$].indexOf(delim);
72 	}
73 	if (headersEnd < 0)
74 	{
75 		if (data.length > 1)
76 		{
77 			// coagulate first two blocks
78 			startFrom = data0.length > delim.length ? data0.length - (DELIM1.length-1) : 0;
79 			data = [data[0] ~ data[1]] ~ data[2..$];
80 			goto searchAgain;
81 		}
82 		else
83 			return false;
84 	}
85 	headersEnd += startFrom;
86 
87 	auto headerData = data0[0..headersEnd].idup; // copy Data slice to heap
88 	data[0] = data[0][headersEnd + delim.length .. data[0].length];
89 
90 	headers = parseHeadersImpl!FIRST_LINE(headerData, firstLine);
91 	return true;
92 }
93 
94 Headers parseHeadersImpl(bool FIRST_LINE)(string headerData, out string firstLine)
95 {
96 	headerData = headerData.replace("\r\n", "\n").replace("\n\t", " ").replace("\n ", " ");
97 	string[] lines = splitAsciiLines(headerData);
98 	static if (FIRST_LINE)
99 	{
100 		firstLine = lines[0];
101 		lines = lines[1 .. lines.length];
102 	}
103 
104 	Headers headers;
105 	foreach (line; lines)
106 	{
107 		auto valueStart = line.indexOf(':');
108 		if (valueStart > 0)
109 			headers.add(line[0..valueStart].strip(), line[valueStart+1..$].strip());
110 	}
111 
112 	return headers;
113 }
114 
115 unittest
116 {
117 	void test(string message)
118 	{
119 		auto data = [Data(message)];
120 		Headers headers;
121 		assert(parseHeaders(data, headers));
122 		assert(headers["From"] == "John Smith <john@smith.net>");
123 		assert(headers["To"] == "Mary Smith <john@smith.net>");
124 		assert(headers["Subject"] == "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
125 		assert(cast(string)data.joinToHeap() == "Message body goes here");
126 	}
127 
128 	string message = q"EOS
129 From : John Smith <john@smith.net>
130 to:Mary Smith <john@smith.net> 
131 Subject: Lorem ipsum dolor sit amet, consectetur
132  adipisicing elit, sed do eiusmod tempor
133 	incididunt ut labore et dolore magna aliqua.
134 
135 Message body goes here
136 EOS".strip();
137 
138 	message = message.replace("\r\n", "\n");
139 	test(message);
140 	message = message.replace("\n", "\r\n");
141 	test(message);
142 }