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 +169 -106
- package/cjs/send-utils.js +3 -2
- package/esm/httpsig.js +153 -78
- package/esm/send-utils.js +2 -1
- package/package.json +1 -1
package/cjs/httpsig.js
CHANGED
|
@@ -348,18 +348,48 @@ function encodeBodyPart(partName, bodyPart, inlineKey) {
|
|
|
348
348
|
return "";
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
//
|
|
352
|
-
function isBinaryData(
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
408
|
-
var
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
455
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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)(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
616
|
-
var
|
|
617
|
-
if (
|
|
618
|
-
delete result[
|
|
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
|
|
657
|
-
var _Object$
|
|
658
|
-
key = _Object$
|
|
659
|
-
value = _Object$
|
|
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
|
|
683
|
-
var _Object$
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (
|
|
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[
|
|
690
|
-
} else if (
|
|
691
|
-
bodyMap[
|
|
692
|
-
} else if (_typeof(
|
|
693
|
-
bodyMap[
|
|
694
|
-
} else if (typeof
|
|
695
|
-
headers[normalizeKey(
|
|
696
|
-
} else if (Buffer.isBuffer(
|
|
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(
|
|
699
|
-
} else if (
|
|
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[
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
if (_typeof(
|
|
733
|
-
var encoded = encodeBodyPart("".concat(
|
|
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(
|
|
798
|
+
name: "".concat(_key4, "/body"),
|
|
736
799
|
body: encoded
|
|
737
800
|
});
|
|
738
|
-
bodyKeysList.push(
|
|
801
|
+
bodyKeysList.push(_key4);
|
|
739
802
|
} else {
|
|
740
|
-
var _encoded = encodeBodyPart(
|
|
803
|
+
var _encoded = encodeBodyPart(_key4, _value4, inlineKeyVal);
|
|
741
804
|
parts.push({
|
|
742
|
-
name:
|
|
805
|
+
name: _key4,
|
|
743
806
|
body: _encoded
|
|
744
807
|
});
|
|
745
|
-
bodyKeysList.push(
|
|
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
|
-
|
|
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
|
-
//
|
|
295
|
-
function isBinaryData(
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
|
352
|
-
let
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
410
|
-
|
|
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
|
-
//
|
|
435
|
-
result[partName] =
|
|
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
|
-
//
|
|
453
|
-
result[partName] =
|
|
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
|
-
//
|
|
526
|
+
// Keep as Buffer if it's binary data
|
|
458
527
|
result[partName] = {
|
|
459
528
|
...restHeaders,
|
|
460
|
-
body:
|
|
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(
|
|
545
|
-
|
|
546
|
-
|
|
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
|
|
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)
|