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 }