@sveltejs/kit 1.0.0-next.371 → 1.0.0-next.372

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.
@@ -27,32 +27,34 @@ function to_headers(object) {
27
27
  * @param {string[]} types
28
28
  */
29
29
  function negotiate(accept, types) {
30
- const parts = accept
31
- .split(',')
32
- .map((str, i) => {
33
- const match = /([^/]+)\/([^;]+)(?:;q=([0-9.]+))?/.exec(str);
34
- if (match) {
35
- const [, type, subtype, q = '1'] = match;
36
- return { type, subtype, q: +q, i };
37
- }
30
+ /** @type {Array<{ type: string, subtype: string, q: number, i: number }>} */
31
+ const parts = [];
38
32
 
39
- throw new Error(`Invalid Accept header: ${accept}`);
40
- })
41
- .sort((a, b) => {
42
- if (a.q !== b.q) {
43
- return b.q - a.q;
44
- }
33
+ accept.split(',').forEach((str, i) => {
34
+ const match = /([^/]+)\/([^;]+)(?:;q=([0-9.]+))?/.exec(str);
45
35
 
46
- if ((a.subtype === '*') !== (b.subtype === '*')) {
47
- return a.subtype === '*' ? 1 : -1;
48
- }
36
+ // no match equals invalid header ignore
37
+ if (match) {
38
+ const [, type, subtype, q = '1'] = match;
39
+ parts.push({ type, subtype, q: +q, i });
40
+ }
41
+ });
49
42
 
50
- if ((a.type === '*') !== (b.type === '*')) {
51
- return a.type === '*' ? 1 : -1;
52
- }
43
+ parts.sort((a, b) => {
44
+ if (a.q !== b.q) {
45
+ return b.q - a.q;
46
+ }
53
47
 
54
- return a.i - b.i;
55
- });
48
+ if ((a.subtype === '*') !== (b.subtype === '*')) {
49
+ return a.subtype === '*' ? 1 : -1;
50
+ }
51
+
52
+ if ((a.type === '*') !== (b.type === '*')) {
53
+ return a.type === '*' ? 1 : -1;
54
+ }
55
+
56
+ return a.i - b.i;
57
+ });
56
58
 
57
59
  let accepted;
58
60
  let min_priority = Infinity;
@@ -975,9 +977,6 @@ function base64(bytes) {
975
977
  return result;
976
978
  }
977
979
 
978
- /** @type {Promise<void>} */
979
- let csp_ready;
980
-
981
980
  const array = new Uint8Array(16);
982
981
 
983
982
  function generate_nonce() {
@@ -997,13 +996,12 @@ const quoted = new Set([
997
996
 
998
997
  const crypto_pattern = /^(nonce|sha\d\d\d)-/;
999
998
 
1000
- class Csp {
999
+ // CSP and CSP Report Only are extremely similar with a few caveats
1000
+ // the easiest/DRYest way to express this is with some private encapsulation
1001
+ class BaseProvider {
1001
1002
  /** @type {boolean} */
1002
1003
  #use_hashes;
1003
1004
 
1004
- /** @type {boolean} */
1005
- #dev;
1006
-
1007
1005
  /** @type {boolean} */
1008
1006
  #script_needs_csp;
1009
1007
 
@@ -1019,21 +1017,18 @@ class Csp {
1019
1017
  /** @type {import('types').Csp.Source[]} */
1020
1018
  #style_src;
1021
1019
 
1020
+ /** @type {string} */
1021
+ #nonce;
1022
+
1022
1023
  /**
1023
- * @param {{
1024
- * mode: string,
1025
- * directives: import('types').CspDirectives
1026
- * }} config
1027
- * @param {{
1028
- * dev: boolean;
1029
- * prerender: boolean;
1030
- * needs_nonce: boolean;
1031
- * }} opts
1024
+ * @param {boolean} use_hashes
1025
+ * @param {import('types').CspDirectives} directives
1026
+ * @param {string} nonce
1027
+ * @param {boolean} dev
1032
1028
  */
1033
- constructor({ mode, directives }, { dev, prerender, needs_nonce }) {
1034
- this.#use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
1029
+ constructor(use_hashes, directives, nonce, dev) {
1030
+ this.#use_hashes = use_hashes;
1035
1031
  this.#directives = dev ? { ...directives } : directives; // clone in dev so we can safely mutate
1036
- this.#dev = dev;
1037
1032
 
1038
1033
  const d = this.#directives;
1039
1034
 
@@ -1075,10 +1070,7 @@ class Csp {
1075
1070
 
1076
1071
  this.script_needs_nonce = this.#script_needs_csp && !this.#use_hashes;
1077
1072
  this.style_needs_nonce = this.#style_needs_csp && !this.#use_hashes;
1078
-
1079
- if (this.script_needs_nonce || this.style_needs_nonce || needs_nonce) {
1080
- this.nonce = generate_nonce();
1081
- }
1073
+ this.#nonce = nonce;
1082
1074
  }
1083
1075
 
1084
1076
  /** @param {string} content */
@@ -1087,7 +1079,7 @@ class Csp {
1087
1079
  if (this.#use_hashes) {
1088
1080
  this.#script_src.push(`sha256-${sha256(content)}`);
1089
1081
  } else if (this.#script_src.length === 0) {
1090
- this.#script_src.push(`nonce-${this.nonce}`);
1082
+ this.#script_src.push(`nonce-${this.#nonce}`);
1091
1083
  }
1092
1084
  }
1093
1085
  }
@@ -1098,12 +1090,14 @@ class Csp {
1098
1090
  if (this.#use_hashes) {
1099
1091
  this.#style_src.push(`sha256-${sha256(content)}`);
1100
1092
  } else if (this.#style_src.length === 0) {
1101
- this.#style_src.push(`nonce-${this.nonce}`);
1093
+ this.#style_src.push(`nonce-${this.#nonce}`);
1102
1094
  }
1103
1095
  }
1104
1096
  }
1105
1097
 
1106
- /** @param {boolean} [is_meta] */
1098
+ /**
1099
+ * @param {boolean} [is_meta]
1100
+ */
1107
1101
  get_header(is_meta = false) {
1108
1102
  const header = [];
1109
1103
 
@@ -1134,7 +1128,7 @@ class Csp {
1134
1128
  continue;
1135
1129
  }
1136
1130
 
1137
- // @ts-expect-error gimme a break typescript, `key` is obviously a member of directives
1131
+ // @ts-expect-error gimme a break typescript, `key` is obviously a member of internal_directives
1138
1132
  const value = /** @type {string[] | true} */ (directives[key]);
1139
1133
 
1140
1134
  if (!value) continue;
@@ -1155,13 +1149,81 @@ class Csp {
1155
1149
 
1156
1150
  return header.join('; ');
1157
1151
  }
1152
+ }
1158
1153
 
1154
+ class CspProvider extends BaseProvider {
1159
1155
  get_meta() {
1160
1156
  const content = escape_html_attr(this.get_header(true));
1161
1157
  return `<meta http-equiv="content-security-policy" content=${content}>`;
1162
1158
  }
1163
1159
  }
1164
1160
 
1161
+ class CspReportOnlyProvider extends BaseProvider {
1162
+ /**
1163
+ * @param {boolean} use_hashes
1164
+ * @param {import('types').CspDirectives} directives
1165
+ * @param {string} nonce
1166
+ * @param {boolean} dev
1167
+ */
1168
+ constructor(use_hashes, directives, nonce, dev) {
1169
+ super(use_hashes, directives, nonce, dev);
1170
+
1171
+ if (Object.values(directives).filter((v) => !!v).length > 0) {
1172
+ // If we're generating content-security-policy-report-only,
1173
+ // if there are any directives, we need a report-uri or report-to (or both)
1174
+ // else it's just an expensive noop.
1175
+ const has_report_to = directives['report-to']?.length ?? 0 > 0;
1176
+ const has_report_uri = directives['report-uri']?.length ?? 0 > 0;
1177
+ if (!has_report_to && !has_report_uri) {
1178
+ throw Error(
1179
+ '`content-security-policy-report-only` must be specified with either the `report-to` or `report-uri` directives, or both'
1180
+ );
1181
+ }
1182
+ }
1183
+ }
1184
+ }
1185
+
1186
+ class Csp {
1187
+ /** @readonly */
1188
+ nonce = generate_nonce();
1189
+
1190
+ /** @type {CspProvider} */
1191
+ csp_provider;
1192
+
1193
+ /** @type {CspReportOnlyProvider} */
1194
+ report_only_provider;
1195
+
1196
+ /**
1197
+ * @param {import('./types').CspConfig} config
1198
+ * @param {import('./types').CspOpts} opts
1199
+ */
1200
+ constructor({ mode, directives, reportOnly }, { prerender, dev }) {
1201
+ const use_hashes = mode === 'hash' || (mode === 'auto' && prerender);
1202
+ this.csp_provider = new CspProvider(use_hashes, directives, this.nonce, dev);
1203
+ this.report_only_provider = new CspReportOnlyProvider(use_hashes, reportOnly, this.nonce, dev);
1204
+ }
1205
+
1206
+ get script_needs_nonce() {
1207
+ return this.csp_provider.script_needs_nonce || this.report_only_provider.script_needs_nonce;
1208
+ }
1209
+
1210
+ get style_needs_nonce() {
1211
+ return this.csp_provider.style_needs_nonce || this.report_only_provider.style_needs_nonce;
1212
+ }
1213
+
1214
+ /** @param {string} content */
1215
+ add_script(content) {
1216
+ this.csp_provider.add_script(content);
1217
+ this.report_only_provider.add_script(content);
1218
+ }
1219
+
1220
+ /** @param {string} content */
1221
+ add_style(content) {
1222
+ this.csp_provider.add_style(content);
1223
+ this.report_only_provider.add_style(content);
1224
+ }
1225
+ }
1226
+
1165
1227
  const absolute = /^([a-z]+:)?\/?\//;
1166
1228
  const scheme = /^[a-z]+:/;
1167
1229
 
@@ -1387,11 +1449,9 @@ async function render_response({
1387
1449
 
1388
1450
  let { head, html: body } = rendered;
1389
1451
 
1390
- await csp_ready;
1391
1452
  const csp = new Csp(options.csp, {
1392
1453
  dev: options.dev,
1393
- prerender: !!state.prerendering,
1394
- needs_nonce: options.template_contains_nonce
1454
+ prerender: !!state.prerendering
1395
1455
  });
1396
1456
 
1397
1457
  const target = hash(body);
@@ -1502,7 +1562,7 @@ async function render_response({
1502
1562
  if (state.prerendering) {
1503
1563
  const http_equiv = [];
1504
1564
 
1505
- const csp_headers = csp.get_meta();
1565
+ const csp_headers = csp.csp_provider.get_meta();
1506
1566
  if (csp_headers) {
1507
1567
  http_equiv.push(csp_headers);
1508
1568
  }
@@ -1534,10 +1594,14 @@ async function render_response({
1534
1594
  }
1535
1595
 
1536
1596
  if (!state.prerendering) {
1537
- const csp_header = csp.get_header();
1597
+ const csp_header = csp.csp_provider.get_header();
1538
1598
  if (csp_header) {
1539
1599
  headers.set('content-security-policy', csp_header);
1540
1600
  }
1601
+ const report_only_header = csp.report_only_provider.get_header();
1602
+ if (report_only_header) {
1603
+ headers.set('content-security-policy-report-only', report_only_header);
1604
+ }
1541
1605
  }
1542
1606
 
1543
1607
  return new Response(html, {
@@ -111,6 +111,40 @@ function init(open, close) {
111
111
 
112
112
  /** @typedef {import('./types').Validator} Validator */
113
113
 
114
+ const directives = object({
115
+ 'child-src': string_array(),
116
+ 'default-src': string_array(),
117
+ 'frame-src': string_array(),
118
+ 'worker-src': string_array(),
119
+ 'connect-src': string_array(),
120
+ 'font-src': string_array(),
121
+ 'img-src': string_array(),
122
+ 'manifest-src': string_array(),
123
+ 'media-src': string_array(),
124
+ 'object-src': string_array(),
125
+ 'prefetch-src': string_array(),
126
+ 'script-src': string_array(),
127
+ 'script-src-elem': string_array(),
128
+ 'script-src-attr': string_array(),
129
+ 'style-src': string_array(),
130
+ 'style-src-elem': string_array(),
131
+ 'style-src-attr': string_array(),
132
+ 'base-uri': string_array(),
133
+ sandbox: string_array(),
134
+ 'form-action': string_array(),
135
+ 'frame-ancestors': string_array(),
136
+ 'navigate-to': string_array(),
137
+ 'report-uri': string_array(),
138
+ 'report-to': string_array(),
139
+ 'require-trusted-types-for': string_array(),
140
+ 'trusted-types': string_array(),
141
+ 'upgrade-insecure-requests': boolean(false),
142
+ 'require-sri-for': string_array(),
143
+ 'block-all-mixed-content': boolean(false),
144
+ 'plugin-types': string_array(),
145
+ referrer: string_array()
146
+ });
147
+
114
148
  /** @type {Validator} */
115
149
  const options = object(
116
150
  {
@@ -189,39 +223,8 @@ const options = object(
189
223
 
190
224
  csp: object({
191
225
  mode: list(['auto', 'hash', 'nonce']),
192
- directives: object({
193
- 'child-src': string_array(),
194
- 'default-src': string_array(),
195
- 'frame-src': string_array(),
196
- 'worker-src': string_array(),
197
- 'connect-src': string_array(),
198
- 'font-src': string_array(),
199
- 'img-src': string_array(),
200
- 'manifest-src': string_array(),
201
- 'media-src': string_array(),
202
- 'object-src': string_array(),
203
- 'prefetch-src': string_array(),
204
- 'script-src': string_array(),
205
- 'script-src-elem': string_array(),
206
- 'script-src-attr': string_array(),
207
- 'style-src': string_array(),
208
- 'style-src-elem': string_array(),
209
- 'style-src-attr': string_array(),
210
- 'base-uri': string_array(),
211
- sandbox: string_array(),
212
- 'form-action': string_array(),
213
- 'frame-ancestors': string_array(),
214
- 'navigate-to': string_array(),
215
- 'report-uri': string_array(),
216
- 'report-to': string_array(),
217
- 'require-trusted-types-for': string_array(),
218
- 'trusted-types': string_array(),
219
- 'upgrade-insecure-requests': boolean(false),
220
- 'require-sri-for': string_array(),
221
- 'block-all-mixed-content': boolean(false),
222
- 'plugin-types': string_array(),
223
- referrer: string_array()
224
- })
226
+ directives,
227
+ reportOnly: directives
225
228
  }),
226
229
 
227
230
  // TODO: remove this for the 1.0 release
@@ -410,7 +413,7 @@ const options = object(
410
413
  * @param {boolean} [allow_unknown]
411
414
  * @returns {Validator}
412
415
  */
413
- function object(children, allow_unknown) {
416
+ function object(children, allow_unknown = false) {
414
417
  return (input, keypath) => {
415
418
  /** @type {Record<string, any>} */
416
419
  const output = {};
package/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ function handle_error(e) {
18
18
  process.exit(1);
19
19
  }
20
20
 
21
- const prog = sade('svelte-kit').version('1.0.0-next.371');
21
+ const prog = sade('svelte-kit').version('1.0.0-next.372');
22
22
 
23
23
  prog
24
24
  .command('package')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.0.0-next.371",
3
+ "version": "1.0.0-next.372",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
package/types/index.d.ts CHANGED
@@ -105,6 +105,7 @@ export interface KitConfig {
105
105
  csp?: {
106
106
  mode?: 'hash' | 'nonce' | 'auto';
107
107
  directives?: CspDirectives;
108
+ reportOnly?: CspDirectives;
108
109
  };
109
110
  moduleExtensions?: string[];
110
111
  files?: {