braid-http 0.3.12 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -73,7 +73,7 @@ function braidify_http (http) {
73
73
 
74
74
  // That will run each time we get new data
75
75
  res.orig_on('data', (chunk) => {
76
- parser.read(chunk.toString())
76
+ parser.read(chunk)
77
77
  })
78
78
  }
79
79
 
@@ -186,7 +186,7 @@ async function braid_fetch (url, params = {}) {
186
186
  if (params.patches.length === 1) {
187
187
  let patch = params.patches[0]
188
188
  params.headers.set('Content-Range', `${patch.unit} ${patch.range}`)
189
- params.headers.set('Content-Length', `${patch.content.length}`)
189
+ params.headers.set('Content-Length', `${(new TextEncoder().encode(patch.content)).length}`)
190
190
  params.body = patch.content
191
191
  }
192
192
 
@@ -194,7 +194,7 @@ async function braid_fetch (url, params = {}) {
194
194
  else {
195
195
  params.headers.set('Patches', params.patches.length)
196
196
  params.body = (params.patches).map(patch => {
197
- var length = `content-length: ${patch.content.length}`
197
+ var length = `content-length: ${(new TextEncoder().encode(patch.content)).length}`
198
198
  var range = `content-range: ${patch.unit} ${patch.range}`
199
199
  return `${length}\r\n${range}\r\n\r\n${patch.content}\r\n`
200
200
  }).join('\r\n')
@@ -318,7 +318,6 @@ async function handle_fetch_stream (stream, cb) {
318
318
 
319
319
  // Set up a reader
320
320
  var reader = stream.getReader(),
321
- decoder = new TextDecoder('utf-8'),
322
321
  parser = subscription_parser(cb)
323
322
 
324
323
  while (true) {
@@ -341,7 +340,7 @@ async function handle_fetch_stream (stream, cb) {
341
340
  }
342
341
 
343
342
  // Tell the parser to process some more stream
344
- parser.read(decoder.decode(value))
343
+ parser.read(value)
345
344
  }
346
345
  }
347
346
 
@@ -353,7 +352,7 @@ async function handle_fetch_stream (stream, cb) {
353
352
 
354
353
  var subscription_parser = (cb) => ({
355
354
  // A parser keeps some parse state
356
- state: {input: ''},
355
+ state: {input: []},
357
356
 
358
357
  // And reports back new versions as soon as they are ready
359
358
  cb: cb,
@@ -363,10 +362,10 @@ var subscription_parser = (cb) => ({
363
362
  read (input) {
364
363
 
365
364
  // Store the new input!
366
- this.state.input += input
365
+ this.state.input.push(...input)
367
366
 
368
367
  // Now loop through the input and parse until we hit a dead end
369
- while (this.state.input.trim() !== '') {
368
+ while (this.state.input.length) {
370
369
 
371
370
  // Try to parse an update
372
371
  try {
@@ -445,33 +444,18 @@ function parse_update (state) {
445
444
  return parse_body(state)
446
445
  }
447
446
 
448
- function swallow_blank_lines (input) {
449
- var blank_lines = /(\r\n|\n)*/.exec(input)[0]
450
- return input.substr(blank_lines.length)
451
- }
452
-
453
447
  // Parsing helpers
454
448
  function parse_headers (input) {
455
- input = swallow_blank_lines(input)
456
449
 
457
- // First, find the start & end block of the headers. The headers start
458
- // when there are no longer newlines, and end at the first double-newline.
459
-
460
- // Look for the double-newline at the end of the headers
461
- var headers_end = input.match(/(\r?\n)\r?\n/)
450
+ var h = extractHeader(input)
451
+ if (!h) return {result: 'waiting'}
462
452
 
463
- // ...if we found none, then we need to wait for more input to complete
464
- // the headers..
465
- if (!headers_end)
466
- return {result: 'waiting'}
467
-
468
- // We now know where the headers are to parse!
469
- var headers_length = headers_end.index + headers_end[1].length,
470
- headers_source = input.substring(0, headers_length)
453
+ var headers_source = h.header_string
454
+ var headers_length = headers_source.length
471
455
 
472
456
  // Let's parse them! First define some variables:
473
457
  var headers = {},
474
- header_regex = /([\w-_]+):\s?(.*)\r?\n/gy, // Parses one line a time
458
+ header_regex = /([\w-_]+):\s?(.*)\r?\n?/gy, // Parses one line a time
475
459
  match,
476
460
  found_last_match = false
477
461
 
@@ -504,15 +488,7 @@ function parse_headers (input) {
504
488
  headers.patches = JSON.parse(headers.patches)
505
489
 
506
490
  // Update the input
507
- input = input.substring(headers_length)
508
-
509
- // Swallow the final blank line ending the headers
510
- if (input.substr(0, 2) === '\r\n')
511
- // Swallow \r\n
512
- input = input.substr(2)
513
- else
514
- // Swallow \n
515
- input = input.substr(1)
491
+ input = h.remaining_bytes
516
492
 
517
493
  // And return the parsed result
518
494
  return { result: 'success', headers, input }
@@ -538,7 +514,6 @@ function parse_body (state) {
538
514
  }
539
515
 
540
516
  // We have the whole block!
541
- var consumed_length = content_length + 2
542
517
  state.result = 'success'
543
518
 
544
519
  // If we have a content-range, then this is a patch
@@ -553,7 +528,7 @@ function parse_body (state) {
553
528
  state.patches = [{
554
529
  unit: match.unit,
555
530
  range: match.range,
556
- content: state.input.substring(0, content_length),
531
+ content: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input.slice(0, content_length))),
557
532
 
558
533
  // Question: Perhaps we should include headers here, like we do for
559
534
  // the Patches: N headers below?
@@ -564,9 +539,9 @@ function parse_body (state) {
564
539
 
565
540
  // Otherwise, this is a snapshot body
566
541
  else
567
- state.body = state.input.substring(0, content_length)
542
+ state.body = (new TextDecoder('utf-8')).decode(new Uint8Array(state.input.slice(0, content_length)))
568
543
 
569
- state.input = state.input.substring(consumed_length)
544
+ state.input = state.input.slice(content_length)
570
545
  return state
571
546
  }
572
547
 
@@ -581,8 +556,6 @@ function parse_body (state) {
581
556
  while (!(state.patches.length === state.headers.patches
582
557
  && (state.patches.length === 0 || 'content' in last_patch))) {
583
558
 
584
- state.input = state.input.trimStart()
585
-
586
559
  // Are we starting a new patch?
587
560
  if (!last_patch || 'content' in last_patch) {
588
561
  last_patch = {}
@@ -614,14 +587,14 @@ function parse_body (state) {
614
587
  return {
615
588
  result: 'error',
616
589
  message: 'no content-length in patch',
617
- patch: last_patch, input: state.input
590
+ patch: last_patch, input: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input))
618
591
  }
619
592
 
620
593
  if (!('content-range' in last_patch.headers))
621
594
  return {
622
595
  result: 'error',
623
596
  message: 'no content-range in patch',
624
- patch: last_patch, input: state.input
597
+ patch: last_patch, input: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input))
625
598
  }
626
599
 
627
600
  var content_length = parseInt(last_patch.headers['content-length'])
@@ -637,17 +610,17 @@ function parse_body (state) {
637
610
  return {
638
611
  result: 'error',
639
612
  message: 'cannot parse content-range in patch',
640
- patch: last_patch, input: state.input
613
+ patch: last_patch, input: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input))
641
614
  }
642
615
 
643
616
  last_patch.unit = match.unit
644
617
  last_patch.range = match.range
645
- last_patch.content = state.input.substr(0, content_length)
618
+ last_patch.content = (new TextDecoder('utf-8')).decode(new Uint8Array(state.input.slice(0, content_length)))
646
619
  last_patch.extra_headers = extra_headers(last_patch.headers)
647
620
  delete last_patch.headers // We only keep the extra headers ^^
648
621
 
649
622
  // Consume the parsed input
650
- state.input = state.input.substring(content_length)
623
+ state.input = state.input.slice(content_length)
651
624
  }
652
625
  }
653
626
 
@@ -684,6 +657,60 @@ function extra_headers (headers) {
684
657
  return result
685
658
  }
686
659
 
660
+ // a parsing utility function that will inspect a byte array of incoming data
661
+ // to see if there is header information at the beginning,
662
+ // namely some non-newline characters followed by two newlines
663
+ function extractHeader(input) {
664
+ // Find the start of the headers
665
+ let begin_headers_i = 0;
666
+ while (input[begin_headers_i] === 13 || input[begin_headers_i] === 10) {
667
+ begin_headers_i++;
668
+ }
669
+ if (begin_headers_i === input.length) {
670
+ return null; // Incomplete headers
671
+ }
672
+
673
+ // Look for the double-newline at the end of the headers
674
+ let end_headers_i = begin_headers_i;
675
+ let size_of_tail = 0;
676
+ while (end_headers_i < input.length) {
677
+ if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 10) {
678
+ size_of_tail = 2;
679
+ break;
680
+ }
681
+ if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 13 && input[end_headers_i + 2] === 10) {
682
+ size_of_tail = 3;
683
+ break;
684
+ }
685
+ if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 10) {
686
+ size_of_tail = 3;
687
+ break;
688
+ }
689
+ if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 13 && input[end_headers_i + 3] === 10) {
690
+ size_of_tail = 4;
691
+ break;
692
+ }
693
+
694
+ end_headers_i++;
695
+ }
696
+
697
+ // If no double-newline is found, wait for more input
698
+ if (end_headers_i === input.length) {
699
+ return null; // Incomplete headers
700
+ }
701
+
702
+ // Extract the header string
703
+ const headerBytes = input.slice(begin_headers_i, end_headers_i);
704
+ const headerString = new TextDecoder('utf-8').decode(new Uint8Array(headerBytes));
705
+
706
+ // Return the remaining bytes and the header string
707
+ const remainingBytes = input.slice(end_headers_i + size_of_tail);
708
+ return {
709
+ remaining_bytes: remainingBytes,
710
+ header_string: headerString
711
+ };
712
+ }
713
+
687
714
  // ****************************
688
715
  // Exports
689
716
  // ****************************
@@ -54,7 +54,7 @@ function generate_patches(res, patches) {
54
54
 
55
55
  let extra_headers = Object.fromEntries(Object.entries(patch).filter(([k, v]) => k != 'unit' && k != 'range' && k != 'content'))
56
56
 
57
- result += `Content-Length: ${patch.content.length}\r
57
+ result += `Content-Length: ${(new TextEncoder().encode(patch.content)).length}\r
58
58
  Content-Range: ${patch.unit} ${patch.range}\r
59
59
  ${Object.entries(extra_headers).map(([k, v]) => `${k}: ${v}\r\n`).join('')}\r
60
60
  ${patch.content}`
@@ -89,12 +89,12 @@ function parse_patches (req, cb) {
89
89
  var [unit, range] = parse_content_range(req.headers['content-range'])
90
90
 
91
91
  // The contents of the patch is in the request body
92
- var buffer = ''
92
+ var buffer = []
93
93
  // Read the body one chunk at a time
94
- req.on('data', chunk => buffer = buffer + chunk)
94
+ req.on('data', chunk => buffer.push(chunk))
95
95
  // Then return it
96
96
  req.on('end', () => {
97
- patches = [{unit, range, content: buffer}]
97
+ patches = [{unit, range, content: Buffer.concat(buffer).toString('utf8')}]
98
98
  cb(patches)
99
99
  })
100
100
  }
@@ -103,7 +103,7 @@ function parse_patches (req, cb) {
103
103
  else {
104
104
  num_patches = parseInt(num_patches)
105
105
  let patches = []
106
- let buffer = ""
106
+ let buffer = []
107
107
 
108
108
  // We check to send send patches each time we parse one. But if there
109
109
  // are zero to parse, we will never check to send them.
@@ -113,57 +113,39 @@ function parse_patches (req, cb) {
113
113
  req.on('data', function parse (chunk) {
114
114
 
115
115
  // Merge the latest chunk into our buffer
116
- buffer = (buffer + chunk)
116
+ buffer.push(...chunk)
117
117
 
118
118
  while (patches.length < num_patches) {
119
- // We might have extra newlines at the start, because patches
120
- // can be separated by arbitrary numbers of newlines
121
- buffer = buffer.trimStart()
122
-
123
- // First parse the patch headers. It ends with a double-newline.
124
- // Let's see where that is.
125
- var headers_end = buffer.match(/(\r?\n)(\r?\n)/)
126
-
127
- // Give up if we don't have a set of headers yet.
128
- if (!headers_end)
129
- return
130
-
131
- // Now we know where things end
132
- var first_newline = headers_end[1],
133
- headers_length = headers_end.index + first_newline.length,
134
- blank_line = headers_end[2]
119
+ let h = extractHeader(buffer)
120
+ if (!h) return
135
121
 
136
122
  // Now let's parse those headers.
137
- var headers = require('parse-headers')(
138
- buffer.substring(0, headers_length)
139
- )
123
+ var headers = require('parse-headers')(h.header_string)
140
124
 
141
125
  // We require `content-length` to declare the length of the patch.
142
126
  if (!('content-length' in headers)) {
143
127
  // Print a nice error if it's missing
144
128
  console.error('No content-length in', JSON.stringify(headers),
145
- 'from', {buffer, headers_length})
129
+ 'from', {buffer})
146
130
  process.exit(1)
147
131
  }
148
132
 
149
133
  var body_length = parseInt(headers['content-length'])
150
134
 
151
135
  // Give up if we don't have the full patch yet.
152
- if (buffer.length < headers_length + blank_line.length + body_length)
136
+ if (h.remaining_bytes.length < body_length)
153
137
  return
154
138
 
155
139
  // XX Todo: support custom patch types beyond content-range.
156
140
 
157
141
  // Content-range is of the form '<unit> <range>' e.g. 'json .index'
158
142
  var [unit, range] = parse_content_range(headers['content-range'])
159
- var patch_content =
160
- buffer.substring(headers_length + blank_line.length,
161
- headers_length + blank_line.length + body_length)
143
+ var patch_content = new TextDecoder('utf-8').decode(new Uint8Array(h.remaining_bytes.slice(0, body_length)))
162
144
 
163
145
  // We've got our patch!
164
146
  patches.push({unit, range, content: patch_content})
165
147
 
166
- buffer = buffer.substring(headers_length + blank_line.length + body_length)
148
+ buffer = h.remaining_bytes.slice(body_length)
167
149
  }
168
150
 
169
151
  // We got all the patches! Pause the stream and tell the callback!
@@ -375,5 +357,58 @@ function send_update(res, data, url, peer) {
375
357
  }
376
358
  }
377
359
 
360
+ // a parsing utility function that will inspect a byte array of incoming data
361
+ // to see if there is header information at the beginning,
362
+ // namely some non-newline characters followed by two newlines
363
+ function extractHeader(input) {
364
+ // Find the start of the headers
365
+ let begin_headers_i = 0;
366
+ while (input[begin_headers_i] === 13 || input[begin_headers_i] === 10) {
367
+ begin_headers_i++;
368
+ }
369
+ if (begin_headers_i === input.length) {
370
+ return null; // Incomplete headers
371
+ }
372
+
373
+ // Look for the double-newline at the end of the headers
374
+ let end_headers_i = begin_headers_i;
375
+ let size_of_tail = 0;
376
+ while (end_headers_i < input.length) {
377
+ if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 10) {
378
+ size_of_tail = 2;
379
+ break;
380
+ }
381
+ if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 13 && input[end_headers_i + 2] === 10) {
382
+ size_of_tail = 3;
383
+ break;
384
+ }
385
+ if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 10) {
386
+ size_of_tail = 3;
387
+ break;
388
+ }
389
+ if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 13 && input[end_headers_i + 3] === 10) {
390
+ size_of_tail = 4;
391
+ break;
392
+ }
393
+
394
+ end_headers_i++;
395
+ }
396
+
397
+ // If no double-newline is found, wait for more input
398
+ if (end_headers_i === input.length) {
399
+ return null; // Incomplete headers
400
+ }
401
+
402
+ // Extract the header string
403
+ const headerBytes = input.slice(begin_headers_i, end_headers_i);
404
+ const headerString = new TextDecoder('utf-8').decode(new Uint8Array(headerBytes));
405
+
406
+ // Return the remaining bytes and the header string
407
+ const remainingBytes = input.slice(end_headers_i + size_of_tail);
408
+ return {
409
+ remaining_bytes: remainingBytes,
410
+ header_string: headerString
411
+ };
412
+ }
378
413
 
379
414
  module.exports = braidify
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-http",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "An implementation of Braid-HTTP for Node.js and Browsers",
5
5
  "scripts": {
6
6
  "test": "node test/server.js"