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