hbsig 0.2.2 → 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,55 +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
- // 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
+ // 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");
453
506
  } else {
454
- // No colon found, skip this line
455
- 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++;
456
514
  }
457
515
  }
458
516
  var disposition = headers["content-disposition"];
@@ -477,8 +535,8 @@ function parseMultipart(contentType, body) {
477
535
 
478
536
  // If there's body content in the inline part, add it as 'body'
479
537
  if (partBody) {
480
- // Convert back to Buffer to preserve binary data
481
- 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;
482
540
  }
483
541
  } else {
484
542
  // Handle named form-data parts
@@ -492,14 +550,15 @@ function parseMultipart(contentType, body) {
492
550
  var _restHeaders = _objectSpread({}, headers);
493
551
  delete _restHeaders["content-disposition"];
494
552
  if (Object.keys(_restHeaders).length === 0) {
495
- // Convert body back to Buffer
496
- 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;
497
555
  } else if (!partBody) {
556
+ // ao-types should stay with this part, not be extracted
498
557
  result[partName] = _restHeaders;
499
558
  } else {
500
- // Convert body back to Buffer
559
+ // Keep as Buffer if it's binary data
501
560
  result[partName] = _objectSpread(_objectSpread({}, _restHeaders), {}, {
502
- body: Buffer.from(partBody, "binary")
561
+ body: isBinaryData(partBody) ? Buffer.from(partBody, "binary") : partBody
503
562
  });
504
563
  }
505
564
  }
@@ -575,27 +634,27 @@ function httpsig_from(http) {
575
634
 
576
635
  // Convert flat structure to nested using flat.js
577
636
  var flat = {};
637
+ var nonFlat = {};
578
638
  for (var _i9 = 0, _Object$entries9 = Object.entries(withBodyKeys); _i9 < _Object$entries9.length; _i9++) {
579
639
  var _Object$entries9$_i = _slicedToArray(_Object$entries9[_i9], 2),
580
640
  key = _Object$entries9$_i[0],
581
641
  value = _Object$entries9$_i[1];
582
642
  if (key.includes("/")) {
583
643
  flat[key] = value;
644
+ } else {
645
+ nonFlat[key] = value;
584
646
  }
585
647
  }
586
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
+
587
653
  // 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
- }
654
+ var nested = (0, _flat.flat_from)(combined);
655
+
656
+ // The nested result already has everything merged
657
+ withBodyKeys = nested;
599
658
  }
600
659
  } else if (body) {
601
660
  withBodyKeys[inlinedKey] = body;
@@ -612,10 +671,10 @@ function httpsig_from(http) {
612
671
  delete result["content-digest"];
613
672
 
614
673
  // 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];
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];
619
678
  }
620
679
  }
621
680
  return result;
@@ -653,10 +712,10 @@ function httpsig_to(tabm) {
653
712
  // For flat structures, just return with normalized keys
654
713
  // This matches Erlang which returns the map unchanged
655
714
  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];
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];
660
719
  // Keep Buffers as Buffers - don't convert to strings
661
720
  result[key] = value;
662
721
  }
@@ -679,26 +738,26 @@ function httpsig_to(tabm) {
679
738
  var headers = _objectSpread({}, inlineFieldHdrs);
680
739
 
681
740
  // 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") {
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") {
687
746
  // Top-level ao-types goes to headers only
688
747
  // 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") {
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") {
697
756
  // Keep buffers as buffers for headers
698
- headers[normalizeKey(_key5)] = _value4;
699
- } else if (_key5 !== "ao-types") {
757
+ headers[normalizeKey(_key3)] = _value3;
758
+ } else if (_key3 !== "ao-types") {
700
759
  // Only add to bodyMap if it's not ao-types
701
- bodyMap[_key5] = _value4;
760
+ bodyMap[_key3] = _value3;
702
761
  }
703
762
  }
704
763
 
@@ -727,22 +786,22 @@ function httpsig_to(tabm) {
727
786
  try {
728
787
  for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
729
788
  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");
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");
734
793
  parts.push({
735
- name: "".concat(_key6, "/body"),
794
+ name: "".concat(_key4, "/body"),
736
795
  body: encoded
737
796
  });
738
- bodyKeysList.push(_key6);
797
+ bodyKeysList.push(_key4);
739
798
  } else {
740
- var _encoded = encodeBodyPart(_key6, _value5, inlineKeyVal);
799
+ var _encoded = encodeBodyPart(_key4, _value4, inlineKeyVal);
741
800
  parts.push({
742
- name: _key6,
801
+ name: _key4,
743
802
  body: _encoded
744
803
  });
745
- bodyKeysList.push(_key6);
804
+ bodyKeysList.push(_key4);
746
805
  }
747
806
  }
748
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,66 +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
- // 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)
395
386
 
396
- value = headerBlock.substring(valueStart, valueEnd)
397
- if (value.endsWith("\r")) {
398
- value = value.substring(0, value.length - 1)
399
- }
387
+ if (colonPos === -1) {
388
+ // No more headers
389
+ break
390
+ }
400
391
 
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)
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
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
+ }
407
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")
408
463
  } else {
409
- // No colon found, skip this line
410
- 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++
411
471
  }
412
472
  }
413
473
 
@@ -431,8 +491,10 @@ function parseMultipart(contentType, body) {
431
491
 
432
492
  // If there's body content in the inline part, add it as 'body'
433
493
  if (partBody) {
434
- // Convert back to Buffer to preserve binary data
435
- 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
436
498
  }
437
499
  } else {
438
500
  // Handle named form-data parts
@@ -449,15 +511,20 @@ function parseMultipart(contentType, body) {
449
511
  delete restHeaders["content-disposition"]
450
512
 
451
513
  if (Object.keys(restHeaders).length === 0) {
452
- // Convert body back to Buffer
453
- 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
454
518
  } else if (!partBody) {
519
+ // ao-types should stay with this part, not be extracted
455
520
  result[partName] = restHeaders
456
521
  } else {
457
- // Convert body back to Buffer
522
+ // Keep as Buffer if it's binary data
458
523
  result[partName] = {
459
524
  ...restHeaders,
460
- body: Buffer.from(partBody, "binary"),
525
+ body: isBinaryData(partBody)
526
+ ? Buffer.from(partBody, "binary")
527
+ : partBody,
461
528
  }
462
529
  }
463
530
  }
@@ -533,21 +600,25 @@ export function httpsig_from(http) {
533
600
 
534
601
  // Convert flat structure to nested using flat.js
535
602
  const flat = {}
603
+ const nonFlat = {}
536
604
  for (const [key, value] of Object.entries(withBodyKeys)) {
537
605
  if (key.includes("/")) {
538
606
  flat[key] = value
607
+ } else {
608
+ nonFlat[key] = value
539
609
  }
540
610
  }
541
611
 
542
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
+
543
617
  // 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
- }
618
+ const nested = flat_from(combined)
619
+
620
+ // The nested result already has everything merged
621
+ withBodyKeys = nested
551
622
  }
552
623
  } else if (body) {
553
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.2",
3
+ "version": "0.2.3",
4
4
  "main": "cjs/index.js",
5
5
  "license": "MIT",
6
6
  "devDependencies": {