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