Skip to content

Commit d21ff2f

Browse files
author
bors-servo
committed
Auto merge of servo#10189 - KiChjang:cors-preflight-fetch, r=jdm
Implement CORS preflight fetch Fixes servo#10145. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10189) <!-- Reviewable:end -->
2 parents d566f1c + e42481d commit d21ff2f

File tree

8 files changed

+237
-47
lines changed

8 files changed

+237
-47
lines changed

components/net/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ openssl = "0.7.6"
4444
rustc-serialize = "0.3"
4545
threadpool = "1.0"
4646
time = "0.1.17"
47+
unicase = "1.4.0"
4748
url = {version = "0.5.7", features = ["heap_size"]}
4849
uuid = { version = "0.2", features = ["v4"] }
4950
websocket = "0.16.1"

components/net/fetch/methods.rs

Lines changed: 134 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
use data_loader::decode;
66
use fetch::cors_cache::{BasicCORSCache, CORSCache, CacheRequestDetails};
77
use http_loader::{NetworkHttpRequestFactory, create_http_connector, obtain_response};
8-
use hyper::header::{Accept, CacheControl, IfMatch, IfRange, IfUnmodifiedSince, Location};
9-
use hyper::header::{AcceptLanguage, ContentLength, ContentLanguage, HeaderView, Pragma};
10-
use hyper::header::{AccessControlAllowCredentials, AccessControlAllowOrigin};
11-
use hyper::header::{Authorization, Basic, CacheDirective, ContentEncoding, Encoding};
12-
use hyper::header::{ContentType, Headers, IfModifiedSince, IfNoneMatch};
13-
use hyper::header::{QualityItem, q, qitem, Referer as RefererHeader, UserAgent};
8+
use hyper::header::{Accept, AcceptLanguage, Authorization, AccessControlAllowCredentials};
9+
use hyper::header::{AccessControlAllowOrigin, AccessControlAllowHeaders, AccessControlAllowMethods};
10+
use hyper::header::{AccessControlRequestHeaders, AccessControlMaxAge, AccessControlRequestMethod, Basic};
11+
use hyper::header::{CacheControl, CacheDirective, ContentEncoding, ContentLength, ContentLanguage, ContentType};
12+
use hyper::header::{Encoding, HeaderView, Headers, IfMatch, IfRange, IfUnmodifiedSince, IfModifiedSince};
13+
use hyper::header::{IfNoneMatch, Pragma, Location, QualityItem, Referer as RefererHeader, UserAgent, q, qitem};
1414
use hyper::method::Method;
1515
use hyper::mime::{Mime, SubLevel, TopLevel};
1616
use hyper::status::StatusCode;
@@ -20,9 +20,12 @@ use net_traits::request::{RedirectMode, Referer, Request, RequestMode, ResponseT
2020
use net_traits::response::{HttpsState, TerminationReason};
2121
use net_traits::response::{Response, ResponseBody, ResponseType};
2222
use resource_thread::CancellationListener;
23+
use std::collections::HashSet;
2324
use std::io::Read;
25+
use std::iter::FromIterator;
2426
use std::rc::Rc;
2527
use std::thread;
28+
use unicase::UniCase;
2629
use url::idna::domain_to_ascii;
2730
use url::{Origin as UrlOrigin, OpaqueOrigin, Url, UrlParser, whatwg_scheme_type_mapper};
2831
use util::thread::spawn_named;
@@ -210,7 +213,7 @@ fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Re
210213
let internal_response = if response.is_network_error() {
211214
&network_error_res
212215
} else {
213-
response.get_actual_response()
216+
response.actual_response()
214217
};
215218

216219
// Step 13
@@ -245,7 +248,7 @@ fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Re
245248

246249
// Step 16
247250
if request.synchronous {
248-
response.get_actual_response().wait_until_done();
251+
response.actual_response().wait_until_done();
249252
return response;
250253
}
251254

@@ -263,7 +266,7 @@ fn main_fetch(request: Rc<Request>, cors_flag: bool, recursive_flag: bool) -> Re
263266
let internal_response = if response.is_network_error() {
264267
&network_error_res
265268
} else {
266-
response.get_actual_response()
269+
response.actual_response()
267270
};
268271

269272
// Step 18
@@ -365,7 +368,7 @@ fn http_fetch(request: Rc<Request>,
365368
}
366369

367370
// Substep 4
368-
let actual_response = res.get_actual_response();
371+
let actual_response = res.actual_response();
369372
if actual_response.url_list.borrow().is_empty() {
370373
*actual_response.url_list.borrow_mut() = request.url_list.borrow().clone();
371374
}
@@ -390,7 +393,7 @@ fn http_fetch(request: Rc<Request>,
390393
}, request.method.borrow().clone());
391394

