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