Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 108x 27x 12x 12x 12x 12x 12x 12x 12x 12x 12x 11x 11x 14x 14x 14x 14x 14x 14x 20x 20x 1x 1x 19x 19x 15x 15x 15x 4x 4x 4x 4x 4x 4x 14x 14x 15x 14x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x | import { Decoder } from "./2047"; import { Poster } from "./Poster"; import { AntiSpamReport } from "./row/Antispam"; import { ForefrontAntiSpamReport } from "./row/ForefrontAntispam"; import { Header } from "./row/Header"; import { Summary } from "./Summary"; import { Other } from "./table/Other"; import { Received } from "./table/Received"; export class HeaderModel { public originalHeaders: string; public summary: Summary; public receivedHeaders: Received; public forefrontAntiSpamReport: ForefrontAntiSpamReport; public antiSpamReport: AntiSpamReport; public otherHeaders: Other; private hasDataInternal: boolean; private statusInternal: string; public get hasData(): boolean { return this.hasDataInternal || !!this.statusInternal; } public get status(): string { return this.statusInternal; } public set status(value) { this.statusInternal = value; } [index: string]: unknown; constructor(headers?: string) { this.summary = new Summary(); this.receivedHeaders = new Received(); this.forefrontAntiSpamReport = new ForefrontAntiSpamReport(); this.antiSpamReport = new AntiSpamReport(); this.otherHeaders = new Other(); this.originalHeaders = ""; this.statusInternal = ""; this.hasDataInternal = false; if (headers) { this.parseHeaders(headers); Poster.postMessageToParent("modelToString", this.toString()); } } public getHeaderList(headers: string): Header[] { // First, break up out input by lines. // Keep empty lines for recognizing the boundary between the header section & the body. const lines: string[] = headers.split(/\r\n|\r|\n/); const headerList: Header[] = []; let iNextHeader = 0; let prevHeader: Header | undefined; let body = false; headerSection: while (!body) { unfoldLines: for (let line of lines) { // Handling empty lines. The body is separated from the header section by an empty line (RFC 5322, 2.1). // To avoid processing the body as headers we should stop there, as someone might paste an entire message. // Empty lines at the beginning can be omitted, because that could be a common copy-paste error. Iif (body) break headerSection; if (line === "") { if (headerList.length > 0) body = true; continue unfoldLines; } // Recognizing a header: // - First colon comes before first white space. // - We're not strictly honoring white space folding because initial white space // - is commonly lost. Instead, we heuristically assume that space before a colon must have been folded. // This expression will give us: // match[1] - everything before the first colon, assuming no spaces (header). // match[2] - everything after the first colon (value). const match: RegExpMatchArray | null = line.match(/(^[\w-.]*?): ?(.*)/); // There's one false positive we might get: if the time in a Received header has been // folded to the next line, the line might start with something like "16:20:05 -0400". // This matches our regular expression. The RFC does not preclude such a header, but I've // never seen one in practice, so we check for and exclude 'headers' that // consist only of 1 or 2 digits. if (match && match[1] && !match[1].match(/^\d{1,2}$/)) { headerList[iNextHeader] = new Header(match[1], match[2] ?? ""); prevHeader = headerList[iNextHeader]; iNextHeader++; } else { if (iNextHeader > 0) { // Tack this line to the previous line // All folding whitespace should collapse to a single space line = line.replace(/^[\s]+/, ""); Iif (!line) continue unfoldLines; if (prevHeader) { const separator: string = prevHeader.value ? " " : ""; prevHeader.value += separator + line; } } else E{ // If we didn't have a previous line, go ahead and use this line Iif (line.match(/\S/g)) { headerList[iNextHeader] = new Header("", line); prevHeader = headerList[iNextHeader]; iNextHeader++; } } } } break headerSection; } // 2047 decode our headers now headerList.forEach((header: Header) => { // Clean 2047 encoding // Strip nulls // Strip trailing carriage returns header.value = Decoder.clean2047Encoding(header.value).replace(/\0/g, "").replace(/[\n\r]+$/, ""); }); return headerList; } public parseHeaders(headers: string): void { // Initialize originalHeaders in case we have parsing problems // Flatten CRLF to LF to avoid extra blank lines this.originalHeaders = headers.replace(/(?:\r\n|\r|\n)/g, "\n"); const headerList: Header[] = this.getHeaderList(headers); if (headerList.length > 0) { this.hasDataInternal = true; } headerList.forEach((header: Header) => { // Grab values for our summary pane Iif (this.summary.add(header)) return; // Properties with special parsing Iif (this.forefrontAntiSpamReport.add(header)) return; Iif (this.antiSpamReport.add(header)) return; if (this.receivedHeaders.add(header)) return; this.otherHeaders.add(header); }); this.summary.totalTime = this.receivedHeaders.computeDeltas(); } public toString(): string { const ret: string[] = []; Iif (this.summary.exists()) ret.push(this.summary.toString()); if (this.receivedHeaders.exists()) ret.push(this.receivedHeaders.toString()); Iif (this.forefrontAntiSpamReport.exists()) ret.push(this.forefrontAntiSpamReport.toString()); Iif (this.antiSpamReport.exists()) ret.push(this.antiSpamReport.toString()); Iif (this.otherHeaders.exists()) ret.push(this.otherHeaders.toString()); return ret.join("\n\n"); } } |