Skip to content
File

Blob: src/workerd/api/form-data.h

cpp221 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7#include "blob.h"
8#include <kj/vector.h>
9#include <workerd/jsg/jsg.h>
10#include <workerd/io/compatibility-date.capnp.h>
11 
12namespace workerd::api {
13 
14// Implements the FormData interface as prescribed by:
15// https://xhr.spec.whatwg.org/#interface-formdata
16class FormData: public jsg::Object {
17private:
18 using EntryType = kj::OneOf<jsg::Ref<File>, kj::String>;
19 using EntryIteratorType = kj::Array<EntryType>;
20 using KeyIteratorType = kj::String;
21 using ValueIteratorType = EntryType;
22 
23 struct IteratorState final {
24 jsg::Ref<FormData> parent;
25 uint index = 0;
26 
27 void visitForGc(jsg::GcVisitor& visitor) {
28 visitor.visit(parent);
29 }
30 
31 JSG_MEMORY_INFO(IteratorState) {
32 tracker.trackField("parent", parent);
33 }
34 };
35 
36public:
37 
38 using ParseCallback = kj::FunctionParam<void(kj::StringPtr name,
39 kj::Maybe<kj::StringPtr> filename,
40 kj::Maybe<kj::StringPtr> type,
41 kj::ArrayPtr<const kj::byte> data)>;
42 
43 static void parseFormDataImpl(kj::ArrayPtr<const char> rawText,
44 kj::StringPtr boundary,
45 ParseCallback callback);
46 struct EntryWithoutLock {
47 kj::String name;
48 kj::Maybe<kj::String> filename;
49 kj::Maybe<kj::String> type;
50 kj::OneOf<kj::Array<kj::byte>, kj::String> value;
51 };
52 
53 // Parse `rawText`, storing the results in this FormData object. `contentType` must be either
54 // multipart/form-data or application/x-www-form-urlencoded.
55 //
56 // `convertFilesToStrings` is for backwards-compatibility. The first implementation of this
57 // class in Workers incorrectly represented files as strings (of their content). Changing this
58 // could break deployed code, so this has to be controlled by a compatibility flag.
59 //
60 // Parsing may or may not pass a jsg::Lock. If a lock is passed, any File objects created will
61 // track their internal allocated memory in the associated isolate. If a lock is not passed,
62 // the internal allocated memory will not be tracked.
63 void parse(jsg::Lock& js,
64 kj::ArrayPtr<const char> rawText,
65 kj::StringPtr contentType,
66 bool convertFilesToStrings);
67 
68 // Given a delimiter string `boundary`, serialize all fields in this form data to an array of
69 // bytes suitable for use as an HTTP message body.
70 kj::Array<kj::byte> serialize(kj::ArrayPtr<const char> boundary);
71 
72 struct Entry {
73 kj::String name;
74 kj::OneOf<jsg::Ref<File>, kj::String> value;
75 
76 JSG_MEMORY_INFO(Entry) {
77 tracker.trackField("name", name);
78 KJ_SWITCH_ONEOF(value) {
79 KJ_CASE_ONEOF(file, jsg::Ref<File>) {
80 tracker.trackField("value", file);
81 }
82 KJ_CASE_ONEOF(str, kj::String) {
83 tracker.trackField("value", str);
84 }
85 }
86 }
87 };
88 
89 kj::ArrayPtr<const Entry> getData() { return data; }
90 
91 // JS API
92 
93 // The spec allows a FormData to be constructed from a <form> HTML element. We don't support that,
94 // for obvious reasons, so this constructor doesn't take any parameters. If someone tries to use
95 // FormData to represent a <form> element we probably don't have to worry about making the error
96 // message they receive too pretty: they won't get farther than `document.getElementById()`.
97 static jsg::Ref<FormData> constructor(jsg::Lock& js);
98 
99 void append(jsg::Lock& js, kj::String name,
100 kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>, kj::String> value,
101 jsg::Optional<kj::String> filename);
102 
103 void delete_(kj::String name);
104 
105 kj::Maybe<kj::OneOf<jsg::Ref<File>, kj::String>> get(jsg::Lock& js, kj::String name);
106 
107 kj::Array<kj::OneOf<jsg::Ref<File>, kj::String>> getAll(jsg::Lock& js, kj::String name);
108 
109 bool has(kj::String name);
110 
111 void set(jsg::Lock& js, kj::String name,
112 kj::OneOf<jsg::Ref<File>, jsg::Ref<Blob>, kj::String> value,
113 jsg::Optional<kj::String> filename);
114 
115 JSG_ITERATOR(EntryIterator, entries,
116 EntryIteratorType,
117 IteratorState,
118 iteratorNext<EntryIteratorType>);
119 JSG_ITERATOR(KeyIterator, keys,
120 KeyIteratorType,
121 IteratorState,
122 iteratorNext<KeyIteratorType>);
123 JSG_ITERATOR(ValueIterator,
124 values,
125 ValueIteratorType,
126 IteratorState,
127 iteratorNext<ValueIteratorType>);
128 
129 void forEach(
130 jsg::Lock& js,
131 jsg::Function<void(EntryType, kj::StringPtr, jsg::Ref<FormData>)> callback,
132 jsg::Optional<jsg::Value> thisArg);
133 
134 JSG_RESOURCE_TYPE(FormData, CompatibilityFlags::Reader flags) {
135 JSG_METHOD(append);
136 JSG_METHOD_NAMED(delete, delete_);
137 JSG_METHOD(get);
138 JSG_METHOD(getAll);
139 JSG_METHOD(has);
140 JSG_METHOD(set);
141 JSG_METHOD(entries);
142 JSG_METHOD(keys);
143 JSG_METHOD(values);
144 
145 JSG_METHOD(forEach);
146 JSG_ITERABLE(entries);
147 
148 if (flags.getFormDataParserSupportsFiles()) {
149 JSG_TS_OVERRIDE({
150 append(name: string, value: string | Blob): void;
151 append(name: string, value: string): void;
152 append(name: string, value: Blob, filename?: string): void;
153 
154 set(name: string, value: string | Blob): void;
155 set(name: string, value: string): void;
156 set(name: string, value: Blob, filename?: string): void;
157 
158 entries(): IterableIterator<[key: string, value: File | string]>;
159 [Symbol.iterator](): IterableIterator<[key: string, value: File | string]>;
160 
161 forEach<This = unknown>(callback: (this: This, value: File | string, key: string, parent: FormData) => void, thisArg?: This): void;
162 });
163 } else {
164 JSG_TS_OVERRIDE({
165 get(name: string): string | null;
166 getAll(name: string): string[];
167 
168 append(name: string, value: string | Blob): void;
169 append(name: string, value: string): void;
170 append(name: string, value: Blob, filename?: string): void;
171 
172 set(name: string, value: string | Blob): void;
173 set(name: string, value: string): void;
174 set(name: string, value: Blob, filename?: string): void;
175 
176 entries(): IterableIterator<[key: string, value: string]>;
177 [Symbol.iterator](): IterableIterator<[key: string, value: string]>;
178 
179 forEach<This = unknown>(callback: (this: This, value: string, key: string, parent: FormData) => void, thisArg?: This): void;
180 });
181 }
182 }
183 
184 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
185 tracker.trackField("data", data.asPtr());
186 }
187 
188private:
189 kj::Vector<Entry> data;
190 
191 static EntryType clone(jsg::Lock& js, EntryType& value);
192 
193 template <typename Type>
194 static kj::Maybe<Type> iteratorNext(jsg::Lock& js, IteratorState& state) {
195 if (state.index >= state.parent->data.size()) {
196 return kj::none;
197 }
198 auto& [key, value] = state.parent->data[state.index++];
199 if constexpr (kj::isSameType<Type, EntryIteratorType>()) {
200 return kj::arr<EntryType>(kj::str(key), clone(js, value));
201 } else if constexpr (kj::isSameType<Type, KeyIteratorType>()) {
202 return kj::str(key);
203 } else if constexpr (kj::isSameType<Type, ValueIteratorType>()) {
204 return clone(js, value);
205 } else {
206 KJ_UNREACHABLE;
207 }
208 }
209};
210 
211#define EW_FORMDATA_ISOLATE_TYPES \
212 api::FormData, \
213 api::FormData::EntryIterator, \
214 api::FormData::EntryIterator::Next, \
215 api::FormData::KeyIterator, \
216 api::FormData::KeyIterator::Next, \
217 api::FormData::ValueIterator, \
218 api::FormData::ValueIterator::Next
219 
220} // namespace workerd::api