forked from watson/http-headers
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
137 lines (119 loc) · 3.67 KB
/
index.js
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
'use strict'
var nextLine = require('next-line')
// RFC-2068 Start-Line definitions:
// Request-Line: Method SP Request-URI SP HTTP-Version CRLF
// Status-Line: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
var startLine = /^[A-Z_]+(\/\d\.\d)? /
var requestLine = /^([A-Z_]+) (.+) [A-Z]+\/(\d)\.(\d)$/
var statusLine = /^[A-Z]+\/(\d)\.(\d) (\d{3}) (.*)$/
module.exports = function (data, onlyHeaders) {
return parse(normalize(data), onlyHeaders)
}
function parse (str, onlyHeaders) {
var line = firstLine(str)
var match
if (onlyHeaders && startLine.test(line)) {
return parseHeaders(str)
} else if ((match = line.match(requestLine)) !== null) {
return {
method: match[1],
url: match[2],
version: { major: parseInt(match[3], 10), minor: parseInt(match[4], 10) },
headers: parseHeaders(str)
}
} else if ((match = line.match(statusLine)) !== null) {
return {
version: { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) },
statusCode: parseInt(match[3], 10),
statusMessage: match[4],
headers: parseHeaders(str)
}
} else {
return parseHeaders(str)
}
}
function parseHeaders (str) {
var headers = {}
var next = nextLine(str)
var line = next()
var index, name, value
if (startLine.test(line)) line = next()
while (line) {
// subsequent lines in multi-line headers start with whitespace
if (line[0] === ' ' || line[0] === '\t') {
value += ' ' + line.trim()
line = next()
continue
}
if (name) addHeaderLine(name, value, headers)
index = line.indexOf(':')
name = line.substr(0, index)
value = line.substr(index + 1).trim()
line = next()
}
if (name) addHeaderLine(name, value, headers)
return headers
}
function normalize (str) {
if (str && str._header) str = str._header // extra headers from http.ServerResponse object
if (!str || typeof str.toString !== 'function') return ''
return str.toString().trim()
}
function firstLine (str) {
var nl = str.indexOf('\r\n')
if (nl === -1) return str
else return str.slice(0, nl)
}
// The following function is lifted from:
// https://github.com/nodejs/node/blob/f1294f5bfd7f02bce8029818be9c92de59749137/lib/_http_incoming.js#L116-L170
//
// Add the given (field, value) pair to the message
//
// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
// same header with a ', ' if the header in question supports specification of
// multiple values this way. If not, we declare the first instance the winner
// and drop the second. Extended header fields (those beginning with 'x-') are
// always joined.
function addHeaderLine (field, value, dest) {
field = field.toLowerCase()
switch (field) {
// Array headers:
case 'set-cookie':
if (dest[field] !== undefined) {
dest[field].push(value)
} else {
dest[field] = [value]
}
break
// list is taken from:
// https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp
case 'content-type':
case 'content-length':
case 'user-agent':
case 'referer':
case 'host':
case 'authorization':
case 'proxy-authorization':
case 'if-modified-since':
case 'if-unmodified-since':
case 'from':
case 'location':
case 'max-forwards':
case 'retry-after':
case 'etag':
case 'last-modified':
case 'server':
case 'age':
case 'expires':
// drop duplicates
if (dest[field] === undefined) dest[field] = value
break
default:
// make comma-separated list
if (typeof dest[field] === 'string') {
dest[field] += ', ' + value
} else {
dest[field] = value
}
}
}