1 /**
2  * HTTP / mail / etc. headers
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.headers;
15 
16 import std.algorithm;
17 import std.string;
18 import std.ascii;
19 import std.exception;
20 
21 import ae.utils.text;
22 import ae.utils.aa;
23 
24 /// AA-like superset structure with the purpose of maintaining
25 /// compatibility with the old HTTP string[string] headers field
26 struct Headers
27 {
28 	struct Header { string name, value; }
29 
30 	private Header[][CIAsciiString] headers;
31 
32 	this(string[string] aa)
33 	{
34 		foreach (k, v; aa)
35 			this.add(k, v);
36 	}
37 
38 	this(string[][string] aa)
39 	{
40 		foreach (k, vals; aa)
41 			foreach (v; vals)
42 				this.add(k, v);
43 	}
44 
45 	/// If multiple headers with this name are present,
46 	/// only the first one is returned.
47 	ref inout(string) opIndex(string name) inout
48 	{
49 		return headers[CIAsciiString(name)][0].value;
50 	}
51 
52 	string opIndexAssign(string value, string name)
53 	{
54 		headers[CIAsciiString(name)] = [Header(name, value)];
55 		return value;
56 	}
57 
58 	inout(string)* opBinaryRight(string op)(string name) inout @nogc
59 	if (op == "in")
60 	{
61 		auto pvalues = CIAsciiString(name) in headers;
62 		if (pvalues && (*pvalues).length)
63 			return &(*pvalues)[0].value;
64 		return null;
65 	}
66 
67 	void remove(string name)
68 	{
69 		headers.remove(CIAsciiString(name));
70 	}
71 
72 	// D forces these to be "ref"
73 	int opApply(int delegate(ref string name, ref string value) dg)
74 	{
75 		int ret;
76 		outer:
77 		foreach (key, values; headers)
78 			foreach (header; values)
79 			{
80 				ret = dg(header.name, header.value);
81 				if (ret)
82 					break outer;
83 			}
84 		return ret;
85 	}
86 
87 	// Copy-paste because of https://issues.dlang.org/show_bug.cgi?id=7543
88 	int opApply(int delegate(ref const(string) name, ref const(string) value) dg) const
89 	{
90 		int ret;
91 		outer:
92 		foreach (name, values; headers)
93 			foreach (header; values)
94 			{
95 				ret = dg(header.name, header.value);
96 				if (ret)
97 					break outer;
98 			}
99 		return ret;
100 	}
101 
102 	void add(string name, string value)
103 	{
104 		auto key = CIAsciiString(name);
105 		if (key !in headers)
106 			headers[key] = [Header(name, value)];
107 		else
108 			headers[key] ~= Header(name, value);
109 	}
110 
111 	string get(string key, string def) const
112 	{
113 		return getLazy(key, def);
114 	}
115 
116 	string getLazy(string key, lazy string def) const
117 	{
118 		auto pvalue = key in this;
119 		return pvalue ? *pvalue : def;
120 	}
121 
122 	inout(string)[] getAll(string key) inout
123 	{
124 		inout(string)[] result;
125 		foreach (header; headers.get(CIAsciiString(key), null))
126 			result ~= header.value;
127 		return result;
128 	}
129 
130 	ref string require(string key, lazy string value)
131 	{
132 		return headers.require(CIAsciiString(key), [Header(key, value)])[0].value;
133 	}
134 
135 	/// Warning: discards repeating headers
136 	string[string] opCast(T)() const
137 		if (is(T == string[string]))
138 	{
139 		string[string] result;
140 		foreach (key, value; this)
141 			result[key] = value;
142 		return result;
143 	}
144 
145 	string[][string] opCast(T)() inout
146 		if (is(T == string[][string]))
147 	{
148 		string[][string] result;
149 		foreach (k, v; this)
150 			result[k] ~= v;
151 		return result;
152 	}
153 
154 	@property Headers dup()
155 	{
156 		Headers c;
157 		foreach (k, v; this)
158 			c.add(k, v);
159 		return c;
160 	}
161 
162 	@property size_t length() const
163 	{
164 		return headers.length;
165 	}
166 }
167 
168 unittest
169 {
170 	Headers headers;
171 	headers["test"] = "test";
172 
173 	void test(T)(T headers)
174 	{
175 		assert("TEST" in headers);
176 		assert(headers["TEST"] == "test");
177 
178 		foreach (k, v; headers)
179 			assert(k == "test" && v == "test");
180 
181 		auto aas = cast(string[string])headers;
182 		assert(aas == ["test" : "test"]);
183 
184 		auto aaa = cast(string[][string])headers;
185 		assert(aaa == ["test" : ["test"]]);
186 	}
187 
188 	test(headers);
189 
190 	const constHeaders = headers;
191 	test(constHeaders);
192 }
193 
194 /// Normalize capitalization
195 string normalizeHeaderName(string header) pure
196 {
197 	alias std.ascii.toUpper toUpper;
198 	alias std.ascii.toLower toLower;
199 
200 	auto s = header.dup;
201 	auto segments = s.split("-");
202 	foreach (segment; segments)
203 	{
204 		foreach (ref c; segment)
205 			c = cast(char)toUpper(c);
206 		switch (segment)
207 		{
208 			case "ID":
209 			case "IP":
210 			case "NNTP":
211 			case "TE":
212 			case "WWW":
213 				continue;
214 			case "ETAG":
215 				segment[] = "ETag";
216 				break;
217 			default:
218 				foreach (ref c; segment[1..$])
219 					c = cast(char)toLower(c);
220 				break;
221 		}
222 	}
223 	return s;
224 }
225 
226 unittest
227 {
228 	assert(normalizeHeaderName("X-ORIGINATING-IP") == "X-Originating-IP");
229 }
230 
231 struct TokenHeader
232 {
233 	string value;
234 	string[string] properties;
235 }
236 
237 TokenHeader decodeTokenHeader(string s)
238 {
239 	string take(char until)
240 	{
241 		string result;
242 		auto p = s.indexOf(until);
243 		if (p < 0)
244 			result = s,
245 			s = null;
246 		else
247 			result = s[0..p],
248 			s = asciiStrip(s[p+1..$]);
249 		return result;
250 	}
251 
252 	TokenHeader result;
253 	result.value = take(';');
254 
255 	while (s.length)
256 	{
257 		string name = take('=').toLower();
258 		string value;
259 		if (s.length && s[0] == '"')
260 		{
261 			s = s[1..$];
262 			value = take('"');
263 			take(';');
264 		}
265 		else
266 			value = take(';');
267 		result.properties[name] = value;
268 	}
269 
270 	return result;
271 }