blob: c56da2faaee3c2f1873721ea95c3540d04ad80ef [file] [log] [blame]
/*
* 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