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.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 DataVec 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 DataVec 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 DataVec 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[0] = data[0] ~ data[1]; 81 data.remove(1); 82 goto searchAgain; 83 } 84 else 85 return false; 86 } 87 headersEnd += startFrom; 88 89 auto headerData = data0[0..headersEnd].idup; // copy Data slice to heap 90 data[0] = data[0][headersEnd + delim.length .. data[0].length]; 91 92 headers = parseHeadersImpl!FIRST_LINE(headerData, firstLine); 93 return true; 94 } 95 96 Headers parseHeadersImpl(bool FIRST_LINE)(string headerData, out string firstLine) 97 { 98 headerData = headerData.replace("\r\n", "\n").replace("\n\t", " ").replace("\n ", " "); 99 string[] lines = splitAsciiLines(headerData); 100 static if (FIRST_LINE) 101 { 102 enforce(lines.length, "Empty first line in headers"); 103 firstLine = lines[0]; 104 lines = lines[1 .. lines.length]; 105 } 106 107 Headers headers; 108 foreach (line; lines) 109 { 110 auto valueStart = line.indexOf(':'); 111 if (valueStart > 0) 112 headers.add(line[0..valueStart].strip(), line[valueStart+1..$].strip()); 113 } 114 115 return headers; 116 } 117 118 unittest 119 { 120 void test(string message) 121 { 122 auto data = DataVec(Data(message)); 123 Headers headers; 124 assert(parseHeaders(data, headers)); 125 assert(headers["From"] == "John Smith <john@smith.net>"); 126 assert(headers["To"] == "Mary Smith <john@smith.net>"); 127 assert(headers["Subject"] == "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); 128 assert(cast(string)data.joinToHeap() == "Message body goes here"); 129 } 130 131 string message = q"EOS 132 From : John Smith <john@smith.net> 133 to:Mary Smith <john@smith.net> 134 Subject: Lorem ipsum dolor sit amet, consectetur 135 adipisicing elit, sed do eiusmod tempor 136 incididunt ut labore et dolore magna aliqua. 137 138 Message body goes here 139 EOS".strip(); 140 141 message = message.replace("\r\n", "\n"); 142 test(message); 143 message = message.replace("\n", "\r\n"); 144 test(message); 145 }