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 }