| /* |
| * Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <algorithm> |
| |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/string_util.h" |
| #include "cobalt/loader/cors_preflight_cache.h" |
| |
| namespace { |
| bool MethodNameToRequestType(const std::string& method, |
| net::URLFetcher::RequestType* request_type) { |
| if (base::LowerCaseEqualsASCII(method, "get")) { |
| *request_type = net::URLFetcher::GET; |
| } else if (base::LowerCaseEqualsASCII(method, "post")) { |
| *request_type = net::URLFetcher::POST; |
| } else if (base::LowerCaseEqualsASCII(method, "head")) { |
| *request_type = net::URLFetcher::HEAD; |
| } else if (base::LowerCaseEqualsASCII(method, "delete")) { |
| *request_type = net::URLFetcher::DELETE_REQUEST; |
| } else if (base::LowerCaseEqualsASCII(method, "put")) { |
| *request_type = net::URLFetcher::PUT; |
| } else { |
| return false; |
| } |
| return true; |
| } |
| const char* kAuthorization = "authorization"; |
| } // namespace |
| |
| namespace cobalt { |
| namespace loader { |
| |
| void CORSPreflightCache::AppendEntry( |
| const std::string& url_str, const std::string& origin, int max_age, |
| bool has_credentials, const std::vector<std::string>& methods_vec, |
| const std::vector<std::string>& headernames_vec) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (max_age <= 0) { |
| return; |
| } |
| base::TimeDelta valid_duration = base::TimeDelta::FromSeconds(max_age); |
| scoped_refptr<CORSPreflightCacheEntry> new_entry = |
| new CORSPreflightCacheEntry(); |
| new_entry->credentials = has_credentials; |
| new_entry->expiration_time = base::Time::Now() + valid_duration; |
| |
| if (methods_vec.size() == 1 && methods_vec.at(0) == "*") { |
| new_entry->allow_all_methods = true; |
| } else { |
| for (const auto& method : methods_vec) { |
| net::URLFetcher::RequestType request_type; |
| if (MethodNameToRequestType(method, &request_type)) { |
| new_entry->methods.insert(request_type); |
| } |
| } |
| } |
| |
| if (headernames_vec.size() == 1 && headernames_vec.at(0) == "*") { |
| new_entry->allow_all_headers_except_non_wildcard = true; |
| } |
| // TODO: Consider change this function to use std::copy with std::inserter. |
| // Currently compilers on some machines do not support it. |
| for (const auto& headername : headernames_vec) { |
| new_entry->headernames.insert(headername); |
| } |
| |
| auto insert_result = |
| content_[url_str].insert(std::make_pair(origin, new_entry)); |
| if (!insert_result.second) { |
| insert_result.first->second = new_entry; |
| } |
| |
| expiration_time_heap_.push( |
| ExpirationHeapEntry{new_entry->expiration_time, url_str, origin}); |
| } |
| |
| // https://fetch.spec.whatwg.org/#concept-cache-match |
| bool CORSPreflightCache::HaveEntry( |
| const std::string& url_str, const std::string& origin, |
| bool credentials_mode_is_include, |
| const net::URLFetcher::RequestType& new_request_method, |
| const std::vector<std::string>& unsafe_headernames) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (url_str.empty() || origin.empty()) { |
| return false; |
| } |
| ClearObsoleteEntries(); |
| // There is a cache match for request if origin is request's origin, |
| // url is request's current url, either credentials is true or request's |
| // credentials_mode is not "include" |
| auto url_iter = content_.find(url_str); |
| if (url_iter == content_.end()) { |
| return false; |
| } |
| auto origin_iter = url_iter->second.find(origin); |
| if (origin_iter == url_iter->second.end()) { |
| return false; |
| } |
| auto entry_ptr = origin_iter->second; |
| if (!entry_ptr->credentials && credentials_mode_is_include) { |
| return false; |
| } |
| // Either last preflight's Access-Control-Allow-Methods header has * or |
| // new request's method, otherwise return false. |
| if (!entry_ptr->allow_all_methods && |
| entry_ptr->methods.find(new_request_method) == entry_ptr->methods.end()) { |
| return false; |
| } |
| // Header name is safe if it's not CORS non-wildcard request-header |
| // name("Authentication") and last preflight allowed * headers. |
| if (entry_ptr->allow_all_headers_except_non_wildcard) { |
| bool has_auth_header = false; |
| for (const auto& header : unsafe_headernames) { |
| if (SbStringCompareNoCase(header.c_str(), kAuthorization)) { |
| has_auth_header = true; |
| break; |
| } |
| } |
| // wildcard header is allowed if entry's allowed headers include it. |
| return !has_auth_header || |
| (entry_ptr->headernames.find(std::string(kAuthorization)) != |
| entry_ptr->headernames.end()); |
| } |
| // If last preflight does not allow arbitrary header, then match each header |
| // with allowed headers. |
| for (const auto& unsafe_headername : unsafe_headernames) { |
| if (entry_ptr->headernames.find(unsafe_headername) == |
| entry_ptr->headernames.end()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void CORSPreflightCache::ClearObsoleteEntries() { |
| while (expiration_time_heap_.size() > 0 && |
| expiration_time_heap_.top().expiration_time < base::Time::Now()) { |
| auto heap_top = expiration_time_heap_.top(); |
| expiration_time_heap_.pop(); |
| auto url_iter = content_.find(heap_top.url_str); |
| if (url_iter == content_.end()) { |
| continue; |
| } |
| auto entry_iter = url_iter->second.find(heap_top.origin); |
| if (entry_iter == url_iter->second.end()) { |
| continue; |
| } |
| // The entry could have been updated and should only delete obsolete ones. |
| if (entry_iter->second->expiration_time < base::Time::Now()) { |
| url_iter->second.erase(entry_iter); |
| } |
| } |
| } |
| |
| } // namespace loader |
| } // namespace cobalt |