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