@sproutboat/toolchain 0.4.3 → 0.4.5
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/CHANGELOG.md +13 -0
- package/package.json +1 -1
- package/src/patch.test.ts +4 -0
- package/src/patch.ts +274 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.4.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5ee065c: Stream standalone direct R2 byte ranges safely and support ETag conditionals.
|
|
8
|
+
|
|
9
|
+
## 0.4.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- d8b7877: Add native standalone R2 direct-download tickets with bounded, backpressure-aware
|
|
14
|
+
file reads, including upgrades for existing patched Porffor caches.
|
|
15
|
+
|
|
3
16
|
## 0.4.3
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/package.json
CHANGED
package/src/patch.test.ts
CHANGED
|
@@ -169,6 +169,10 @@ test("#156: status-line fallback synthesizes a line for unlisted codes, idempote
|
|
|
169
169
|
expect(once).toContain("sb_r2_transfer_open");
|
|
170
170
|
expect(once).toContain("sb_r2_transfer_write");
|
|
171
171
|
expect(once).toContain("try_handle_r2_transfer(res, req, method)");
|
|
172
|
+
expect(once).toContain("sb_r2_transfer_download_open");
|
|
173
|
+
expect(once).toContain("sb_r2_transfer_download_v1");
|
|
174
|
+
expect(once).toContain("sb_r2_transfer_range_v1");
|
|
175
|
+
expect(once).toContain("sb_r2_transfer_conditional_v1");
|
|
172
176
|
|
|
173
177
|
// #163: collect_headers takes res, drops a client-sent x-sb-remote-addr, and
|
|
174
178
|
// appends the real peer; the call site passes res through.
|
package/src/patch.ts
CHANGED
|
@@ -423,6 +423,11 @@ const R2_TRANSFER_INJECT = `extern "C" {
|
|
|
423
423
|
int sb_r2_transfer_write(sb_r2_transfer_ctx*, const char*, size_t);
|
|
424
424
|
int sb_r2_transfer_finish(sb_r2_transfer_ctx*);
|
|
425
425
|
void sb_r2_transfer_abort(sb_r2_transfer_ctx*);
|
|
426
|
+
int sb_r2_transfer_download_open(const char*, const char*, sb_r2_transfer_ctx**);
|
|
427
|
+
size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);
|
|
428
|
+
const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);
|
|
429
|
+
size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx*, size_t, char*, size_t);
|
|
430
|
+
void sb_r2_transfer_download_close(sb_r2_transfer_ctx*);
|
|
426
431
|
}
|
|
427
432
|
static bool sb_r2_transfer_path(std::string_view path, std::string* bucket, std::string* token) {
|
|
428
433
|
static constexpr std::string_view prefix = "/__sb/r2/transfer/";
|
|
@@ -447,7 +452,66 @@ static std::string sb_r2_transfer_status(int status) {
|
|
|
447
452
|
static bool try_handle_r2_transfer(uWS::HttpResponse<false>* res, uWS::HttpRequest* req, std::string_view method) {
|
|
448
453
|
std::string bucket, token;
|
|
449
454
|
if (!sb_r2_transfer_path(req->getUrl(), &bucket, &token)) return false;
|
|
450
|
-
|
|
455
|
+
// sb_r2_transfer_download_v1
|
|
456
|
+
// sb_r2_transfer_range_v1
|
|
457
|
+
// sb_r2_transfer_range_header_v2
|
|
458
|
+
// sb_r2_transfer_conditional_v1
|
|
459
|
+
// sb_r2_transfer_streaming_v2
|
|
460
|
+
if (method == "GET") {
|
|
461
|
+
sb_r2_transfer_ctx* download = nullptr;
|
|
462
|
+
const int open_status = sb_r2_transfer_download_open(bucket.c_str(), token.c_str(), &download);
|
|
463
|
+
if (open_status != 0 || !download) { respond_with_error(res, sb_r2_transfer_status(open_status), "R2 transfer unavailable", true); return true; }
|
|
464
|
+
const std::string etag = sb_r2_transfer_download_etag(download);
|
|
465
|
+
const std::string_view if_none_match = req->getHeader("if-none-match");
|
|
466
|
+
if (if_none_match == "*" || if_none_match == etag || (if_none_match.size() == etag.size() + 2 && if_none_match.front() == '"' && if_none_match.back() == '"' && if_none_match.substr(1, etag.size()) == etag)) {
|
|
467
|
+
sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->writeHeader("ETag", etag); res->end(); return true;
|
|
468
|
+
}
|
|
469
|
+
const uintmax_t full_size = sb_r2_transfer_download_size(download);
|
|
470
|
+
uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
|
|
471
|
+
std::string_view range = req->getHeader("range"); if (range.empty()) range = req->getHeader("Range");
|
|
472
|
+
if (!range.empty()) {
|
|
473
|
+
if (range.substr(0, 6) != "bytes=") { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
474
|
+
const std::string_view spec = range.substr(6); const size_t dash = spec.find('-');
|
|
475
|
+
if (dash == std::string_view::npos || dash == 0) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
476
|
+
first = 0; for (char c : spec.substr(0, dash)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } first = first * 10 + (c - '0'); }
|
|
477
|
+
if (dash + 1 < spec.size()) { last = 0; for (char c : spec.substr(dash + 1)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } last = last * 10 + (c - '0'); } }
|
|
478
|
+
if (first >= full_size || last < first) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "range outside object", true); return true; }
|
|
479
|
+
if (last >= full_size) last = full_size - 1;
|
|
480
|
+
char content_range[96]; snprintf(content_range, sizeof(content_range), "bytes %llu-%llu/%llu", (unsigned long long)first, (unsigned long long)last, (unsigned long long)full_size);
|
|
481
|
+
res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
|
|
482
|
+
}
|
|
483
|
+
res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
|
|
484
|
+
struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; char chunk[65536]; };
|
|
485
|
+
auto state = std::make_shared<Download>(Download{download, first, last - first + 1});
|
|
486
|
+
auto pump = std::make_shared<std::function<bool()>>();
|
|
487
|
+
*pump = [res, state, pump]() {
|
|
488
|
+
if (state->closed) return false;
|
|
489
|
+
for (;;) {
|
|
490
|
+
const size_t want = state->size - state->sent < sizeof(state->chunk) ? (size_t)(state->size - state->sent) : sizeof(state->chunk);
|
|
491
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + state->sent), state->chunk, want);
|
|
492
|
+
const auto result = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
493
|
+
if (result.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
494
|
+
if (!result.first) {
|
|
495
|
+
res->onWritable([res, state](uintmax_t offset) {
|
|
496
|
+
if (state->closed) return true;
|
|
497
|
+
const size_t want = state->size - offset < sizeof(state->chunk) ? (size_t)(state->size - offset) : sizeof(state->chunk);
|
|
498
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), state->chunk, want);
|
|
499
|
+
const auto resumed = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
500
|
+
if (resumed.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return true; }
|
|
501
|
+
return resumed.first;
|
|
502
|
+
});
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
state->sent += bytes;
|
|
506
|
+
if (state->sent == state->size) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
res->writeHeader("Content-Type", "application/octet-stream");
|
|
510
|
+
res->onAborted([state]() { if (!state->closed) { sb_r2_transfer_download_close(state->ctx); state->closed = true; } });
|
|
511
|
+
(*pump)();
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
if (method != "PUT") { respond_with_error(res, "405 Method Not Allowed", "R2 transfer requires GET or PUT", true); return true; }
|
|
451
515
|
size_t declared = 0;
|
|
452
516
|
const std::string_view content_length = req->getHeader("content-length");
|
|
453
517
|
if (!content_length.empty() && !parse_content_length(content_length, &declared)) { respond_with_error(res, "400 Bad Request", "invalid content-length", true); return true; }
|
|
@@ -475,6 +539,80 @@ const R2_TRANSFER_CALL_ANCHOR = " __porffor_js_enter();\n const i32 url_ptr =
|
|
|
475
539
|
const R2_TRANSFER_CALL_INJECT =
|
|
476
540
|
" if (try_handle_r2_transfer(res, req, method)) return;\n" + R2_TRANSFER_CALL_ANCHOR;
|
|
477
541
|
const R2_TRANSFER_CALL_MARKER = "try_handle_r2_transfer(res, req, method)";
|
|
542
|
+
const R2_DOWNLOAD_CACHE_MARKER = "sb_r2_transfer_download_v1";
|
|
543
|
+
const R2_RANGE_CACHE_MARKER = "sb_r2_transfer_range_v1";
|
|
544
|
+
const R2_CONDITIONAL_CACHE_MARKER = "sb_r2_transfer_conditional_v1";
|
|
545
|
+
const R2_STREAMING_CACHE_MARKER = "sb_r2_transfer_streaming_v2";
|
|
546
|
+
const R2_RANGE_HEADER_CACHE_MARKER = "sb_r2_transfer_range_header_v2";
|
|
547
|
+
const R2_DOWNLOAD_DECL_ANCHOR = " void sb_r2_transfer_abort(sb_r2_transfer_ctx*);\n}";
|
|
548
|
+
const R2_DOWNLOAD_DECL_INJECT =
|
|
549
|
+
" void sb_r2_transfer_abort(sb_r2_transfer_ctx*);\n" +
|
|
550
|
+
" int sb_r2_transfer_download_open(const char*, const char*, sb_r2_transfer_ctx**);\n" +
|
|
551
|
+
" size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);\n" +
|
|
552
|
+
" const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n" +
|
|
553
|
+
" size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx*, size_t, char*, size_t);\n" +
|
|
554
|
+
" void sb_r2_transfer_download_close(sb_r2_transfer_ctx*);\n}";
|
|
555
|
+
const R2_DOWNLOAD_BRANCH_ANCHOR = ' if (method != "PUT") { respond_with_error(res, "405 Method Not Allowed", "R2 transfer requires PUT", true); return true; }';
|
|
556
|
+
const R2_DOWNLOAD_BRANCH_INJECT = ` // sb_r2_transfer_download_v1
|
|
557
|
+
// sb_r2_transfer_range_v1
|
|
558
|
+
// sb_r2_transfer_range_header_v2
|
|
559
|
+
// sb_r2_transfer_conditional_v1
|
|
560
|
+
// sb_r2_transfer_streaming_v2
|
|
561
|
+
if (method == "GET") {
|
|
562
|
+
sb_r2_transfer_ctx* download = nullptr;
|
|
563
|
+
const int status = sb_r2_transfer_download_open(bucket.c_str(), token.c_str(), &download);
|
|
564
|
+
if (status != 0 || !download) { respond_with_error(res, sb_r2_transfer_status(status), "R2 transfer unavailable", true); return true; }
|
|
565
|
+
const std::string etag = sb_r2_transfer_download_etag(download);
|
|
566
|
+
const std::string_view if_none_match = req->getHeader("if-none-match");
|
|
567
|
+
if (if_none_match == "*" || if_none_match == etag || (if_none_match.size() == etag.size() + 2 && if_none_match.front() == '"' && if_none_match.back() == '"' && if_none_match.substr(1, etag.size()) == etag)) {
|
|
568
|
+
sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->writeHeader("ETag", etag); res->end(); return true;
|
|
569
|
+
}
|
|
570
|
+
const uintmax_t full_size = sb_r2_transfer_download_size(download);
|
|
571
|
+
uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
|
|
572
|
+
std::string_view range = req->getHeader("range"); if (range.empty()) range = req->getHeader("Range");
|
|
573
|
+
if (!range.empty()) {
|
|
574
|
+
if (range.substr(0, 6) != "bytes=") { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
575
|
+
const std::string_view spec = range.substr(6); const size_t dash = spec.find('-');
|
|
576
|
+
if (dash == std::string_view::npos || dash == 0) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
577
|
+
first = 0; for (char c : spec.substr(0, dash)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } first = first * 10 + (c - '0'); }
|
|
578
|
+
if (dash + 1 < spec.size()) { last = 0; for (char c : spec.substr(dash + 1)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } last = last * 10 + (c - '0'); } }
|
|
579
|
+
if (first >= full_size || last < first) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "range outside object", true); return true; }
|
|
580
|
+
if (last >= full_size) last = full_size - 1;
|
|
581
|
+
char content_range[96]; snprintf(content_range, sizeof(content_range), "bytes %llu-%llu/%llu", (unsigned long long)first, (unsigned long long)last, (unsigned long long)full_size);
|
|
582
|
+
res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
|
|
583
|
+
}
|
|
584
|
+
res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
|
|
585
|
+
struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; char chunk[65536]; };
|
|
586
|
+
auto state = std::make_shared<Download>(Download{download, first, last - first + 1});
|
|
587
|
+
auto pump = std::make_shared<std::function<bool()>>();
|
|
588
|
+
*pump = [res, state, pump]() {
|
|
589
|
+
if (state->closed) return false;
|
|
590
|
+
for (;;) {
|
|
591
|
+
const size_t want = state->size - state->sent < sizeof(state->chunk) ? (size_t)(state->size - state->sent) : sizeof(state->chunk);
|
|
592
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + state->sent), state->chunk, want);
|
|
593
|
+
const auto result = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
594
|
+
if (result.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
595
|
+
if (!result.first) {
|
|
596
|
+
res->onWritable([res, state](uintmax_t offset) {
|
|
597
|
+
if (state->closed) return true;
|
|
598
|
+
const size_t want = state->size - offset < sizeof(state->chunk) ? (size_t)(state->size - offset) : sizeof(state->chunk);
|
|
599
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), state->chunk, want);
|
|
600
|
+
const auto resumed = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
601
|
+
if (resumed.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return true; }
|
|
602
|
+
return resumed.first;
|
|
603
|
+
});
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
state->sent += bytes;
|
|
607
|
+
if (state->sent == state->size) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
res->writeHeader("Content-Type", "application/octet-stream");
|
|
611
|
+
res->onAborted([state]() { if (!state->closed) { sb_r2_transfer_download_close(state->ctx); state->closed = true; } });
|
|
612
|
+
(*pump)();
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
if (method != "PUT") { respond_with_error(res, "405 Method Not Allowed", "R2 transfer requires GET or PUT", true); return true; }`;
|
|
478
616
|
|
|
479
617
|
const done = new Set<string>();
|
|
480
618
|
|
|
@@ -516,6 +654,141 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
516
654
|
const file = resolve(root, "compiler/uwebsockets.js");
|
|
517
655
|
let src = await readFile(file, "utf8");
|
|
518
656
|
let changed = false;
|
|
657
|
+
// Upgrade already-patched caches from the initial upload-only bridge. New
|
|
658
|
+
// caches receive the full bridge below; old ones need declarations plus the
|
|
659
|
+
// GET dispatch grafted onto their existing transfer handler.
|
|
660
|
+
if (src.includes(R2_TRANSFER_MARKER) && !src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
661
|
+
if (!src.includes(R2_DOWNLOAD_DECL_ANCHOR) || !src.includes(R2_DOWNLOAD_BRANCH_ANCHOR))
|
|
662
|
+
throw new Error(`could not upgrade Porffor's direct R2 transfer bridge: anchor not found in ${file}.`);
|
|
663
|
+
src = src.replace(R2_DOWNLOAD_DECL_ANCHOR, R2_DOWNLOAD_DECL_INJECT);
|
|
664
|
+
src = src.replace(R2_DOWNLOAD_BRANCH_ANCHOR, R2_DOWNLOAD_BRANCH_INJECT);
|
|
665
|
+
changed = true;
|
|
666
|
+
}
|
|
667
|
+
if (src.includes(R2_DOWNLOAD_CACHE_MARKER) && !src.includes(R2_RANGE_CACHE_MARKER)) {
|
|
668
|
+
const stateAnchor = " struct Download { sb_r2_transfer_ctx* ctx; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, sb_r2_transfer_download_size(download)});";
|
|
669
|
+
const range = ` const uintmax_t full_size = sb_r2_transfer_download_size(download);
|
|
670
|
+
uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
|
|
671
|
+
const std::string_view range = req->getHeader("range");
|
|
672
|
+
if (!range.empty()) {
|
|
673
|
+
if (range.substr(0, 6) != "bytes=") { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
674
|
+
const std::string_view spec = range.substr(6); const size_t dash = spec.find('-');
|
|
675
|
+
if (dash == std::string_view::npos || dash == 0) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
676
|
+
first = 0; for (char c : spec.substr(0, dash)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } first = first * 10 + (c - '0'); }
|
|
677
|
+
if (dash + 1 < spec.size()) { last = 0; for (char c : spec.substr(dash + 1)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } last = last * 10 + (c - '0'); } }
|
|
678
|
+
if (first >= full_size || last < first) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "range outside object", true); return true; }
|
|
679
|
+
if (last >= full_size) last = full_size - 1;
|
|
680
|
+
char content_range[96]; snprintf(content_range, sizeof(content_range), "bytes %llu-%llu/%llu", (unsigned long long)first, (unsigned long long)last, (unsigned long long)full_size);
|
|
681
|
+
res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
|
|
682
|
+
}
|
|
683
|
+
`;
|
|
684
|
+
const state = " struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, first, last - first + 1});";
|
|
685
|
+
if (!src.includes(stateAnchor)) throw new Error(`could not upgrade Porffor's direct R2 range bridge: anchor not found in ${file}.`);
|
|
686
|
+
src = src.replace(" // sb_r2_transfer_download_v1\n", " // sb_r2_transfer_download_v1\n // sb_r2_transfer_range_v1\n");
|
|
687
|
+
src = src.replace(stateAnchor, range + state);
|
|
688
|
+
src = src.replace("sb_r2_transfer_download_read(state->ctx, (size_t)offset, chunk, sizeof(chunk))", "sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk))");
|
|
689
|
+
changed = true;
|
|
690
|
+
}
|
|
691
|
+
if (src.includes(R2_RANGE_CACHE_MARKER) && !src.includes(R2_CONDITIONAL_CACHE_MARKER)) {
|
|
692
|
+
const declaration = " size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);\n";
|
|
693
|
+
const declarationReplacement = declaration + " const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n";
|
|
694
|
+
const anchor = " const uintmax_t full_size = sb_r2_transfer_download_size(download);\n";
|
|
695
|
+
const inject = ` // sb_r2_transfer_conditional_v1
|
|
696
|
+
const std::string etag = sb_r2_transfer_download_etag(download);
|
|
697
|
+
res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
|
|
698
|
+
const std::string_view if_none_match = req->getHeader("if-none-match");
|
|
699
|
+
if (if_none_match == "*" || if_none_match == etag || (if_none_match.size() == etag.size() + 2 && if_none_match.front() == '"' && if_none_match.back() == '"' && if_none_match.substr(1, etag.size()) == etag)) {
|
|
700
|
+
sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->end(); return true;
|
|
701
|
+
}
|
|
702
|
+
` + anchor;
|
|
703
|
+
if (!src.includes(declaration) || !src.includes(anchor)) throw new Error(`could not upgrade Porffor's direct R2 conditional bridge: anchor not found in ${file}.`);
|
|
704
|
+
src = src.replace(declaration, declarationReplacement).replace(anchor, inject);
|
|
705
|
+
changed = true;
|
|
706
|
+
}
|
|
707
|
+
if (src.includes(R2_CONDITIONAL_CACHE_MARKER) && !src.includes(R2_STREAMING_CACHE_MARKER)) {
|
|
708
|
+
const oldPump = ` const uintmax_t offset = res->getWriteOffset();
|
|
709
|
+
char chunk[65536];
|
|
710
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
|
|
711
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
712
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
713
|
+
if (!result.first) res->onWritable([pump](uintmax_t) { return (*pump)(); });
|
|
714
|
+
return false;`;
|
|
715
|
+
const newPump = ` for (;;) {
|
|
716
|
+
const uintmax_t offset = res->getWriteOffset(); char chunk[65536];
|
|
717
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
|
|
718
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
719
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
720
|
+
if (!result.first) { res->onWritable([pump](uintmax_t) { return (*pump)(); }); return false; }
|
|
721
|
+
}`;
|
|
722
|
+
if (!src.includes(oldPump)) throw new Error(`could not upgrade Porffor's direct R2 streaming bridge: anchor not found in ${file}.`);
|
|
723
|
+
src = src.replace(" // sb_r2_transfer_conditional_v1\n", " // sb_r2_transfer_conditional_v1\n // sb_r2_transfer_streaming_v2\n").replace(oldPump, newPump);
|
|
724
|
+
changed = true;
|
|
725
|
+
}
|
|
726
|
+
// A short-lived v2 cache marker was written before its pump fix. Repair
|
|
727
|
+
// those caches as well as applying the current form to future upgrades.
|
|
728
|
+
if (src.includes("state->base + offset") && src.includes("uintmax_t size; bool closed = false;")) {
|
|
729
|
+
const oldState = "struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };";
|
|
730
|
+
const newState = "struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; };";
|
|
731
|
+
const oldPump = ` const uintmax_t offset = res->getWriteOffset(); char chunk[65536];
|
|
732
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
|
|
733
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
734
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
735
|
+
if (!result.first) { res->onWritable([pump](uintmax_t) { return (*pump)(); }); return false; }
|
|
736
|
+
}`;
|
|
737
|
+
const newPump = ` char chunk[65536];
|
|
738
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + state->sent), chunk, sizeof(chunk));
|
|
739
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
740
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
741
|
+
if (!result.first) { res->onWritable([pump, state](uintmax_t offset) { state->sent = offset; return (*pump)(); }); return false; }
|
|
742
|
+
state->sent += bytes;
|
|
743
|
+
}`;
|
|
744
|
+
if (!src.includes(oldState) || !src.includes(oldPump)) throw new Error(`could not repair Porffor's direct R2 streaming bridge: anchor not found in ${file}.`);
|
|
745
|
+
src = src.replace(oldState, newState).replace(oldPump, newPump);
|
|
746
|
+
changed = true;
|
|
747
|
+
}
|
|
748
|
+
const duplicatePump = " for (;;) {\n char chunk[65536];\n for (;;) {\n";
|
|
749
|
+
if (src.includes(duplicatePump)) {
|
|
750
|
+
src = src.replace(duplicatePump, " for (;;) {\n char chunk[65536];\n");
|
|
751
|
+
changed = true;
|
|
752
|
+
}
|
|
753
|
+
const unclosedPump = " state->sent += bytes;\n };\n";
|
|
754
|
+
if (src.includes(unclosedPump)) {
|
|
755
|
+
src = src.replace(unclosedPump, " state->sent += bytes;\n }\n };\n");
|
|
756
|
+
changed = true;
|
|
757
|
+
}
|
|
758
|
+
if (src.includes(R2_STREAMING_CACHE_MARKER) && !src.includes(R2_RANGE_HEADER_CACHE_MARKER)) {
|
|
759
|
+
const rangeHeader = " const std::string_view range = req->getHeader(\"range\");\n";
|
|
760
|
+
if (!src.includes(rangeHeader)) throw new Error(`could not upgrade Porffor's direct R2 range-header bridge: anchor not found in ${file}.`);
|
|
761
|
+
src = src.replace(" // sb_r2_transfer_range_v1\n", " // sb_r2_transfer_range_v1\n // sb_r2_transfer_range_header_v2\n");
|
|
762
|
+
src = src.replace(rangeHeader, " std::string_view range = req->getHeader(\"range\"); if (range.empty()) range = req->getHeader(\"Range\");\n");
|
|
763
|
+
changed = true;
|
|
764
|
+
}
|
|
765
|
+
const earlyConditionalHeaders = " res->writeHeader(\"ETag\", etag); res->writeHeader(\"Accept-Ranges\", \"bytes\");\n";
|
|
766
|
+
if (src.includes(earlyConditionalHeaders) && src.indexOf(earlyConditionalHeaders) < src.indexOf(" const uintmax_t full_size = sb_r2_transfer_download_size(download);")) {
|
|
767
|
+
const old304 = " sb_r2_transfer_download_close(download); res->writeStatus(\"304 Not Modified\"); res->end(); return true;";
|
|
768
|
+
const new304 = " sb_r2_transfer_download_close(download); res->writeStatus(\"304 Not Modified\"); res->writeHeader(\"ETag\", etag); res->end(); return true;";
|
|
769
|
+
const rangeEnd = " res->writeStatus(\"206 Partial Content\"); res->writeHeader(\"Content-Range\", content_range);\n }\n";
|
|
770
|
+
if (!src.includes(old304) || !src.includes(rangeEnd)) throw new Error(`could not repair Porffor's direct R2 response ordering: anchor not found in ${file}.`);
|
|
771
|
+
src = src.replace(earlyConditionalHeaders, "").replace(old304, new304).replace(rangeEnd, rangeEnd + " res->writeHeader(\"ETag\", etag); res->writeHeader(\"Accept-Ranges\", \"bytes\");\n");
|
|
772
|
+
changed = true;
|
|
773
|
+
}
|
|
774
|
+
if (src.includes("state->sent") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
775
|
+
const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
|
|
776
|
+
const stateEnd = src.indexOf(" res->writeHeader(\"Content-Type\"", stateStart);
|
|
777
|
+
const freshStart = R2_DOWNLOAD_BRANCH_INJECT.indexOf(" struct Download {");
|
|
778
|
+
const freshEnd = R2_DOWNLOAD_BRANCH_INJECT.indexOf(" res->writeHeader(\"Content-Type\"", freshStart);
|
|
779
|
+
if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0) throw new Error(`could not replace Porffor's direct R2 stream pump: anchor not found in ${file}.`);
|
|
780
|
+
src = src.slice(0, stateStart) + R2_DOWNLOAD_BRANCH_INJECT.slice(freshStart, freshEnd) + src.slice(stateEnd);
|
|
781
|
+
changed = true;
|
|
782
|
+
}
|
|
783
|
+
if (src.includes("writable_offset") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
784
|
+
const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
|
|
785
|
+
const stateEnd = src.indexOf(" res->writeHeader(\"Content-Type\"", stateStart);
|
|
786
|
+
const freshStart = R2_TRANSFER_INJECT.indexOf(" struct Download {");
|
|
787
|
+
const freshEnd = R2_TRANSFER_INJECT.indexOf(" res->writeHeader(\"Content-Type\"", freshStart);
|
|
788
|
+
if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0) throw new Error(`could not restore Porffor's direct R2 stream pump: anchor not found in ${file}.`);
|
|
789
|
+
src = src.slice(0, stateStart) + R2_TRANSFER_INJECT.slice(freshStart, freshEnd) + src.slice(stateEnd);
|
|
790
|
+
changed = true;
|
|
791
|
+
}
|
|
519
792
|
for (const [marker, anchor, inject, what] of UWS_EDITS) {
|
|
520
793
|
if (src.includes(marker)) continue;
|
|
521
794
|
if (!src.includes(anchor)) {
|