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 +165 -106
- package/cjs/send-utils.js +3 -2
- package/esm/httpsig.js +150 -79
- package/esm/send-utils.js +3 -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 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
|
-
|
|
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,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
|
|
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
|
+
// 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
|
-
|
|
455
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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)(
|
|
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
|
-
}
|
|
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
|
|
616
|
-
var
|
|
617
|
-
if (
|
|
618
|
-
delete result[
|
|
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
|
|
657
|
-
var _Object$
|
|
658
|
-
key = _Object$
|
|
659
|
-
value = _Object$
|
|
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
|
|
683
|
-
var _Object$
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (
|
|
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[
|
|
690
|
-
} else if (
|
|
691
|
-
bodyMap[
|
|
692
|
-
} else if (_typeof(
|
|
693
|
-
bodyMap[
|
|
694
|
-
} else if (typeof
|
|
695
|
-
headers[normalizeKey(
|
|
696
|
-
} else if (Buffer.isBuffer(
|
|
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(
|
|
699
|
-
} else if (
|
|
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[
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
if (_typeof(
|
|
733
|
-
var encoded = encodeBodyPart("".concat(
|
|
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(
|
|
794
|
+
name: "".concat(_key4, "/body"),
|
|
736
795
|
body: encoded
|
|
737
796
|
});
|
|
738
|
-
bodyKeysList.push(
|
|
797
|
+
bodyKeysList.push(_key4);
|
|
739
798
|
} else {
|
|
740
|
-
var _encoded = encodeBodyPart(
|
|
799
|
+
var _encoded = encodeBodyPart(_key4, _value4, inlineKeyVal);
|
|
741
800
|
parts.push({
|
|
742
|
-
name:
|
|
801
|
+
name: _key4,
|
|
743
802
|
body: _encoded
|
|
744
803
|
});
|
|
745
|
-
bodyKeysList.push(
|
|
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
|
-
|
|
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
|
-
//
|
|
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 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
|
-
|
|
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,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
|
|
352
|
-
let
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
387
|
+
if (colonPos === -1) {
|
|
388
|
+
// No more headers
|
|
389
|
+
break
|
|
390
|
+
}
|
|
400
391
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
410
|
-
|
|
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
|
-
//
|
|
435
|
-
result[partName] =
|
|
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
|
-
//
|
|
453
|
-
result[partName] =
|
|
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
|
-
//
|
|
522
|
+
// Keep as Buffer if it's binary data
|
|
458
523
|
result[partName] = {
|
|
459
524
|
...restHeaders,
|
|
460
|
-
body:
|
|
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(
|
|
545
|
-
|
|
546
|
-
|
|
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
|
|
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)
|