OriginParser.cpp (12391B)
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/. */ 6 7 #include "OriginParser.h" 8 9 #include <regex> 10 11 #include "mozilla/OriginAttributes.h" 12 #include "mozilla/dom/quota/Constants.h" 13 #include "mozilla/dom/quota/QuotaCommon.h" 14 15 namespace mozilla::dom::quota { 16 17 // static 18 auto OriginParser::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec, 19 OriginAttributes* aAttrs, 20 nsCString& aOriginalSuffix) -> ResultType { 21 MOZ_ASSERT(!aOrigin.IsEmpty()); 22 MOZ_ASSERT(aAttrs); 23 24 nsCString origin(aOrigin); 25 int32_t pos = origin.RFindChar('^'); 26 27 if (pos == kNotFound) { 28 aOriginalSuffix.Truncate(); 29 } else { 30 aOriginalSuffix = Substring(origin, pos); 31 } 32 33 OriginAttributes originAttributes; 34 35 nsCString originNoSuffix; 36 bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix); 37 if (!ok) { 38 return InvalidOrigin; 39 } 40 41 OriginParser parser(originNoSuffix); 42 43 *aAttrs = originAttributes; 44 return parser.Parse(aSpec); 45 } 46 47 auto OriginParser::Parse(nsACString& aSpec) -> ResultType { 48 while (mTokenizer.hasMoreTokens()) { 49 const nsDependentCSubstring& token = mTokenizer.nextToken(); 50 51 HandleToken(token); 52 53 if (mError) { 54 break; 55 } 56 57 if (!mHandledTokens.IsEmpty()) { 58 mHandledTokens.AppendLiteral(", "); 59 } 60 mHandledTokens.Append('\''); 61 mHandledTokens.Append(token); 62 mHandledTokens.Append('\''); 63 } 64 65 if (!mError && mTokenizer.separatorAfterCurrentToken()) { 66 HandleTrailingSeparator(); 67 } 68 69 if (mError) { 70 QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(), 71 mHandledTokens.get()); 72 73 return (mSchemeType == eChrome || mSchemeType == eAbout) ? ObsoleteOrigin 74 : InvalidOrigin; 75 } 76 77 MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator); 78 79 // For IPv6 URL, it should at least have three groups. 80 MOZ_ASSERT_IF(mIPGroup > 0, mIPGroup >= 3); 81 82 nsAutoCString spec(mScheme); 83 84 if (mSchemeType == eFile) { 85 spec.AppendLiteral("://"); 86 87 if (mUniversalFileOrigin) { 88 MOZ_ASSERT(mPathnameComponents.Length() == 1); 89 90 spec.Append(mPathnameComponents[0]); 91 } else { 92 for (uint32_t count = mPathnameComponents.Length(), index = 0; 93 index < count; index++) { 94 spec.Append('/'); 95 spec.Append(mPathnameComponents[index]); 96 } 97 } 98 99 aSpec = spec; 100 101 return ValidOrigin; 102 } 103 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; 109 } 110 spec.Append(':'); 111 } else if (mSchemeType != eChrome) { 112 spec.AppendLiteral("://"); 113 } 114 115 spec.Append(mHost); 116 117 if (!mPort.IsNull()) { 118 spec.Append(':'); 119 spec.AppendInt(mPort.Value()); 120 } 121 122 aSpec = spec; 123 124 return mScheme.EqualsLiteral("app") ? ObsoleteOrigin : ValidOrigin; 125 } 126 127 void OriginParser::HandleScheme(const nsDependentCSubstring& aToken) { 128 MOZ_ASSERT(!aToken.IsEmpty()); 129 MOZ_ASSERT(mState == eExpectingAppIdOrScheme || mState == eExpectingScheme); 130 131 bool isAbout = false; 132 bool isMozSafeAbout = false; 133 bool isFile = 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")) { 144 mScheme = aToken; 145 146 if (isAbout) { 147 mSchemeType = eAbout; 148 mState = isMozSafeAbout ? eExpectingEmptyToken1OrHost : eExpectingHost; 149 } else if (isChrome) { 150 mSchemeType = eChrome; 151 if (mTokenizer.hasMoreTokens()) { 152 mError = true; 153 } 154 mState = eComplete; 155 } else { 156 if (isFile) { 157 mSchemeType = eFile; 158 } 159 mState = eExpectingEmptyToken1; 160 } 161 162 return; 163 } 164 165 QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken).get()); 166 167 mError = true; 168 } 169 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); 176 177 mPathnameComponents.AppendElement(aToken); 178 179 mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent 180 : eComplete; 181 } 182 183 void OriginParser::HandleToken(const nsDependentCSubstring& aToken) { 184 switch (mState) { 185 case eExpectingAppIdOrScheme: { 186 if (aToken.IsEmpty()) { 187 QM_WARNING("Expected an app id or scheme (not an empty string)!"); 188 189 mError = true; 190 return; 191 } 192 193 if (IsAsciiDigit(aToken.First())) { 194 // nsDependentCSubstring doesn't provice ToInteger() 195 nsCString token(aToken); 196 197 nsresult rv; 198 (void)token.ToInteger(&rv); 199 if (NS_SUCCEEDED(rv)) { 200 mState = eExpectingInMozBrowser; 201 return; 202 } 203 } 204 205 HandleScheme(aToken); 206 207 return; 208 } 209 210 case eExpectingInMozBrowser: { 211 if (aToken.Length() != 1) { 212 QM_WARNING("'%zu' is not a valid length for the inMozBrowser flag!", 213 aToken.Length()); 214 215 mError = true; 216 return; 217 } 218 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()); 222 223 mError = true; 224 return; 225 } 226 227 mState = eExpectingScheme; 228 229 return; 230 } 231 232 case eExpectingScheme: { 233 if (aToken.IsEmpty()) { 234 QM_WARNING("Expected a scheme (not an empty string)!"); 235 236 mError = true; 237 return; 238 } 239 240 HandleScheme(aToken); 241 242 return; 243 } 244 245 case eExpectingEmptyToken1: { 246 if (!aToken.IsEmpty()) { 247 QM_WARNING("Expected the first empty token!"); 248 249 mError = true; 250 return; 251 } 252 253 mState = eExpectingEmptyToken2; 254 255 return; 256 } 257 258 case eExpectingEmptyToken2: { 259 if (!aToken.IsEmpty()) { 260 QM_WARNING("Expected the second empty token!"); 261 262 mError = true; 263 return; 264 } 265 266 if (mSchemeType == eFile) { 267 mState = eExpectingEmptyTokenOrUniversalFileOrigin; 268 } else { 269 if (mSchemeType == eAbout) { 270 mMaybeObsolete = true; 271 } 272 mState = eExpectingHost; 273 } 274 275 return; 276 } 277 278 case eExpectingEmptyTokenOrUniversalFileOrigin: { 279 MOZ_ASSERT(mSchemeType == eFile); 280 281 if (aToken.IsEmpty()) { 282 mState = mTokenizer.hasMoreTokens() 283 ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent 284 : eComplete; 285 286 return; 287 } 288 289 if (aToken.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) { 290 mUniversalFileOrigin = true; 291 292 mPathnameComponents.AppendElement(aToken); 293 294 mState = eComplete; 295 296 return; 297 } 298 299 QM_WARNING( 300 "Expected the third empty token or " 301 "UNIVERSAL_FILE_URI_ORIGIN!"); 302 303 mError = true; 304 return; 305 } 306 307 case eExpectingHost: { 308 if (aToken.IsEmpty()) { 309 QM_WARNING("Expected a host (not an empty string)!"); 310 311 mError = true; 312 return; 313 } 314 315 mHost = aToken; 316 317 if (aToken.First() == '[') { 318 MOZ_ASSERT(mIPGroup == 0); 319 320 ++mIPGroup; 321 mState = eExpectingIPV6Token; 322 323 MOZ_ASSERT(mTokenizer.hasMoreTokens()); 324 return; 325 } 326 327 if (mTokenizer.hasMoreTokens()) { 328 if (mSchemeType == eAbout) { 329 QM_WARNING("Expected an empty string after host!"); 330 331 mError = true; 332 return; 333 } 334 335 mState = eExpectingPort; 336 337 return; 338 } 339 340 mState = eComplete; 341 342 return; 343 } 344 345 case eExpectingPort: { 346 MOZ_ASSERT(mSchemeType == eNone); 347 348 if (aToken.IsEmpty()) { 349 QM_WARNING("Expected a port (not an empty string)!"); 350 351 mError = true; 352 return; 353 } 354 355 // nsDependentCSubstring doesn't provice ToInteger() 356 nsCString token(aToken); 357 358 nsresult rv; 359 uint32_t port = token.ToInteger(&rv); 360 if (NS_SUCCEEDED(rv)) { 361 mPort.SetValue() = port; 362 } else { 363 QM_WARNING("'%s' is not a valid port number!", token.get()); 364 365 mError = true; 366 return; 367 } 368 369 mState = eComplete; 370 371 return; 372 } 373 374 case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: { 375 MOZ_ASSERT(mSchemeType == eFile); 376 377 if (aToken.IsEmpty()) { 378 mPathnameComponents.AppendElement(""_ns); 379 380 mState = mTokenizer.hasMoreTokens() 381 ? eExpectingEmptyTokenOrPathnameComponent 382 : eComplete; 383 384 return; 385 } 386 387 if (aToken.Length() == 1 && IsAsciiAlpha(aToken.First())) { 388 mMaybeDriveLetter = true; 389 390 mPathnameComponents.AppendElement(aToken); 391 392 mState = mTokenizer.hasMoreTokens() 393 ? eExpectingEmptyTokenOrPathnameComponent 394 : eComplete; 395 396 return; 397 } 398 399 HandlePathnameComponent(aToken); 400 401 return; 402 } 403 404 case eExpectingEmptyTokenOrPathnameComponent: { 405 MOZ_ASSERT(mSchemeType == eFile); 406 407 if (aToken.IsEmpty()) { 408 if (mMaybeDriveLetter) { 409 MOZ_ASSERT(mPathnameComponents.Length() == 1); 410 411 nsCString& pathnameComponent = mPathnameComponents[0]; 412 pathnameComponent.Append(':'); 413 414 mMaybeDriveLetter = false; 415 } else { 416 mPathnameComponents.AppendElement(""_ns); 417 } 418 419 mState = mTokenizer.hasMoreTokens() 420 ? eExpectingEmptyTokenOrPathnameComponent 421 : eComplete; 422 423 return; 424 } 425 426 HandlePathnameComponent(aToken); 427 428 return; 429 } 430 431 case eExpectingEmptyToken1OrHost: { 432 MOZ_ASSERT(mSchemeType == eAbout && 433 mScheme.EqualsLiteral("moz-safe-about")); 434 435 if (aToken.IsEmpty()) { 436 mState = eExpectingEmptyToken2; 437 } else { 438 mHost = aToken; 439 mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete; 440 } 441 442 return; 443 } 444 445 case eExpectingIPV6Token: { 446 // A safe check for preventing infinity recursion. 447 if (++mIPGroup > 8) { 448 mError = true; 449 return; 450 } 451 452 mHost.AppendLiteral(":"); 453 mHost.Append(aToken); 454 if (!aToken.IsEmpty() && aToken.Last() == ']') { 455 mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete; 456 } 457 458 return; 459 } 460 461 default: 462 MOZ_CRASH("Should never get here!"); 463 } 464 } 465 466 void OriginParser::HandleTrailingSeparator() { 467 MOZ_ASSERT(mState == eComplete); 468 MOZ_ASSERT(mSchemeType == eFile); 469 470 mPathnameComponents.AppendElement(""_ns); 471 472 mState = eHandledTrailingSeparator; 473 } 474 475 bool IsUUIDOrigin(const nsCString& aOrigin) { 476 if (!StringBeginsWith(aOrigin, kUUIDOriginScheme)) { 477 return false; 478 } 479 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}$"); 483 484 return regex_match(aOrigin.get(), pattern); 485 } 486 487 bool IsUserContextSuffix(const nsACString& aSuffix, uint32_t aUserContextId) { 488 OriginAttributes originAttributes; 489 MOZ_ALWAYS_TRUE(originAttributes.PopulateFromSuffix(aSuffix)); 490 return originAttributes.mUserContextId == aUserContextId; 491 } 492 493 bool IsUserContextPattern(const OriginAttributesPattern& aPattern, 494 uint32_t aUserContextId) { 495 const auto& userContextId = aPattern.mUserContextId; 496 497 if (!userContextId.WasPassed()) { 498 return false; 499 } 500 501 return userContextId.Value() == aUserContextId; 502 } 503 504 } // namespace mozilla::dom::quota