33/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
44
55import { DEV } from 'esm-env' ;
6- import * as svelte from 'svelte' ;
7- // Svelte 4 and under don't have `untrack` - you'll not be able to use remote functions with Svelte 4 but this will still be loaded
8- const untrack = svelte . untrack ?? ( ( value ) => value ( ) ) ;
96
107/**
11- * Sets a value in a nested object using a path string, not mutating the original object but returning a new object
8+ * Sets a value in a nested object using a path string, mutating the original object
129 * @param {Record<string, any> } object
1310 * @param {string } path_string
1411 * @param {any } value
@@ -22,7 +19,7 @@ export function set_nested_value(object, path_string, value) {
2219 value = value === 'on' ;
2320 }
2421
25- return deep_set ( object , split_path ( path_string ) , value ) ;
22+ deep_set ( object , split_path ( path_string ) , value ) ;
2623}
2724
2825/**
@@ -31,7 +28,7 @@ export function set_nested_value(object, path_string, value) {
3128 */
3229export function convert_formdata ( data ) {
3330 /** @type {Record<string, any> } */
34- let result = Object . create ( null ) ; // guard against prototype pollution
31+ const result = { } ;
3532
3633 for ( let key of data . keys ( ) ) {
3734 if ( key . startsWith ( 'sveltekit:' ) ) {
@@ -61,7 +58,7 @@ export function convert_formdata(data) {
6158 values = values . map ( ( v ) => v === 'on' ) ;
6259 }
6360
64- result = set_nested_value ( result , key , is_array ? values : values [ 0 ] ) ;
61+ set_nested_value ( result , key , is_array ? values : values [ 0 ] ) ;
6562 }
6663
6764 return result ;
@@ -81,18 +78,32 @@ export function split_path(path) {
8178}
8279
8380/**
84- * Sets a value in a nested object using an array of keys.
85- * Does not mutate the original object; returns a new object.
81+ * Check if a property key is dangerous and could lead to prototype pollution
82+ * @param {string } key
83+ */
84+ function check_prototype_pollution ( key ) {
85+ if ( key === '__proto__' || key === 'constructor' || key === 'prototype' ) {
86+ throw new Error (
87+ `Invalid key "${ key } "` +
88+ ( DEV ? ': This key is not allowed to prevent prototype pollution.' : '' )
89+ ) ;
90+ }
91+ }
92+
93+ /**
94+ * Sets a value in a nested object using an array of keys, mutating the original object.
8695 * @param {Record<string, any> } object
8796 * @param {string[] } keys
8897 * @param {any } value
8998 */
9099export function deep_set ( object , keys , value ) {
91- const result = Object . assign ( Object . create ( null ) , object ) ; // guard against prototype pollution
92- let current = result ;
100+ let current = object ;
93101
94102 for ( let i = 0 ; i < keys . length - 1 ; i += 1 ) {
95103 const key = keys [ i ] ;
104+
105+ check_prototype_pollution ( key ) ;
106+
96107 const is_array = / ^ \d + $ / . test ( keys [ i + 1 ] ) ;
97108 const exists = key in current ;
98109 const inner = current [ key ] ;
@@ -101,18 +112,16 @@ export function deep_set(object, keys, value) {
101112 throw new Error ( `Invalid array key ${ keys [ i + 1 ] } ` ) ;
102113 }
103114
104- current [ key ] = is_array
105- ? exists
106- ? [ ...inner ]
107- : [ ]
108- : // guard against prototype pollution
109- Object . assign ( Object . create ( null ) , inner ) ;
115+ if ( ! exists ) {
116+ current [ key ] = is_array ? [ ] : { } ;
117+ }
110118
111119 current = current [ key ] ;
112120 }
113121
114- current [ keys [ keys . length - 1 ] ] = value ;
115- return result ;
122+ const final_key = keys [ keys . length - 1 ] ;
123+ check_prototype_pollution ( final_key ) ;
124+ current [ final_key ] = value ;
116125}
117126
118127/**
@@ -196,18 +205,14 @@ export function deep_get(object, path) {
196205 * Creates a proxy-based field accessor for form data
197206 * @param {any } target - Function or empty POJO
198207 * @param {() => Record<string, any> } get_input - Function to get current input data
199- * @param {(path: string) => void } depend - Function to make an effect depend on a specific field
200208 * @param {(path: (string | number)[], value: any) => void } set_input - Function to set input data
201209 * @param {() => Record<string, InternalRemoteFormIssue[]> } get_issues - Function to get current issues
202210 * @param {(string | number)[] } path - Current access path
203211 * @returns {any } Proxy object with name(), value(), and issues() methods
204212 */
205- export function create_field_proxy ( target , get_input , depend , set_input , get_issues , path = [ ] ) {
206- const path_string = build_path_string ( path ) ;
207-
213+ export function create_field_proxy ( target , get_input , set_input , get_issues , path = [ ] ) {
208214 const get_value = ( ) => {
209- depend ( path_string ) ;
210- return untrack ( ( ) => deep_get ( get_input ( ) , path ) ) ;
215+ return deep_get ( get_input ( ) , path ) ;
211216 } ;
212217
213218 return new Proxy ( target , {
@@ -216,7 +221,7 @@ export function create_field_proxy(target, get_input, depend, set_input, get_iss
216221
217222 // Handle array access like jobs[0]
218223 if ( / ^ \d + $ / . test ( prop ) ) {
219- return create_field_proxy ( { } , get_input , depend , set_input , get_issues , [
224+ return create_field_proxy ( { } , get_input , set_input , get_issues , [
220225 ...path ,
221226 parseInt ( prop , 10 )
222227 ] ) ;
@@ -229,17 +234,11 @@ export function create_field_proxy(target, get_input, depend, set_input, get_iss
229234 set_input ( path , newValue ) ;
230235 return newValue ;
231236 } ;
232- return create_field_proxy ( set_func , get_input , depend , set_input , get_issues , [
233- ...path ,
234- prop
235- ] ) ;
237+ return create_field_proxy ( set_func , get_input , set_input , get_issues , [ ...path , prop ] ) ;
236238 }
237239
238240 if ( prop === 'value' ) {
239- return create_field_proxy ( get_value , get_input , depend , set_input , get_issues , [
240- ...path ,
241- prop
242- ] ) ;
241+ return create_field_proxy ( get_value , get_input , set_input , get_issues , [ ...path , prop ] ) ;
243242 }
244243
245244 if ( prop === 'issues' || prop === 'allIssues' ) {
@@ -259,10 +258,7 @@ export function create_field_proxy(target, get_input, depend, set_input, get_iss
259258 } ) ) ;
260259 } ;
261260
262- return create_field_proxy ( issues_func , get_input , depend , set_input , get_issues , [
263- ...path ,
264- prop
265- ] ) ;
261+ return create_field_proxy ( issues_func , get_input , set_input , get_issues , [ ...path , prop ] ) ;
266262 }
267263
268264 if ( prop === 'as' ) {
@@ -411,14 +407,11 @@ export function create_field_proxy(target, get_input, depend, set_input, get_iss
411407 } ) ;
412408 } ;
413409
414- return create_field_proxy ( as_func , get_input , depend , set_input , get_issues , [
415- ...path ,
416- 'as'
417- ] ) ;
410+ return create_field_proxy ( as_func , get_input , set_input , get_issues , [ ...path , 'as' ] ) ;
418411 }
419412
420413 // Handle property access (nested fields)
421- return create_field_proxy ( { } , get_input , depend , set_input , get_issues , [ ...path , prop ] ) ;
414+ return create_field_proxy ( { } , get_input , set_input , get_issues , [ ...path , prop ] ) ;
422415 }
423416 } ) ;
424417}
0 commit comments