Skip to content

Commit

Permalink
Implement url encoded FormData parser
Browse files Browse the repository at this point in the history
  • Loading branch information
andreiltd committed Feb 12, 2025
1 parent 20e69a2 commit da64701
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 92 deletions.
7 changes: 5 additions & 2 deletions builtins/web/fetch/request-response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2212,8 +2212,11 @@ bool Response::redirect(JSContext *cx, unsigned argc, Value *vp) {
if (!url_str.data) {
return false;
}
auto parsedURL =
new_jsurl_with_base(&url_str, url::URL::url(worker_location::WorkerLocation::url));

auto deleter = [&](auto *url) { jsurl::free_jsurl(url); };
std::unique_ptr<jsurl::JSUrl, decltype(deleter)> parsedURL(
new_jsurl_with_base(&url_str, url::URL::url(worker_location::WorkerLocation::url)), deleter);

if (!parsedURL) {
return api::throw_error(cx, api::Errors::TypeError, "Response.redirect", "url",
"be a valid URL");
Expand Down
63 changes: 60 additions & 3 deletions builtins/web/form-data/form-data-parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "form-data.h"
#include "rust-encoding.h"
#include "rust-multipart-ffi.h"
#include "rust-url.h"

#include "../file.h"

Expand All @@ -25,8 +26,9 @@ JSObject *to_owned_buffer(JSContext *cx, jsmultipart::Slice src) {

std::copy_n(src.data, src.len, buf.get());

JS::RootedObject buffer(cx, JS::NewArrayBufferWithContents(
cx, src.len, buf.get(), JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory));
JS::RootedObject buffer(
cx, JS::NewArrayBufferWithContents(cx, src.len, buf.get(),
JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory));
if (!buffer) {
JS_ReportOutOfMemory(cx);
return nullptr;
Expand Down Expand Up @@ -215,6 +217,61 @@ JSObject *MultipartParser::parse(JSContext *cx, std::string_view body) {
return formdata;
}

class UrlParser : public FormDataParser {
virtual JSObject *parse(JSContext *cx, std::string_view body) override;
};

JSObject *UrlParser::parse(JSContext *cx, std::string_view body) {
RootedObject formdata(cx, FormData::create(cx));
if (!formdata) {
return nullptr;
}

if (body.empty()) {
return formdata;
}

jsurl::SpecString spec((uint8_t *)body.data(), body.size(), body.size());

auto deleter = [&](auto *params) { jsurl::free_params(params); };
std::unique_ptr<jsurl::JSUrlSearchParams, decltype(deleter)> params(jsurl::new_params(), deleter);
if (!params) {
JS_ReportOutOfMemory(cx);
return nullptr;
}

jsurl::params_init(params.get(), &spec);
auto index = 0;

jsurl::JSSearchParam param{};

while (true) {
jsurl::params_at(params.get(), index, &param);
if (param.done || param.name.data == nullptr || param.value.data == nullptr) {
break;
}

auto val_chars = JS::UTF8Chars((char *)param.value.data, param.value.len);
JS::RootedString val_str(cx, JS_NewStringCopyUTF8N(cx, val_chars));
if (!val_str) {
JS_ReportOutOfMemory(cx);
return nullptr;
}

auto name = std::string_view((char *)param.name.data, param.name.len);
JS::RootedValue value_val(cx, JS::StringValue(val_str));

auto res = FormData::append(cx, formdata, name, value_val, UndefinedHandleValue);
if (!res) {
return nullptr;
}

index += 1;
}

return formdata;
}

std::unique_ptr<FormDataParser> FormDataParser::create(std::string_view content_type) {
if (content_type.starts_with("multipart/form-data")) {
jsmultipart::Slice content_slice{(uint8_t *)(content_type.data()), content_type.size()};
Expand All @@ -228,7 +285,7 @@ std::unique_ptr<FormDataParser> FormDataParser::create(std::string_view content_
std::string_view boundary((char *)boundary_slice.data, boundary_slice.len);
return std::make_unique<MultipartParser>(boundary);
} else if (content_type.starts_with("application/x-www-form-urlencoded")) {
// TODO: add form url encoded content parser
return std::make_unique<UrlParser>();
} else if (content_type.starts_with("text/plain")) {
// TODO: add plain text content parser
}
Expand Down
1 change: 1 addition & 0 deletions builtins/web/form-data/form-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class FormData : public TraceableBuiltinImpl<FormData> {
using EntryList = JS::GCVector<FormDataEntry, 0, js::SystemAllocPolicy>;
static EntryList *entry_list(JSObject *self);

friend class UrlParser;
friend class FormDataIterator;
friend class MultipartParser;
friend class MultipartFormData;
Expand Down
2 changes: 1 addition & 1 deletion builtins/web/url.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ JSObject *URL::create(JSContext *cx, JS::HandleObject self, JS::HandleValue url_
void URL::finalize(JS::GCContext *gcx, JSObject *self) {
jsurl::JSUrl *url =
static_cast<jsurl::JSUrl *>(JS::GetReservedSlot(self, Slots::Url).toPrivate());
free(url);
jsurl::free_jsurl(url);
}

JSObject *URL::create(JSContext *cx, JS::HandleObject self, JS::HandleValue url_val,
Expand Down
10 changes: 7 additions & 3 deletions crates/rust-url/rust-url.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ JSUrl *new_jsurl(const SpecString *spec);

JSUrl *new_jsurl_with_base(const SpecString *spec, const JSUrl *base);

void free_jsurl(JSUrl *url);

SpecSlice authority(const JSUrl *url);

SpecSlice path_with_query(const JSUrl *url);
Expand Down Expand Up @@ -161,6 +163,8 @@ JSUrlSearchParams *url_search_params(JSUrl *url);

JSUrlSearchParams *new_params();

void free_params(JSUrlSearchParams *params);

void params_init(JSUrlSearchParams *params, const SpecString *init);

void params_append(JSUrlSearchParams *params, SpecString name, SpecString value);
Expand All @@ -181,8 +185,8 @@ void params_sort(JSUrlSearchParams *params);

SpecSlice params_to_string(const JSUrlSearchParams *params);

} // extern "C"
} // extern "C"

} // namespace jsurl
} // namespace jsurl

#endif // rust_url_bindings_h
#endif // rust_url_bindings_h
57 changes: 40 additions & 17 deletions crates/rust-url/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::missing_safety_doc)]

/// Wrapper for the Url crate and a URLSearchParams implementation that enables use from C++.
use std::marker::PhantomData;
use std::slice;
Expand Down Expand Up @@ -45,14 +47,13 @@ impl JSUrlSearchParams {
}
}
UrlOrString::Str(_) => {
let str;
if self.list.is_empty() {
str = form_urlencoded::Serializer::new(String::new()).finish();
let str = if self.list.is_empty() {
form_urlencoded::Serializer::new(String::new()).finish()
} else {
str = form_urlencoded::Serializer::new(String::new())
form_urlencoded::Serializer::new(String::new())
.extend_pairs(&*self.list)
.finish();
}
.finish()
};
self.url_or_str = UrlOrString::Str(str);
}
}
Expand Down Expand Up @@ -81,6 +82,19 @@ pub unsafe extern "C" fn new_jsurl_with_base(spec: &SpecString, base: &JSUrl) ->
}
}

