@sveltejs/kit 3.0.0-next.0 → 3.0.0-next.10
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/package.json +33 -21
- package/src/core/adapt/builder.js +29 -43
- package/src/core/adapt/index.js +1 -4
- package/src/core/config/index.js +132 -71
- package/src/core/config/options.js +294 -244
- package/src/core/config/types.d.ts +1 -1
- package/src/core/env.js +121 -5
- package/src/core/generate_manifest/index.js +12 -15
- package/src/core/postbuild/analyse.js +7 -19
- package/src/core/postbuild/crawl.js +22 -6
- package/src/core/postbuild/prerender.js +42 -23
- package/src/core/sync/create_manifest_data/index.js +53 -50
- package/src/core/sync/sync.js +0 -2
- package/src/core/sync/write_client_manifest.js +8 -15
- package/src/core/sync/write_env.js +2 -1
- package/src/core/sync/write_non_ambient.js +12 -9
- package/src/core/sync/write_server.js +13 -14
- package/src/core/sync/write_tsconfig.js +16 -8
- package/src/core/sync/write_tsconfig_test/package.json +7 -0
- package/src/core/sync/write_types/index.js +28 -24
- package/src/core/utils.js +2 -2
- package/src/exports/env/index.js +12 -0
- package/src/exports/hooks/index.js +3 -9
- package/src/exports/hooks/sequence.js +3 -2
- package/src/exports/index.js +35 -13
- package/src/exports/internal/client.js +5 -0
- package/src/exports/internal/env.js +2 -2
- package/src/exports/internal/index.js +1 -90
- package/src/exports/internal/{event.js → server/event.js} +1 -2
- package/src/exports/internal/server/index.js +33 -0
- package/src/exports/internal/shared.js +89 -0
- package/src/exports/node/index.js +62 -15
- package/src/exports/params.js +63 -0
- package/src/exports/public.d.ts +323 -170
- package/src/exports/url.js +86 -0
- package/src/exports/vite/build/build_server.js +53 -59
- package/src/exports/vite/build/remote.js +18 -11
- package/src/exports/vite/build/utils.js +0 -8
- package/src/exports/vite/dev/index.js +42 -44
- package/src/exports/vite/index.js +810 -593
- package/src/exports/vite/preview/index.js +29 -22
- package/src/exports/vite/static_analysis/index.js +2 -4
- package/src/exports/vite/static_analysis/types.d.ts +14 -0
- package/src/exports/vite/utils.js +21 -36
- package/src/runtime/app/env/internal.js +4 -4
- package/src/runtime/app/env/types.d.ts +1 -1
- package/src/runtime/app/environment/index.js +6 -0
- package/src/runtime/app/forms.js +24 -7
- package/src/runtime/app/paths/client.js +4 -10
- package/src/runtime/app/paths/internal/client.js +4 -2
- package/src/runtime/app/paths/internal/server.js +2 -23
- package/src/runtime/app/paths/public.d.ts +0 -28
- package/src/runtime/app/paths/server.js +9 -5
- package/src/runtime/app/server/remote/command.js +0 -3
- package/src/runtime/app/server/remote/form.js +27 -15
- package/src/runtime/app/server/remote/prerender.js +29 -36
- package/src/runtime/app/server/remote/query.js +101 -99
- package/src/runtime/app/server/remote/requested.js +22 -14
- package/src/runtime/app/server/remote/shared.js +30 -26
- package/src/runtime/app/state/client.js +1 -2
- package/src/runtime/app/stores.js +13 -76
- package/src/runtime/client/bundle.js +1 -1
- package/src/runtime/client/client-entry.js +3 -0
- package/src/runtime/client/client.js +411 -318
- package/src/runtime/client/entry.js +24 -3
- package/src/runtime/client/fetcher.js +3 -2
- package/src/runtime/client/ndjson.js +6 -33
- package/src/runtime/client/payload.js +17 -0
- package/src/runtime/client/remote-functions/cache.svelte.js +3 -1
- package/src/runtime/client/remote-functions/command.svelte.js +7 -32
- package/src/runtime/client/remote-functions/form.svelte.js +183 -112
- package/src/runtime/client/remote-functions/prerender.svelte.js +29 -8
- package/src/runtime/client/remote-functions/query/index.js +7 -14
- package/src/runtime/client/remote-functions/query/instance.svelte.js +63 -18
- package/src/runtime/client/remote-functions/query/proxy.js +3 -3
- package/src/runtime/client/remote-functions/query-batch.svelte.js +60 -68
- package/src/runtime/client/remote-functions/query-live/instance.svelte.js +60 -18
- package/src/runtime/client/remote-functions/query-live/iterator.js +36 -55
- package/src/runtime/client/remote-functions/shared.svelte.js +88 -62
- package/src/runtime/client/sse.js +32 -0
- package/src/runtime/client/state.svelte.js +65 -49
- package/src/runtime/client/stream.js +38 -0
- package/src/runtime/client/types.d.ts +11 -7
- package/src/runtime/client/utils.js +0 -96
- package/src/runtime/components/root.svelte +66 -0
- package/src/runtime/env/dynamic/private.js +7 -0
- package/src/runtime/env/dynamic/public.js +7 -0
- package/src/runtime/env/static/private.js +6 -0
- package/src/runtime/env/static/public.js +6 -0
- package/src/runtime/form-utils.js +100 -22
- package/src/runtime/server/cookie.js +69 -52
- package/src/runtime/server/csrf.js +65 -0
- package/src/runtime/server/data/index.js +14 -18
- package/src/runtime/server/env_module.js +0 -5
- package/src/runtime/server/index.js +2 -2
- package/src/runtime/server/page/actions.js +41 -26
- package/src/runtime/server/page/index.js +9 -15
- package/src/runtime/server/page/load_data.js +1 -1
- package/src/runtime/server/page/render.js +112 -187
- package/src/runtime/server/page/respond_with_error.js +7 -8
- package/src/runtime/server/page/server_routing.js +27 -18
- package/src/runtime/server/remote.js +355 -216
- package/src/runtime/server/respond.js +97 -61
- package/src/runtime/server/utils.js +32 -9
- package/src/runtime/shared.js +83 -13
- package/src/runtime/telemetry/otel.js +1 -1
- package/src/runtime/types.d.ts +8 -0
- package/src/types/ambient.d.ts +10 -5
- package/src/types/global-private.d.ts +15 -22
- package/src/types/internal.d.ts +89 -74
- package/src/types/private.d.ts +33 -1
- package/src/utils/error.js +24 -4
- package/src/utils/import.js +6 -1
- package/src/utils/imports.js +83 -0
- package/src/utils/mime.js +9 -0
- package/src/utils/params.js +66 -0
- package/src/utils/routing.js +90 -44
- package/src/utils/shared-iterator.js +5 -0
- package/src/utils/streaming.js +14 -4
- package/src/utils/url.js +20 -2
- package/src/version.js +1 -1
- package/types/index.d.ts +467 -537
- package/types/index.d.ts.map +22 -39
- package/src/core/sync/write_root.js +0 -148
- package/src/exports/internal/server.js +0 -22
- package/src/types/synthetic/$lib.md +0 -5
- /package/src/exports/internal/{remote-functions.js → server/remote-functions.js} +0 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { DEV } from 'esm-env';
|
|
5
5
|
import * as devalue from 'devalue';
|
|
6
6
|
import { text_encoder } from './utils.js';
|
|
7
|
+
import { noop } from '../utils/functions.js';
|
|
7
8
|
import { SvelteKitError } from '@sveltejs/kit/internal';
|
|
8
9
|
|
|
9
10
|
const decoder = new TextDecoder();
|
|
@@ -26,6 +27,9 @@ export function set_nested_value(object, path_string, value) {
|
|
|
26
27
|
deep_set(object, split_path(path_string), value);
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/** Pass this to set_nested_value to delete the last part of the given path */
|
|
31
|
+
export const DELETE_KEY = {};
|
|
32
|
+
|
|
29
33
|
/**
|
|
30
34
|
* Convert `FormData` into a POJO
|
|
31
35
|
* @param {FormData} data
|
|
@@ -41,14 +45,11 @@ export function convert_formdata(data) {
|
|
|
41
45
|
|
|
42
46
|
if (is_array) key = key.slice(0, -2);
|
|
43
47
|
|
|
44
|
-
if (values.length > 1 && !is_array) {
|
|
45
|
-
throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
48
|
// an empty `<input type="file">` will submit a non-existent file, bizarrely
|
|
49
49
|
values = values.filter(
|
|
50
50
|
(entry) => typeof entry === 'string' || entry.name !== '' || entry.size > 0
|
|
51
51
|
);
|
|
52
|
+
if (values.length === 0 && !is_array) continue;
|
|
52
53
|
|
|
53
54
|
if (key.startsWith('n:')) {
|
|
54
55
|
key = key.slice(2);
|
|
@@ -58,6 +59,10 @@ export function convert_formdata(data) {
|
|
|
58
59
|
values = values.map((v) => v === 'on');
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
if (values.length > 1 && !is_array) {
|
|
63
|
+
throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
set_nested_value(result, key, is_array ? values : values[0]);
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -145,10 +150,7 @@ export async function deserialize_binary_form(request) {
|
|
|
145
150
|
throw deserialize_error('no body');
|
|
146
151
|
}
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
const reader = /** @type {ReadableStreamDefaultReader<Uint8Array<ArrayBuffer>>} */ (
|
|
150
|
-
request.body.getReader()
|
|
151
|
-
);
|
|
153
|
+
const reader = request.body.getReader();
|
|
152
154
|
|
|
153
155
|
/** @type {Array<Promise<Uint8Array<ArrayBuffer> | undefined>>} */
|
|
154
156
|
const chunks = [];
|
|
@@ -319,7 +321,7 @@ export async function deserialize_binary_form(request) {
|
|
|
319
321
|
const chunk = await get_chunk(chunks.length);
|
|
320
322
|
has_more = !!chunk;
|
|
321
323
|
}
|
|
322
|
-
})();
|
|
324
|
+
})().catch(noop); // prevent unhandled rejection potentially crashing the process
|
|
323
325
|
|
|
324
326
|
return { data, meta, form_data: null };
|
|
325
327
|
}
|
|
@@ -503,6 +505,10 @@ export function deep_set(object, keys, value) {
|
|
|
503
505
|
}
|
|
504
506
|
|
|
505
507
|
if (!exists) {
|
|
508
|
+
if (value === DELETE_KEY) {
|
|
509
|
+
// don't create the nested structure if we want to delete the key anyway
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
506
512
|
current[key] = is_array ? [] : {};
|
|
507
513
|
}
|
|
508
514
|
|
|
@@ -511,7 +517,12 @@ export function deep_set(object, keys, value) {
|
|
|
511
517
|
|
|
512
518
|
const final_key = keys[keys.length - 1];
|
|
513
519
|
check_prototype_pollution(final_key);
|
|
514
|
-
|
|
520
|
+
|
|
521
|
+
if (value === DELETE_KEY) {
|
|
522
|
+
delete current[final_key];
|
|
523
|
+
} else {
|
|
524
|
+
current[final_key] = value;
|
|
525
|
+
}
|
|
515
526
|
}
|
|
516
527
|
|
|
517
528
|
/**
|
|
@@ -608,18 +619,53 @@ function get_type_prefix(field_type, is_array, input_value) {
|
|
|
608
619
|
return '';
|
|
609
620
|
}
|
|
610
621
|
|
|
622
|
+
/**
|
|
623
|
+
* A deep-clone implementation specifically for form data, where
|
|
624
|
+
* we don't need to worry about cycles and whatnot
|
|
625
|
+
* @param {any} value
|
|
626
|
+
* @returns {any}
|
|
627
|
+
*/
|
|
628
|
+
function deep_clone(value) {
|
|
629
|
+
if (value !== null && typeof value === 'object') {
|
|
630
|
+
if (value instanceof Date) {
|
|
631
|
+
return new Date(value.getTime());
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (value instanceof File) {
|
|
635
|
+
return value;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (Array.isArray(value)) {
|
|
639
|
+
return value.map(deep_clone);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** @type {Record<string, any>} */
|
|
643
|
+
const clone = {};
|
|
644
|
+
for (const key of Object.keys(value)) {
|
|
645
|
+
clone[key] = deep_clone(value[key]);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return clone;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
|
|
611
654
|
/**
|
|
612
655
|
* Creates a proxy-based field accessor for form data
|
|
613
656
|
* @param {any} target - Function or empty POJO
|
|
614
|
-
* @param {() => Record<string, any>}
|
|
615
|
-
* @param {(path: (string | number)[], value: any) => void}
|
|
657
|
+
* @param {() => Record<string, any>} get - Function to get current input data
|
|
658
|
+
* @param {(path: (string | number)[], value: any) => void} set - Function to set input data
|
|
616
659
|
* @param {(path?: (string | number)[], all?: boolean) => Record<string, InternalRemoteFormIssue[]>} get_issues - Function to get current issues
|
|
660
|
+
* @param {() => Record<string, boolean>} get_touched - Function to get touched fields
|
|
661
|
+
* @param {() => Record<string, boolean>} get_dirty - Function to get dirty fields
|
|
617
662
|
* @param {(string | number)[]} path - Current access path
|
|
618
663
|
* @returns {any} Proxy object with name(), value(), and issues() methods
|
|
619
664
|
*/
|
|
620
|
-
export function create_field_proxy(target,
|
|
665
|
+
export function create_field_proxy(target, get, set, get_issues, get_touched, get_dirty, path) {
|
|
621
666
|
const get_value = () => {
|
|
622
|
-
|
|
667
|
+
const value = deep_get(get(), path);
|
|
668
|
+
return deep_clone(value);
|
|
623
669
|
};
|
|
624
670
|
|
|
625
671
|
return new Proxy(target, {
|
|
@@ -628,24 +674,26 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
|
|
|
628
674
|
|
|
629
675
|
// Handle array access like jobs[0]
|
|
630
676
|
if (/^\d+$/.test(prop)) {
|
|
631
|
-
return create_field_proxy({},
|
|
677
|
+
return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, [
|
|
632
678
|
...path,
|
|
633
679
|
parseInt(prop, 10)
|
|
634
680
|
]);
|
|
635
681
|
}
|
|
636
682
|
|
|
637
683
|
const key = build_path_string(path);
|
|
684
|
+
const next = [...path, prop];
|
|
638
685
|
|
|
639
686
|
if (prop === 'set') {
|
|
640
687
|
const set_func = function (/** @type {any} */ newValue) {
|
|
641
|
-
|
|
688
|
+
set(path, newValue);
|
|
642
689
|
return newValue;
|
|
643
690
|
};
|
|
644
|
-
|
|
691
|
+
|
|
692
|
+
return create_field_proxy(set_func, get, set, get_issues, get_touched, get_dirty, next);
|
|
645
693
|
}
|
|
646
694
|
|
|
647
695
|
if (prop === 'value') {
|
|
648
|
-
return create_field_proxy(get_value,
|
|
696
|
+
return create_field_proxy(get_value, get, set, get_issues, get_touched, get_dirty, next);
|
|
649
697
|
}
|
|
650
698
|
|
|
651
699
|
if (prop === 'issues' || prop === 'allIssues') {
|
|
@@ -659,15 +707,45 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
|
|
|
659
707
|
}));
|
|
660
708
|
}
|
|
661
709
|
|
|
662
|
-
|
|
710
|
+
const issues = all_issues
|
|
663
711
|
?.filter((issue) => issue.name === key)
|
|
664
712
|
?.map((issue) => ({
|
|
665
713
|
path: issue.path,
|
|
666
714
|
message: issue.message
|
|
667
715
|
}));
|
|
716
|
+
|
|
717
|
+
return issues?.length ? issues : undefined;
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
return create_field_proxy(issues_func, get, set, get_issues, get_touched, get_dirty, next);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (prop === 'touched' || prop === 'dirty') {
|
|
724
|
+
const fn = () => {
|
|
725
|
+
const object = prop === 'dirty' ? get_dirty() : get_touched();
|
|
726
|
+
|
|
727
|
+
if (key === '') {
|
|
728
|
+
return Object.keys(object).length > 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (Object.hasOwn(object, key)) {
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
for (const candidate in object) {
|
|
736
|
+
if (!Object.hasOwn(object, candidate)) continue;
|
|
737
|
+
if (!candidate.startsWith(key)) continue;
|
|
738
|
+
|
|
739
|
+
const next = candidate[key.length];
|
|
740
|
+
if (next === '.' || next === '[') {
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return false;
|
|
668
746
|
};
|
|
669
747
|
|
|
670
|
-
return create_field_proxy(
|
|
748
|
+
return create_field_proxy(fn, get, set, get_issues, get_touched, get_dirty, next);
|
|
671
749
|
}
|
|
672
750
|
|
|
673
751
|
if (prop === 'as') {
|
|
@@ -833,11 +911,11 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat
|
|
|
833
911
|
});
|
|
834
912
|
};
|
|
835
913
|
|
|
836
|
-
return create_field_proxy(as_func,
|
|
914
|
+
return create_field_proxy(as_func, get, set, get_issues, get_touched, get_dirty, next);
|
|
837
915
|
}
|
|
838
916
|
|
|
839
917
|
// Handle property access (nested fields)
|
|
840
|
-
return create_field_proxy({},
|
|
918
|
+
return create_field_proxy({}, get, set, get_issues, get_touched, get_dirty, next);
|
|
841
919
|
}
|
|
842
920
|
});
|
|
843
921
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseCookie, parseSetCookie, stringifySetCookie } from 'cookie';
|
|
2
2
|
import { DEV } from 'esm-env';
|
|
3
3
|
import { normalize_path, resolve } from '../../utils/url.js';
|
|
4
4
|
import { add_data_suffix } from '../pathname.js';
|
|
@@ -12,10 +12,11 @@ import { text_encoder } from '../utils.js';
|
|
|
12
12
|
const cookie_paths = {};
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Cookies
|
|
16
|
-
*
|
|
15
|
+
* Cookies whose name and value combined are larger than this size are
|
|
16
|
+
* discarded by browsers. This is the limit codified for the name/value pair
|
|
17
|
+
* in RFC 6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20#section-5.6-7.5.1
|
|
17
18
|
*/
|
|
18
|
-
const MAX_COOKIE_SIZE =
|
|
19
|
+
const MAX_COOKIE_SIZE = 4096;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Generates a unique key for a cookie based on its domain, path, and name in
|
|
@@ -39,16 +40,35 @@ function generate_cookie_key(domain, path, name) {
|
|
|
39
40
|
export function get_cookies(request, url) {
|
|
40
41
|
const header = request.headers.get('cookie') ?? '';
|
|
41
42
|
const initial_cookies = /** @type {Record<string, string>} */ (
|
|
42
|
-
|
|
43
|
+
parseCookie(header, { decode: (value) => value })
|
|
43
44
|
);
|
|
44
45
|
|
|
46
|
+
/** @type {ReturnType<typeof parseCookie> | undefined} */
|
|
47
|
+
let default_cookies;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The header never changes during the request, so the default-decode parse is cached
|
|
51
|
+
* @param {import('cookie').ParseOptions} [opts]
|
|
52
|
+
*/
|
|
53
|
+
function parse_header(opts) {
|
|
54
|
+
return opts?.decode ? parseCookie(header, opts) : (default_cookies ??= parseCookie(header));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @param {import('./page/types.js').Cookie} cookie */
|
|
58
|
+
function matches_url(cookie) {
|
|
59
|
+
return (
|
|
60
|
+
domain_matches(url.hostname, cookie.options.domain) &&
|
|
61
|
+
path_matches(url.pathname, cookie.options.path)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
/** @type {string | undefined} */
|
|
46
66
|
let normalized_url;
|
|
47
67
|
|
|
48
68
|
/** @type {Map<string, import('./page/types.js').Cookie>} */
|
|
49
69
|
const new_cookies = new Map();
|
|
50
70
|
|
|
51
|
-
/** @type {import('cookie').
|
|
71
|
+
/** @type {Omit<import('cookie').SetCookie, 'name' | 'value'>} */
|
|
52
72
|
const defaults = {
|
|
53
73
|
httpOnly: true,
|
|
54
74
|
path: '/',
|
|
@@ -63,28 +83,25 @@ export function get_cookies(request, url) {
|
|
|
63
83
|
// typescript users. `@type {import('@sveltejs/kit').Cookies}` above is not
|
|
64
84
|
// sufficient to do so.
|
|
65
85
|
|
|
66
|
-
/**
|
|
67
|
-
* @param {string} name
|
|
68
|
-
* @param {import('cookie').ParseOptions} [opts]
|
|
69
|
-
*/
|
|
70
86
|
get(name, opts) {
|
|
71
87
|
// Look for the most specific matching cookie from new_cookies
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
/** @type {import('./page/types.js').Cookie | undefined} */
|
|
89
|
+
let best_match;
|
|
90
|
+
for (const c of new_cookies.values()) {
|
|
91
|
+
if (
|
|
92
|
+
c.name === name &&
|
|
93
|
+
matches_url(c) &&
|
|
94
|
+
(!best_match || c.options.path.length > best_match.options.path.length)
|
|
95
|
+
) {
|
|
96
|
+
best_match = c;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
81
99
|
|
|
82
100
|
if (best_match) {
|
|
83
101
|
return best_match.options.maxAge === 0 ? undefined : best_match.value;
|
|
84
102
|
}
|
|
85
103
|
|
|
86
|
-
const
|
|
87
|
-
const cookie = req_cookies[name]; // the decoded string or undefined
|
|
104
|
+
const cookie = parse_header(opts)[name]; // the decoded string or undefined
|
|
88
105
|
|
|
89
106
|
// in development, if the cookie was set during this session with `cookies.set`,
|
|
90
107
|
// but at a different path, warn the user. (ignore cookies from request headers,
|
|
@@ -106,20 +123,15 @@ export function get_cookies(request, url) {
|
|
|
106
123
|
return cookie;
|
|
107
124
|
},
|
|
108
125
|
|
|
109
|
-
/**
|
|
110
|
-
* @param {import('cookie').ParseOptions} [opts]
|
|
111
|
-
*/
|
|
112
126
|
getAll(opts) {
|
|
113
|
-
|
|
127
|
+
// copy, so the cached parse isn't mutated below
|
|
128
|
+
const cookies = { ...parse_header(opts) };
|
|
114
129
|
|
|
115
130
|
// Group cookies by name and find the most specific one for each name
|
|
116
131
|
const lookup = new Map();
|
|
117
132
|
|
|
118
133
|
for (const c of new_cookies.values()) {
|
|
119
|
-
if (
|
|
120
|
-
domain_matches(url.hostname, c.options.domain) &&
|
|
121
|
-
path_matches(url.pathname, c.options.path)
|
|
122
|
-
) {
|
|
134
|
+
if (matches_url(c)) {
|
|
123
135
|
const existing = lookup.get(c.name);
|
|
124
136
|
|
|
125
137
|
// If no existing cookie or this one has a more specific (longer) path, use this one
|
|
@@ -131,7 +143,13 @@ export function get_cookies(request, url) {
|
|
|
131
143
|
|
|
132
144
|
// Add the most specific cookies to the result
|
|
133
145
|
for (const c of lookup.values()) {
|
|
134
|
-
cookies
|
|
146
|
+
// tombstones (deleted cookies) shadow request-header cookies,
|
|
147
|
+
// mirroring the behavior of `get()`
|
|
148
|
+
if (c.options.maxAge === 0) {
|
|
149
|
+
delete cookies[c.name];
|
|
150
|
+
} else {
|
|
151
|
+
cookies[c.name] = c.value;
|
|
152
|
+
}
|
|
135
153
|
}
|
|
136
154
|
|
|
137
155
|
return /** @type {Array<{ name: string; value: string }>} */ (
|
|
@@ -141,29 +159,17 @@ export function get_cookies(request, url) {
|
|
|
141
159
|
);
|
|
142
160
|
},
|
|
143
161
|
|
|
144
|
-
/**
|
|
145
|
-
* @param {string} name
|
|
146
|
-
* @param {string} value
|
|
147
|
-
* @param {import('cookie').SerializeOptions} options
|
|
148
|
-
*/
|
|
149
162
|
set(name, value, options) {
|
|
150
163
|
set_internal(name, value, { ...defaults, ...options });
|
|
151
164
|
},
|
|
152
165
|
|
|
153
|
-
/**
|
|
154
|
-
* @param {string} name
|
|
155
|
-
* @param {import('cookie').SerializeOptions} options
|
|
156
|
-
*/
|
|
157
166
|
delete(name, options) {
|
|
158
167
|
cookies.set(name, '', { ...options, maxAge: 0 });
|
|
159
168
|
},
|
|
160
169
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
* @param {import('cookie').SerializeOptions} options
|
|
165
|
-
*/
|
|
166
|
-
serialize(name, value, options) {
|
|
170
|
+
parse: parseSetCookie,
|
|
171
|
+
|
|
172
|
+
serialize(name, value, { encode, ...options }) {
|
|
167
173
|
let path = options.path ?? '/';
|
|
168
174
|
|
|
169
175
|
if (!options.domain || options.domain === url.hostname) {
|
|
@@ -173,7 +179,7 @@ export function get_cookies(request, url) {
|
|
|
173
179
|
path = resolve(normalized_url, path);
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
return
|
|
182
|
+
return stringifySetCookie({ name, value, ...defaults, ...options, path }, { encode });
|
|
177
183
|
}
|
|
178
184
|
};
|
|
179
185
|
|
|
@@ -200,7 +206,7 @@ export function get_cookies(request, url) {
|
|
|
200
206
|
// explicit header has highest precedence
|
|
201
207
|
if (header) {
|
|
202
208
|
const parsed = /** @type {Record<string, string>} */ (
|
|
203
|
-
|
|
209
|
+
parseCookie(header, { decode: (value) => value })
|
|
204
210
|
);
|
|
205
211
|
for (const name in parsed) {
|
|
206
212
|
combined_cookies[name] = parsed[name];
|
|
@@ -238,8 +244,12 @@ export function get_cookies(request, url) {
|
|
|
238
244
|
new_cookies.set(cookie_key, cookie);
|
|
239
245
|
|
|
240
246
|
if (DEV) {
|
|
241
|
-
const
|
|
242
|
-
|
|
247
|
+
const size =
|
|
248
|
+
// only the name/value pair counts towards MAX_COOKIE_SIZE, not the other attributes
|
|
249
|
+
text_encoder.encode(name).byteLength +
|
|
250
|
+
text_encoder.encode((options.encode ?? encodeURIComponent)(value)).byteLength;
|
|
251
|
+
|
|
252
|
+
if (size > MAX_COOKIE_SIZE) {
|
|
243
253
|
throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
|
|
244
254
|
}
|
|
245
255
|
|
|
@@ -296,15 +306,22 @@ export function path_matches(path, constraint) {
|
|
|
296
306
|
*/
|
|
297
307
|
export function add_cookies_to_headers(headers, cookies) {
|
|
298
308
|
for (const new_cookie of cookies) {
|
|
299
|
-
const {
|
|
300
|
-
|
|
309
|
+
const {
|
|
310
|
+
name,
|
|
311
|
+
value,
|
|
312
|
+
options: { encode, ...options }
|
|
313
|
+
} = new_cookie;
|
|
314
|
+
headers.append('set-cookie', stringifySetCookie({ name, value, ...options }, { encode }));
|
|
301
315
|
|
|
302
316
|
// special case — for routes ending with .html, the route data lives in a sibling
|
|
303
317
|
// `.html__data.json` file rather than a child `/__data.json` file, which means
|
|
304
318
|
// we need to duplicate the cookie
|
|
305
319
|
if (options.path.endsWith('.html')) {
|
|
306
320
|
const path = add_data_suffix(options.path);
|
|
307
|
-
headers.append(
|
|
321
|
+
headers.append(
|
|
322
|
+
'set-cookie',
|
|
323
|
+
stringifySetCookie({ name, value, ...options, path }, { encode })
|
|
324
|
+
);
|
|
308
325
|
}
|
|
309
326
|
}
|
|
310
327
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { is_form_content_type } from '../../utils/http.js';
|
|
2
|
+
|
|
3
|
+
const mutating_form_methods = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The origin SvelteKit treats as "self" when validating the `Origin` header on
|
|
7
|
+
* cross-site requests.
|
|
8
|
+
*
|
|
9
|
+
* By default (`paths.origin` is `undefined`), SvelteKit derives the origin
|
|
10
|
+
* from `request.url` (which is set by the adapter, and ultimately by the
|
|
11
|
+
* platform). When `paths.origin` is configured — for example so that a preview
|
|
12
|
+
* deployment whose URL isn't known at build time, or an app behind a reverse
|
|
13
|
+
* proxy, can declare a canonical origin — that value takes precedence.
|
|
14
|
+
*
|
|
15
|
+
* @param {string | undefined} paths_origin the configured `kit.paths.origin`
|
|
16
|
+
* @param {string} url_origin the origin derived from `request.url`
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export function get_self_origin(paths_origin, url_origin) {
|
|
20
|
+
return paths_origin || url_origin;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines whether a non-remote request should be rejected as a cross-site
|
|
25
|
+
* form submission (CSRF). Used by `respond.js` to gate form `POST`/`PUT`/
|
|
26
|
+
* `PATCH`/`DELETE` requests whose `Origin` header doesn't match the app's
|
|
27
|
+
* self-origin (and isn't in `trusted_origins`).
|
|
28
|
+
*
|
|
29
|
+
* @param {{
|
|
30
|
+
* request: Request;
|
|
31
|
+
* request_origin: string | null;
|
|
32
|
+
* self_origin: string;
|
|
33
|
+
* trusted_origins: string[];
|
|
34
|
+
* }} input
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
export function is_csrf_forbidden({ request, request_origin, self_origin, trusted_origins }) {
|
|
38
|
+
return (
|
|
39
|
+
(!request.headers.get('content-type') || is_form_content_type(request)) &&
|
|
40
|
+
mutating_form_methods.has(request.method) &&
|
|
41
|
+
request_origin !== self_origin &&
|
|
42
|
+
(!request_origin || !trusted_origins.includes(request_origin))
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Determines whether a remote-function request should be rejected as cross-site.
|
|
48
|
+
*
|
|
49
|
+
* Unlike form submissions, remote functions accept any content type (e.g.
|
|
50
|
+
* `application/json`), so the check is solely on the request method and origin:
|
|
51
|
+
* a non-`GET` request is forbidden when its `Origin` header doesn't match the
|
|
52
|
+
* app's self-origin. Unlike `is_csrf_forbidden`, entries in `trusted_origins`
|
|
53
|
+
* are *not* honoured — remote function endpoints are an implementation detail,
|
|
54
|
+
* not a public API, so cross-origin calls are forbidden regardless.
|
|
55
|
+
*
|
|
56
|
+
* @param {{
|
|
57
|
+
* request: Request;
|
|
58
|
+
* request_origin: string | null;
|
|
59
|
+
* self_origin: string;
|
|
60
|
+
* }} input
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
export function is_remote_forbidden({ request, request_origin, self_origin }) {
|
|
64
|
+
return request.method !== 'GET' && request_origin !== self_origin;
|
|
65
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { text } from '@sveltejs/kit';
|
|
2
|
-
import {
|
|
2
|
+
import { Redirect } from '@sveltejs/kit/internal';
|
|
3
3
|
import { normalize_error } from '../../../utils/error.js';
|
|
4
4
|
import { once } from '../../../utils/functions.js';
|
|
5
5
|
import { server_data_serializer_json } from '../page/data_serializer.js';
|
|
@@ -96,31 +96,25 @@ export async function render_data(
|
|
|
96
96
|
return fn();
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
promises.map((p, i) =>
|
|
102
|
-
p.catch(async (error) => {
|
|
99
|
+
const data_serializer = server_data_serializer_json(event, event_state, options);
|
|
100
|
+
await Promise.all(
|
|
101
|
+
promises.map(async (p, i) => {
|
|
102
|
+
const node = await p.catch(async (error) => {
|
|
103
103
|
if (error instanceof Redirect) {
|
|
104
104
|
throw error;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
length = Math.min(length, i + 1);
|
|
107
|
+
const transformed = await handle_error_and_jsonify(event, event_state, options, error);
|
|
109
108
|
|
|
110
109
|
return /** @type {import('types').ServerErrorNode} */ ({
|
|
111
110
|
type: 'error',
|
|
112
|
-
error:
|
|
113
|
-
status:
|
|
114
|
-
error instanceof HttpError || error instanceof SvelteKitError
|
|
115
|
-
? error.status
|
|
116
|
-
: undefined
|
|
111
|
+
error: transformed
|
|
117
112
|
});
|
|
118
|
-
})
|
|
119
|
-
)
|
|
120
|
-
);
|
|
113
|
+
});
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
data_serializer.add_node(i, node);
|
|
116
|
+
})
|
|
117
|
+
);
|
|
124
118
|
const { data, chunks } = data_serializer.get_data();
|
|
125
119
|
|
|
126
120
|
if (!chunks) {
|
|
@@ -156,7 +150,8 @@ export async function render_data(
|
|
|
156
150
|
if (error instanceof Redirect) {
|
|
157
151
|
return redirect_json_response(error);
|
|
158
152
|
} else {
|
|
159
|
-
|
|
153
|
+
const transformed = await handle_error_and_jsonify(event, event_state, options, error);
|
|
154
|
+
return json_response(transformed, transformed.status);
|
|
160
155
|
}
|
|
161
156
|
}
|
|
162
157
|
}
|
|
@@ -182,6 +177,7 @@ export function redirect_json_response(redirect) {
|
|
|
182
177
|
return json_response(
|
|
183
178
|
/** @type {import('types').ServerRedirectNode} */ ({
|
|
184
179
|
type: 'redirect',
|
|
180
|
+
status: redirect.status,
|
|
185
181
|
location: redirect.location
|
|
186
182
|
})
|
|
187
183
|
);
|
|
@@ -16,7 +16,6 @@ let headers;
|
|
|
16
16
|
*/
|
|
17
17
|
export function get_public_env(request) {
|
|
18
18
|
const env = rendered_env;
|
|
19
|
-
const script = request.url.endsWith('.script.js');
|
|
20
19
|
|
|
21
20
|
payload ??= devalue.uneval(env);
|
|
22
21
|
etag ??= `W/${Date.now()}`;
|
|
@@ -29,9 +28,5 @@ export function get_public_env(request) {
|
|
|
29
28
|
return new Response(undefined, { status: 304, headers });
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
if (script) {
|
|
33
|
-
return new Response(`globalThis.__sveltekit_sw={env:${payload}}`, { headers });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
return new Response(`export const env=${payload}`, { headers });
|
|
37
32
|
}
|
|
@@ -27,7 +27,7 @@ export class Server {
|
|
|
27
27
|
this.#manifest = manifest;
|
|
28
28
|
|
|
29
29
|
// Since AsyncLocalStorage is not working in webcontainers, we don't reset `sync_store`
|
|
30
|
-
// in `src/exports/internal/event.js` and handle only one request at a time.
|
|
30
|
+
// in `src/exports/internal/server/event.js` and handle only one request at a time.
|
|
31
31
|
if (IN_WEBCONTAINER) {
|
|
32
32
|
const respond = this.respond.bind(this);
|
|
33
33
|
|
|
@@ -120,7 +120,7 @@ export class Server {
|
|
|
120
120
|
module.handleValidationError ||
|
|
121
121
|
(({ issues }) => {
|
|
122
122
|
console.error('Remote function schema validation failed:', issues);
|
|
123
|
-
return { message: 'Bad Request' };
|
|
123
|
+
return { message: 'Bad Request', status: 400 };
|
|
124
124
|
}),
|
|
125
125
|
reroute: module.reroute || noop,
|
|
126
126
|
transport: module.transport || {}
|