1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "OriginParser.h"
11 #include "mozilla/OriginAttributes.h"
12 #include "mozilla/dom/quota/Constants.h"
13 #include "mozilla/dom/quota/QuotaCommon.h"
15 namespace mozilla::dom::quota
{
18 auto OriginParser::ParseOrigin(const nsACString
& aOrigin
, nsCString
& aSpec
,
19 OriginAttributes
* aAttrs
,
20 nsCString
& aOriginalSuffix
) -> ResultType
{
21 MOZ_ASSERT(!aOrigin
.IsEmpty());
24 nsCString
origin(aOrigin
);
25 int32_t pos
= origin
.RFindChar('^');
27 if (pos
== kNotFound
) {
28 aOriginalSuffix
.Truncate();
30 aOriginalSuffix
= Substring(origin
, pos
);
33 OriginAttributes originAttributes
;
35 nsCString originNoSuffix
;
36 bool ok
= originAttributes
.PopulateFromOrigin(aOrigin
, originNoSuffix
);
41 OriginParser
parser(originNoSuffix
);
43 *aAttrs
= originAttributes
;
44 return parser
.Parse(aSpec
);
47 auto OriginParser::Parse(nsACString
& aSpec
) -> ResultType
{
48 while (mTokenizer
.hasMoreTokens()) {
49 const nsDependentCSubstring
& token
= mTokenizer
.nextToken();
57 if (!mHandledTokens
.IsEmpty()) {
58 mHandledTokens
.AppendLiteral(", ");
60 mHandledTokens
.Append('\'');
61 mHandledTokens
.Append(token
);
62 mHandledTokens
.Append('\'');
65 if (!mError
&& mTokenizer
.separatorAfterCurrentToken()) {
66 HandleTrailingSeparator();
70 QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin
.get(),
71 mHandledTokens
.get());
73 return (mSchemeType
== eChrome
|| mSchemeType
== eAbout
) ? ObsoleteOrigin
77 MOZ_ASSERT(mState
== eComplete
|| mState
== eHandledTrailingSeparator
);
79 // For IPv6 URL, it should at least have three groups.
80 MOZ_ASSERT_IF(mIPGroup
> 0, mIPGroup
>= 3);
82 nsAutoCString
spec(mScheme
);
84 if (mSchemeType
== eFile
) {
85 spec
.AppendLiteral("://");
87 if (mUniversalFileOrigin
) {
88 MOZ_ASSERT(mPathnameComponents
.Length() == 1);
90 spec
.Append(mPathnameComponents
[0]);
92 for (uint32_t count
= mPathnameComponents
.Length(), index
= 0;
93 index
< count
; index
++) {
95 spec
.Append(mPathnameComponents
[index
]);
104 if (mSchemeType
== eAbout
) {
105 if (mMaybeObsolete
) {
106 // The "moz-safe-about+++home" was acciedntally created by a buggy nightly
107 // and can be safely removed.
108 return mHost
.EqualsLiteral("home") ? ObsoleteOrigin
: InvalidOrigin
;
111 } else if (mSchemeType
!= eChrome
) {
112 spec
.AppendLiteral("://");
117 if (!mPort
.IsNull()) {
119 spec
.AppendInt(mPort
.Value());
124 return mScheme
.EqualsLiteral("app") ? ObsoleteOrigin
: ValidOrigin
;
127 void OriginParser::HandleScheme(const nsDependentCSubstring
& aToken
) {
128 MOZ_ASSERT(!aToken
.IsEmpty());
129 MOZ_ASSERT(mState
== eExpectingAppIdOrScheme
|| mState
== eExpectingScheme
);
131 bool isAbout
= false;
132 bool isMozSafeAbout
= false;
134 bool isChrome
= false;
135 if (aToken
.EqualsLiteral("http") || aToken
.EqualsLiteral("https") ||
136 (isAbout
= aToken
.EqualsLiteral("about") ||
137 (isMozSafeAbout
= aToken
.EqualsLiteral("moz-safe-about"))) ||
138 aToken
.EqualsLiteral("indexeddb") ||
139 (isFile
= aToken
.EqualsLiteral("file")) || aToken
.EqualsLiteral("app") ||
140 aToken
.EqualsLiteral("resource") ||
141 aToken
.EqualsLiteral("moz-extension") ||
142 (isChrome
= aToken
.EqualsLiteral(kChromeOrigin
)) ||
143 aToken
.EqualsLiteral("uuid")) {
147 mSchemeType
= eAbout
;
148 mState
= isMozSafeAbout
? eExpectingEmptyToken1OrHost
: eExpectingHost
;
149 } else if (isChrome
) {
150 mSchemeType
= eChrome
;
151 if (mTokenizer
.hasMoreTokens()) {
159 mState
= eExpectingEmptyToken1
;
165 QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken
).get());
170 void OriginParser::HandlePathnameComponent(
171 const nsDependentCSubstring
& aToken
) {
172 MOZ_ASSERT(!aToken
.IsEmpty());
173 MOZ_ASSERT(mState
== eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
||
174 mState
== eExpectingEmptyTokenOrPathnameComponent
);
175 MOZ_ASSERT(mSchemeType
== eFile
);
177 mPathnameComponents
.AppendElement(aToken
);
179 mState
= mTokenizer
.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
183 void OriginParser::HandleToken(const nsDependentCSubstring
& aToken
) {
185 case eExpectingAppIdOrScheme
: {
186 if (aToken
.IsEmpty()) {
187 QM_WARNING("Expected an app id or scheme (not an empty string)!");
193 if (IsAsciiDigit(aToken
.First())) {
194 // nsDependentCSubstring doesn't provice ToInteger()
195 nsCString
token(aToken
);
198 Unused
<< token
.ToInteger(&rv
);
199 if (NS_SUCCEEDED(rv
)) {
200 mState
= eExpectingInMozBrowser
;
205 HandleScheme(aToken
);
210 case eExpectingInMozBrowser
: {
211 if (aToken
.Length() != 1) {
212 QM_WARNING("'%zu' is not a valid length for the inMozBrowser flag!",
219 if ((aToken
.First() != 't') && (aToken
.First() != 'f')) {
220 QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
221 nsCString(aToken
).get());
227 mState
= eExpectingScheme
;
232 case eExpectingScheme
: {
233 if (aToken
.IsEmpty()) {
234 QM_WARNING("Expected a scheme (not an empty string)!");
240 HandleScheme(aToken
);
245 case eExpectingEmptyToken1
: {
246 if (!aToken
.IsEmpty()) {
247 QM_WARNING("Expected the first empty token!");
253 mState
= eExpectingEmptyToken2
;
258 case eExpectingEmptyToken2
: {
259 if (!aToken
.IsEmpty()) {
260 QM_WARNING("Expected the second empty token!");
266 if (mSchemeType
== eFile
) {
267 mState
= eExpectingEmptyTokenOrUniversalFileOrigin
;
269 if (mSchemeType
== eAbout
) {
270 mMaybeObsolete
= true;
272 mState
= eExpectingHost
;
278 case eExpectingEmptyTokenOrUniversalFileOrigin
: {
279 MOZ_ASSERT(mSchemeType
== eFile
);
281 if (aToken
.IsEmpty()) {
282 mState
= mTokenizer
.hasMoreTokens()
283 ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
289 if (aToken
.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) {
290 mUniversalFileOrigin
= true;
292 mPathnameComponents
.AppendElement(aToken
);
300 "Expected the third empty token or "
301 "UNIVERSAL_FILE_URI_ORIGIN!");
307 case eExpectingHost
: {
308 if (aToken
.IsEmpty()) {
309 QM_WARNING("Expected a host (not an empty string)!");
317 if (aToken
.First() == '[') {
318 MOZ_ASSERT(mIPGroup
== 0);
321 mState
= eExpectingIPV6Token
;
323 MOZ_ASSERT(mTokenizer
.hasMoreTokens());
327 if (mTokenizer
.hasMoreTokens()) {
328 if (mSchemeType
== eAbout
) {
329 QM_WARNING("Expected an empty string after host!");
335 mState
= eExpectingPort
;
345 case eExpectingPort
: {
346 MOZ_ASSERT(mSchemeType
== eNone
);
348 if (aToken
.IsEmpty()) {
349 QM_WARNING("Expected a port (not an empty string)!");
355 // nsDependentCSubstring doesn't provice ToInteger()
356 nsCString
token(aToken
);
359 uint32_t port
= token
.ToInteger(&rv
);
360 if (NS_SUCCEEDED(rv
)) {
361 mPort
.SetValue() = port
;
363 QM_WARNING("'%s' is not a valid port number!", token
.get());
374 case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
: {
375 MOZ_ASSERT(mSchemeType
== eFile
);
377 if (aToken
.IsEmpty()) {
378 mPathnameComponents
.AppendElement(""_ns
);
380 mState
= mTokenizer
.hasMoreTokens()
381 ? eExpectingEmptyTokenOrPathnameComponent
387 if (aToken
.Length() == 1 && IsAsciiAlpha(aToken
.First())) {
388 mMaybeDriveLetter
= true;
390 mPathnameComponents
.AppendElement(aToken
);
392 mState
= mTokenizer
.hasMoreTokens()
393 ? eExpectingEmptyTokenOrPathnameComponent
399 HandlePathnameComponent(aToken
);
404 case eExpectingEmptyTokenOrPathnameComponent
: {
405 MOZ_ASSERT(mSchemeType
== eFile
);
407 if (aToken
.IsEmpty()) {
408 if (mMaybeDriveLetter
) {
409 MOZ_ASSERT(mPathnameComponents
.Length() == 1);
411 nsCString
& pathnameComponent
= mPathnameComponents
[0];
412 pathnameComponent
.Append(':');
414 mMaybeDriveLetter
= false;
416 mPathnameComponents
.AppendElement(""_ns
);
419 mState
= mTokenizer
.hasMoreTokens()
420 ? eExpectingEmptyTokenOrPathnameComponent
426 HandlePathnameComponent(aToken
);
431 case eExpectingEmptyToken1OrHost
: {
432 MOZ_ASSERT(mSchemeType
== eAbout
&&
433 mScheme
.EqualsLiteral("moz-safe-about"));
435 if (aToken
.IsEmpty()) {
436 mState
= eExpectingEmptyToken2
;
439 mState
= mTokenizer
.hasMoreTokens() ? eExpectingPort
: eComplete
;
445 case eExpectingIPV6Token
: {
446 // A safe check for preventing infinity recursion.
447 if (++mIPGroup
> 8) {
452 mHost
.AppendLiteral(":");
453 mHost
.Append(aToken
);
454 if (!aToken
.IsEmpty() && aToken
.Last() == ']') {
455 mState
= mTokenizer
.hasMoreTokens() ? eExpectingPort
: eComplete
;
462 MOZ_CRASH("Should never get here!");
466 void OriginParser::HandleTrailingSeparator() {
467 MOZ_ASSERT(mState
== eComplete
);
468 MOZ_ASSERT(mSchemeType
== eFile
);
470 mPathnameComponents
.AppendElement(""_ns
);
472 mState
= eHandledTrailingSeparator
;
475 bool IsUUIDOrigin(const nsCString
& aOrigin
) {
476 if (!StringBeginsWith(aOrigin
, kUUIDOriginScheme
)) {
480 static const std::regex
pattern(
481 "^uuid://[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab"
482 "][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$");
484 return regex_match(aOrigin
.get(), pattern
);
487 bool IsUserContextSuffix(const nsACString
& aSuffix
, uint32_t aUserContextId
) {
488 OriginAttributes originAttributes
;
489 MOZ_ALWAYS_TRUE(originAttributes
.PopulateFromSuffix(aSuffix
));
490 return originAttributes
.mUserContextId
== aUserContextId
;
493 bool IsUserContextPattern(const OriginAttributesPattern
& aPattern
,
494 uint32_t aUserContextId
) {
495 const auto& userContextId
= aPattern
.mUserContextId
;
497 if (!userContextId
.WasPassed()) {
501 return userContextId
.Value() == aUserContextId
;
504 } // namespace mozilla::dom::quota