1 /** 2 * Header parsing 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.headerparse; 15 16 import std.exception; 17 import std.string; 18 import std.array; 19 20 import ae.net.ietf.headers; 21 import ae.sys.data; 22 import ae.utils.text; 23 24 /** 25 * Check if the passed data contains a full set of headers 26 * (that is, contain a \r\n\r\n sequence), and if so - 27 * parses them, removes them from data (so that it contains 28 * only the message body), and returns true; otherwise 29 * returns false. 30 * The header string data is duplicated to the managed 31 * heap. 32 */ 33 /// ditto 34 bool parseHeaders(ref Data[] data, out Headers headers) 35 { 36 string dummy; 37 return parseHeadersImpl!false(data, dummy, headers); 38 } 39 40 /// As above, but treat the first line differently, and 41 /// return it in firstLine. 42 bool parseHeaders(ref Data[] data, out string firstLine, out Headers headers) 43 { 44 return parseHeadersImpl!true(data, firstLine, headers); 45 } 46 47 /// Parse headers from the given string. 48 Headers parseHeaders(string headerData) 49 { 50 string firstLine; // unused 51 return parseHeadersImpl!false(headerData, firstLine); 52 } 53 54 private: 55 56 bool parseHeadersImpl(bool FIRST_LINE)(ref Data[] data, out string firstLine, out Headers headers) 57 { 58 if (!data.length) 59 return false; 60 61 static const DELIM1 = "\r\n\r\n"; 62 static const DELIM2 = "\n\n"; 63 64 size_t startFrom = 0; 65 string delim; 66 searchAgain: 67 auto data0 = cast(const(char)[])data[0].contents; 68 sizediff_t headersEnd; 69 delim = DELIM1; headersEnd = data0[startFrom..$].indexOf(delim); 70 if (headersEnd < 0) 71 { 72 delim = DELIM2; headersEnd = data0[startFrom..$].indexOf(delim); 73 } 74 if (headersEnd < 0) 75 { 76 if (data.length > 1) 77 { 78 // coagulate first two blocks 79 startFrom = data0.length > delim.length ? data0.length - (DELIM1.length-1) : 0; 80 data = [data[0] ~ data[1]] ~ data[2..$]; 81 goto searchAgain; 82 } 83 else 84 return false; 85 } 86 headersEnd += startFrom; 87 88 auto headerData = data0[0..headersEnd].idup; // copy Data slice to heap 89 data[0] = data[0][headersEnd + delim.length .. data[0].length]; 90 91 headers = parseHeadersImpl!FIRST_LINE(headerData, firstLine); 92 return true; 93 } 94 95 Headers parseHeadersImpl(bool FIRST_LINE)(string headerData, out string firstLine) 96 { 97 headerData = headerData.replace("\r\n", "\n").replace("\n\t", " ").replace("\n ", " "); 98 string[] lines = splitAsciiLines(headerData); 99 static if (FIRST_LINE) 100 { 101 enforce(lines.length, "Empty first line in headers"); 102 firstLine = lines[0]; 103 lines = lines[1 .. lines.length]; 104 } 105 106 Headers headers; 107 foreach (line; lines) 108 { 109 auto valueStart = line.indexOf(':'); 110 if (valueStart > 0) 111 headers.add(line[0..valueStart].strip(), line[valueStart+1..$].strip()); 112 } 113 114 return headers; 115 } 116 117 unittest 118 { 119 void test(string message) 120 { 121 auto data = [Data(message)]; 122 Headers headers; 123 assert(parseHeaders(data, headers)); 124 assert(headers["From"] == "John Smith <john@smith.net>"); 125 assert(headers["To"] == "Mary Smith <john@smith.net>"); 126 assert(headers["Subject"] == "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); 127 assert(cast(string)data.joinToHeap() == "Message body goes here"); 128 } 129 130 string message = q"EOS 131 From : John Smith <john@smith.net> 132 to:Mary Smith <john@smith.net> 133 Subject: Lorem ipsum dolor sit amet, consectetur 134 adipisicing elit, sed do eiusmod tempor 135 incididunt ut labore et dolore magna aliqua. 136 137 Message body goes here 138 EOS".strip(); 139 140 message = message.replace("\r\n", "\n"); 141 test(message); 142 message = message.replace("\n", "\r\n"); 143 test(message); 144 }