mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
Support form submission of multipart/form-data
This commit is contained in:
parent
8db0367301
commit
cfde40e225
8 changed files with 135 additions and 17 deletions
|
@ -44,6 +44,7 @@ hyper = { version = "0.9", features = [ "serde-serialization" ] }
|
||||||
image = "0.9"
|
image = "0.9"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
log = "0.3.5"
|
log = "0.3.5"
|
||||||
|
mime = "0.2.0"
|
||||||
num-traits = "0.1.32"
|
num-traits = "0.1.32"
|
||||||
offscreen_gl_context = "0.1.2"
|
offscreen_gl_context = "0.1.2"
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use dom::attr::AttrValue;
|
use dom::attr::AttrValue;
|
||||||
|
use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
|
||||||
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
|
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
|
||||||
use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
|
use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
|
||||||
use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
|
use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
|
||||||
|
@ -15,10 +16,12 @@ use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, Nod
|
||||||
use dom::bindings::js::{JS, MutNullableHeap, Root};
|
use dom::bindings::js::{JS, MutNullableHeap, Root};
|
||||||
use dom::bindings::refcounted::Trusted;
|
use dom::bindings::refcounted::Trusted;
|
||||||
use dom::bindings::reflector::Reflectable;
|
use dom::bindings::reflector::Reflectable;
|
||||||
|
use dom::blob::Blob;
|
||||||
use dom::document::Document;
|
use dom::document::Document;
|
||||||
use dom::element::Element;
|
use dom::element::Element;
|
||||||
use dom::event::{EventBubbles, EventCancelable};
|
use dom::event::{EventBubbles, EventCancelable};
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::EventTarget;
|
||||||
|
use dom::file::File;
|
||||||
use dom::htmlbuttonelement::HTMLButtonElement;
|
use dom::htmlbuttonelement::HTMLButtonElement;
|
||||||
use dom::htmlcollection::CollectionFilter;
|
use dom::htmlcollection::CollectionFilter;
|
||||||
use dom::htmldatalistelement::HTMLDataListElement;
|
use dom::htmldatalistelement::HTMLDataListElement;
|
||||||
|
@ -33,19 +36,23 @@ use dom::htmltextareaelement::HTMLTextAreaElement;
|
||||||
use dom::node::{Node, document_from_node, window_from_node};
|
use dom::node::{Node, document_from_node, window_from_node};
|
||||||
use dom::virtualmethods::VirtualMethods;
|
use dom::virtualmethods::VirtualMethods;
|
||||||
use dom::window::Window;
|
use dom::window::Window;
|
||||||
use hyper::header::ContentType;
|
use encoding::EncodingRef;
|
||||||
|
use encoding::all::UTF_8;
|
||||||
|
use encoding::label::encoding_from_whatwg_label;
|
||||||
|
use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType};
|
||||||
use hyper::method::Method;
|
use hyper::method::Method;
|
||||||
use hyper::mime;
|
|
||||||
use msg::constellation_msg::{LoadData, PipelineId};
|
use msg::constellation_msg::{LoadData, PipelineId};
|
||||||
|
use rand::random;
|
||||||
use script_runtime::ScriptChan;
|
use script_runtime::ScriptChan;
|
||||||
use script_thread::{MainThreadScriptMsg, Runnable};
|
use script_thread::{MainThreadScriptMsg, Runnable};
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::str::from_utf8;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use string_cache::Atom;
|
use string_cache::Atom;
|
||||||
use task_source::dom_manipulation::DOMManipulationTask;
|
use task_source::dom_manipulation::DOMManipulationTask;
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
use util::str::DOMString;
|
use util::str::{DOMString, split_html_space_chars};
|
||||||
|
|
||||||
#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)]
|
#[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)]
|
||||||
pub struct GenerationId(u32);
|
pub struct GenerationId(u32);
|
||||||
|
@ -237,6 +244,86 @@ pub enum ResetFrom {
|
||||||
|
|
||||||
|
|
||||||
impl HTMLFormElement {
|
impl HTMLFormElement {
|
||||||
|
fn generate_boundary(&self) -> String {
|
||||||
|
let i1 = random::<u32>();
|
||||||
|
let i2 = random::<u32>();
|
||||||
|
|
||||||
|
format!("---------------------------{0}{1}", i1, i2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#picking-an-encoding-for-the-form
|
||||||
|
fn pick_encoding(&self) -> EncodingRef {
|
||||||
|
// Step 2
|
||||||
|
if self.upcast::<Element>().has_attribute(&atom!("accept-charset")) {
|
||||||
|
// Substep 1
|
||||||
|
let input = self.upcast::<Element>().get_string_attribute(&atom!("accept-charset"));
|
||||||
|
|
||||||
|
// Substep 2, 3, 4
|
||||||
|
let mut candidate_encodings = split_html_space_chars(&*input).filter_map(encoding_from_whatwg_label);
|
||||||
|
|
||||||
|
// Substep 5, 6
|
||||||
|
return candidate_encodings.next().unwrap_or(UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1, 3
|
||||||
|
document_from_node(self).encoding()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#multipart/form-data-encoding-algorithm
|
||||||
|
fn encode_form_data(&self, form_data: &mut Vec<FormDatum>,
|
||||||
|
encoding: Option<EncodingRef>,
|
||||||
|
boundary: String) -> String {
|
||||||
|
// Step 1
|
||||||
|
let mut result = "".to_owned();
|
||||||
|
|
||||||
|
// Step 2
|
||||||
|
// (maybe take encoding as input)
|
||||||
|
let encoding = encoding.unwrap_or(self.pick_encoding());
|
||||||
|
|
||||||
|
// Step 3
|
||||||
|
let charset = &*encoding.whatwg_name().unwrap();
|
||||||
|
|
||||||
|
// Step 4
|
||||||
|
for entry in form_data.iter_mut() {
|
||||||
|
// Substep 1
|
||||||
|
if entry.name == "_charset_" && entry.ty == "hidden" {
|
||||||
|
entry.value = FormDatumValue::String(DOMString::from(charset.clone()));
|
||||||
|
}
|
||||||
|
// TODO: Substep 2
|
||||||
|
|
||||||
|
// Step 5
|
||||||
|
// https://tools.ietf.org/html/rfc7578#section-4
|
||||||
|
result.push_str(&*format!("\r\n--{}\r\n", boundary));
|
||||||
|
let mut content_disposition = ContentDisposition {
|
||||||
|
disposition: DispositionType::Ext("form-data".to_owned()),
|
||||||
|
parameters: vec![DispositionParam::Ext("name".to_owned(), String::from(entry.name.clone()))]
|
||||||
|
};
|
||||||
|
|
||||||
|
match entry.value {
|
||||||
|
FormDatumValue::String(ref s) =>
|
||||||
|
result.push_str(&*format!("Content-Disposition: {}\r\n\r\n{}",
|
||||||
|
content_disposition,
|
||||||
|
s)),
|
||||||
|
FormDatumValue::File(ref f) => {
|
||||||
|
content_disposition.parameters.push(
|
||||||
|
DispositionParam::Filename(Charset::Ext(String::from(charset.clone())),
|
||||||
|
None,
|
||||||
|
f.name().clone().into()));
|
||||||
|
let content_type = ContentType(f.upcast::<Blob>().Type().parse().unwrap());
|
||||||
|
result.push_str(&*format!("Content-Disposition: {}\r\n{}\r\n\r\n",
|
||||||
|
content_disposition,
|
||||||
|
content_type));
|
||||||
|
|
||||||
|
result.push_str(from_utf8(&f.upcast::<Blob>().get_data().get_bytes()).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_str(&*format!("\r\n--{}--", boundary));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
|
/// [Form submission](https://html.spec.whatwg.org/multipage/#concept-form-submit)
|
||||||
pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
|
pub fn submit(&self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
|
||||||
// Step 1
|
// Step 1
|
||||||
|
@ -264,7 +351,7 @@ impl HTMLFormElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Step 6
|
// Step 6
|
||||||
let form_data = self.get_form_dataset(Some(submitter));
|
let mut form_data = self.get_form_dataset(Some(submitter));
|
||||||
// Step 7
|
// Step 7
|
||||||
let mut action = submitter.action();
|
let mut action = submitter.action();
|
||||||
// Step 8
|
// Step 8
|
||||||
|
@ -287,13 +374,21 @@ impl HTMLFormElement {
|
||||||
|
|
||||||
let parsed_data = match enctype {
|
let parsed_data = match enctype {
|
||||||
FormEncType::UrlEncoded => {
|
FormEncType::UrlEncoded => {
|
||||||
let mime: mime::Mime = "application/x-www-form-urlencoded".parse().unwrap();
|
load_data.headers.set(ContentType::form_url_encoded());
|
||||||
load_data.headers.set(ContentType(mime));
|
|
||||||
form_urlencoded::Serializer::new(String::new())
|
form_urlencoded::Serializer::new(String::new())
|
||||||
.extend_pairs(form_data.into_iter().map(|field| (field.name, field.value)))
|
.extend_pairs(form_data.into_iter().map(|field| (field.name.clone(), field.value_str())))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
_ => "".to_owned() // TODO: Add serializers for the other encoding types
|
FormEncType::FormDataEncoded => {
|
||||||
|
let boundary = self.generate_boundary();
|
||||||
|
let mime = mime!(Multipart / FormData; Boundary =(&boundary));
|
||||||
|
load_data.headers.set(ContentType(mime));
|
||||||
|
|
||||||
|
self.encode_form_data(&mut form_data, None, boundary)
|
||||||
|
}
|
||||||
|
// TODO: Support plain text encoding
|
||||||
|
FormEncType::TextPlainEncoded => "".to_owned()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 18
|
// Step 18
|
||||||
|
@ -435,7 +530,7 @@ impl HTMLFormElement {
|
||||||
data_set.push(FormDatum {
|
data_set.push(FormDatum {
|
||||||
ty: textarea.Type(),
|
ty: textarea.Type(),
|
||||||
name: name,
|
name: name,
|
||||||
value: textarea.Value()
|
value: FormDatumValue::String(textarea.Value())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -489,7 +584,10 @@ impl HTMLFormElement {
|
||||||
"file" | "textarea" => (),
|
"file" | "textarea" => (),
|
||||||
_ => {
|
_ => {
|
||||||
datum.name = clean_crlf(&datum.name);
|
datum.name = clean_crlf(&datum.name);
|
||||||
datum.value = clean_crlf(&datum.value);
|
datum.value = FormDatumValue::String(clean_crlf( match datum.value {
|
||||||
|
FormDatumValue::String(ref s) => s,
|
||||||
|
FormDatumValue::File(_) => unreachable!()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -544,12 +642,25 @@ impl HTMLFormElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add file support
|
pub enum FormDatumValue {
|
||||||
#[derive(HeapSizeOf)]
|
File(Root<File>),
|
||||||
|
String(DOMString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[derive(HeapSizeOf)]
|
||||||
pub struct FormDatum {
|
pub struct FormDatum {
|
||||||
pub ty: DOMString,
|
pub ty: DOMString,
|
||||||
pub name: DOMString,
|
pub name: DOMString,
|
||||||
pub value: DOMString
|
pub value: FormDatumValue
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormDatum {
|
||||||
|
pub fn value_str(&self) -> String {
|
||||||
|
match self.value {
|
||||||
|
FormDatumValue::String(ref s) => String::from(s.clone()),
|
||||||
|
FormDatumValue::File(ref f) => String::from(f.name().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, HeapSizeOf)]
|
#[derive(Copy, Clone, HeapSizeOf)]
|
||||||
|
|
|
@ -22,7 +22,7 @@ use dom::event::{Event, EventBubbles, EventCancelable};
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::EventTarget;
|
||||||
use dom::htmlelement::HTMLElement;
|
use dom::htmlelement::HTMLElement;
|
||||||
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
||||||
use dom::htmlformelement::{FormControl, FormDatum, FormSubmitter, HTMLFormElement};
|
use dom::htmlformelement::{FormDatumValue, FormControl, FormDatum, FormSubmitter, HTMLFormElement};
|
||||||
use dom::htmlformelement::{ResetFrom, SubmittedFrom};
|
use dom::htmlformelement::{ResetFrom, SubmittedFrom};
|
||||||
use dom::keyboardevent::KeyboardEvent;
|
use dom::keyboardevent::KeyboardEvent;
|
||||||
use dom::node::{Node, NodeDamage, UnbindContext};
|
use dom::node::{Node, NodeDamage, UnbindContext};
|
||||||
|
@ -625,6 +625,7 @@ impl HTMLInputElement {
|
||||||
atom!("radio") | atom!("checkbox") => if !self.Checked() || name.is_empty() {
|
atom!("radio") | atom!("checkbox") => if !self.Checked() || name.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
},
|
},
|
||||||
|
|
||||||
atom!("image") | atom!("file") => return None, // Unimplemented
|
atom!("image") | atom!("file") => return None, // Unimplemented
|
||||||
// Step 3.1: it's not the "Image Button" and doesn't have a name attribute.
|
// Step 3.1: it's not the "Image Button" and doesn't have a name attribute.
|
||||||
_ => if name.is_empty() {
|
_ => if name.is_empty() {
|
||||||
|
@ -637,7 +638,7 @@ impl HTMLInputElement {
|
||||||
Some(FormDatum {
|
Some(FormDatum {
|
||||||
ty: DOMString::from(&*ty), // FIXME(ajeffrey): Convert directly from Atoms to DOMStrings
|
ty: DOMString::from(&*ty), // FIXME(ajeffrey): Convert directly from Atoms to DOMStrings
|
||||||
name: name,
|
name: name,
|
||||||
value: self.Value()
|
value: FormDatumValue::String(self.Value())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ use dom::document::Document;
|
||||||
use dom::element::{AttributeMutation, Element};
|
use dom::element::{AttributeMutation, Element};
|
||||||
use dom::htmlelement::HTMLElement;
|
use dom::htmlelement::HTMLElement;
|
||||||
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
use dom::htmlfieldsetelement::HTMLFieldSetElement;
|
||||||
use dom::htmlformelement::{FormControl, FormDatum, HTMLFormElement};
|
use dom::htmlformelement::{FormDatumValue, FormControl, FormDatum, HTMLFormElement};
|
||||||
use dom::htmloptionelement::HTMLOptionElement;
|
use dom::htmloptionelement::HTMLOptionElement;
|
||||||
use dom::node::{Node, UnbindContext, window_from_node};
|
use dom::node::{Node, UnbindContext, window_from_node};
|
||||||
use dom::nodelist::NodeList;
|
use dom::nodelist::NodeList;
|
||||||
|
@ -94,7 +94,7 @@ impl HTMLSelectElement {
|
||||||
data_set.push(FormDatum {
|
data_set.push(FormDatum {
|
||||||
ty: self.Type(),
|
ty: self.Type(),
|
||||||
name: self.Name(),
|
name: self.Name(),
|
||||||
value: opt.Value()
|
value: FormDatumValue::String(opt.Value())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,8 @@ extern crate js;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate mime;
|
||||||
extern crate msg;
|
extern crate msg;
|
||||||
extern crate net_traits;
|
extern crate net_traits;
|
||||||
extern crate num_traits;
|
extern crate num_traits;
|
||||||
|
|
1
components/servo/Cargo.lock
generated
1
components/servo/Cargo.lock
generated
|
@ -1812,6 +1812,7 @@ dependencies = [
|
||||||
"js 0.1.2 (git+https://github.com/servo/rust-mozjs)",
|
"js 0.1.2 (git+https://github.com/servo/rust-mozjs)",
|
||||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net_traits 0.0.1",
|
"net_traits 0.0.1",
|
||||||
"num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
1
ports/cef/Cargo.lock
generated
1
ports/cef/Cargo.lock
generated
|
@ -1681,6 +1681,7 @@ dependencies = [
|
||||||
"js 0.1.2 (git+https://github.com/servo/rust-mozjs)",
|
"js 0.1.2 (git+https://github.com/servo/rust-mozjs)",
|
||||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net_traits 0.0.1",
|
"net_traits 0.0.1",
|
||||||
"num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
1
ports/gonk/Cargo.lock
generated
1
ports/gonk/Cargo.lock
generated
|
@ -1664,6 +1664,7 @@ dependencies = [
|
||||||
"js 0.1.2 (git+https://github.com/servo/rust-mozjs)",
|
"js 0.1.2 (git+https://github.com/servo/rust-mozjs)",
|
||||||
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"msg 0.0.1",
|
"msg 0.0.1",
|
||||||
"net_traits 0.0.1",
|
"net_traits 0.0.1",
|
||||||
"num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue