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