blob: 92b9eee0d48f9c0d7c770386ddf621c901254608 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/service_worker/service_worker_utils.h"
#include <sstream>
#include <string>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/origin_util.h"
namespace content {
namespace {
bool PathContainsDisallowedCharacter(const GURL& url) {
std::string path = url.path();
DCHECK(base::IsStringUTF8(path));
// We should avoid these escaped characters in the path component because
// these can be handled differently depending on server implementation.
if (path.find("%2f") != std::string::npos ||
path.find("%2F") != std::string::npos) {
return true;
}
if (path.find("%5c") != std::string::npos ||
path.find("%5C") != std::string::npos) {
return true;
}
return false;
}
} // namespace
// static
bool ServiceWorkerUtils::ScopeMatches(const GURL& scope, const GURL& url) {
DCHECK(!scope.has_ref());
return base::StartsWith(url.spec(), scope.spec(),
base::CompareCase::SENSITIVE);
}
// static
bool ServiceWorkerUtils::IsPathRestrictionSatisfied(
const GURL& scope,
const GURL& script_url,
const std::string* service_worker_allowed_header_value,
std::string* error_message) {
DCHECK(scope.is_valid());
DCHECK(!scope.has_ref());
DCHECK(script_url.is_valid());
DCHECK(!script_url.has_ref());
DCHECK(error_message);
if (ContainsDisallowedCharacter(scope, script_url, error_message))
return false;
std::string max_scope_string;
if (service_worker_allowed_header_value) {
GURL max_scope = script_url.Resolve(*service_worker_allowed_header_value);
if (!max_scope.is_valid()) {
*error_message = "An invalid Service-Worker-Allowed header value ('";
error_message->append(*service_worker_allowed_header_value);
error_message->append("') was received when fetching the script.");
return false;
}
max_scope_string = max_scope.path();
} else {
max_scope_string = script_url.GetWithoutFilename().path();
}
std::string scope_string = scope.path();
if (!base::StartsWith(scope_string, max_scope_string,
base::CompareCase::SENSITIVE)) {
*error_message = "The path of the provided scope ('";
error_message->append(scope_string);
error_message->append("') is not under the max scope allowed (");
if (service_worker_allowed_header_value)
error_message->append("set by Service-Worker-Allowed: ");
error_message->append("'");
error_message->append(max_scope_string);
error_message->append(
"'). Adjust the scope, move the Service Worker script, or use the "
"Service-Worker-Allowed HTTP header to allow the scope.");
return false;
}
return true;
}
// static
bool ServiceWorkerUtils::ContainsDisallowedCharacter(
const GURL& scope,
const GURL& script_url,
std::string* error_message) {
if (PathContainsDisallowedCharacter(scope) ||
PathContainsDisallowedCharacter(script_url)) {
*error_message = "The provided scope ('";
error_message->append(scope.spec());
error_message->append("') or scriptURL ('");
error_message->append(script_url.spec());
error_message->append("') includes a disallowed escape character.");
return true;
}
return false;
}
// static
bool ServiceWorkerUtils::AllOriginsMatchAndCanAccessServiceWorkers(
const std::vector<GURL>& urls) {
// (A) Check if all origins can access service worker. Every URL must be
// checked despite the same-origin check below in (B), because GetOrigin()
// uses the inner URL for filesystem URLs so that https://foo/ and
// filesystem:https://foo/ are considered equal, but filesystem URLs cannot
// access service worker.
for (const GURL& url : urls) {
if (!OriginCanAccessServiceWorkers(url))
return false;
}
// (B) Check if all origins are equal. Cross-origin access is permitted when
// --disable-web-security is set.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity)) {
return true;
}
const GURL& first = urls.front();
for (const GURL& url : urls) {
if (first.GetOrigin() != url.GetOrigin())
return false;
}
return true;
}
// static
bool ServiceWorkerUtils::IsServicificationEnabled() {
return IsBrowserSideNavigationEnabled() &&
base::FeatureList::IsEnabled(features::kNetworkService);
}
// static
bool ServiceWorkerUtils::IsScriptStreamingEnabled() {
return base::FeatureList::IsEnabled(features::kServiceWorkerScriptStreaming);
}
// static
std::string ServiceWorkerUtils::ErrorTypeToString(
blink::mojom::ServiceWorkerErrorType error) {
std::ostringstream oss;
oss << error;
return oss.str();
}
bool LongestScopeMatcher::MatchLongest(const GURL& scope) {
if (!ServiceWorkerUtils::ScopeMatches(scope, url_))
return false;
if (match_.is_empty() || match_.spec().size() < scope.spec().size()) {
match_ = scope;
return true;
}
return false;
}
} // namespace content