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