/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CacheLog.h" #include "CacheFileUtils.h" #include "LoadContextInfo.h" #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "nsString.h" namespace mozilla { namespace net { namespace CacheFileUtils { namespace { // anon /** * A simple recursive descent parser for the mapping key. */ class KeyParser { public: KeyParser(nsACString::const_iterator aCaret, nsACString::const_iterator aEnd) : caret(aCaret) , end(aEnd) // Initialize attributes to their default values , appId(nsILoadContextInfo::NO_APP_ID) , isPrivate(false) , isInBrowser(false) , isAnonymous(false) // Initialize the cache key to a zero length by default , cacheKey(aEnd) , lastTag(0) { } private: // Current character being parsed nsACString::const_iterator caret; // The end of the buffer nsACString::const_iterator const end; // Results uint32_t appId; bool isPrivate; bool isInBrowser; bool isAnonymous; nsCString idEnhance; // Position of the cache key, if present nsACString::const_iterator cacheKey; // Keeps the last tag name, used for alphabetical sort checking char lastTag; bool ParseTags() { // Expects to be at the tag name or at the end if (caret == end) return true; // 'Read' the tag name and move to the next char char const tag = *caret++; // Check the alphabetical order, hard-fail on disobedience if (!(lastTag < tag || tag == ':')) return false; lastTag = tag; switch (tag) { case ':': // last possible tag, when present there is the cacheKey following, // not terminated with ',' and no need to unescape. cacheKey = caret; caret = end; return true; case 'p': isPrivate = true; break; case 'b': isInBrowser = true; break; case 'a': isAnonymous = true; break; case 'i': { nsAutoCString appIdString; if (!ParseValue(&appIdString)) return false; nsresult rv; int64_t appId64 = appIdString.ToInteger64(&rv); if (NS_FAILED(rv)) return false; // appid value is mandatory if (appId64 < 0 || appId64 > PR_UINT32_MAX) return false; // not in the range appId = static_cast(appId64); break; } case '~': if (!ParseValue(&idEnhance)) return false; break; default: if (!ParseValue()) // skip any tag values, optional return false; break; } // Recurse to the next tag return ParseNextTagOrEnd(); } bool ParseNextTagOrEnd() { // We expect a comma after every tag if (caret == end || *caret++ != ',') return false; // Go to another tag return ParseTags(); } bool ParseValue(nsACString * result = nullptr) { // If at the end, fail since we expect a comma ; value may be empty tho if (caret == end) return false; // Remeber where the value starts nsACString::const_iterator val = caret; nsACString::const_iterator comma = end; bool escape = false; while (caret != end) { nsACString::const_iterator at = caret; ++caret; // we can safely break/continue the loop now if (*at == ',') { if (comma != end) { // another comma (we have found ",," -> escape) comma = end; escape = true; } else { comma = at; } continue; } if (comma != end) { // after a single comma break; } } // At this point |comma| points to the last and lone ',' we've hit. // If a lone comma was not found, |comma| is at the end of the buffer, // that is not expected and we return failure. caret = comma; if (result) { if (escape) { // No ReplaceSubstring on nsACString.. nsAutoCString _result(Substring(val, caret)); _result.ReplaceSubstring(NS_LITERAL_CSTRING(",,"), NS_LITERAL_CSTRING(",")); result->Assign(_result); } else { result->Assign(Substring(val, caret)); } } return caret != end; } public: already_AddRefed Parse() { nsRefPtr info; if (ParseTags()) info = GetLoadContextInfo(isPrivate, appId, isInBrowser, isAnonymous); return info.forget(); } void URISpec(nsACString &result) { // cacheKey is either pointing to end or the position where the cache key is. result.Assign(Substring(cacheKey, end)); } void IdEnhance(nsACString &result) { result.Assign(idEnhance); } }; } // anon already_AddRefed ParseKey(const nsCSubstring &aKey, nsCSubstring *aIdEnhance, nsCSubstring *aURISpec) { nsACString::const_iterator caret, end; aKey.BeginReading(caret); aKey.EndReading(end); KeyParser parser(caret, end); nsRefPtr info = parser.Parse(); if (info) { if (aIdEnhance) parser.IdEnhance(*aIdEnhance); if (aURISpec) parser.URISpec(*aURISpec); } return info.forget(); } void AppendKeyPrefix(nsILoadContextInfo* aInfo, nsACString &_retval) { /** * This key is used to salt file hashes. When form of the key is changed * cache entries will fail to find on disk. * * IMPORTANT NOTE: * Keep the attributes list sorted according their ASCII code. */ if (aInfo->IsAnonymous()) { _retval.Append(NS_LITERAL_CSTRING("a,")); } if (aInfo->IsInBrowserElement()) { _retval.Append(NS_LITERAL_CSTRING("b,")); } if (aInfo->AppId() != nsILoadContextInfo::NO_APP_ID) { _retval.Append('i'); _retval.AppendInt(aInfo->AppId()); _retval.Append(','); } if (aInfo->IsPrivate()) { _retval.Append(NS_LITERAL_CSTRING("p,")); } } void AppendTagWithValue(nsACString & aTarget, char const aTag, nsCSubstring const & aValue) { aTarget.Append(aTag); // First check the value string to save some memory copying // for cases we don't need to escape at all (most likely). if (!aValue.IsEmpty()) { if (aValue.FindChar(',') == kNotFound) { // No need to escape aTarget.Append(aValue); } else { nsAutoCString escapedValue(aValue); escapedValue.ReplaceSubstring( NS_LITERAL_CSTRING(","), NS_LITERAL_CSTRING(",,")); aTarget.Append(escapedValue); } } aTarget.Append(','); } nsresult KeyMatchesLoadContextInfo(const nsACString &aKey, nsILoadContextInfo *aInfo, bool *_retval) { nsCOMPtr info = ParseKey(aKey); if (!info) { return NS_ERROR_FAILURE; } *_retval = info->Equals(aInfo); return NS_OK; } } // CacheFileUtils } // net } // mozilla