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