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 }