hbsig 0.2.1 → 0.2.3

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.
package/cjs/httpsig.js CHANGED
@@ -348,18 +348,48 @@ function encodeBodyPart(partName, bodyPart, inlineKey) {
348
348
  return "";
349
349
  }
350
350
 
351
- // Helper to detect if a Buffer contains binary data
352
- function isBinaryData(buf) {
353
- // Check first 100 bytes for binary indicators
354
- for (var i = 0; i < Math.min(buf.length, 100); i++) {
351
+ // Improved helper to detect if data is likely binary
352
+ function isBinaryData(data) {
353
+ var buf;
354
+ if (typeof data === "string") {
355
+ // Convert string to buffer using binary encoding to check byte values
356
+ buf = Buffer.from(data, "binary");
357
+ } else if (Buffer.isBuffer(data)) {
358
+ buf = data;
359
+ } else {
360
+ return false;
361
+ }
362
+
363
+ // Check a larger sample for better detection
364
+ var sampleSize = Math.min(buf.length, 512);
365
+ var nullCount = 0;
366
+ var controlCount = 0;
367
+ var highByteCount = 0;
368
+ for (var i = 0; i < sampleSize; i++) {
355
369
  var _byte = buf[i];
356
- // Non-printable chars (except CR/LF/TAB)
357
- if (_byte < 32 && _byte !== 9 && _byte !== 10 && _byte !== 13) return true;
358
- // High byte range that's not valid text
359
- if (_byte > 126 && _byte < 160) return true;
360
- // Null byte is definitely binary
361
- if (_byte === 0) return true;
370
+
371
+ // Null bytes are a strong indicator of binary
372
+ if (_byte === 0) {
373
+ nullCount++;
374
+ if (nullCount > 0) return true; // Even one null byte indicates binary
375
+ }
376
+
377
+ // Control characters (except common text ones: TAB, LF, CR)
378
+ if (_byte < 32 && _byte !== 9 && _byte !== 10 && _byte !== 13) {
379
+ controlCount++;
380
+ }
381
+
382
+ // High byte values that aren't valid UTF-8 continuation bytes
383
+ if (_byte > 127) {
384
+ highByteCount++;
385
+ }
362
386
  }
387
+
388
+ // If more than 10% of bytes are control chars, likely binary
389
+ if (controlCount / sampleSize > 0.1) return true;
390
+
391
+ // If more than 30% are high bytes without valid UTF-8 sequences, likely binary
392
+ if (highByteCount / sampleSize > 0.3) return true;
363
393
  return false;
364
394
  }
365
395
 
@@ -404,73 +434,83 @@ function parseMultipart(contentType, body) {
404
434
  // Parse headers more carefully to handle binary data
405
435
  var currentPos = 0;
406
436
  while (currentPos < headerBlock.length) {
407
- // Find next line ending (but be careful with binary data)
408
- var lineEnd = headerBlock.indexOf("\r\n", currentPos);
409
- if (lineEnd === -1) lineEnd = headerBlock.indexOf("\n", currentPos);
410
- if (lineEnd === -1) lineEnd = headerBlock.length;
411
- var line = headerBlock.substring(currentPos, lineEnd);
412
- var colonIndex = line.indexOf(": ");
413
- if (colonIndex > 0) {
414
- var name = line.substring(0, colonIndex).toLowerCase();
415
- var value = line.substring(colonIndex + 2);
416
-
417
- // Special handling for known binary fields or detected binary data
418
- if (name === "owner" || name === "signature") {
419
- // These fields contain binary data that may have embedded newlines
420
- // We need to read until we find the next header or end of headers
421
- var valueStart = currentPos + colonIndex + 2;
422
-
423
- // Look ahead to find where this field really ends
424
- // The next header will start with a valid header name followed by ": "
425
- var searchPos = valueStart;
426
- var valueEnd = headerBlock.length;
427
-
428
- // Look for the next valid header pattern
429
- while (searchPos < headerBlock.length) {
430
- var nextNewline = headerBlock.indexOf("\n", searchPos);
431
- if (nextNewline === -1) break;
432
-
433
- // Check if what follows looks like a header
434
- var nextLineStart = nextNewline + 1;
435
- var nextColon = headerBlock.indexOf(":", nextLineStart);
436
-
437
- // Valid header should have colon relatively close to line start
438
- if (nextColon > nextLineStart && nextColon < nextLineStart + 50) {
439
- // Check if the text before colon looks like a header name (ASCII text)
440
- var possibleHeaderName = headerBlock.substring(nextLineStart, nextColon);
441
- var looksLikeHeader = /^[a-zA-Z0-9-]+$/.test(possibleHeaderName);
442
- if (looksLikeHeader) {
443
- // Found the next header, value ends at the newline before it
444
- valueEnd = nextNewline;
445
- break;
446
- }
447
- }
448
- searchPos = nextNewline + 1;
449
- }
437
+ // Find the next colon to identify a potential header
438
+ var colonPos = headerBlock.indexOf(": ", currentPos);
439
+ if (colonPos === -1) {
440
+ // No more headers
441
+ break;
442
+ }
450
443
 
451
- // Extract the full value as binary string
452
- value = headerBlock.substring(valueStart, valueEnd);
444
+ // Look backwards from colon to find the start of this line
445
+ var lineStart = currentPos;
446
+ var searchBack = colonPos - 1;
447
+ while (searchBack >= currentPos) {
448
+ if (headerBlock[searchBack] === "\n") {
449
+ lineStart = searchBack + 1;
450
+ break;
451
+ }
452
+ searchBack--;
453
+ }
453
454
 
454
- // Remove trailing CR if present (since we found the LF)
455
- if (value.endsWith("\r")) {
456
- value = value.substring(0, value.length - 1);
457
- }
455
+ // Extract the header name
456
+ var name = headerBlock.substring(lineStart, colonPos).trim().toLowerCase();
458
457
 
459
- // Convert to Buffer to preserve binary
460
- headers[name] = Buffer.from(value, "binary");
461
- currentPos = valueEnd + 1;
462
- } else if (isBinaryData(Buffer.from(value, "binary"))) {
463
- // Regular field but contains binary data
464
- headers[name] = Buffer.from(value, "binary");
465
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1);
466
- } else {
467
- // Regular text field
468
- headers[name] = value;
469
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1);
458
+ // Check if this looks like a valid header name
459
+ if (!/^[a-zA-Z0-9-]+$/.test(name) || name.length > 50) {
460
+ // Not a valid header, skip past this colon
461
+ currentPos = colonPos + 2;
462
+ continue;
463
+ }
464
+
465
+ // Start of value is after ": "
466
+ var valueStart = colonPos + 2;
467
+
468
+ // Find the end of this header's value by looking for the next valid header
469
+ var valueEnd = headerBlock.length;
470
+ var searchPos = valueStart;
471
+ while (searchPos < headerBlock.length) {
472
+ var nextNewline = headerBlock.indexOf("\n", searchPos);
473
+ if (nextNewline === -1) break;
474
+ var nextLineStart = nextNewline + 1;
475
+ if (nextLineStart >= headerBlock.length) break;
476
+
477
+ // Check if next line starts with a header pattern
478
+ var nextColon = headerBlock.indexOf(": ", nextLineStart);
479
+ if (nextColon > nextLineStart && nextColon < nextLineStart + 50) {
480
+ var possibleHeaderName = headerBlock.substring(nextLineStart, nextColon).trim();
481
+
482
+ // Must be valid header name format
483
+ if (/^[a-zA-Z0-9-]+$/.test(possibleHeaderName)) {
484
+ valueEnd = nextNewline;
485
+ break;
486
+ }
470
487
  }
488
+ searchPos = nextNewline + 1;
489
+ }
490
+
491
+ // Extract the value
492
+ var value = headerBlock.substring(valueStart, valueEnd);
493
+
494
+ // Trim trailing CRLF or LF
495
+ if (value.endsWith("\r\n")) {
496
+ value = value.substring(0, value.length - 2);
497
+ } else if (value.endsWith("\n")) {
498
+ value = value.substring(0, value.length - 1);
499
+ } else if (value.endsWith("\r")) {
500
+ value = value.substring(0, value.length - 1);
501
+ }
502
+
503
+ // Determine if this is binary data
504
+ if (value.length > 0 && isBinaryData(value)) {
505
+ headers[name] = Buffer.from(value, "binary");
471
506
  } else {
472
- // No colon found, skip this line
473
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1);
507
+ headers[name] = value;
508
+ }
509
+
510
+ // Move to the end of this header's value
511
+ currentPos = valueEnd + 1;
512
+ if (headerBlock[valueEnd] === "\r") {
513
+ currentPos++;
474
514
  }
475
515
  }
476
516
  var disposition = headers["content-disposition"];
@@ -495,8 +535,8 @@ function parseMultipart(contentType, body) {
495
535
 
496
536
  // If there's body content in the inline part, add it as 'body'
497
537
  if (partBody) {
498
- // Convert back to Buffer to preserve binary data
499
- result[partName] = Buffer.from(partBody, "binary");
538
+ // Keep as Buffer if it's binary data
539
+ result[partName] = isBinaryData(partBody) ? Buffer.from(partBody, "binary") : partBody;
500
540
  }
501
541
  } else {
502
542
  // Handle named form-data parts
@@ -510,14 +550,15 @@ function parseMultipart(contentType, body) {
510
550
  var _restHeaders = _objectSpread({}, headers);
511
551
  delete _restHeaders["content-disposition"];
512
552
  if (Object.keys(_restHeaders).length === 0) {
513
- // Convert body back to Buffer
514
- result[partName] = Buffer.from(partBody, "binary");
553
+ // Keep as Buffer if it's binary data
554
+ result[partName] = isBinaryData(partBody) ? Buffer.from(partBody, "binary") : partBody;
515
555
  } else if (!partBody) {
556
+ // ao-types should stay with this part, not be extracted
516
557
  result[partName] = _restHeaders;
517
558
  } else {
518
- // Convert body back to Buffer
559
+ // Keep as Buffer if it's binary data
519
560
  result[partName] = _objectSpread(_objectSpread({}, _restHeaders), {}, {
520
- body: Buffer.from(partBody, "binary")
561
+ body: isBinaryData(partBody) ? Buffer.from(partBody, "binary") : partBody
521
562
  });
522
563
  }
523
564
  }
@@ -593,27 +634,27 @@ function httpsig_from(http) {
593
634
 
594
635
  // Convert flat structure to nested using flat.js
595
636
  var flat = {};
637
+ var nonFlat = {};
596
638
  for (var _i9 = 0, _Object$entries9 = Object.entries(withBodyKeys); _i9 < _Object$entries9.length; _i9++) {
597
639
  var _Object$entries9$_i = _slicedToArray(_Object$entries9[_i9], 2),
598
640
  key = _Object$entries9$_i[0],
599
641
  value = _Object$entries9$_i[1];
600
642
  if (key.includes("/")) {
601
643
  flat[key] = value;
644
+ } else {
645
+ nonFlat[key] = value;
602
646
  }
603
647
  }
604
648
  if (Object.keys(flat).length > 0) {
649
+ // Merge non-flat keys into flat for processing
650
+ // This ensures flat_from can see existing objects like results: { "ao-types": "..." }
651
+ var combined = _objectSpread(_objectSpread({}, nonFlat), flat);
652
+
605
653
  // Use flat_from to convert flat structure to nested
606
- var nested = (0, _flat.flat_from)(flat);
607
- for (var _i0 = 0, _Object$entries0 = Object.entries(nested); _i0 < _Object$entries0.length; _i0++) {
608
- var _Object$entries0$_i = _slicedToArray(_Object$entries0[_i0], 2),
609
- _key2 = _Object$entries0$_i[0],
610
- _value3 = _Object$entries0$_i[1];
611
- withBodyKeys[_key2] = _value3;
612
- }
613
- for (var _i1 = 0, _Object$keys = Object.keys(flat); _i1 < _Object$keys.length; _i1++) {
614
- var _key3 = _Object$keys[_i1];
615
- delete withBodyKeys[_key3];
616
- }
654
+ var nested = (0, _flat.flat_from)(combined);
655
+
656
+ // The nested result already has everything merged
657
+ withBodyKeys = nested;
617
658
  }
618
659
  } else if (body) {
619
660
  withBodyKeys[inlinedKey] = body;
@@ -630,10 +671,10 @@ function httpsig_from(http) {
630
671
  delete result["content-digest"];
631
672
 
632
673
  // Extract hashpaths if any
633
- for (var _i10 = 0, _Object$keys2 = Object.keys(result); _i10 < _Object$keys2.length; _i10++) {
634
- var _key4 = _Object$keys2[_i10];
635
- if (_key4.startsWith("hashpath")) {
636
- delete result[_key4];
674
+ for (var _i0 = 0, _Object$keys = Object.keys(result); _i0 < _Object$keys.length; _i0++) {
675
+ var _key2 = _Object$keys[_i0];
676
+ if (_key2.startsWith("hashpath")) {
677
+ delete result[_key2];
637
678
  }
638
679
  }
639
680
  return result;
@@ -671,10 +712,10 @@ function httpsig_to(tabm) {
671
712
  // For flat structures, just return with normalized keys
672
713
  // This matches Erlang which returns the map unchanged
673
714
  var result = _objectSpread({}, inlineFieldHdrs);
674
- for (var _i11 = 0, _Object$entries1 = Object.entries(stripped); _i11 < _Object$entries1.length; _i11++) {
675
- var _Object$entries1$_i = _slicedToArray(_Object$entries1[_i11], 2),
676
- key = _Object$entries1$_i[0],
677
- value = _Object$entries1$_i[1];
715
+ for (var _i1 = 0, _Object$entries0 = Object.entries(stripped); _i1 < _Object$entries0.length; _i1++) {
716
+ var _Object$entries0$_i = _slicedToArray(_Object$entries0[_i1], 2),
717
+ key = _Object$entries0$_i[0],
718
+ value = _Object$entries0$_i[1];
678
719
  // Keep Buffers as Buffers - don't convert to strings
679
720
  result[key] = value;
680
721
  }
@@ -697,26 +738,26 @@ function httpsig_to(tabm) {
697
738
  var headers = _objectSpread({}, inlineFieldHdrs);
698
739
 
699
740
  // Process each field - ao-types at top level should go to headers
700
- for (var _i12 = 0, _Object$entries10 = Object.entries(stripped); _i12 < _Object$entries10.length; _i12++) {
701
- var _Object$entries10$_i = _slicedToArray(_Object$entries10[_i12], 2),
702
- _key5 = _Object$entries10$_i[0],
703
- _value4 = _Object$entries10$_i[1];
704
- if (_key5 === "ao-types") {
741
+ for (var _i10 = 0, _Object$entries1 = Object.entries(stripped); _i10 < _Object$entries1.length; _i10++) {
742
+ var _Object$entries1$_i = _slicedToArray(_Object$entries1[_i10], 2),
743
+ _key3 = _Object$entries1$_i[0],
744
+ _value3 = _Object$entries1$_i[1];
745
+ if (_key3 === "ao-types") {
705
746
  // Top-level ao-types goes to headers only
706
747
  // Keep as Buffer if it's a Buffer, otherwise use as-is
707
- headers[_key5] = _value4;
708
- } else if (_key5 === "body" || _key5 === inlineKeyVal) {
709
- bodyMap[_key5 === inlineKeyVal ? inlineKeyVal : "body"] = _value4;
710
- } else if (_typeof(_value4) === "object" && _value4 !== null && !Array.isArray(_value4) && !Buffer.isBuffer(_value4)) {
711
- bodyMap[_key5] = _value4;
712
- } else if (typeof _value4 === "string" && _value4.length <= MAX_HEADER_LENGTH && _key5 !== "ao-types") {
713
- headers[normalizeKey(_key5)] = _value4;
714
- } else if (Buffer.isBuffer(_value4) && _value4.length <= MAX_HEADER_LENGTH && _key5 !== "ao-types") {
748
+ headers[_key3] = _value3;
749
+ } else if (_key3 === "body" || _key3 === inlineKeyVal) {
750
+ bodyMap[_key3 === inlineKeyVal ? inlineKeyVal : "body"] = _value3;
751
+ } else if (_typeof(_value3) === "object" && _value3 !== null && !Array.isArray(_value3) && !Buffer.isBuffer(_value3)) {
752
+ bodyMap[_key3] = _value3;
753
+ } else if (typeof _value3 === "string" && _value3.length <= MAX_HEADER_LENGTH && _key3 !== "ao-types") {
754
+ headers[normalizeKey(_key3)] = _value3;
755
+ } else if (Buffer.isBuffer(_value3) && _value3.length <= MAX_HEADER_LENGTH && _key3 !== "ao-types") {
715
756
  // Keep buffers as buffers for headers
716
- headers[normalizeKey(_key5)] = _value4;
717
- } else if (_key5 !== "ao-types") {
757
+ headers[normalizeKey(_key3)] = _value3;
758
+ } else if (_key3 !== "ao-types") {
718
759
  // Only add to bodyMap if it's not ao-types
719
- bodyMap[_key5] = _value4;
760
+ bodyMap[_key3] = _value3;
720
761
  }
721
762
  }
722
763
 
@@ -745,22 +786,22 @@ function httpsig_to(tabm) {
745
786
  try {
746
787
  for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
747
788
  var _step4$value = _slicedToArray(_step4.value, 2),
748
- _key6 = _step4$value[0],
749
- _value5 = _step4$value[1];
750
- if (_typeof(_value5) === "object" && _value5 !== null && Object.keys(_value5).length === 1 && "body" in _value5) {
751
- var encoded = encodeBodyPart("".concat(_key6, "/body"), _value5, "body");
789
+ _key4 = _step4$value[0],
790
+ _value4 = _step4$value[1];
791
+ if (_typeof(_value4) === "object" && _value4 !== null && Object.keys(_value4).length === 1 && "body" in _value4) {
792
+ var encoded = encodeBodyPart("".concat(_key4, "/body"), _value4, "body");
752
793
  parts.push({
753
- name: "".concat(_key6, "/body"),
794
+ name: "".concat(_key4, "/body"),
754
795
  body: encoded
755
796
  });
756
- bodyKeysList.push(_key6);
797
+ bodyKeysList.push(_key4);
757
798
  } else {
758
- var _encoded = encodeBodyPart(_key6, _value5, inlineKeyVal);
799
+ var _encoded = encodeBodyPart(_key4, _value4, inlineKeyVal);
759
800
  parts.push({
760
- name: _key6,
801
+ name: _key4,
761
802
  body: _encoded
762
803
  });
763
- bodyKeysList.push(_key6);
804
+ bodyKeysList.push(_key4);
764
805
  }
765
806
  }
766
807
  } catch (err) {
package/cjs/send-utils.js CHANGED
@@ -951,7 +951,7 @@ var toMsg = /*#__PURE__*/function () {
951
951
  var result = exports.result = /*#__PURE__*/function () {
952
952
  var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(response) {
953
953
  var _from$signer, _from$hashpath;
954
- var headers, msg, out, body, http, _from;
954
+ var headers, msg, tabm, out, body, http, _from;
955
955
  return _regenerator().w(function (_context2) {
956
956
  while (1) switch (_context2.n) {
957
957
  case 0:
@@ -963,7 +963,8 @@ var result = exports.result = /*#__PURE__*/function () {
963
963
  return toMsg(response);
964
964
  case 1:
965
965
  msg = _context2.v;
966
- out = (0, _structured.structured_to)((0, _httpsig.httpsig_from)(msg));
966
+ tabm = (0, _httpsig.httpsig_from)(msg); //console.log("TABM:", JSON.stringify(tabm))
967
+ out = (0, _structured.structured_to)(tabm);
967
968
  body = Buffer.from(msg.body).toString();
968
969
  http = {
969
970
  headers: headers,
package/esm/httpsig.js CHANGED
@@ -291,18 +291,51 @@ function encodeBodyPart(partName, bodyPart, inlineKey) {
291
291
  return ""
292
292
  }
293
293
 
294
- // Helper to detect if a Buffer contains binary data
295
- function isBinaryData(buf) {
296
- // Check first 100 bytes for binary indicators
297
- for (let i = 0; i < Math.min(buf.length, 100); i++) {
294
+ // Improved helper to detect if data is likely binary
295
+ function isBinaryData(data) {
296
+ let buf
297
+
298
+ if (typeof data === "string") {
299
+ // Convert string to buffer using binary encoding to check byte values
300
+ buf = Buffer.from(data, "binary")
301
+ } else if (Buffer.isBuffer(data)) {
302
+ buf = data
303
+ } else {
304
+ return false
305
+ }
306
+
307
+ // Check a larger sample for better detection
308
+ const sampleSize = Math.min(buf.length, 512)
309
+ let nullCount = 0
310
+ let controlCount = 0
311
+ let highByteCount = 0
312
+
313
+ for (let i = 0; i < sampleSize; i++) {
298
314
  const byte = buf[i]
299
- // Non-printable chars (except CR/LF/TAB)
300
- if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) return true
301
- // High byte range that's not valid text
302
- if (byte > 126 && byte < 160) return true
303
- // Null byte is definitely binary
304
- if (byte === 0) return true
315
+
316
+ // Null bytes are a strong indicator of binary
317
+ if (byte === 0) {
318
+ nullCount++
319
+ if (nullCount > 0) return true // Even one null byte indicates binary
320
+ }
321
+
322
+ // Control characters (except common text ones: TAB, LF, CR)
323
+ if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
324
+ controlCount++
325
+ }
326
+
327
+ // High byte values that aren't valid UTF-8 continuation bytes
328
+ if (byte > 127) {
329
+ highByteCount++
330
+ }
305
331
  }
332
+
333
+ // If more than 10% of bytes are control chars, likely binary
334
+ if (controlCount / sampleSize > 0.1) return true
335
+
336
+ // If more than 30% are high bytes without valid UTF-8 sequences, likely binary
337
+ if (highByteCount / sampleSize > 0.3) return true
338
+
306
339
  return false
307
340
  }
308
341
 
@@ -348,79 +381,93 @@ function parseMultipart(contentType, body) {
348
381
  // Parse headers more carefully to handle binary data
349
382
  let currentPos = 0
350
383
  while (currentPos < headerBlock.length) {
351
- // Find next line ending (but be careful with binary data)
352
- let lineEnd = headerBlock.indexOf("\r\n", currentPos)
353
- if (lineEnd === -1) lineEnd = headerBlock.indexOf("\n", currentPos)
354
- if (lineEnd === -1) lineEnd = headerBlock.length
355
-
356
- const line = headerBlock.substring(currentPos, lineEnd)
357
- const colonIndex = line.indexOf(": ")
358
-
359
- if (colonIndex > 0) {
360
- const name = line.substring(0, colonIndex).toLowerCase()
361
- let value = line.substring(colonIndex + 2)
362
-
363
- // Special handling for known binary fields or detected binary data
364
- if (name === "owner" || name === "signature") {
365
- // These fields contain binary data that may have embedded newlines
366
- // We need to read until we find the next header or end of headers
367
- let valueStart = currentPos + colonIndex + 2
368
-
369
- // Look ahead to find where this field really ends
370
- // The next header will start with a valid header name followed by ": "
371
- let searchPos = valueStart
372
- let valueEnd = headerBlock.length
373
-
374
- // Look for the next valid header pattern
375
- while (searchPos < headerBlock.length) {
376
- let nextNewline = headerBlock.indexOf("\n", searchPos)
377
- if (nextNewline === -1) break
378
-
379
- // Check if what follows looks like a header
380
- let nextLineStart = nextNewline + 1
381
- let nextColon = headerBlock.indexOf(":", nextLineStart)
382
-
383
- // Valid header should have colon relatively close to line start
384
- if (nextColon > nextLineStart && nextColon < nextLineStart + 50) {
385
- // Check if the text before colon looks like a header name (ASCII text)
386
- let possibleHeaderName = headerBlock.substring(
387
- nextLineStart,
388
- nextColon
389
- )
390
- let looksLikeHeader = /^[a-zA-Z0-9-]+$/.test(possibleHeaderName)
391
-
392
- if (looksLikeHeader) {
393
- // Found the next header, value ends at the newline before it
394
- valueEnd = nextNewline
395
- break
396
- }
397
- }
398
- searchPos = nextNewline + 1
399
- }
384
+ // Find the next colon to identify a potential header
385
+ let colonPos = headerBlock.indexOf(": ", currentPos)
400
386
 
401
- // Extract the full value as binary string
402
- value = headerBlock.substring(valueStart, valueEnd)
387
+ if (colonPos === -1) {
388
+ // No more headers
389
+ break
390
+ }
403
391
 
404
- // Remove trailing CR if present (since we found the LF)
405
- if (value.endsWith("\r")) {
406
- value = value.substring(0, value.length - 1)
407
- }
392
+ // Look backwards from colon to find the start of this line
393
+ let lineStart = currentPos
394
+ let searchBack = colonPos - 1
395
+ while (searchBack >= currentPos) {
396
+ if (headerBlock[searchBack] === "\n") {
397
+ lineStart = searchBack + 1
398
+ break
399
+ }
400
+ searchBack--
401
+ }
408
402
 
409
- // Convert to Buffer to preserve binary
410
- headers[name] = Buffer.from(value, "binary")
411
- currentPos = valueEnd + 1
412
- } else if (isBinaryData(Buffer.from(value, "binary"))) {
413
- // Regular field but contains binary data
414
- headers[name] = Buffer.from(value, "binary")
415
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1)
416
- } else {
417
- // Regular text field
418
- headers[name] = value
419
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1)
403
+ // Extract the header name
404
+ const name = headerBlock
405
+ .substring(lineStart, colonPos)
406
+ .trim()
407
+ .toLowerCase()
408
+
409
+ // Check if this looks like a valid header name
410
+ if (!/^[a-zA-Z0-9-]+$/.test(name) || name.length > 50) {
411
+ // Not a valid header, skip past this colon
412
+ currentPos = colonPos + 2
413
+ continue
414
+ }
415
+
416
+ // Start of value is after ": "
417
+ let valueStart = colonPos + 2
418
+
419
+ // Find the end of this header's value by looking for the next valid header
420
+ let valueEnd = headerBlock.length
421
+ let searchPos = valueStart
422
+
423
+ while (searchPos < headerBlock.length) {
424
+ let nextNewline = headerBlock.indexOf("\n", searchPos)
425
+ if (nextNewline === -1) break
426
+
427
+ let nextLineStart = nextNewline + 1
428
+ if (nextLineStart >= headerBlock.length) break
429
+
430
+ // Check if next line starts with a header pattern
431
+ let nextColon = headerBlock.indexOf(": ", nextLineStart)
432
+
433
+ if (nextColon > nextLineStart && nextColon < nextLineStart + 50) {
434
+ let possibleHeaderName = headerBlock
435
+ .substring(nextLineStart, nextColon)
436
+ .trim()
437
+
438
+ // Must be valid header name format
439
+ if (/^[a-zA-Z0-9-]+$/.test(possibleHeaderName)) {
440
+ valueEnd = nextNewline
441
+ break
442
+ }
420
443
  }
444
+
445
+ searchPos = nextNewline + 1
446
+ }
447
+
448
+ // Extract the value
449
+ let value = headerBlock.substring(valueStart, valueEnd)
450
+
451
+ // Trim trailing CRLF or LF
452
+ if (value.endsWith("\r\n")) {
453
+ value = value.substring(0, value.length - 2)
454
+ } else if (value.endsWith("\n")) {
455
+ value = value.substring(0, value.length - 1)
456
+ } else if (value.endsWith("\r")) {
457
+ value = value.substring(0, value.length - 1)
458
+ }
459
+
460
+ // Determine if this is binary data
461
+ if (value.length > 0 && isBinaryData(value)) {
462
+ headers[name] = Buffer.from(value, "binary")
421
463
  } else {
422
- // No colon found, skip this line
423
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1)
464
+ headers[name] = value
465
+ }
466
+
467
+ // Move to the end of this header's value
468
+ currentPos = valueEnd + 1
469
+ if (headerBlock[valueEnd] === "\r") {
470
+ currentPos++
424
471
  }
425
472
  }
426
473
 
@@ -444,8 +491,10 @@ function parseMultipart(contentType, body) {
444
491
 
445
492
  // If there's body content in the inline part, add it as 'body'
446
493
  if (partBody) {
447
- // Convert back to Buffer to preserve binary data
448
- result[partName] = Buffer.from(partBody, "binary")
494
+ // Keep as Buffer if it's binary data
495
+ result[partName] = isBinaryData(partBody)
496
+ ? Buffer.from(partBody, "binary")
497
+ : partBody
449
498
  }
450
499
  } else {
451
500
  // Handle named form-data parts
@@ -462,15 +511,20 @@ function parseMultipart(contentType, body) {
462
511
  delete restHeaders["content-disposition"]
463
512
 
464
513
  if (Object.keys(restHeaders).length === 0) {
465
- // Convert body back to Buffer
466
- result[partName] = Buffer.from(partBody, "binary")
514
+ // Keep as Buffer if it's binary data
515
+ result[partName] = isBinaryData(partBody)
516
+ ? Buffer.from(partBody, "binary")
517
+ : partBody
467
518
  } else if (!partBody) {
519
+ // ao-types should stay with this part, not be extracted
468
520
  result[partName] = restHeaders
469
521
  } else {
470
- // Convert body back to Buffer
522
+ // Keep as Buffer if it's binary data
471
523
  result[partName] = {
472
524
  ...restHeaders,
473
- body: Buffer.from(partBody, "binary"),
525
+ body: isBinaryData(partBody)
526
+ ? Buffer.from(partBody, "binary")
527
+ : partBody,
474
528
  }
475
529
  }
476
530
  }
@@ -546,21 +600,25 @@ export function httpsig_from(http) {
546
600
 
547
601
  // Convert flat structure to nested using flat.js
548
602
  const flat = {}
603
+ const nonFlat = {}
549
604
  for (const [key, value] of Object.entries(withBodyKeys)) {
550
605
  if (key.includes("/")) {
551
606
  flat[key] = value
607
+ } else {
608
+ nonFlat[key] = value
552
609
  }
553
610
  }
554
611
 
555
612
  if (Object.keys(flat).length > 0) {
613
+ // Merge non-flat keys into flat for processing
614
+ // This ensures flat_from can see existing objects like results: { "ao-types": "..." }
615
+ const combined = { ...nonFlat, ...flat }
616
+
556
617
  // Use flat_from to convert flat structure to nested
557
- const nested = flat_from(flat)
558
- for (const [key, value] of Object.entries(nested)) {
559
- withBodyKeys[key] = value
560
- }
561
- for (const key of Object.keys(flat)) {
562
- delete withBodyKeys[key]
563
- }
618
+ const nested = flat_from(combined)
619
+
620
+ // The nested result already has everything merged
621
+ withBodyKeys = nested
564
622
  }
565
623
  } else if (body) {
566
624
  withBodyKeys[inlinedKey] = body
package/esm/send-utils.js CHANGED
@@ -878,7 +878,9 @@ export const result = async response => {
878
878
  let headers = {}
879
879
  response.headers.forEach((v, k) => (headers[k] = v))
880
880
  const msg = await toMsg(response)
881
- const out = structured_to(httpsig_from(msg))
881
+ const tabm = httpsig_from(msg)
882
+ //console.log("TABM:", JSON.stringify(tabm))
883
+ const out = structured_to(tabm)
882
884
  const body = Buffer.from(msg.body).toString()
883
885
  const http = { headers, body }
884
886
  const _from = from(http)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hbsig",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "main": "cjs/index.js",
5
5
  "license": "MIT",
6
6
  "devDependencies": {