@sveltejs/kit 1.0.0-next.243 → 1.0.0-next.247
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/assets/server/index.js +636 -113
- package/dist/chunks/amp_hook.js +56 -0
- package/dist/chunks/index.js +657 -169
- package/dist/chunks/index3.js +13 -13
- package/dist/chunks/index5.js +13 -9
- package/dist/chunks/index7.js +17 -13
- package/dist/cli.js +61 -8
- package/dist/install-fetch.js +1 -1
- package/dist/node.js +11 -1
- package/package.json +1 -1
- package/types/app.d.ts +7 -3
- package/types/config.d.ts +5 -0
- package/types/csp.d.ts +115 -0
- package/types/endpoint.d.ts +6 -2
- package/types/helper.d.ts +1 -1
- package/types/hooks.d.ts +11 -15
- package/types/internal.d.ts +24 -7
package/assets/server/index.js
CHANGED
|
@@ -181,7 +181,7 @@ function is_pojo(body) {
|
|
|
181
181
|
return true;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
|
|
184
|
+
var chars$1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
|
|
185
185
|
var unsafeChars = /[<>\b\f\n\r\t\0\u2028\u2029]/g;
|
|
186
186
|
var reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/;
|
|
187
187
|
var escaped = {
|
|
@@ -341,8 +341,8 @@ function devalue(value) {
|
|
|
341
341
|
function getName(num) {
|
|
342
342
|
var name = '';
|
|
343
343
|
do {
|
|
344
|
-
name = chars[num % chars.length] + name;
|
|
345
|
-
num = ~~(num / chars.length) - 1;
|
|
344
|
+
name = chars$1[num % chars$1.length] + name;
|
|
345
|
+
num = ~~(num / chars$1.length) - 1;
|
|
346
346
|
} while (num >= 0);
|
|
347
347
|
return reserved.test(name) ? name + "_" : name;
|
|
348
348
|
}
|
|
@@ -564,6 +564,455 @@ function create_prerendering_url_proxy(url) {
|
|
|
564
564
|
});
|
|
565
565
|
}
|
|
566
566
|
|
|
567
|
+
const encoder = new TextEncoder();
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
|
|
571
|
+
* modified and redistributed under BSD license
|
|
572
|
+
* @param {string} data
|
|
573
|
+
*/
|
|
574
|
+
function sha256(data) {
|
|
575
|
+
if (!key[0]) precompute();
|
|
576
|
+
|
|
577
|
+
const out = init.slice(0);
|
|
578
|
+
const array = encode(data);
|
|
579
|
+
|
|
580
|
+
for (let i = 0; i < array.length; i += 16) {
|
|
581
|
+
const w = array.subarray(i, i + 16);
|
|
582
|
+
|
|
583
|
+
let tmp;
|
|
584
|
+
let a;
|
|
585
|
+
let b;
|
|
586
|
+
|
|
587
|
+
let out0 = out[0];
|
|
588
|
+
let out1 = out[1];
|
|
589
|
+
let out2 = out[2];
|
|
590
|
+
let out3 = out[3];
|
|
591
|
+
let out4 = out[4];
|
|
592
|
+
let out5 = out[5];
|
|
593
|
+
let out6 = out[6];
|
|
594
|
+
let out7 = out[7];
|
|
595
|
+
|
|
596
|
+
/* Rationale for placement of |0 :
|
|
597
|
+
* If a value can overflow is original 32 bits by a factor of more than a few
|
|
598
|
+
* million (2^23 ish), there is a possibility that it might overflow the
|
|
599
|
+
* 53-bit mantissa and lose precision.
|
|
600
|
+
*
|
|
601
|
+
* To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
|
|
602
|
+
* propagates around the loop, and on the hash state out[]. I don't believe
|
|
603
|
+
* that the clamps on out4 and on out0 are strictly necessary, but it's close
|
|
604
|
+
* (for out4 anyway), and better safe than sorry.
|
|
605
|
+
*
|
|
606
|
+
* The clamps on out[] are necessary for the output to be correct even in the
|
|
607
|
+
* common case and for short inputs.
|
|
608
|
+
*/
|
|
609
|
+
|
|
610
|
+
for (let i = 0; i < 64; i++) {
|
|
611
|
+
// load up the input word for this round
|
|
612
|
+
|
|
613
|
+
if (i < 16) {
|
|
614
|
+
tmp = w[i];
|
|
615
|
+
} else {
|
|
616
|
+
a = w[(i + 1) & 15];
|
|
617
|
+
|
|
618
|
+
b = w[(i + 14) & 15];
|
|
619
|
+
|
|
620
|
+
tmp = w[i & 15] =
|
|
621
|
+
(((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
|
|
622
|
+
((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
|
|
623
|
+
w[i & 15] +
|
|
624
|
+
w[(i + 9) & 15]) |
|
|
625
|
+
0;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
tmp =
|
|
629
|
+
tmp +
|
|
630
|
+
out7 +
|
|
631
|
+
((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
|
|
632
|
+
(out6 ^ (out4 & (out5 ^ out6))) +
|
|
633
|
+
key[i]; // | 0;
|
|
634
|
+
|
|
635
|
+
// shift register
|
|
636
|
+
out7 = out6;
|
|
637
|
+
out6 = out5;
|
|
638
|
+
out5 = out4;
|
|
639
|
+
|
|
640
|
+
out4 = (out3 + tmp) | 0;
|
|
641
|
+
|
|
642
|
+
out3 = out2;
|
|
643
|
+
out2 = out1;
|
|
644
|
+
out1 = out0;
|
|
645
|
+
|
|
646
|
+
out0 =
|
|
647
|
+
(tmp +
|
|
648
|
+
((out1 & out2) ^ (out3 & (out1 ^ out2))) +
|
|
649
|
+
((out1 >>> 2) ^
|
|
650
|
+
(out1 >>> 13) ^
|
|
651
|
+
(out1 >>> 22) ^
|
|
652
|
+
(out1 << 30) ^
|
|
653
|
+
(out1 << 19) ^
|
|
654
|
+
(out1 << 10))) |
|
|
655
|
+
0;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
out[0] = (out[0] + out0) | 0;
|
|
659
|
+
out[1] = (out[1] + out1) | 0;
|
|
660
|
+
out[2] = (out[2] + out2) | 0;
|
|
661
|
+
out[3] = (out[3] + out3) | 0;
|
|
662
|
+
out[4] = (out[4] + out4) | 0;
|
|
663
|
+
out[5] = (out[5] + out5) | 0;
|
|
664
|
+
out[6] = (out[6] + out6) | 0;
|
|
665
|
+
out[7] = (out[7] + out7) | 0;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const bytes = new Uint8Array(out.buffer);
|
|
669
|
+
reverse_endianness(bytes);
|
|
670
|
+
|
|
671
|
+
return base64(bytes);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** The SHA-256 initialization vector */
|
|
675
|
+
const init = new Uint32Array(8);
|
|
676
|
+
|
|
677
|
+
/** The SHA-256 hash key */
|
|
678
|
+
const key = new Uint32Array(64);
|
|
679
|
+
|
|
680
|
+
/** Function to precompute init and key. */
|
|
681
|
+
function precompute() {
|
|
682
|
+
/** @param {number} x */
|
|
683
|
+
function frac(x) {
|
|
684
|
+
return (x - Math.floor(x)) * 0x100000000;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
let prime = 2;
|
|
688
|
+
|
|
689
|
+
for (let i = 0; i < 64; prime++) {
|
|
690
|
+
let is_prime = true;
|
|
691
|
+
|
|
692
|
+
for (let factor = 2; factor * factor <= prime; factor++) {
|
|
693
|
+
if (prime % factor === 0) {
|
|
694
|
+
is_prime = false;
|
|
695
|
+
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (is_prime) {
|
|
701
|
+
if (i < 8) {
|
|
702
|
+
init[i] = frac(prime ** (1 / 2));
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
key[i] = frac(prime ** (1 / 3));
|
|
706
|
+
|
|
707
|
+
i++;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** @param {Uint8Array} bytes */
|
|
713
|
+
function reverse_endianness(bytes) {
|
|
714
|
+
for (let i = 0; i < bytes.length; i += 4) {
|
|
715
|
+
const a = bytes[i + 0];
|
|
716
|
+
const b = bytes[i + 1];
|
|
717
|
+
const c = bytes[i + 2];
|
|
718
|
+
const d = bytes[i + 3];
|
|
719
|
+
|
|
720
|
+
bytes[i + 0] = d;
|
|
721
|
+
bytes[i + 1] = c;
|
|
722
|
+
bytes[i + 2] = b;
|
|
723
|
+
bytes[i + 3] = a;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** @param {string} str */
|
|
728
|
+
function encode(str) {
|
|
729
|
+
const encoded = encoder.encode(str);
|
|
730
|
+
const length = encoded.length * 8;
|
|
731
|
+
|
|
732
|
+
// result should be a multiple of 512 bits in length,
|
|
733
|
+
// with room for a 1 (after the data) and two 32-bit
|
|
734
|
+
// words containing the original input bit length
|
|
735
|
+
const size = 512 * Math.ceil((length + 65) / 512);
|
|
736
|
+
const bytes = new Uint8Array(size / 8);
|
|
737
|
+
bytes.set(encoded);
|
|
738
|
+
|
|
739
|
+
// append a 1
|
|
740
|
+
bytes[encoded.length] = 0b10000000;
|
|
741
|
+
|
|
742
|
+
reverse_endianness(bytes);
|
|
743
|
+
|
|
744
|
+
// add the input bit length
|
|
745
|
+
const words = new Uint32Array(bytes.buffer);
|
|
746
|
+
words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
|
|
747
|
+
words[words.length - 1] = length;
|
|
748
|
+
|
|
749
|
+
return words;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/*
|
|
753
|
+
Based on https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
|
754
|
+
|
|
755
|
+
MIT License
|
|
756
|
+
Copyright (c) 2020 Egor Nepomnyaschih
|
|
757
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
758
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
759
|
+
in the Software without restriction, including without limitation the rights
|
|
760
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
761
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
762
|
+
furnished to do so, subject to the following conditions:
|
|
763
|
+
The above copyright notice and this permission notice shall be included in all
|
|
764
|
+
copies or substantial portions of the Software.
|
|
765
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
766
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
767
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
768
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
769
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
770
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
771
|
+
SOFTWARE.
|
|
772
|
+
*/
|
|
773
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
|
774
|
+
|
|
775
|
+
/** @param {Uint8Array} bytes */
|
|
776
|
+
function base64(bytes) {
|
|
777
|
+
const l = bytes.length;
|
|
778
|
+
|
|
779
|
+
let result = '';
|
|
780
|
+
let i;
|
|
781
|
+
|
|
782
|
+
for (i = 2; i < l; i += 3) {
|
|
783
|
+
result += chars[bytes[i - 2] >> 2];
|
|
784
|
+
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
785
|
+
result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
|
|
786
|
+
result += chars[bytes[i] & 0x3f];
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (i === l + 1) {
|
|
790
|
+
// 1 octet yet to write
|
|
791
|
+
result += chars[bytes[i - 2] >> 2];
|
|
792
|
+
result += chars[(bytes[i - 2] & 0x03) << 4];
|
|
793
|
+
result += '==';
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (i === l) {
|
|
797
|
+
// 2 octets yet to write
|
|
798
|
+
result += chars[bytes[i - 2] >> 2];
|
|
799
|
+
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
800
|
+
result += chars[(bytes[i - 1] & 0x0f) << 2];
|
|
801
|
+
result += '=';
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return result;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/** @type {Promise<void>} */
|
|
808
|
+
let csp_ready;
|
|
809
|
+
|
|
810
|
+
/** @type {() => string} */
|
|
811
|
+
let generate_nonce;
|
|
812
|
+
|
|
813
|
+
/** @type {(input: string) => string} */
|
|
814
|
+
let generate_hash;
|
|
815
|
+
|
|
816
|
+
if (typeof crypto !== 'undefined') {
|
|
817
|
+
const array = new Uint8Array(16);
|
|
818
|
+
|
|
819
|
+
generate_nonce = () => {
|
|
820
|
+
crypto.getRandomValues(array);
|
|
821
|
+
return base64(array);
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
generate_hash = sha256;
|
|
825
|
+
} else {
|
|
826
|
+
// TODO: remove this in favor of web crypto API once we no longer support Node 14
|
|
827
|
+
const name = 'crypto'; // store in a variable to fool esbuild when adapters bundle kit
|
|
828
|
+
csp_ready = import(name).then((crypto) => {
|
|
829
|
+
generate_nonce = () => {
|
|
830
|
+
return crypto.randomBytes(16).toString('base64');
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
generate_hash = (input) => {
|
|
834
|
+
return crypto.createHash('sha256').update(input, 'utf-8').digest().toString('base64');
|
|
835
|
+
};
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const quoted = new Set([
|
|
840
|
+
'self',
|
|
841
|
+
'unsafe-eval',
|
|
842
|
+
'unsafe-hashes',
|
|
843
|
+
'unsafe-inline',
|
|
844
|
+
'none',
|
|
845
|
+
'strict-dynamic',
|
|
846
|
+
'report-sample'
|
|
847
|
+
]);
|
|
848
|
+
|
|
849
|
+
const crypto_pattern = /^(nonce|sha\d\d\d)-/;
|
|
850
|
+
|
|
851
|
+
class Csp {
|
|
852
|
+
/** @type {boolean} */
|
|
853
|
+
#use_hashes;
|
|
854
|
+
|
|
855
|
+
/** @type {boolean} */
|
|
856
|
+
#dev;
|
|
857
|
+
|
|
858
|
+
/** @type {boolean} */
|
|
859
|
+
#script_needs_csp;
|
|
860
|
+
|
|
861
|
+
/** @type {boolean} */
|
|
862
|
+
#style_needs_csp;
|
|
863
|
+
|
|
864
|
+
/** @type {import('types/csp').CspDirectives} */
|
|
865
|
+
#directives;
|
|
866
|
+
|
|
867
|
+
/** @type {import('types/csp').Source[]} */
|
|
868
|
+
#script_src;
|
|
869
|
+
|
|
870
|
+
/** @type {import('types/csp').Source[]} */
|
|
871
|
+
#style_src;
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* @param {{
|
|
875
|
+
* mode: string,
|
|
876
|
+
* directives: import('types/csp').CspDirectives
|
|
877
|
+
* }} config
|
|
878
|
+
* @param {{
|
|
879
|
+
* dev: boolean;
|
|
880
|
+
* prerender: boolean;
|
|
881
|
+
* needs_nonce: boolean;
|
|
882
|
+
* }} opts
|
|
883
|
+
*/
|
|
884
|
+
constructor({ mode, directives }, { dev, prerender, needs_nonce }) {
|
|
885
|
+
this.#use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
|
|
886
|
+
this.#directives = dev ? { ...directives } : directives; // clone in dev so we can safely mutate
|
|
887
|
+
this.#dev = dev;
|
|
888
|
+
|
|
889
|
+
const d = this.#directives;
|
|
890
|
+
|
|
891
|
+
if (dev) {
|
|
892
|
+
// remove strict-dynamic in dev...
|
|
893
|
+
// TODO reinstate this if we can figure out how to make strict-dynamic work
|
|
894
|
+
// if (d['default-src']) {
|
|
895
|
+
// d['default-src'] = d['default-src'].filter((name) => name !== 'strict-dynamic');
|
|
896
|
+
// if (d['default-src'].length === 0) delete d['default-src'];
|
|
897
|
+
// }
|
|
898
|
+
|
|
899
|
+
// if (d['script-src']) {
|
|
900
|
+
// d['script-src'] = d['script-src'].filter((name) => name !== 'strict-dynamic');
|
|
901
|
+
// if (d['script-src'].length === 0) delete d['script-src'];
|
|
902
|
+
// }
|
|
903
|
+
|
|
904
|
+
const effective_style_src = d['style-src'] || d['default-src'];
|
|
905
|
+
|
|
906
|
+
// ...and add unsafe-inline so we can inject <style> elements
|
|
907
|
+
if (effective_style_src && !effective_style_src.includes('unsafe-inline')) {
|
|
908
|
+
d['style-src'] = [...effective_style_src, 'unsafe-inline'];
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
this.#script_src = [];
|
|
913
|
+
this.#style_src = [];
|
|
914
|
+
|
|
915
|
+
const effective_script_src = d['script-src'] || d['default-src'];
|
|
916
|
+
const effective_style_src = d['style-src'] || d['default-src'];
|
|
917
|
+
|
|
918
|
+
this.#script_needs_csp =
|
|
919
|
+
!!effective_script_src &&
|
|
920
|
+
effective_script_src.filter((value) => value !== 'unsafe-inline').length > 0;
|
|
921
|
+
|
|
922
|
+
this.#style_needs_csp =
|
|
923
|
+
!dev &&
|
|
924
|
+
!!effective_style_src &&
|
|
925
|
+
effective_style_src.filter((value) => value !== 'unsafe-inline').length > 0;
|
|
926
|
+
|
|
927
|
+
this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes;
|
|
928
|
+
this.style_needs_nonce = this.#style_needs_csp && !this.#use_hashes;
|
|
929
|
+
|
|
930
|
+
if (this.script_needs_nonce || this.style_needs_nonce || needs_nonce) {
|
|
931
|
+
this.nonce = generate_nonce();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/** @param {string} content */
|
|
936
|
+
add_script(content) {
|
|
937
|
+
if (this.#script_needs_csp) {
|
|
938
|
+
if (this.#use_hashes) {
|
|
939
|
+
this.#script_src.push(`sha256-${generate_hash(content)}`);
|
|
940
|
+
} else if (this.#script_src.length === 0) {
|
|
941
|
+
this.#script_src.push(`nonce-${this.nonce}`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/** @param {string} content */
|
|
947
|
+
add_style(content) {
|
|
948
|
+
if (this.#style_needs_csp) {
|
|
949
|
+
if (this.#use_hashes) {
|
|
950
|
+
this.#style_src.push(`sha256-${generate_hash(content)}`);
|
|
951
|
+
} else if (this.#style_src.length === 0) {
|
|
952
|
+
this.#style_src.push(`nonce-${this.nonce}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** @param {boolean} [is_meta] */
|
|
958
|
+
get_header(is_meta = false) {
|
|
959
|
+
const header = [];
|
|
960
|
+
|
|
961
|
+
// due to browser inconsistencies, we can't append sources to default-src
|
|
962
|
+
// (specifically, Firefox appears to not ignore nonce-{nonce} directives
|
|
963
|
+
// on default-src), so we ensure that script-src and style-src exist
|
|
964
|
+
|
|
965
|
+
const directives = { ...this.#directives };
|
|
966
|
+
|
|
967
|
+
if (this.#style_src.length > 0) {
|
|
968
|
+
directives['style-src'] = [
|
|
969
|
+
...(directives['style-src'] || directives['default-src'] || []),
|
|
970
|
+
...this.#style_src
|
|
971
|
+
];
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (this.#script_src.length > 0) {
|
|
975
|
+
directives['script-src'] = [
|
|
976
|
+
...(directives['script-src'] || directives['default-src'] || []),
|
|
977
|
+
...this.#script_src
|
|
978
|
+
];
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
for (const key in directives) {
|
|
982
|
+
if (is_meta && (key === 'frame-ancestors' || key === 'report-uri' || key === 'sandbox')) {
|
|
983
|
+
// these values cannot be used with a <meta> tag
|
|
984
|
+
// TODO warn?
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// @ts-expect-error gimme a break typescript, `key` is obviously a member of directives
|
|
989
|
+
const value = /** @type {string[] | true} */ (directives[key]);
|
|
990
|
+
|
|
991
|
+
if (!value) continue;
|
|
992
|
+
|
|
993
|
+
const directive = [key];
|
|
994
|
+
if (Array.isArray(value)) {
|
|
995
|
+
value.forEach((value) => {
|
|
996
|
+
if (quoted.has(value) || crypto_pattern.test(value)) {
|
|
997
|
+
directive.push(`'${value}'`);
|
|
998
|
+
} else {
|
|
999
|
+
directive.push(value);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
header.push(directive.join(' '));
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
return header.join('; ');
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
get_meta() {
|
|
1011
|
+
const content = escape_html_attr(this.get_header(true));
|
|
1012
|
+
return `<meta http-equiv="content-security-policy" content=${content}>`;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
567
1016
|
// TODO rename this function/module
|
|
568
1017
|
|
|
569
1018
|
/**
|
|
@@ -594,8 +1043,18 @@ async function render_response({
|
|
|
594
1043
|
ssr,
|
|
595
1044
|
stuff
|
|
596
1045
|
}) {
|
|
597
|
-
|
|
598
|
-
|
|
1046
|
+
if (state.prerender) {
|
|
1047
|
+
if (options.csp.mode === 'nonce') {
|
|
1048
|
+
throw new Error('Cannot use prerendering if config.kit.csp.mode === "nonce"');
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (options.template_contains_nonce) {
|
|
1052
|
+
throw new Error('Cannot use prerendering if page template contains %svelte.nonce%');
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const stylesheets = new Set(options.manifest._.entry.css);
|
|
1057
|
+
const modulepreloads = new Set(options.manifest._.entry.js);
|
|
599
1058
|
/** @type {Map<string, string>} */
|
|
600
1059
|
const styles = new Map();
|
|
601
1060
|
|
|
@@ -613,8 +1072,8 @@ async function render_response({
|
|
|
613
1072
|
|
|
614
1073
|
if (ssr) {
|
|
615
1074
|
branch.forEach(({ node, loaded, fetched, uses_credentials }) => {
|
|
616
|
-
if (node.css) node.css.forEach((url) =>
|
|
617
|
-
if (node.js) node.js.forEach((url) =>
|
|
1075
|
+
if (node.css) node.css.forEach((url) => stylesheets.add(url));
|
|
1076
|
+
if (node.js) node.js.forEach((url) => modulepreloads.add(url));
|
|
618
1077
|
if (node.styles) Object.entries(node.styles).forEach(([k, v]) => styles.set(k, v));
|
|
619
1078
|
|
|
620
1079
|
// TODO probably better if `fetched` wasn't populated unless `hydrate`
|
|
@@ -686,11 +1145,44 @@ async function render_response({
|
|
|
686
1145
|
|
|
687
1146
|
const inlined_style = Array.from(styles.values()).join('\n');
|
|
688
1147
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1148
|
+
await csp_ready;
|
|
1149
|
+
const csp = new Csp(options.csp, {
|
|
1150
|
+
dev: options.dev,
|
|
1151
|
+
prerender: !!state.prerender,
|
|
1152
|
+
needs_nonce: options.template_contains_nonce
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
// prettier-ignore
|
|
1156
|
+
const init_app = `
|
|
1157
|
+
import { start } from ${s(options.prefix + options.manifest._.entry.file)};
|
|
1158
|
+
start({
|
|
1159
|
+
target: ${options.target ? `document.querySelector(${s(options.target)})` : 'document.body'},
|
|
1160
|
+
paths: ${s(options.paths)},
|
|
1161
|
+
session: ${try_serialize($session, (error) => {
|
|
1162
|
+
throw new Error(`Failed to serialize session data: ${error.message}`);
|
|
1163
|
+
})},
|
|
1164
|
+
route: ${!!page_config.router},
|
|
1165
|
+
spa: ${!ssr},
|
|
1166
|
+
trailing_slash: ${s(options.trailing_slash)},
|
|
1167
|
+
hydrate: ${ssr && page_config.hydrate ? `{
|
|
1168
|
+
status: ${status},
|
|
1169
|
+
error: ${serialize_error(error)},
|
|
1170
|
+
nodes: [
|
|
1171
|
+
${(branch || [])
|
|
1172
|
+
.map(({ node }) => `import(${s(options.prefix + node.entry)})`)
|
|
1173
|
+
.join(',\n\t\t\t\t\t\t')}
|
|
1174
|
+
],
|
|
1175
|
+
url: new URL(${s(url.href)}),
|
|
1176
|
+
params: ${devalue(params)}
|
|
1177
|
+
}` : 'null'}
|
|
1178
|
+
});
|
|
1179
|
+
`;
|
|
1180
|
+
|
|
1181
|
+
const init_service_worker = `
|
|
1182
|
+
if ('serviceWorker' in navigator) {
|
|
1183
|
+
navigator.serviceWorker.register('${options.service_worker}');
|
|
692
1184
|
}
|
|
693
|
-
|
|
1185
|
+
`;
|
|
694
1186
|
|
|
695
1187
|
if (options.amp) {
|
|
696
1188
|
head += `
|
|
@@ -708,49 +1200,54 @@ async function render_response({
|
|
|
708
1200
|
}
|
|
709
1201
|
} else {
|
|
710
1202
|
if (inlined_style) {
|
|
711
|
-
|
|
1203
|
+
const attributes = [];
|
|
1204
|
+
if (options.dev) attributes.push(' data-svelte');
|
|
1205
|
+
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
|
|
1206
|
+
|
|
1207
|
+
csp.add_style(inlined_style);
|
|
1208
|
+
|
|
1209
|
+
head += `\n\t<style${attributes.join('')}>${inlined_style}</style>`;
|
|
712
1210
|
}
|
|
1211
|
+
|
|
713
1212
|
// prettier-ignore
|
|
714
|
-
head += Array.from(
|
|
715
|
-
.map((dep) =>
|
|
1213
|
+
head += Array.from(stylesheets)
|
|
1214
|
+
.map((dep) => {
|
|
1215
|
+
const attributes = [
|
|
1216
|
+
'rel="stylesheet"',
|
|
1217
|
+
`href="${options.prefix + dep}"`
|
|
1218
|
+
];
|
|
1219
|
+
|
|
1220
|
+
if (csp.style_needs_nonce) {
|
|
1221
|
+
attributes.push(`nonce="${csp.nonce}"`);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (styles.has(dep)) {
|
|
1225
|
+
attributes.push('disabled', 'media="(max-width: 0)"');
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
return `\n\t<link ${attributes.join(' ')}>`;
|
|
1229
|
+
})
|
|
716
1230
|
.join('');
|
|
717
1231
|
|
|
718
1232
|
if (page_config.router || page_config.hydrate) {
|
|
719
|
-
head += Array.from(
|
|
1233
|
+
head += Array.from(modulepreloads)
|
|
720
1234
|
.map((dep) => `\n\t<link rel="modulepreload" href="${options.prefix + dep}">`)
|
|
721
1235
|
.join('');
|
|
722
|
-
// prettier-ignore
|
|
723
|
-
head += `
|
|
724
|
-
<script type="module">
|
|
725
|
-
import { start } from ${s(options.prefix + options.manifest._.entry.file)};
|
|
726
|
-
start({
|
|
727
|
-
target: ${options.target ? `document.querySelector(${s(options.target)})` : 'document.body'},
|
|
728
|
-
paths: ${s(options.paths)},
|
|
729
|
-
session: ${try_serialize($session, (error) => {
|
|
730
|
-
throw new Error(`Failed to serialize session data: ${error.message}`);
|
|
731
|
-
})},
|
|
732
|
-
route: ${!!page_config.router},
|
|
733
|
-
spa: ${!ssr},
|
|
734
|
-
trailing_slash: ${s(options.trailing_slash)},
|
|
735
|
-
hydrate: ${ssr && page_config.hydrate ? `{
|
|
736
|
-
status: ${status},
|
|
737
|
-
error: ${serialize_error(error)},
|
|
738
|
-
nodes: [
|
|
739
|
-
${(branch || [])
|
|
740
|
-
.map(({ node }) => `import(${s(options.prefix + node.entry)})`)
|
|
741
|
-
.join(',\n\t\t\t\t\t\t')}
|
|
742
|
-
],
|
|
743
|
-
url: new URL(${s(url.href)}),
|
|
744
|
-
params: ${devalue(params)}
|
|
745
|
-
}` : 'null'}
|
|
746
|
-
});
|
|
747
|
-
</script>`;
|
|
748
1236
|
|
|
1237
|
+
const attributes = ['type="module"'];
|
|
1238
|
+
|
|
1239
|
+
csp.add_script(init_app);
|
|
1240
|
+
|
|
1241
|
+
if (csp.script_needs_nonce) {
|
|
1242
|
+
attributes.push(`nonce="${csp.nonce}"`);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
head += `<script ${attributes.join(' ')}>${init_app}</script>`;
|
|
1246
|
+
|
|
1247
|
+
// prettier-ignore
|
|
749
1248
|
body += serialized_data
|
|
750
1249
|
.map(({ url, body, json }) => {
|
|
751
|
-
let attributes = `type="application/json" data-type="svelte-data" data-url=${escape_html_attr(
|
|
752
|
-
url
|
|
753
|
-
)}`;
|
|
1250
|
+
let attributes = `type="application/json" data-type="svelte-data" data-url=${escape_html_attr(url)}`;
|
|
754
1251
|
if (body) attributes += ` data-body="${hash(body)}"`;
|
|
755
1252
|
|
|
756
1253
|
return `<script ${attributes}>${json}</script>`;
|
|
@@ -760,12 +1257,27 @@ async function render_response({
|
|
|
760
1257
|
|
|
761
1258
|
if (options.service_worker) {
|
|
762
1259
|
// always include service worker unless it's turned off explicitly
|
|
1260
|
+
csp.add_script(init_service_worker);
|
|
1261
|
+
|
|
763
1262
|
head += `
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1263
|
+
<script${csp.script_needs_nonce ? ` nonce="${csp.nonce}"` : ''}>${init_service_worker}</script>`;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (state.prerender) {
|
|
1268
|
+
const http_equiv = [];
|
|
1269
|
+
|
|
1270
|
+
const csp_headers = csp.get_meta();
|
|
1271
|
+
if (csp_headers) {
|
|
1272
|
+
http_equiv.push(csp_headers);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (maxage) {
|
|
1276
|
+
http_equiv.push(`<meta http-equiv="cache-control" content="max-age=${maxage}">`);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (http_equiv.length > 0) {
|
|
1280
|
+
head = http_equiv.join('\n') + head;
|
|
769
1281
|
}
|
|
770
1282
|
}
|
|
771
1283
|
|
|
@@ -773,7 +1285,7 @@ async function render_response({
|
|
|
773
1285
|
const assets =
|
|
774
1286
|
options.paths.assets || (segments.length > 0 ? segments.map(() => '..').join('/') : '.');
|
|
775
1287
|
|
|
776
|
-
const html = options.template({ head, body, assets });
|
|
1288
|
+
const html = options.template({ head, body, assets, nonce: /** @type {string} */ (csp.nonce) });
|
|
777
1289
|
|
|
778
1290
|
const headers = new Headers({
|
|
779
1291
|
'content-type': 'text/html',
|
|
@@ -788,6 +1300,13 @@ async function render_response({
|
|
|
788
1300
|
headers.set('permissions-policy', 'interest-cohort=()');
|
|
789
1301
|
}
|
|
790
1302
|
|
|
1303
|
+
if (!state.prerender) {
|
|
1304
|
+
const csp_header = csp.get_header();
|
|
1305
|
+
if (csp_header) {
|
|
1306
|
+
headers.set('content-security-policy', csp_header);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
791
1310
|
return new Response(html, {
|
|
792
1311
|
status,
|
|
793
1312
|
headers
|
|
@@ -1021,6 +1540,9 @@ async function load_node({
|
|
|
1021
1540
|
/** @type {Response} */
|
|
1022
1541
|
let response;
|
|
1023
1542
|
|
|
1543
|
+
/** @type {import('types/internal').PrerenderDependency} */
|
|
1544
|
+
let dependency;
|
|
1545
|
+
|
|
1024
1546
|
// handle fetch requests for static assets. e.g. prebaked data, etc.
|
|
1025
1547
|
// we need to support everything the browser's fetch supports
|
|
1026
1548
|
const prefix = options.paths.assets || options.paths.base;
|
|
@@ -1047,8 +1569,6 @@ async function load_node({
|
|
|
1047
1569
|
response = await fetch(`${url.origin}/${file}`, /** @type {RequestInit} */ (opts));
|
|
1048
1570
|
}
|
|
1049
1571
|
} else if (is_root_relative(resolved)) {
|
|
1050
|
-
const relative = resolved;
|
|
1051
|
-
|
|
1052
1572
|
if (opts.credentials !== 'omit') {
|
|
1053
1573
|
uses_credentials = true;
|
|
1054
1574
|
|
|
@@ -1072,28 +1592,14 @@ async function load_node({
|
|
|
1072
1592
|
throw new Error('Request body must be a string');
|
|
1073
1593
|
}
|
|
1074
1594
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
fetched: requested,
|
|
1080
|
-
initiator: route
|
|
1081
|
-
}
|
|
1082
|
-
);
|
|
1083
|
-
|
|
1084
|
-
if (rendered) {
|
|
1085
|
-
if (state.prerender) {
|
|
1086
|
-
state.prerender.dependencies.set(relative, rendered);
|
|
1087
|
-
}
|
|
1595
|
+
response = await respond(new Request(new URL(requested, event.url).href, opts), options, {
|
|
1596
|
+
fetched: requested,
|
|
1597
|
+
initiator: route
|
|
1598
|
+
});
|
|
1088
1599
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
// so we need to make an actual HTTP request
|
|
1093
|
-
response = await fetch(new URL(requested, event.url).href, {
|
|
1094
|
-
method: opts.method || 'GET',
|
|
1095
|
-
headers: opts.headers
|
|
1096
|
-
});
|
|
1600
|
+
if (state.prerender) {
|
|
1601
|
+
dependency = { response, body: null };
|
|
1602
|
+
state.prerender.dependencies.set(resolved, dependency);
|
|
1097
1603
|
}
|
|
1098
1604
|
} else {
|
|
1099
1605
|
// external
|
|
@@ -1126,59 +1632,69 @@ async function load_node({
|
|
|
1126
1632
|
response = await options.hooks.externalFetch.call(null, external_request);
|
|
1127
1633
|
}
|
|
1128
1634
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
headers[key] = value;
|
|
1142
|
-
}
|
|
1635
|
+
const proxy = new Proxy(response, {
|
|
1636
|
+
get(response, key, _receiver) {
|
|
1637
|
+
async function text() {
|
|
1638
|
+
const body = await response.text();
|
|
1639
|
+
|
|
1640
|
+
/** @type {import('types/helper').ResponseHeaders} */
|
|
1641
|
+
const headers = {};
|
|
1642
|
+
for (const [key, value] of response.headers) {
|
|
1643
|
+
if (key === 'set-cookie') {
|
|
1644
|
+
set_cookie_headers = set_cookie_headers.concat(value);
|
|
1645
|
+
} else if (key !== 'etag') {
|
|
1646
|
+
headers[key] = value;
|
|
1143
1647
|
}
|
|
1648
|
+
}
|
|
1144
1649
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1650
|
+
if (!opts.body || typeof opts.body === 'string') {
|
|
1651
|
+
// prettier-ignore
|
|
1652
|
+
fetched.push({
|
|
1148
1653
|
url: requested,
|
|
1149
1654
|
body: /** @type {string} */ (opts.body),
|
|
1150
1655
|
json: `{"status":${response.status},"statusText":${s(response.statusText)},"headers":${s(headers)},"body":"${escape_json_string_in_html(body)}"}`
|
|
1151
1656
|
});
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
return body;
|
|
1155
1657
|
}
|
|
1156
1658
|
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1659
|
+
if (dependency) {
|
|
1660
|
+
dependency.body = body;
|
|
1159
1661
|
}
|
|
1160
1662
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1663
|
+
return body;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
if (key === 'arrayBuffer') {
|
|
1667
|
+
return async () => {
|
|
1668
|
+
const buffer = await response.arrayBuffer();
|
|
1166
1669
|
|
|
1167
|
-
|
|
1670
|
+
if (dependency) {
|
|
1671
|
+
dependency.body = new Uint8Array(buffer);
|
|
1672
|
+
}
|
|
1168
1673
|
|
|
1169
|
-
|
|
1674
|
+
// TODO should buffer be inlined into the page (albeit base64'd)?
|
|
1675
|
+
// any conditions in which it shouldn't be?
|
|
1676
|
+
|
|
1677
|
+
return buffer;
|
|
1678
|
+
};
|
|
1170
1679
|
}
|
|
1171
|
-
});
|
|
1172
1680
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1681
|
+
if (key === 'text') {
|
|
1682
|
+
return text;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
if (key === 'json') {
|
|
1686
|
+
return async () => {
|
|
1687
|
+
return JSON.parse(await text());
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1175
1690
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1691
|
+
// TODO arrayBuffer?
|
|
1692
|
+
|
|
1693
|
+
return Reflect.get(response, key, response);
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
return proxy;
|
|
1182
1698
|
},
|
|
1183
1699
|
stuff: { ...stuff }
|
|
1184
1700
|
};
|
|
@@ -1671,7 +2187,8 @@ async function respond(request, options, state = {}) {
|
|
|
1671
2187
|
request,
|
|
1672
2188
|
url,
|
|
1673
2189
|
params: {},
|
|
1674
|
-
locals: {}
|
|
2190
|
+
locals: {},
|
|
2191
|
+
platform: state.platform
|
|
1675
2192
|
};
|
|
1676
2193
|
|
|
1677
2194
|
// TODO remove this for 1.0
|
|
@@ -1733,7 +2250,9 @@ async function respond(request, options, state = {}) {
|
|
|
1733
2250
|
let decoded = decodeURI(event.url.pathname);
|
|
1734
2251
|
|
|
1735
2252
|
if (options.paths.base) {
|
|
1736
|
-
if (!decoded.startsWith(options.paths.base))
|
|
2253
|
+
if (!decoded.startsWith(options.paths.base)) {
|
|
2254
|
+
return new Response(undefined, { status: 404 });
|
|
2255
|
+
}
|
|
1737
2256
|
decoded = decoded.slice(options.paths.base.length) || '/';
|
|
1738
2257
|
}
|
|
1739
2258
|
|
|
@@ -1798,6 +2317,10 @@ async function respond(request, options, state = {}) {
|
|
|
1798
2317
|
ssr
|
|
1799
2318
|
});
|
|
1800
2319
|
}
|
|
2320
|
+
|
|
2321
|
+
// we can't load the endpoint from our own manifest,
|
|
2322
|
+
// so we need to make an actual HTTP request
|
|
2323
|
+
return await fetch(request);
|
|
1801
2324
|
},
|
|
1802
2325
|
|
|
1803
2326
|
// TODO remove for 1.0
|