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.string; 17 import std.ascii; 18 import std.exception; 19 20 /// AA-like superset structure with the purpose of maintaining 21 /// compatibility with the old HTTP string[string] headers field 22 struct Headers 23 { 24 // All keys are internally upper-case. 25 private string[][string] headers; 26 27 /// If multiple headers with this name are present, 28 /// only the first one is returned. 29 ref inout(string) opIndex(string name) inout 30 { 31 return headers[toUpper(name)][0]; 32 } 33 34 string opIndexAssign(string value, string name) 35 { 36 headers[toUpper(name)] = [value]; 37 return value; 38 } 39 40 inout(string)* opIn_r(string name) inout 41 { 42 auto pvalues = toUpper(name) in headers; 43 if (pvalues && (*pvalues).length) 44 return (*pvalues).ptr; 45 return null; 46 } 47 48 void remove(string name) 49 { 50 headers.remove(toUpper(name)); 51 } 52 53 // D forces these to be "ref" 54 int opApply(int delegate(ref string name, ref string value) dg) 55 { 56 int ret; 57 outer: 58 foreach (name, values; headers) 59 foreach (value; values) 60 { 61 auto normName = normalizeHeaderName(name); 62 ret = dg(normName, value); 63 if (ret) 64 break outer; 65 } 66 return ret; 67 } 68 69 // Copy-paste because of https://issues.dlang.org/show_bug.cgi?id=7543 70 int opApply(int delegate(ref string name, ref const(string) value) dg) const 71 { 72 int ret; 73 outer: 74 foreach (name, values; headers) 75 foreach (value; values) 76 { 77 auto normName = normalizeHeaderName(name); 78 ret = dg(normName, value); 79 if (ret) 80 break outer; 81 } 82 return ret; 83 } 84 85 void add(string name, string value) 86 { 87 name = toUpper(name); 88 if (name !in headers) 89 headers[name] = [value]; 90 else 91 headers[name] ~= value; 92 } 93 94 string get(string key, string def) const 95 { 96 return getLazy(key, def); 97 } 98 99 string getLazy(string key, lazy string def) const 100 { 101 auto pvalue = key in this; 102 return pvalue ? *pvalue : def; 103 } 104 105 inout(string)[] getAll(string key) inout 106 { 107 return headers[toUpper(key)]; 108 } 109 110 /// Warning: discards repeating headers 111 string[string] opCast(T)() const 112 if (is(T == string[string])) 113 { 114 string[string] result; 115 foreach (key, value; this) 116 result[key] = value; 117 return result; 118 } 119 120 string[][string] opCast(T)() inout 121 if (is(T == string[][string])) 122 { 123 string[][string] result; 124 foreach (k, v; this) 125 result[k] ~= v; 126 return result; 127 } 128 } 129 130 unittest 131 { 132 Headers headers; 133 headers["test"] = "test"; 134 135 void test(T)(T headers) 136 { 137 assert("TEST" in headers); 138 assert(headers["TEST"] == "test"); 139 140 foreach (k, v; headers) 141 assert(k == "Test" && v == "test"); 142 143 auto aas = cast(string[string])headers; 144 assert(aas == ["Test" : "test"]); 145 146 auto aaa = cast(string[][string])headers; 147 assert(aaa == ["Test" : ["test"]]); 148 } 149 150 test(headers); 151 152 const constHeaders = headers; 153 test(constHeaders); 154 } 155 156 /// Normalize capitalization 157 string normalizeHeaderName(string header) 158 { 159 alias std.ascii.toUpper toUpper; 160 alias std.ascii.toLower toLower; 161 162 auto s = header.dup; 163 auto segments = s.split("-"); 164 foreach (segment; segments) 165 { 166 foreach (ref c; segment) 167 c = cast(char)toUpper(c); 168 switch (segment) 169 { 170 case "ID": 171 case "IP": 172 case "NNTP": 173 case "TE": 174 case "WWW": 175 continue; 176 case "ETAG": 177 segment[] = "ETag"; 178 break; 179 default: 180 foreach (ref c; segment[1..$]) 181 c = cast(char)toLower(c); 182 break; 183 } 184 } 185 return assumeUnique(s); 186 } 187 188 unittest 189 { 190 assert(normalizeHeaderName("X-ORIGINATING-IP") == "X-Originating-IP"); 191 }