262 lines
9.2 KiB
C++
262 lines
9.2 KiB
C++
/*
|
|
* Copyright (C) 2017 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "ApplicationManifestParser.h"
|
|
|
|
#if ENABLE(APPLICATION_MANIFEST)
|
|
|
|
#include "CSSParser.h"
|
|
#include "Color.h"
|
|
#include "Document.h"
|
|
#include "SecurityOrigin.h"
|
|
#include <JavaScriptCore/ConsoleMessage.h>
|
|
|
|
namespace WebCore {
|
|
|
|
ApplicationManifest ApplicationManifestParser::parse(Document& document, const String& source, const URL& manifestURL, const URL& documentURL)
|
|
{
|
|
ApplicationManifestParser parser { &document };
|
|
return parser.parseManifest(source, manifestURL, documentURL);
|
|
}
|
|
|
|
ApplicationManifest ApplicationManifestParser::parse(const String& source, const URL& manifestURL, const URL& documentURL)
|
|
{
|
|
ApplicationManifestParser parser { nullptr };
|
|
return parser.parseManifest(source, manifestURL, documentURL);
|
|
}
|
|
|
|
ApplicationManifestParser::ApplicationManifestParser(RefPtr<Document> document)
|
|
: m_document(document)
|
|
{
|
|
}
|
|
|
|
ApplicationManifest ApplicationManifestParser::parseManifest(const String& text, const URL& manifestURL, const URL& documentURL)
|
|
{
|
|
m_manifestURL = manifestURL;
|
|
|
|
auto jsonValue = JSON::Value::parseJSON(text);
|
|
if (!jsonValue) {
|
|
logDeveloperWarning("The manifest is not valid JSON data."_s);
|
|
jsonValue = JSON::Object::create();
|
|
}
|
|
|
|
auto manifest = jsonValue->asObject();
|
|
if (!manifest) {
|
|
logDeveloperWarning("The manifest is not a JSON value of type \"object\"."_s);
|
|
manifest = JSON::Object::create();
|
|
}
|
|
|
|
ApplicationManifest parsedManifest;
|
|
|
|
parsedManifest.startURL = parseStartURL(*manifest, documentURL);
|
|
parsedManifest.display = parseDisplay(*manifest);
|
|
parsedManifest.name = parseName(*manifest);
|
|
parsedManifest.description = parseDescription(*manifest);
|
|
parsedManifest.shortName = parseShortName(*manifest);
|
|
parsedManifest.scope = parseScope(*manifest, documentURL, parsedManifest.startURL);
|
|
parsedManifest.themeColor = parseColor(*manifest, "theme_color"_s);
|
|
|
|
if (m_document)
|
|
m_document->processApplicationManifest(parsedManifest);
|
|
|
|
return parsedManifest;
|
|
}
|
|
|
|
void ApplicationManifestParser::logManifestPropertyNotAString(const String& propertyName)
|
|
{
|
|
logDeveloperWarning(makeString("The value of \""_s, propertyName, "\" is not a string."_s));
|
|
}
|
|
|
|
void ApplicationManifestParser::logManifestPropertyInvalidURL(const String& propertyName)
|
|
{
|
|
logDeveloperWarning(makeString("The value of \""_s, propertyName, "\" is not a valid URL."_s));
|
|
}
|
|
|
|
void ApplicationManifestParser::logDeveloperWarning(const String& message)
|
|
{
|
|
if (m_document)
|
|
m_document->addConsoleMessage(makeUnique<Inspector::ConsoleMessage>(JSC::MessageSource::Other, JSC::MessageType::Log, JSC::MessageLevel::Warning, makeString("Parsing application manifest "_s, m_manifestURL.string(), ": "_s, message)));
|
|
}
|
|
|
|
URL ApplicationManifestParser::parseStartURL(const JSON::Object& manifest, const URL& documentURL)
|
|
{
|
|
auto value = manifest.getValue("start_url"_s);
|
|
if (!value)
|
|
return documentURL;
|
|
|
|
auto stringValue = value->asString();
|
|
if (!stringValue) {
|
|
logManifestPropertyNotAString("start_url"_s);
|
|
return documentURL;
|
|
}
|
|
|
|
if (stringValue.isEmpty())
|
|
return documentURL;
|
|
|
|
URL startURL(m_manifestURL, stringValue);
|
|
if (!startURL.isValid()) {
|
|
logManifestPropertyInvalidURL("start_url"_s);
|
|
return documentURL;
|
|
}
|
|
|
|
if (!protocolHostAndPortAreEqual(startURL, documentURL)) {
|
|
auto startURLOrigin = SecurityOrigin::create(startURL);
|
|
auto documentOrigin = SecurityOrigin::create(documentURL);
|
|
logDeveloperWarning(makeString("The start_url's origin of \""_s, startURLOrigin->toString(), "\" is different from the document's origin of \""_s, documentOrigin->toString(), "\"."_s));
|
|
return documentURL;
|
|
}
|
|
|
|
return startURL;
|
|
}
|
|
|
|
ApplicationManifest::Display ApplicationManifestParser::parseDisplay(const JSON::Object& manifest)
|
|
{
|
|
auto value = manifest.getValue("display"_s);
|
|
if (!value)
|
|
return ApplicationManifest::Display::Browser;
|
|
|
|
auto stringValue = value->asString();
|
|
if (!stringValue) {
|
|
logManifestPropertyNotAString("display"_s);
|
|
return ApplicationManifest::Display::Browser;
|
|
}
|
|
|
|
stringValue = stringValue.stripWhiteSpace().convertToASCIILowercase();
|
|
|
|
if (stringValue == "fullscreen")
|
|
return ApplicationManifest::Display::Fullscreen;
|
|
if (stringValue == "standalone")
|
|
return ApplicationManifest::Display::Standalone;
|
|
if (stringValue == "minimal-ui")
|
|
return ApplicationManifest::Display::MinimalUI;
|
|
if (stringValue == "browser")
|
|
return ApplicationManifest::Display::Browser;
|
|
|
|
logDeveloperWarning(makeString("\""_s, stringValue, "\" is not a valid display mode."_s));
|
|
return ApplicationManifest::Display::Browser;
|
|
}
|
|
|
|
String ApplicationManifestParser::parseName(const JSON::Object& manifest)
|
|
{
|
|
return parseGenericString(manifest, "name"_s);
|
|
}
|
|
|
|
String ApplicationManifestParser::parseDescription(const JSON::Object& manifest)
|
|
{
|
|
return parseGenericString(manifest, "description"_s);
|
|
}
|
|
|
|
String ApplicationManifestParser::parseShortName(const JSON::Object& manifest)
|
|
{
|
|
return parseGenericString(manifest, "short_name"_s);
|
|
}
|
|
|
|
static bool isInScope(const URL& scopeURL, const URL& targetURL)
|
|
{
|
|
// 1. If scopeURL is undefined (i.e., it is unbounded because of an error or it was not declared in the manifest), return true.
|
|
if (scopeURL.isNull() || scopeURL.isEmpty())
|
|
return true;
|
|
|
|
// 2. Let target be a new URL using targetURL as input. If target is failure, return false.
|
|
if (!targetURL.isValid())
|
|
return false;
|
|
|
|
// 3. Let scopePath be the elements of scopes's path, separated by U+002F (/).
|
|
auto scopePath = scopeURL.path();
|
|
|
|
// 4. Let targetPath be the elements of target's path, separated by U+002F (/).
|
|
auto targetPath = targetURL.path();
|
|
|
|
// 5. If target is same origin as scope and targetPath starts with scopePath, return true.
|
|
if (protocolHostAndPortAreEqual(scopeURL, targetURL) && targetPath.startsWith(scopePath))
|
|
return true;
|
|
|
|
// 6. Otherwise, return false.
|
|
return false;
|
|
}
|
|
|
|
URL ApplicationManifestParser::parseScope(const JSON::Object& manifest, const URL& documentURL, const URL& startURL)
|
|
{
|
|
URL defaultScope { startURL, "./" };
|
|
|
|
auto value = manifest.getValue("scope");
|
|
if (!value)
|
|
return defaultScope;
|
|
|
|
auto stringValue = value->asString();
|
|
if (!stringValue) {
|
|
logManifestPropertyNotAString("scope"_s);
|
|
return defaultScope;
|
|
}
|
|
|
|
if (stringValue.isEmpty())
|
|
return defaultScope;
|
|
|
|
URL scopeURL(m_manifestURL, stringValue);
|
|
if (!scopeURL.isValid()) {
|
|
logManifestPropertyInvalidURL("scope"_s);
|
|
return defaultScope;
|
|
}
|
|
|
|
if (!protocolHostAndPortAreEqual(scopeURL, documentURL)) {
|
|
auto scopeURLOrigin = SecurityOrigin::create(scopeURL);
|
|
auto documentOrigin = SecurityOrigin::create(documentURL);
|
|
logDeveloperWarning(makeString("The scope's origin of \""_s, scopeURLOrigin->toString(), "\" is different from the document's origin of \""_s, documentOrigin->toString(), "\"."_s));
|
|
return defaultScope;
|
|
}
|
|
|
|
if (!isInScope(scopeURL, startURL)) {
|
|
logDeveloperWarning("The start URL is not within scope of the provided scope URL."_s);
|
|
return defaultScope;
|
|
}
|
|
|
|
return scopeURL;
|
|
}
|
|
|
|
Color ApplicationManifestParser::parseColor(const JSON::Object& manifest, const String& propertyName)
|
|
{
|
|
return CSSParser::parseColor(parseGenericString(manifest, propertyName));
|
|
}
|
|
|
|
String ApplicationManifestParser::parseGenericString(const JSON::Object& manifest, const String& propertyName)
|
|
{
|
|
auto value = manifest.getValue(propertyName);
|
|
if (!value)
|
|
return { };
|
|
|
|
auto stringValue = value->asString();
|
|
if (!stringValue) {
|
|
logManifestPropertyNotAString(propertyName);
|
|
return { };
|
|
}
|
|
|
|
return stringValue.stripWhiteSpace();
|
|
}
|
|
|
|
} // namespace WebCore
|
|
|
|
#endif // ENABLE(APPLICATION_MANIFEST)
|