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.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 DataVec 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 DataVec 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 DataVec 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[0] = data[0] ~ data[1];
81 			data.remove(1);
82 			goto searchAgain;
83 		}
84 		else
85 			return false;
86 	}
87 	headersEnd += startFrom;
88 
89 	auto headerData = data0[0..headersEnd].idup; // copy Data slice to heap
90 	data[0] = data[0][headersEnd + delim.length .. data[0].length];
91 
92 	headers = parseHeadersImpl!FIRST_LINE(headerData, firstLine);
93 	return true;
94 }
95 
96 Headers parseHeadersImpl(bool FIRST_LINE)(string headerData, out string firstLine)
97 {
98 	headerData = headerData.replace("\r\n", "\n").replace("\n\t", " ").replace("\n ", " ");
99 	string[] lines = splitAsciiLines(headerData);
100 	static if (FIRST_LINE)
101 	{
102 		enforce(lines.length, "Empty first line in headers");
103 		firstLine = lines[0];
104 		lines = lines[1 .. lines.length];
105 	}
106 
107 	Headers headers;
108 	foreach (line; lines)
109 	{
110 		auto valueStart = line.indexOf(':');
111 		if (valueStart > 0)
112 			headers.add(line[0..valueStart].strip(), line[valueStart+1..$].strip());
113 	}
114 
115 	return headers;
116 }
117 
118 unittest
119 {
120 	void test(string message)
121 	{
122 		auto data = DataVec(Data(message));
123 		Headers headers;
124 		assert(parseHeaders(data, headers));
125 		assert(headers["From"] == "John Smith <john@smith.net>");
126 		assert(headers["To"] == "Mary Smith <john@smith.net>");
127 		assert(headers["Subject"] == "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
128 		assert(cast(string)data.joinToHeap() == "Message body goes here");
129 	}
130 
131 	string message = q"EOS
132 From : John Smith <john@smith.net>
133 to:Mary Smith <john@smith.net> 
134 Subject: Lorem ipsum dolor sit amet, consectetur
135  adipisicing elit, sed do eiusmod tempor
136 	incididunt ut labore et dolore magna aliqua.
137 
138 Message body goes here
139 EOS".strip();
140 
141 	message = message.replace("\r\n", "\n");
142 	test(message);
143 	message = message.replace("\n", "\r\n");
144 	test(message);
145 }