392395
let method_mismatch = !method_cache_match && (!is_simple_method(&request.method.borrow()) ||
393-
request.use_cors_preflight);
396+
request.use_cors_preflight);
394397
let header_mismatch = request.headers.borrow().iter().any(|view|
395398
!cache.match_header(CacheRequestDetails {
396399
origin: origin.clone(),
@@ -401,7 +404,7 @@ fn http_fetch(request: Rc<Request>,
401404

402405
// Sub-substep 1
403406
if method_mismatch || header_mismatch {
404-
let preflight_result = preflight_fetch(request.clone());
407+
let preflight_result = cors_preflight_fetch(request.clone(), Some(cache));
405408
// Sub-substep 2
406409
if preflight_result.response_type == ResponseType::Error {
407410
return Response::network_error();
@@ -415,8 +418,7 @@ fn http_fetch(request: Rc<Request>,
415418
// Substep 3
416419
let credentials = match request.credentials_mode {
417420
CredentialsMode::Include => true,
418-
CredentialsMode::CredentialsSameOrigin if
419-
request.response_tainting.get() == ResponseTainting::Basic
421+
CredentialsMode::CredentialsSameOrigin if request.response_tainting.get() == ResponseTainting::Basic
420422
=> true,
421423
_ => false
422424
};
@@ -437,7 +439,7 @@ fn http_fetch(request: Rc<Request>,
437439
let mut response = response.unwrap();
438440

439441
// Step 5
440-
match response.get_actual_response().status.unwrap() {
442+
match response.actual_response().status.unwrap() {
441443

442444
// Code 301, 302, 303, 307, 308
443445
StatusCode::MovedPermanently | StatusCode::Found | StatusCode::SeeOther |
@@ -518,21 +520,21 @@ fn http_redirect_fetch(request: Rc<Request>,
518520
assert_eq!(response.return_internal.get(), true);
519521

520522
// Step 3
521-
// this step is done early, because querying if Location is available says
523+
// this step is done early, because querying if Location exists says
522524
// if it is None or Some, making it easy to seperate from the retrieval failure case
523-
if !response.get_actual_response().headers.has::<Location>() {
525+
if !response.actual_response().headers.has::<Location>() {
524526
return Rc::try_unwrap(response).ok().unwrap();
525527
}
526528

527529
// Step 2
528-
let location = match response.get_actual_response().headers.get::<Location>() {
530+
let location = match response.actual_response().headers.get::<Location>() {
529531
Some(&Location(ref location)) => location.clone(),
530532
// Step 4
531533
_ => return Response::network_error()
532534
};
533535

534536
// Step 5
535-
let response_url = response.get_actual_response().url.as_ref().unwrap();
537+
let response_url = response.actual_response().url.as_ref().unwrap();
536538
let location_url = UrlParser::new().base_url(response_url).parse(&*location);
537539

538540
// Step 6
@@ -575,7 +577,7 @@ fn http_redirect_fetch(request: Rc<Request>,
575577
}
576578

577579
// Step 13
578-
let status_code = response.get_actual_response().status.unwrap();
580+
let status_code = response.actual_response().status.unwrap();
579581
if ((status_code == StatusCode::MovedPermanently || status_code == StatusCode::Found) &&
580582
*request.method.borrow() == Method::Post) ||
581583
status_code == StatusCode::SeeOther {
@@ -876,11 +878,11 @@ fn http_network_fetch(request: Rc<Request>,
876878

877879
// Substep 2
878880

879-
// TODO how can I tell if response was retrieved over HTTPS?
881+
// TODO Determine if response was retrieved over HTTPS
880882
// TODO Servo needs to decide what ciphers are to be treated as "deprecated"
881883
response.https_state = HttpsState::None;
882884

883-
// TODO how do I read request?
885+
// TODO Read request
884886

885887
// Step 5
886888
// TODO when https://bugzilla.mozilla.org/show_bug.cgi?id=1030660
@@ -925,35 +927,139 @@ fn http_network_fetch(request: Rc<Request>,
925927
}
926928

927929
/// [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch)
928-
fn preflight_fetch(_request: Rc<Request>) -> Response {
929-
// TODO: Implement preflight fetch spec
930+
fn cors_preflight_fetch(request: Rc<Request>, cache: Option<BasicCORSCache>) -> Response {
931+
// Step 1
932+
let mut preflight = Request::new(request.current_url(), Some(request.origin.borrow().clone()), false);
933+
*preflight.method.borrow_mut() = Method::Options;
934+
preflight.initiator = request.initiator.clone();
935+
preflight.type_ = request.type_.clone();
936+
preflight.destination = request.destination.clone();
937+
preflight.referer = request.referer.clone();
938+
939+
// Step 2
940+
preflight.headers.borrow_mut().set::<AccessControlRequestMethod>(
941+
AccessControlRequestMethod(request.method.borrow().clone()));
942+
943+
// Step 3, 4
944+
let mut value = request.headers.borrow().iter()
945+
.filter_map(|ref view| if is_simple_header(view) {
946+
None
947+
} else {
948+
Some(UniCase(view.name().to_owned()))
949+
}).collect::<Vec<UniCase<String>>>();
950+
value.sort();
951+
952+
// Step 5
953+
preflight.headers.borrow_mut().set::<AccessControlRequestHeaders>(
954+
AccessControlRequestHeaders(value));
955+
956+
// Step 6
957+
let preflight = Rc::new(preflight);
958+
let response = http_network_or_cache_fetch(preflight.clone(), false, false);
959+
960+
// Step 7
961+
if cors_check(request.clone(), &response).is_ok() &&
962+
response.status.map_or(false, |status| status.is_success()) {
963+
// Substep 1
964+
let mut methods = if response.headers.has::<AccessControlAllowMethods>() {
965+
match response.headers.get::<AccessControlAllowMethods>() {
966+
Some(&AccessControlAllowMethods(ref m)) => m.clone(),
967+
// Substep 3
968+
None => return Response::network_error()
969+
}
970+
} else {
971+
vec![]
972+
};
973+
974+
// Substep 2
975+
let header_names = if response.headers.has::<AccessControlAllowHeaders>() {
976+
match response.headers.get::<AccessControlAllowHeaders>() {
977+
Some(&AccessControlAllowHeaders(ref hn)) => hn.clone(),
978+
// Substep 3
979+
None => return Response::network_error()
980+
}
981+
} else {
982+
vec![]
983+
};
984+
985+
// Substep 4
986+
if methods.is_empty() && request.use_cors_preflight {
987+
methods = vec![request.method.borrow().clone()];
988+
}
989+
990+
// Substep 5
991+
if methods.iter().all(|method| *method != *request.method.borrow()) &&
992+
!is_simple_method(&*request.method.borrow()) {
993+
return Response::network_error();
994+
}
995+
996+
// Substep 6
997+
let set: HashSet<&UniCase<String>> = HashSet::from_iter(header_names.iter());
998+
if request.headers.borrow().iter().any(|ref hv| !set.contains(&UniCase(hv.name().to_owned())) &&
999+
!is_simple_header(hv)) {
1000+
return Response::network_error();
1001+
}
1002+
1003+
// Substep 7, 8
1004+
let max_age = response.headers.get::<AccessControlMaxAge>().map(|acma| acma.0).unwrap_or(0);
1005+
1006+
// TODO: Substep 9 - Need to define what an imposed limit on max-age is
1007+
1008+
// Substep 10
1009+
let mut cache = match cache {
1010+
Some(c) => c,
1011+
None => return response
1012+
};
1013+
1014+
// Substep 11, 12
1015+
for method in &methods {
1016+
cache.match_method_and_update(CacheRequestDetails {
1017+
origin: request.origin.borrow().clone(),
1018+
destination: request.current_url(),
1019+
credentials: request.credentials_mode == CredentialsMode::Include
1020+
}, method.clone(), max_age);
1021+
}
1022+
1023+
// Substep 13, 14
1024+
for header_name in &header_names {
1025+
cache.match_header_and_update(CacheRequestDetails {
1026+
origin: request.origin.borrow().clone(),
1027+
destination: request.current_url(),
1028+
credentials: request.credentials_mode == CredentialsMode::Include
1029+
}, &*header_name, max_age);
1030+
}
1031+
1032+
// Substep 15
1033+
return response;
1034+
}
1035+
1036+
// Step 8
9301037
Response::network_error()
9311038
}
9321039

9331040
/// [CORS check](https://fetch.spec.whatwg.org#concept-cors-check)
9341041
fn cors_check(request: Rc<Request>, response: &Response) -> Result<(), ()> {
9351042

9361043
// Step 1
937-
// let headers = request.headers.borrow();
9381044
let origin = response.headers.get::<AccessControlAllowOrigin>().cloned();
9391045

9401046
// Step 2
9411047
let origin = try!(origin.ok_or(()));
9421048

9431049
// Step 3
9441050
if request.credentials_mode != CredentialsMode::Include &&
945-
origin == AccessControlAllowOrigin::Any {
1051+
origin == AccessControlAllowOrigin::Any {
9461052
return Ok(());
9471053
}
9481054

9491055
// Step 4
9501056
let origin = match origin {
9511057
AccessControlAllowOrigin::Value(origin) => origin,
952-
// if it's Any or Null at this point, I see nothing to do but return Err(())
1058+
// if it's Any or Null at this point, there's nothing to do but return Err(())
9531059
_ => return Err(())
9541060
};
9551061

956-
// strings are already utf-8 encoded, so I don't need to re-encode origin for this step
1062+
// strings are already utf-8 encoded, so there's no need to re-encode origin for this step
9571063
match ascii_serialise_origin(&request.origin.borrow()) {
9581064
Ok(request_origin) => {
9591065
if request_origin != origin {

components/net/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern crate openssl;
2929
extern crate rustc_serialize;
3030
extern crate threadpool;
3131
extern crate time;
32+
extern crate unicase;
3233
extern crate url;
3334
extern crate util;
3435
extern crate uuid;
@@ -49,7 +50,7 @@ pub mod resource_thread;
4950
pub mod storage_thread;
5051
pub mod websocket_loader;
5152

52-
/// An implementation of the [Fetch spec](https://fetch.spec.whatwg.org/)
53+
/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
5354
pub mod fetch {
5455
pub mod cors_cache;
5556
pub mod methods;

components/net_traits/response.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ impl Response {
144144
}
145145
}
146146

147-
pub fn get_actual_response(&self) -> &Response {
147+
pub fn actual_response(&self) -> &Response {
148148
if self.return_internal.get() && self.internal_response.is_some() {
149149
&**self.internal_response.as_ref().unwrap()
150150
} else {

components/servo/Cargo.lock

Lines changed: 9 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)