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