11'use strict' ;
22
3+ //
4+ // Allowed token characters:
5+ //
6+ // '!', '#', '$', '%', '&', ''', '*', '+', '-',
7+ // '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
8+ //
9+ // tokenChars[32] === 0 // ' '
10+ // tokenChars[33] === 1 // '!'
11+ // tokenChars[34] === 0 // '"'
12+ // ...
13+ //
14+ const tokenChars = [
15+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , // 0 - 15
16+ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , // 16 - 31
17+ 0 , 1 , 0 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 1 , 1 , 0 , // 32 - 47
18+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , // 48 - 63
19+ 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // 64 - 79
20+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 1 , 1 , // 80 - 95
21+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , // 96 - 111
22+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 , 0 , 1 , 0 // 112 - 127
23+ ] ;
24+
325/**
4- * Parse the `Sec-WebSocket-Extensions` header into an object .
26+ * Adds an offer to the map of extension offers .
527 *
6- * @param {String } value field value of the header
28+ * @param {Object } offers The map of extension offers
29+ * @param {String } name The extension name
30+ * @param {Object } params The extension parameters
31+ * @private
32+ */
33+ function pushOffer ( offers , name , params ) {
34+ if ( Object . hasOwnProperty . call ( offers , name ) ) offers [ name ] . push ( params ) ;
35+ else offers [ name ] = [ params ] ;
36+ }
37+
38+ /**
39+ * Adds a parameter to the map of parameters.
40+ *
41+ * @param {Object } params The map of parameters
42+ * @param {String } name The parameter name
43+ * @param {Object } value The parameter value
44+ * @private
45+ */
46+ function pushParam ( params , name , value ) {
47+ if ( Object . hasOwnProperty . call ( params , name ) ) params [ name ] . push ( value ) ;
48+ else params [ name ] = [ value ] ;
49+ }
50+
51+ /**
52+ * Parses the `Sec-WebSocket-Extensions` header into an object.
53+ *
54+ * @param {String } header The field value of the header
755 * @return {Object } The parsed object
856 * @public
957 */
10- const parse = ( value ) => {
11- value = value || '' ;
58+ function parse ( header ) {
59+ const offers = { } ;
1260
13- const extensions = { } ;
61+ if ( header === undefined || header === '' ) return offers ;
1462
15- value . split ( ',' ) . forEach ( ( v ) => {
16- const params = v . split ( ';' ) ;
17- const token = params . shift ( ) . trim ( ) ;
63+ var params = { } ;
64+ var mustUnescape = false ;
65+ var isEscaping = false ;
66+ var inQuotes = false ;
67+ var extensionName ;
68+ var paramName ;
69+ var start = - 1 ;
70+ var end = - 1 ;
1871
19- if ( extensions [ token ] === undefined ) {
20- extensions [ token ] = [ ] ;
21- } else if ( ! extensions . hasOwnProperty ( token ) ) {
22- return ;
23- }
72+ for ( var i = 0 ; i < header . length ; i ++ ) {
73+ const code = header . charCodeAt ( i ) ;
2474
25- const parsedParams = { } ;
75+ if ( extensionName === undefined ) {
76+ if ( end === - 1 && tokenChars [ code ] === 1 ) {
77+ if ( start === - 1 ) start = i ;
78+ } else if ( code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */ ) {
79+ if ( end === - 1 && start !== - 1 ) end = i ;
80+ } else if ( code === 0x3b /* ';' */ || code === 0x2c /* ',' */ ) {
81+ if ( start === - 1 ) throw new Error ( `unexpected character at index ${ i } ` ) ;
2682
27- params . forEach ( ( param ) => {
28- const parts = param . trim ( ) . split ( '=' ) ;
29- const key = parts [ 0 ] ;
30- var value = parts [ 1 ] ;
83+ if ( end === - 1 ) end = i ;
84+ const name = header . slice ( start , end ) ;
85+ if ( code === 0x2c ) {
86+ pushOffer ( offers , name , params ) ;
87+ params = { } ;
88+ } else {
89+ extensionName = name ;
90+ }
3191
32- if ( value === undefined ) {
33- value = true ;
92+ start = end = - 1 ;
3493 } else {
35- // unquote value
36- if ( value [ 0 ] === '"' ) {
37- value = value . slice ( 1 ) ;
38- }
39- if ( value [ value . length - 1 ] === '"' ) {
40- value = value . slice ( 0 , value . length - 1 ) ;
94+ throw new Error ( `unexpected character at index ${ i } ` ) ;
95+ }
96+ } else if ( paramName === undefined ) {
97+ if ( end === - 1 && tokenChars [ code ] === 1 ) {
98+ if ( start === - 1 ) start = i ;
99+ } else if ( code === 0x20 || code === 0x09 ) {
100+ if ( end === - 1 && start !== - 1 ) end = i ;
101+ } else if ( code === 0x3b || code === 0x2c ) {
102+ if ( start === - 1 ) throw new Error ( `unexpected character at index ${ i } ` ) ;
103+
104+ if ( end === - 1 ) end = i ;
105+ pushParam ( params , header . slice ( start , end ) , true ) ;
106+ if ( code === 0x2c ) {
107+ pushOffer ( offers , extensionName , params ) ;
108+ params = { } ;
109+ extensionName = undefined ;
41110 }
111+
112+ start = end = - 1 ;
113+ } else if ( code === 0x3d /* '=' */ && start !== - 1 && end === - 1 ) {
114+ paramName = header . slice ( start , i ) ;
115+ start = end = - 1 ;
116+ } else {
117+ throw new Error ( `unexpected character at index ${ i } ` ) ;
42118 }
119+ } else {
120+ //
121+ // The value of a quoted-string after unescaping must conform to the
122+ // token ABNF, so only token characters are valid.
123+ // Ref: https://tools.ietf.org/html/rfc6455#section-9.1
124+ //
125+ if ( isEscaping ) {
126+ if ( tokenChars [ code ] !== 1 ) {
127+ throw new Error ( `unexpected character at index ${ i } ` ) ;
128+ }
129+ if ( start === - 1 ) start = i ;
130+ else if ( ! mustUnescape ) mustUnescape = true ;
131+ isEscaping = false ;
132+ } else if ( inQuotes ) {
133+ if ( tokenChars [ code ] === 1 ) {
134+ if ( start === - 1 ) start = i ;
135+ } else if ( code === 0x22 /* '"' */ && start !== - 1 ) {
136+ inQuotes = false ;
137+ end = i ;
138+ } else if ( code === 0x5c /* '\' */ ) {
139+ isEscaping = true ;
140+ } else {
141+ throw new Error ( `unexpected character at index ${ i } ` ) ;
142+ }
143+ } else if ( code === 0x22 && header . charCodeAt ( i - 1 ) === 0x3d ) {
144+ inQuotes = true ;
145+ } else if ( end === - 1 && tokenChars [ code ] === 1 ) {
146+ if ( start === - 1 ) start = i ;
147+ } else if ( start !== - 1 && ( code === 0x20 || code === 0x09 ) ) {
148+ if ( end === - 1 ) end = i ;
149+ } else if ( code === 0x3b || code === 0x2c ) {
150+ if ( start === - 1 ) throw new Error ( `unexpected character at index ${ i } ` ) ;
151+
152+ if ( end === - 1 ) end = i ;
153+ var value = header . slice ( start , end ) ;
154+ if ( mustUnescape ) {
155+ value = value . replace ( / \\ / g, '' ) ;
156+ mustUnescape = false ;
157+ }
158+ pushParam ( params , paramName , value ) ;
159+ if ( code === 0x2c ) {
160+ pushOffer ( offers , extensionName , params ) ;
161+ params = { } ;
162+ extensionName = undefined ;
163+ }
43164
44- if ( parsedParams [ key ] === undefined ) {
45- parsedParams [ key ] = [ value ] ;
46- } else if ( parsedParams . hasOwnProperty ( key ) ) {
47- parsedParams [ key ] . push ( value ) ;
165+ paramName = undefined ;
166+ start = end = - 1 ;
167+ } else {
168+ throw new Error ( `unexpected character at index ${ i } ` ) ;
48169 }
49- } ) ;
170+ }
171+ }
172+
173+ if ( start === - 1 || inQuotes ) throw new Error ( 'unexpected end of input' ) ;
50174
51- extensions [ token ] . push ( parsedParams ) ;
52- } ) ;
175+ if ( end === - 1 ) end = i ;
176+ const token = header . slice ( start , end ) ;
177+ if ( extensionName === undefined ) {
178+ pushOffer ( offers , token , { } ) ;
179+ } else {
180+ if ( paramName === undefined ) {
181+ pushParam ( params , token , true ) ;
182+ } else if ( mustUnescape ) {
183+ pushParam ( params , paramName , token . replace ( / \\ / g, '' ) ) ;
184+ } else {
185+ pushParam ( params , paramName , token ) ;
186+ }
187+ pushOffer ( offers , extensionName , params ) ;
188+ }
53189
54- return extensions ;
55- } ;
190+ return offers ;
191+ }
56192
57193/**
58- * Serialize a parsed `Sec-WebSocket-Extensions` header to a string.
194+ * Serializes a parsed `Sec-WebSocket-Extensions` header to a string.
59195 *
60196 * @param {Object } value The object to format
61197 * @return {String } A string representing the given value
62198 * @public
63199 */
64- const format = ( value ) => {
200+ function format ( value ) {
65201 return Object . keys ( value ) . map ( ( token ) => {
66202 var paramsList = value [ token ] ;
67203 if ( ! Array . isArray ( paramsList ) ) paramsList = [ paramsList ] ;
@@ -73,6 +209,6 @@ const format = (value) => {
73209 } ) ) . join ( '; ' ) ;
74210 } ) . join ( ', ' ) ;
75211 } ) . join ( ', ' ) ;
76- } ;
212+ }
77213
78214module . exports = { format, parse } ;
0 commit comments