1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
6 use dom::bindings::error::{Error, Fallible};
7 use dom::bindings::reflector::DomObject;
8 use dom::bindings::root::DomRoot;
9 use dom::bindings::str::USVString;
10 use dom::blob::{Blob, BlobImpl};
11 use dom::formdata::FormData;
12 use dom::globalscope::GlobalScope;
13 use dom::promise::Promise;
14 use js::jsapi::JSContext;
15 use js::jsapi::JS_ClearPendingException;
16 use js::jsapi::JS_ParseJSON;
17 use js::jsapi::Value as JSValue;
18 use js::jsval::UndefinedValue;
19 use mime::{Mime, TopLevel, SubLevel};
20 use std::cell::Ref;
21 use std::rc::Rc;
22 use std::str;
23 use url::form_urlencoded;
24 
25 #[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
26 pub enum BodyType {
27     Blob,
28     FormData,
29     Json,
30     Text
31 }
32 
33 pub enum FetchedData {
34     Text(String),
35     Json(JSValue),
36     BlobData(DomRoot<Blob>),
37     FormData(DomRoot<FormData>),
38 }
39 
40 // https://fetch.spec.whatwg.org/#concept-body-consume-body
41 #[allow(unrooted_must_root)]
consume_body<T: BodyOperations + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise>42 pub fn consume_body<T: BodyOperations + DomObject>(object: &T, body_type: BodyType) -> Rc<Promise> {
43     let promise = Promise::new(&object.global());
44 
45     // Step 1
46     if object.get_body_used() || object.is_locked() {
47         promise.reject_error(Error::Type(
48             "The response's stream is disturbed or locked".to_string(),
49         ));
50         return promise;
51     }
52 
53     object.set_body_promise(&promise, body_type);
54 
55     // Steps 2-4
56     // TODO: Body does not yet have a stream.
57 
58     consume_body_with_promise(object, body_type, &promise);
59 
60     promise
61 }
62 
63 // https://fetch.spec.whatwg.org/#concept-body-consume-body
64 #[allow(unrooted_must_root)]
consume_body_with_promise<T: BodyOperations + DomObject>(object: &T, body_type: BodyType, promise: &Promise)65 pub fn consume_body_with_promise<T: BodyOperations + DomObject>(object: &T,
66                                                                 body_type: BodyType,
67                                                                 promise: &Promise) {
68     // Step 5
69     let body = match object.take_body() {
70         Some(body) => body,
71         None => return,
72     };
73 
74     let pkg_data_results = run_package_data_algorithm(object,
75                                                       body,
76                                                       body_type,
77                                                       object.get_mime_type());
78 
79     match pkg_data_results {
80         Ok(results) => {
81             match results {
82                 FetchedData::Text(s) => promise.resolve_native(&USVString(s)),
83                 FetchedData::Json(j) => promise.resolve_native(&j),
84                 FetchedData::BlobData(b) => promise.resolve_native(&b),
85                 FetchedData::FormData(f) => promise.resolve_native(&f),
86             };
87         },
88         Err(err) => promise.reject_error(err),
89     }
90 }
91 
92 // https://fetch.spec.whatwg.org/#concept-body-package-data
93 #[allow(unsafe_code)]
run_package_data_algorithm<T: BodyOperations + DomObject>(object: &T, bytes: Vec<u8>, body_type: BodyType, mime_type: Ref<Vec<u8>>) -> Fallible<FetchedData>94 fn run_package_data_algorithm<T: BodyOperations + DomObject>(object: &T,
95                                                              bytes: Vec<u8>,
96                                                              body_type: BodyType,
97                                                              mime_type: Ref<Vec<u8>>)
98                                                              -> Fallible<FetchedData> {
99     let global = object.global();
100     let cx = global.get_cx();
101     let mime = &*mime_type;
102     match body_type {
103         BodyType::Text => run_text_data_algorithm(bytes),
104         BodyType::Json => run_json_data_algorithm(cx, bytes),
105         BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime),
106         BodyType::FormData => run_form_data_algorithm(&global, bytes, mime),
107     }
108 }
109 
run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData>110 fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
111     Ok(FetchedData::Text(String::from_utf8_lossy(&bytes).into_owned()))
112 }
113 
114 #[allow(unsafe_code)]
run_json_data_algorithm(cx: *mut JSContext, bytes: Vec<u8>) -> Fallible<FetchedData>115 fn run_json_data_algorithm(cx: *mut JSContext,
116                            bytes: Vec<u8>) -> Fallible<FetchedData> {
117     let json_text = String::from_utf8_lossy(&bytes);
118     let json_text: Vec<u16> = json_text.encode_utf16().collect();
119     rooted!(in(cx) let mut rval = UndefinedValue());
120     unsafe {
121         if !JS_ParseJSON(cx,
122                          json_text.as_ptr(),
123                          json_text.len() as u32,
124                          rval.handle_mut()) {
125             JS_ClearPendingException(cx);
126             // TODO: See issue #13464. Exception should be thrown instead of cleared.
127             return Err(Error::Type("Failed to parse JSON".to_string()));
128         }
129         Ok(FetchedData::Json(rval.get()))
130     }
131 }
132 
run_blob_data_algorithm(root: &GlobalScope, bytes: Vec<u8>, mime: &[u8]) -> Fallible<FetchedData>133 fn run_blob_data_algorithm(root: &GlobalScope,
134                            bytes: Vec<u8>,
135                            mime: &[u8]) -> Fallible<FetchedData> {
136     let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
137         s
138     } else {
139         "".to_string()
140     };
141     let blob = Blob::new(root, BlobImpl::new_from_bytes(bytes), mime_string);
142     Ok(FetchedData::BlobData(blob))
143 }
144 
run_form_data_algorithm(root: &GlobalScope, bytes: Vec<u8>, mime: &[u8]) -> Fallible<FetchedData>145 fn run_form_data_algorithm(root: &GlobalScope, bytes: Vec<u8>, mime: &[u8]) -> Fallible<FetchedData> {
146     let mime_str = if let Ok(s) = str::from_utf8(mime) {
147         s
148     } else {
149         ""
150     };
151     let mime: Mime = mime_str.parse().map_err(
152         |_| Error::Type("Inappropriate MIME-type for Body".to_string()))?;
153     match mime {
154         // TODO
155         // ... Parser for Mime(TopLevel::Multipart, SubLevel::FormData, _)
156         // ... is not fully determined yet.
157         Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _) => {
158             let entries = form_urlencoded::parse(&bytes);
159             let formdata = FormData::new(None, root);
160             for (k, e) in entries {
161                 formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
162             }
163             return Ok(FetchedData::FormData(formdata));
164         },
165         _ => return Err(Error::Type("Inappropriate MIME-type for Body".to_string())),
166     }
167 }
168 
169 pub trait BodyOperations {
get_body_used(&self) -> bool170     fn get_body_used(&self) -> bool;
set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType)171     fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType);
172     /// Returns `Some(_)` if the body is complete, `None` if there is more to
173     /// come.
take_body(&self) -> Option<Vec<u8>>174     fn take_body(&self) -> Option<Vec<u8>>;
is_locked(&self) -> bool175     fn is_locked(&self) -> bool;
get_mime_type(&self) -> Ref<Vec<u8>>176     fn get_mime_type(&self) -> Ref<Vec<u8>>;
177 }
178