hbsig 0.2.2 → 0.2.4

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 - double to 1024 bytes for better detection
364
+ var sampleSize = Math.min(buf.length, 1024);
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,55 +434,87 @@ 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
- // Check if this might be binary data by looking at the first part
418
- var valueBuf = Buffer.from(value, "binary");
419
- var mightBeBinary = isBinaryData(valueBuf);
420
- if (mightBeBinary) {
421
- // Binary data may contain embedded newlines, so read until next header
422
- var valueStart = currentPos + colonIndex + 2;
423
- var searchPos = valueStart;
424
- var valueEnd = headerBlock.length;
425
-
426
- // Look for the next valid header pattern
427
- while (searchPos < headerBlock.length) {
428
- var nextNewline = headerBlock.indexOf("\n", searchPos);
429
- if (nextNewline === -1) break;
430
- var nextLineStart = nextNewline + 1;
431
- var nextColon = headerBlock.indexOf(":", nextLineStart);
432
- if (nextColon > nextLineStart && nextColon < nextLineStart + 50) {
433
- var possibleHeaderName = headerBlock.substring(nextLineStart, nextColon);
434
- var looksLikeHeader = /^[a-zA-Z0-9-]+$/.test(possibleHeaderName);
435
- if (looksLikeHeader) {
436
- valueEnd = nextNewline;
437
- break;
438
- }
439
- }
440
- searchPos = nextNewline + 1;
441
- }
442
- value = headerBlock.substring(valueStart, valueEnd);
443
- if (value.endsWith("\r")) {
444
- value = value.substring(0, value.length - 1);
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
+ }
443
+
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
+ }
454
+
455
+ // Extract the header name
456
+ var name = headerBlock.substring(lineStart, colonPos).trim().toLowerCase();
457
+
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;
445
486
  }
446
- headers[name] = Buffer.from(value, "binary");
447
- currentPos = valueEnd + 1;
448
- } else {
449
- // Regular text field
450
- headers[name] = value;
451
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1);
452
487
  }
488
+ searchPos = nextNewline + 1;
489
+ }
490
+
491
+ // Extract the value
492
+ var value = headerBlock.substring(valueStart, valueEnd);
493
+
494
+ // Only trim trailing newlines if we actually found a next header
495
+ // (valueEnd < headerBlock.length means we found a boundary)
496
+ if (valueEnd < headerBlock.length) {
497
+ // Trim trailing CRLF or LF only at the boundary
498
+ if (value.endsWith("\r\n")) {
499
+ value = value.substring(0, value.length - 2);
500
+ } else if (value.endsWith("\n")) {
501
+ value = value.substring(0, value.length - 1);
502
+ } else if (value.endsWith("\r")) {
503
+ value = value.substring(0, value.length - 1);
504
+ }
505
+ }
506
+
507
+ // Determine if this is binary data
508
+ if (value.length > 0 && isBinaryData(value)) {
509
+ headers[name] = Buffer.from(value, "binary");
453
510
  } else {
454
- // No colon found, skip this line
455
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1);
511
+ headers[name] = value;
512
+ }
513
+
514
+ // Move to the end of this header's value
515
+ currentPos = valueEnd + 1;
516
+ if (headerBlock[valueEnd] === "\r") {
517
+ currentPos++;
456
518
  }
457
519
  }
458
520
  var disposition = headers["content-disposition"];
@@ -477,8 +539,8 @@ function parseMultipart(contentType, body) {
477
539
 
478
540
  // If there's body content in the inline part, add it as 'body'
479
541
  if (partBody) {
480
- // Convert back to Buffer to preserve binary data
481
- result[partName] = Buffer.from(partBody, "binary");
542
+ // Keep as Buffer if it's binary data
543
+ result[partName] = isBinaryData(partBody) ? Buffer.from(partBody, "binary") : partBody;
482
544
  }
483
545
  } else {
484
546
  // Handle named form-data parts
@@ -492,14 +554,15 @@ function parseMultipart(contentType, body) {
492
554
  var _restHeaders = _objectSpread({}, headers);
493
555
  delete _restHeaders["content-disposition"];
494
556
  if (Object.keys(_restHeaders).length === 0) {
495
- // Convert body back to Buffer
496
- result[partName] = Buffer.from(partBody, "binary");
557
+ // Keep as Buffer if it's binary data
558
+ result[partName] = isBinaryData(partBody) ? Buffer.from(partBody, "binary") : partBody;
497
559
  } else if (!partBody) {
560
+ // ao-types should stay with this part, not be extracted
498
561
  result[partName] = _restHeaders;
499
562
  } else {
500
- // Convert body back to Buffer
563
+ // Keep as Buffer if it's binary data
501
564
  result[partName] = _objectSpread(_objectSpread({}, _restHeaders), {}, {
502
- body: Buffer.from(partBody, "binary")
565
+ body: isBinaryData(partBody) ? Buffer.from(partBody, "binary") : partBody
503
566
  });
504
567
  }
505
568
  }
@@ -575,27 +638,27 @@ function httpsig_from(http) {
575
638
 
576
639
  // Convert flat structure to nested using flat.js
577
640
  var flat = {};
641
+ var nonFlat = {};
578
642
  for (var _i9 = 0, _Object$entries9 = Object.entries(withBodyKeys); _i9 < _Object$entries9.length; _i9++) {
579
643
  var _Object$entries9$_i = _slicedToArray(_Object$entries9[_i9], 2),
580
644
  key = _Object$entries9$_i[0],
581
645
  value = _Object$entries9$_i[1];
582
646
  if (key.includes("/")) {
583
647
  flat[key] = value;
648
+ } else {
649
+ nonFlat[key] = value;
584
650
  }
585
651
  }
586
652
  if (Object.keys(flat).length > 0) {
653
+ // Merge non-flat keys into flat for processing
654
+ // This ensures flat_from can see existing objects like results: { "ao-types": "..." }
655
+ var combined = _objectSpread(_objectSpread({}, nonFlat), flat);
656
+
587
657
  // Use flat_from to convert flat structure to nested
588
- var nested = (0, _flat.flat_from)(flat);
589
- for (var _i0 = 0, _Object$entries0 = Object.entries(nested); _i0 < _Object$entries0.length; _i0++) {
590
- var _Object$entries0$_i = _slicedToArray(_Object$entries0[_i0], 2),
591
- _key2 = _Object$entries0$_i[0],
592
- _value3 = _Object$entries0$_i[1];
593
- withBodyKeys[_key2] = _value3;
594
- }
595
- for (var _i1 = 0, _Object$keys = Object.keys(flat); _i1 < _Object$keys.length; _i1++) {
596
- var _key3 = _Object$keys[_i1];
597
- delete withBodyKeys[_key3];
598
- }
658
+ var nested = (0, _flat.flat_from)(combined);
659
+
660
+ // The nested result already has everything merged
661
+ withBodyKeys = nested;
599
662
  }
600
663
  } else if (body) {
601
664
  withBodyKeys[inlinedKey] = body;
@@ -612,10 +675,10 @@ function httpsig_from(http) {
612
675
  delete result["content-digest"];
613
676
 
614
677
  // Extract hashpaths if any
615
- for (var _i10 = 0, _Object$keys2 = Object.keys(result); _i10 < _Object$keys2.length; _i10++) {
616
- var _key4 = _Object$keys2[_i10];
617
- if (_key4.startsWith("hashpath")) {
618
- delete result[_key4];
678
+ for (var _i0 = 0, _Object$keys = Object.keys(result); _i0 < _Object$keys.length; _i0++) {
679
+ var _key2 = _Object$keys[_i0];
680
+ if (_key2.startsWith("hashpath")) {
681
+ delete result[_key2];
619
682
  }
620
683
  }
621
684
  return result;
@@ -653,10 +716,10 @@ function httpsig_to(tabm) {
653
716
  // For flat structures, just return with normalized keys
654
717
  // This matches Erlang which returns the map unchanged
655
718
  var result = _objectSpread({}, inlineFieldHdrs);
656
- for (var _i11 = 0, _Object$entries1 = Object.entries(stripped); _i11 < _Object$entries1.length; _i11++) {
657
- var _Object$entries1$_i = _slicedToArray(_Object$entries1[_i11], 2),
658
- key = _Object$entries1$_i[0],
659
- value = _Object$entries1$_i[1];
719
+ for (var _i1 = 0, _Object$entries0 = Object.entries(stripped); _i1 < _Object$entries0.length; _i1++) {
720
+ var _Object$entries0$_i = _slicedToArray(_Object$entries0[_i1], 2),
721
+ key = _Object$entries0$_i[0],
722
+ value = _Object$entries0$_i[1];
660
723
  // Keep Buffers as Buffers - don't convert to strings
661
724
  result[key] = value;
662
725
  }
@@ -679,26 +742,26 @@ function httpsig_to(tabm) {
679
742
  var headers = _objectSpread({}, inlineFieldHdrs);
680
743
 
681
744
  // Process each field - ao-types at top level should go to headers
682
- for (var _i12 = 0, _Object$entries10 = Object.entries(stripped); _i12 < _Object$entries10.length; _i12++) {
683
- var _Object$entries10$_i = _slicedToArray(_Object$entries10[_i12], 2),
684
- _key5 = _Object$entries10$_i[0],
685
- _value4 = _Object$entries10$_i[1];
686
- if (_key5 === "ao-types") {
745
+ for (var _i10 = 0, _Object$entries1 = Object.entries(stripped); _i10 < _Object$entries1.length; _i10++) {
746
+ var _Object$entries1$_i = _slicedToArray(_Object$entries1[_i10], 2),
747
+ _key3 = _Object$entries1$_i[0],
748
+ _value3 = _Object$entries1$_i[1];
749
+ if (_key3 === "ao-types") {
687
750
  // Top-level ao-types goes to headers only
688
751
  // Keep as Buffer if it's a Buffer, otherwise use as-is
689
- headers[_key5] = _value4;
690
- } else if (_key5 === "body" || _key5 === inlineKeyVal) {
691
- bodyMap[_key5 === inlineKeyVal ? inlineKeyVal : "body"] = _value4;
692
- } else if (_typeof(_value4) === "object" && _value4 !== null && !Array.isArray(_value4) && !Buffer.isBuffer(_value4)) {
693
- bodyMap[_key5] = _value4;
694
- } else if (typeof _value4 === "string" && _value4.length <= MAX_HEADER_LENGTH && _key5 !== "ao-types") {
695
- headers[normalizeKey(_key5)] = _value4;
696
- } else if (Buffer.isBuffer(_value4) && _value4.length <= MAX_HEADER_LENGTH && _key5 !== "ao-types") {
752
+ headers[_key3] = _value3;
753
+ } else if (_key3 === "body" || _key3 === inlineKeyVal) {
754
+ bodyMap[_key3 === inlineKeyVal ? inlineKeyVal : "body"] = _value3;
755
+ } else if (_typeof(_value3) === "object" && _value3 !== null && !Array.isArray(_value3) && !Buffer.isBuffer(_value3)) {
756
+ bodyMap[_key3] = _value3;
757
+ } else if (typeof _value3 === "string" && _value3.length <= MAX_HEADER_LENGTH && _key3 !== "ao-types") {
758
+ headers[normalizeKey(_key3)] = _value3;
759
+ } else if (Buffer.isBuffer(_value3) && _value3.length <= MAX_HEADER_LENGTH && _key3 !== "ao-types") {
697
760
  // Keep buffers as buffers for headers
698
- headers[normalizeKey(_key5)] = _value4;
699
- } else if (_key5 !== "ao-types") {
761
+ headers[normalizeKey(_key3)] = _value3;
762
+ } else if (_key3 !== "ao-types") {
700
763
  // Only add to bodyMap if it's not ao-types
701
- bodyMap[_key5] = _value4;
764
+ bodyMap[_key3] = _value3;
702
765
  }
703
766
  }
704
767
 
@@ -727,22 +790,22 @@ function httpsig_to(tabm) {
727
790
  try {
728
791
  for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
729
792
  var _step4$value = _slicedToArray(_step4.value, 2),
730
- _key6 = _step4$value[0],
731
- _value5 = _step4$value[1];
732
- if (_typeof(_value5) === "object" && _value5 !== null && Object.keys(_value5).length === 1 && "body" in _value5) {
733
- var encoded = encodeBodyPart("".concat(_key6, "/body"), _value5, "body");
793
+ _key4 = _step4$value[0],
794
+ _value4 = _step4$value[1];
795
+ if (_typeof(_value4) === "object" && _value4 !== null && Object.keys(_value4).length === 1 && "body" in _value4) {
796
+ var encoded = encodeBodyPart("".concat(_key4, "/body"), _value4, "body");
734
797
  parts.push({
735
- name: "".concat(_key6, "/body"),
798
+ name: "".concat(_key4, "/body"),
736
799
  body: encoded
737
800
  });
738
- bodyKeysList.push(_key6);
801
+ bodyKeysList.push(_key4);
739
802
  } else {
740
- var _encoded = encodeBodyPart(_key6, _value5, inlineKeyVal);
803
+ var _encoded = encodeBodyPart(_key4, _value4, inlineKeyVal);
741
804
  parts.push({
742
- name: _key6,
805
+ name: _key4,
743
806
  body: _encoded
744
807
  });
745
- bodyKeysList.push(_key6);
808
+ bodyKeysList.push(_key4);
746
809
  }
747
810
  }
748
811
  } 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);
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 - double to 1024 bytes for better detection
308
+ const sampleSize = Math.min(buf.length, 1024)
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,66 +381,97 @@ 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
- // Check if this might be binary data by looking at the first part
364
- const valueBuf = Buffer.from(value, "binary")
365
- const mightBeBinary = isBinaryData(valueBuf)
366
-
367
- if (mightBeBinary) {
368
- // Binary data may contain embedded newlines, so read until next header
369
- let valueStart = currentPos + colonIndex + 2
370
- let searchPos = valueStart
371
- let valueEnd = headerBlock.length
372
-
373
- // Look for the next valid header pattern
374
- while (searchPos < headerBlock.length) {
375
- let nextNewline = headerBlock.indexOf("\n", searchPos)
376
- if (nextNewline === -1) break
377
-
378
- let nextLineStart = nextNewline + 1
379
- let nextColon = headerBlock.indexOf(":", nextLineStart)
380
-
381
- if (nextColon > nextLineStart && nextColon < nextLineStart + 50) {
382
- let possibleHeaderName = headerBlock.substring(
383
- nextLineStart,
384
- nextColon
385
- )
386
- let looksLikeHeader = /^[a-zA-Z0-9-]+$/.test(possibleHeaderName)
387
-
388
- if (looksLikeHeader) {
389
- valueEnd = nextNewline
390
- break
391
- }
392
- }
393
- searchPos = nextNewline + 1
394
- }
384
+ // Find the next colon to identify a potential header
385
+ let colonPos = headerBlock.indexOf(": ", currentPos)
386
+
387
+ if (colonPos === -1) {
388
+ // No more headers
389
+ break
390
+ }
391
+
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
+ }
402
+
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
395
429
 
396
- value = headerBlock.substring(valueStart, valueEnd)
397
- if (value.endsWith("\r")) {
398
- value = value.substring(0, value.length - 1)
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
399
442
  }
443
+ }
400
444
 
401
- headers[name] = Buffer.from(value, "binary")
402
- currentPos = valueEnd + 1
403
- } else {
404
- // Regular text field
405
- headers[name] = value
406
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1)
445
+ searchPos = nextNewline + 1
446
+ }
447
+
448
+ // Extract the value
449
+ let value = headerBlock.substring(valueStart, valueEnd)
450
+
451
+ // Only trim trailing newlines if we actually found a next header
452
+ // (valueEnd < headerBlock.length means we found a boundary)
453
+ if (valueEnd < headerBlock.length) {
454
+ // Trim trailing CRLF or LF only at the boundary
455
+ if (value.endsWith("\r\n")) {
456
+ value = value.substring(0, value.length - 2)
457
+ } else if (value.endsWith("\n")) {
458
+ value = value.substring(0, value.length - 1)
459
+ } else if (value.endsWith("\r")) {
460
+ value = value.substring(0, value.length - 1)
407
461
  }
462
+ }
463
+
464
+ // Determine if this is binary data
465
+ if (value.length > 0 && isBinaryData(value)) {
466
+ headers[name] = Buffer.from(value, "binary")
408
467
  } else {
409
- // No colon found, skip this line
410
- currentPos = lineEnd + (headerBlock[lineEnd] === "\r" ? 2 : 1)
468
+ headers[name] = value
469
+ }
470
+
471
+ // Move to the end of this header's value
472
+ currentPos = valueEnd + 1
473
+ if (headerBlock[valueEnd] === "\r") {
474
+ currentPos++
411
475
  }
412
476
  }
413
477
 
@@ -431,8 +495,10 @@ function parseMultipart(contentType, body) {
431
495
 
432
496
  // If there's body content in the inline part, add it as 'body'
433
497
  if (partBody) {
434
- // Convert back to Buffer to preserve binary data
435
- result[partName] = Buffer.from(partBody, "binary")
498
+ // Keep as Buffer if it's binary data
499
+ result[partName] = isBinaryData(partBody)
500
+ ? Buffer.from(partBody, "binary")
501
+ : partBody
436
502
  }
437
503
  } else {
438
504
  // Handle named form-data parts
@@ -449,15 +515,20 @@ function parseMultipart(contentType, body) {
449
515
  delete restHeaders["content-disposition"]
450
516
 
451
517
  if (Object.keys(restHeaders).length === 0) {
452
- // Convert body back to Buffer
453
- result[partName] = Buffer.from(partBody, "binary")
518
+ // Keep as Buffer if it's binary data
519
+ result[partName] = isBinaryData(partBody)
520
+ ? Buffer.from(partBody, "binary")
521
+ : partBody
454
522
  } else if (!partBody) {
523
+ // ao-types should stay with this part, not be extracted
455
524
  result[partName] = restHeaders
456
525
  } else {
457
- // Convert body back to Buffer
526
+ // Keep as Buffer if it's binary data
458
527
  result[partName] = {
459
528
  ...restHeaders,
460
- body: Buffer.from(partBody, "binary"),
529
+ body: isBinaryData(partBody)
530
+ ? Buffer.from(partBody, "binary")
531
+ : partBody,
461
532
  }
462
533
  }
463
534
  }
@@ -533,21 +604,25 @@ export function httpsig_from(http) {
533
604
 
534
605
  // Convert flat structure to nested using flat.js
535
606
  const flat = {}
607
+ const nonFlat = {}
536
608
  for (const [key, value] of Object.entries(withBodyKeys)) {
537
609
  if (key.includes("/")) {
538
610
  flat[key] = value
611
+ } else {
612
+ nonFlat[key] = value
539
613
  }
540
614
  }
541
615
 
542
616
  if (Object.keys(flat).length > 0) {
617
+ // Merge non-flat keys into flat for processing
618
+ // This ensures flat_from can see existing objects like results: { "ao-types": "..." }
619
+ const combined = { ...nonFlat, ...flat }
620
+
543
621
  // Use flat_from to convert flat structure to nested
544
- const nested = flat_from(flat)
545
- for (const [key, value] of Object.entries(nested)) {
546
- withBodyKeys[key] = value
547
- }
548
- for (const key of Object.keys(flat)) {
549
- delete withBodyKeys[key]
550
- }
622
+ const nested = flat_from(combined)
623
+
624
+ // The nested result already has everything merged
625
+ withBodyKeys = nested
551
626
  }
552
627
  } else if (body) {
553
628
  withBodyKeys[inlinedKey] = body
package/esm/send-utils.js CHANGED
@@ -878,7 +878,8 @@ 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
+ const out = structured_to(tabm)
882
883
  const body = Buffer.from(msg.body).toString()
883
884
  const http = { headers, body }
884
885
  const _from = from(http)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hbsig",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "main": "cjs/index.js",
5
5
  "license": "MIT",
6
6
  "devDependencies": {