#[no_mangle]
pub unsafe extern "C" fn free_jsurl(url: *mut JSUrl) {
if url.is_null() {
return;
}

if !(*url).params.is_null() {
free_params((*url).params);
}

let _ = Box::from_raw(url);
}

#[no_mangle]
pub extern "C" fn authority(url: &JSUrl) -> SpecSlice {
url.url.authority().into()
Expand Down Expand Up @@ -188,7 +202,7 @@ pub extern "C" fn search(url: &JSUrl) -> SpecSlice {

#[no_mangle]
pub extern "C" fn set_search(url: &mut JSUrl, search: &SpecString) {
let _ = quirks::set_search(&mut url.url, search.into());
quirks::set_search(&mut url.url, search.into());
url.update_params();
}

Expand Down Expand Up @@ -222,13 +236,22 @@ pub unsafe extern "C" fn new_params() -> *mut JSUrlSearchParams {
}))
}

#[no_mangle]
pub unsafe extern "C" fn free_params(params: *mut JSUrlSearchParams) {
if params.is_null() {
return;
}

let _ = Box::from_raw(params);
}

#[no_mangle]
pub extern "C" fn params_init(params: &mut JSUrlSearchParams, init: &SpecString) {
let init = unsafe { slice::from_raw_parts(init.data, init.len) };

// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
// Step 1
let init = if init.len() > 0 && init[0] == '?' as u8 {
let init = if !init.is_empty() && init[0] == b'?' {
&init[1..]
} else {
init
Expand All @@ -251,14 +274,14 @@ pub extern "C" fn params_append(
#[no_mangle]
pub extern "C" fn params_delete(params: &mut JSUrlSearchParams, name: &SpecString) {
let name: &str = name.into();
params.list.retain(|&(ref k, _)| k != &name);
params.list.retain(|(k, _)| k != name);
params.update_url_or_str();
}

#[no_mangle]
pub extern "C" fn params_has(params: &JSUrlSearchParams, name: &SpecString) -> bool {
let name: &str = name.into();
params.list.iter().find(|&kv| kv.0 == name).is_some()
params.list.iter().any(|kv| kv.0 == name)
}

#[no_mangle]
Expand All @@ -271,7 +294,7 @@ pub extern "C" fn params_get<'a>(
.list
.iter()
.find(|&kv| kv.0 == name)
.map(|ref kv| SpecSlice::from(kv.1.as_str()))
.map(|kv| SpecSlice::from(kv.1.as_str()))
.unwrap_or_else(|| SpecSlice::new(std::ptr::null(), 0))
}

Expand Down Expand Up @@ -299,8 +322,8 @@ pub extern "C" fn params_get_all<'a>(
let mut values: Vec<SpecSlice> = params
.list
.iter()
.filter_map(|&(ref k, ref v)| {
if k == &name {
.filter_map(|(k, v)| {
if k == name {
Some(SpecSlice::from(v.as_str()))
} else {
None
Expand All @@ -324,7 +347,7 @@ pub extern "C" fn params_set(params: &mut JSUrlSearchParams, name: SpecString, v

let mut index = None;
let mut i = 0;
params.list.retain(|&(ref k, _)| {
params.list.retain(|(k, _)| {
if index.is_none() {
if k == &name {
index = Some(i);
Expand Down Expand Up @@ -353,7 +376,7 @@ pub extern "C" fn params_sort(params: &mut JSUrlSearchParams) {
}

#[no_mangle]
pub extern "C" fn params_to_string<'a>(params: &'a JSUrlSearchParams) -> SpecSlice<'a> {
pub extern "C" fn params_to_string(params: &JSUrlSearchParams) -> SpecSlice<'_> {
match &params.url_or_str {
UrlOrString::Url(url) => {
let url = unsafe { url.as_mut().unwrap() };
Expand Down Expand Up @@ -439,15 +462,15 @@ impl SpecSlice<'_> {
fn new(data: *const u8, len: usize) -> Self {
SpecSlice {
data,
len: len,
len,
_marker: PhantomData,
}
}
}

impl<'a> From<&'a str> for SpecSlice<'a> {
fn from(s: &'a str) -> SpecSlice<'a> {
let ptr = if s.len() > 0 {
let ptr = if !s.is_empty() {
s.as_ptr()
} else {
std::ptr::null()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"status": "FAIL"
},
"Consume request's body as formData with correct urlencoded type": {
"status": "FAIL"
"status": "PASS"
},
"Consume request's body as formData without correct type (error case)": {
"status": "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"status": "FAIL"
},
"Consume response's body as formData with correct urlencoded type": {
"status": "FAIL"
"status": "PASS"
},
"Consume response's body as formData without correct type (error case)": {
"status": "PASS"
Expand Down
Loading

0 comments on commit da64701

Please sign in to comment.