blob: fdb8ce292d738da42038aa965af3aca178a80ceb [file] [log] [blame]
commit-queue@webkit.org99b22742017-12-05 00:57:33 +00001/*
2 * Copyright (C) 2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ApplicationManifestParser.h"
28
29#if ENABLE(APPLICATION_MANIFEST)
30
31#include "SecurityOrigin.h"
32#include <JavaScriptCore/ConsoleMessage.h>
33
34namespace WebCore {
35
36ApplicationManifest ApplicationManifestParser::parse(ScriptExecutionContext& scriptExecutionContext, const String& source, const URL& manifestURL, const URL& documentURL)
37{
38 ApplicationManifestParser parser { &scriptExecutionContext };
39 return parser.parseManifest(source, manifestURL, documentURL);
40}
41
42ApplicationManifest ApplicationManifestParser::parse(const String& source, const URL& manifestURL, const URL& documentURL)
43{
44 ApplicationManifestParser parser { nullptr };
45 return parser.parseManifest(source, manifestURL, documentURL);
46}
47
48ApplicationManifestParser::ApplicationManifestParser(RefPtr<ScriptExecutionContext> consoleContext)
49 : m_consoleContext(consoleContext)
50{
51}
52
53ApplicationManifest ApplicationManifestParser::parseManifest(const String& text, const URL& manifestURL, const URL& documentURL)
54{
55 m_manifestURL = manifestURL;
56
57 RefPtr<JSON::Value> jsonValue;
58 if (!JSON::Value::parseJSON(text, jsonValue)) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +000059 logDeveloperWarning("The manifest is not valid JSON data."_s);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +000060 jsonValue = JSON::Object::create();
61 }
62
63 RefPtr<JSON::Object> manifest;
64 if (!jsonValue->asObject(manifest)) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +000065 logDeveloperWarning("The manifest is not a JSON value of type \"object\"."_s);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +000066 manifest = JSON::Object::create();
67 }
68
69 ApplicationManifest parsedManifest;
70
71 parsedManifest.startURL = parseStartURL(*manifest, documentURL);
commit-queue@webkit.orgc02dfcf2017-12-07 20:04:37 +000072 parsedManifest.display = parseDisplay(*manifest);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +000073 parsedManifest.name = parseName(*manifest);
74 parsedManifest.description = parseDescription(*manifest);
75 parsedManifest.shortName = parseShortName(*manifest);
76 parsedManifest.scope = parseScope(*manifest, documentURL, parsedManifest.startURL);
77
78 return parsedManifest;
79}
80
81void ApplicationManifestParser::logManifestPropertyNotAString(const String& propertyName)
82{
utatane.tea@gmail.com84077632018-06-23 08:39:34 +000083 logDeveloperWarning(makeString("The value of \""_s, propertyName, "\" is not a string."_s));
commit-queue@webkit.org99b22742017-12-05 00:57:33 +000084}
85
86void ApplicationManifestParser::logManifestPropertyInvalidURL(const String& propertyName)
87{
utatane.tea@gmail.com84077632018-06-23 08:39:34 +000088 logDeveloperWarning(makeString("The value of \""_s, propertyName, "\" is not a valid URL."_s));
commit-queue@webkit.org99b22742017-12-05 00:57:33 +000089}
90
91void ApplicationManifestParser::logDeveloperWarning(const String& message)
92{
93 if (m_consoleContext)
utatane.tea@gmail.com84077632018-06-23 08:39:34 +000094 m_consoleContext->addConsoleMessage(std::make_unique<Inspector::ConsoleMessage>(JSC::MessageSource::Other, JSC::MessageType::Log, JSC::MessageLevel::Warning, makeString("Parsing application manifest "_s, m_manifestURL.string(), ": "_s, message)));
commit-queue@webkit.org99b22742017-12-05 00:57:33 +000095}
96
97URL ApplicationManifestParser::parseStartURL(const JSON::Object& manifest, const URL& documentURL)
98{
99 RefPtr<JSON::Value> value;
100 if (!manifest.getValue("start_url", value))
101 return documentURL;
102
103 String stringValue;
104 if (!value->asString(stringValue)) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000105 logManifestPropertyNotAString("start_url"_s);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000106 return documentURL;
107 }
108
109 if (stringValue.isEmpty())
110 return documentURL;
111
112 URL startURL(m_manifestURL, stringValue);
113 if (!startURL.isValid()) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000114 logManifestPropertyInvalidURL("start_url"_s);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000115 return documentURL;
116 }
117
118 if (!protocolHostAndPortAreEqual(startURL, documentURL)) {
119 auto startURLOrigin = SecurityOrigin::create(startURL);
120 auto documentOrigin = SecurityOrigin::create(documentURL);
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000121 logDeveloperWarning(makeString("The start_url's origin of \""_s, startURLOrigin->toString(), "\" is different from the document's origin of \""_s, documentOrigin->toString(), "\"."_s));
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000122 return documentURL;
123 }
124
125 return startURL;
126}
127
commit-queue@webkit.orgc02dfcf2017-12-07 20:04:37 +0000128ApplicationManifest::Display ApplicationManifestParser::parseDisplay(const JSON::Object& manifest)
129{
130 RefPtr<JSON::Value> value;
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000131 if (!manifest.getValue("display"_s, value))
commit-queue@webkit.orgc02dfcf2017-12-07 20:04:37 +0000132 return ApplicationManifest::Display::Browser;
133
134 String stringValue;
135 if (!value->asString(stringValue)) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000136 logManifestPropertyNotAString("display"_s);
commit-queue@webkit.orgc02dfcf2017-12-07 20:04:37 +0000137 return ApplicationManifest::Display::Browser;
138 }
139
140 stringValue = stringValue.stripWhiteSpace().convertToASCIILowercase();
141
142 if (stringValue == "fullscreen")
143 return ApplicationManifest::Display::Fullscreen;
144 if (stringValue == "standalone")
145 return ApplicationManifest::Display::Standalone;
146 if (stringValue == "minimal-ui")
147 return ApplicationManifest::Display::MinimalUI;
148 if (stringValue == "browser")
149 return ApplicationManifest::Display::Browser;
150
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000151 logDeveloperWarning(makeString("\""_s, stringValue, "\" is not a valid display mode."_s));
commit-queue@webkit.orgc02dfcf2017-12-07 20:04:37 +0000152 return ApplicationManifest::Display::Browser;
153}
154
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000155String ApplicationManifestParser::parseName(const JSON::Object& manifest)
156{
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000157 return parseGenericString(manifest, "name"_s);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000158}
159
160String ApplicationManifestParser::parseDescription(const JSON::Object& manifest)
161{
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000162 return parseGenericString(manifest, "description"_s);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000163}
164
165String ApplicationManifestParser::parseShortName(const JSON::Object& manifest)
166{
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000167 return parseGenericString(manifest, "short_name"_s);
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000168}
169
170static bool isInScope(const URL& scopeURL, const URL& targetURL)
171{
172 // 1. If scopeURL is undefined (i.e., it is unbounded because of an error or it was not declared in the manifest), return true.
173 if (scopeURL.isNull() || scopeURL.isEmpty())
174 return true;
175
176 // 2. Let target be a new URL using targetURL as input. If target is failure, return false.
177 if (!targetURL.isValid())
178 return false;
179
180 // 3. Let scopePath be the elements of scopes's path, separated by U+002F (/).
181 auto scopePath = scopeURL.path();
182
183 // 4. Let targetPath be the elements of target's path, separated by U+002F (/).
184 auto targetPath = targetURL.path();
185
186 // 5. If target is same origin as scope and targetPath starts with scopePath, return true.
187 if (protocolHostAndPortAreEqual(scopeURL, targetURL) && targetPath.startsWith(scopePath))
188 return true;
189
190 // 6. Otherwise, return false.
191 return false;
192}
193
194URL ApplicationManifestParser::parseScope(const JSON::Object& manifest, const URL& documentURL, const URL& startURL)
195{
david_quesada@apple.com5d8b5512018-02-03 00:49:56 +0000196 URL defaultScope { startURL, "./" };
197
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000198 RefPtr<JSON::Value> value;
199 if (!manifest.getValue("scope", value))
david_quesada@apple.com5d8b5512018-02-03 00:49:56 +0000200 return defaultScope;
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000201
202 String stringValue;
203 if (!value->asString(stringValue)) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000204 logManifestPropertyNotAString("scope"_s);
david_quesada@apple.com5d8b5512018-02-03 00:49:56 +0000205 return defaultScope;
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000206 }
207
208 if (stringValue.isEmpty())
david_quesada@apple.com5d8b5512018-02-03 00:49:56 +0000209 return defaultScope;
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000210
211 URL scopeURL(m_manifestURL, stringValue);
212 if (!scopeURL.isValid()) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000213 logManifestPropertyInvalidURL("scope"_s);
david_quesada@apple.com5d8b5512018-02-03 00:49:56 +0000214 return defaultScope;
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000215 }
216
217 if (!protocolHostAndPortAreEqual(scopeURL, documentURL)) {
218 auto scopeURLOrigin = SecurityOrigin::create(scopeURL);
219 auto documentOrigin = SecurityOrigin::create(documentURL);
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000220 logDeveloperWarning(makeString("The scope's origin of \""_s, scopeURLOrigin->toString(), "\" is different from the document's origin of \""_s, documentOrigin->toString(), "\"."_s));
david_quesada@apple.com5d8b5512018-02-03 00:49:56 +0000221 return defaultScope;
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000222 }
223
224 if (!isInScope(scopeURL, startURL)) {
utatane.tea@gmail.com84077632018-06-23 08:39:34 +0000225 logDeveloperWarning("The start URL is not within scope of the provided scope URL."_s);
david_quesada@apple.com5d8b5512018-02-03 00:49:56 +0000226 return defaultScope;
commit-queue@webkit.org99b22742017-12-05 00:57:33 +0000227 }
228
229 return scopeURL;
230}
231
232String ApplicationManifestParser::parseGenericString(const JSON::Object& manifest, const String& propertyName)
233{
234 RefPtr<JSON::Value> value;
235 if (!manifest.getValue(propertyName, value))
236 return { };
237
238 String stringValue;
239 if (!value->asString(stringValue)) {
240 logManifestPropertyNotAString(propertyName);
241 return { };
242 }
243
244 return stringValue.stripWhiteSpace();
245}
246
247} // namespace WebCore
248
249#endif // ENABLE(APPLICATION_MANIFEST)