summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/Helpers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/Helpers.cpp')
-rw-r--r--toolkit/components/places/Helpers.cpp382
1 files changed, 382 insertions, 0 deletions
diff --git a/toolkit/components/places/Helpers.cpp b/toolkit/components/places/Helpers.cpp
new file mode 100644
index 0000000000..90ab263ec1
--- /dev/null
+++ b/toolkit/components/places/Helpers.cpp
@@ -0,0 +1,382 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * 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 "Helpers.h"
+#include "mozIStorageError.h"
+#include "prio.h"
+#include "nsIFile.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsNavHistory.h"
+#include "mozilla/Base64.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/RandomNum.h"
+#include <algorithm>
+#include "mozilla/Services.h"
+
+// The length of guids that are used by history and bookmarks.
+#define GUID_LENGTH 12
+
+// Maximum number of chars to use for calculating hashes. This value has been
+// picked to ensure low hash collisions on a real world common places.sqlite.
+// While collisions are not a big deal for functionality, a low ratio allows
+// for slightly more efficient SELECTs.
+#define MAX_CHARS_TO_HASH 1500U
+
+extern "C" {
+
+// Generates a new Places GUID. This function uses C linkage because it's
+// called from the Rust synced bookmarks mirror, on the storage thread.
+nsresult NS_GeneratePlacesGUID(nsACString* _guid) {
+ return mozilla::places::GenerateGUID(*_guid);
+}
+
+} // extern "C"
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatementCallback
+
+NS_IMPL_ISUPPORTS(AsyncStatementCallback, mozIStorageStatementCallback)
+
+NS_IMETHODIMP
+WeakAsyncStatementCallback::HandleResult(mozIStorageResultSet* aResultSet) {
+ MOZ_ASSERT(false, "Was not expecting a resultset, but got it.");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WeakAsyncStatementCallback::HandleCompletion(uint16_t aReason) { return NS_OK; }
+
+NS_IMETHODIMP
+WeakAsyncStatementCallback::HandleError(mozIStorageError* aError) {
+#ifdef DEBUG
+ int32_t result;
+ nsresult rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString message;
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString warnMsg;
+ warnMsg.AppendLiteral(
+ "An error occurred while executing an async statement: ");
+ warnMsg.AppendInt(result);
+ warnMsg.Append(' ');
+ warnMsg.Append(message);
+ NS_WARNING(warnMsg.get());
+#endif
+
+ return NS_OK;
+}
+
+#define URI_TO_URLCSTRING(uri, spec) \
+ nsAutoCString spec; \
+ if (NS_FAILED(aURI->GetSpec(spec))) { \
+ return NS_ERROR_UNEXPECTED; \
+ }
+
+// Bind URI to statement by index.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement, int32_t aIndex,
+ nsIURI* aURI) {
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aStatement, aIndex, spec);
+}
+
+// Statement URLCString to statement by index.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement, int32_t index,
+ const nsACString& aURLString) {
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ return aStatement->BindUTF8StringByIndex(
+ index, StringHead(aURLString, URI_LENGTH_MAX));
+}
+
+// Bind URI to statement by name.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement, const nsACString& aName,
+ nsIURI* aURI) {
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aStatement, aName, spec);
+}
+
+// Bind URLCString to statement by name.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement, const nsACString& aName,
+ const nsACString& aURLString) {
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ return aStatement->BindUTF8StringByName(
+ aName, StringHead(aURLString, URI_LENGTH_MAX));
+}
+
+// Bind URI to params by index.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams, int32_t aIndex,
+ nsIURI* aURI) {
+ NS_ASSERTION(aParams, "Must have non-null statement");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aParams, aIndex, spec);
+}
+
+// Bind URLCString to params by index.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams, int32_t index,
+ const nsACString& aURLString) {
+ NS_ASSERTION(aParams, "Must have non-null statement");
+ return aParams->BindUTF8StringByIndex(index,
+ StringHead(aURLString, URI_LENGTH_MAX));
+}
+
+// Bind URI to params by name.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams, const nsACString& aName,
+ nsIURI* aURI) {
+ NS_ASSERTION(aParams, "Must have non-null params array");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aParams, aName, spec);
+}
+
+// Bind URLCString to params by name.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams, const nsACString& aName,
+ const nsACString& aURLString) {
+ NS_ASSERTION(aParams, "Must have non-null params array");
+
+ nsresult rv = aParams->BindUTF8StringByName(
+ aName, StringHead(aURLString, URI_LENGTH_MAX));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+#undef URI_TO_URLCSTRING
+
+nsresult GetReversedHostname(nsIURI* aURI, nsString& aRevHost) {
+ nsAutoCString forward8;
+ nsresult rv = aURI->GetHost(forward8);
+ // Not all URIs have a host.
+ if (NS_FAILED(rv)) return rv;
+
+ // can't do reversing in UTF8, better use 16-bit chars
+ GetReversedHostname(NS_ConvertUTF8toUTF16(forward8), aRevHost);
+ return NS_OK;
+}
+
+void GetReversedHostname(const nsString& aForward, nsString& aRevHost) {
+ ReverseString(aForward, aRevHost);
+ aRevHost.Append(char16_t('.'));
+}
+
+void ReverseString(const nsString& aInput, nsString& aReversed) {
+ aReversed.Truncate(0);
+ for (int32_t i = aInput.Length() - 1; i >= 0; i--) {
+ aReversed.Append(aInput[i]);
+ }
+}
+
+nsresult GenerateGUID(nsACString& _guid) {
+ _guid.Truncate();
+
+ // Request raw random bytes and base64url encode them. For each set of three
+ // bytes, we get one character.
+ const uint32_t kRequiredBytesLength =
+ static_cast<uint32_t>(GUID_LENGTH / 4 * 3);
+
+ uint8_t buffer[kRequiredBytesLength];
+ if (!mozilla::GenerateRandomBytesFromOS(buffer, kRequiredBytesLength)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = Base64URLEncode(kRequiredBytesLength, buffer,
+ Base64URLEncodePaddingPolicy::Omit, _guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(_guid.Length() == GUID_LENGTH, "GUID is not the right size!");
+ return NS_OK;
+}
+
+bool IsValidGUID(const nsACString& aGUID) {
+ nsCString::size_type len = aGUID.Length();
+ if (len != GUID_LENGTH) {
+ return false;
+ }
+
+ for (nsCString::size_type i = 0; i < len; i++) {
+ char c = aGUID[i];
+ if ((c >= 'a' && c <= 'z') || // a-z
+ (c >= 'A' && c <= 'Z') || // A-Z
+ (c >= '0' && c <= '9') || // 0-9
+ c == '-' || c == '_') { // - or _
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+void TruncateTitle(const nsACString& aTitle, nsACString& aTrimmed) {
+ if (aTitle.IsVoid()) {
+ return;
+ }
+ aTrimmed = aTitle;
+ if (aTitle.Length() > TITLE_LENGTH_MAX) {
+ aTrimmed = StringHead(aTitle, TITLE_LENGTH_MAX);
+ }
+}
+
+PRTime RoundToMilliseconds(PRTime aTime) {
+ return aTime - (aTime % PR_USEC_PER_MSEC);
+}
+
+PRTime RoundedPRNow() { return RoundToMilliseconds(PR_Now()); }
+
+nsresult HashURL(const nsACString& aSpec, const nsACString& aMode,
+ uint64_t* _hash) {
+ NS_ENSURE_ARG_POINTER(_hash);
+
+ // HashString doesn't stop at the string boundaries if a length is passed to
+ // it, so ensure to pass a proper value.
+ const uint32_t maxLenToHash =
+ std::min(static_cast<uint32_t>(aSpec.Length()), MAX_CHARS_TO_HASH);
+
+ if (aMode.IsEmpty()) {
+ // URI-like strings (having a prefix before a colon), are handled specially,
+ // as a 48 bit hash, where first 16 bits are the prefix hash, while the
+ // other 32 are the string hash.
+ // The 16 bits have been decided based on the fact hashing all of the IANA
+ // known schemes, plus "places", does not generate collisions.
+ // Since we only care about schemes, we just search in the first 50 chars.
+ // The longest known IANA scheme, at this time, is 30 chars.
+ const nsDependentCSubstring& strHead = StringHead(aSpec, 50);
+ nsACString::const_iterator start, tip, end;
+ strHead.BeginReading(tip);
+ start = tip;
+ strHead.EndReading(end);
+ uint32_t strHash = HashString(aSpec.BeginReading(), maxLenToHash);
+ if (FindCharInReadable(':', tip, end)) {
+ const nsDependentCSubstring& prefix = Substring(start, tip);
+ uint64_t prefixHash =
+ static_cast<uint64_t>(HashString(prefix) & 0x0000FFFF);
+ // The second half of the url is more likely to be unique, so we add it.
+ *_hash = (prefixHash << 32) + strHash;
+ } else {
+ *_hash = strHash;
+ }
+ } else if (aMode.EqualsLiteral("prefix_lo")) {
+ // Keep only 16 bits.
+ *_hash = static_cast<uint64_t>(
+ HashString(aSpec.BeginReading(), maxLenToHash) & 0x0000FFFF)
+ << 32;
+ } else if (aMode.EqualsLiteral("prefix_hi")) {
+ // Keep only 16 bits.
+ *_hash = static_cast<uint64_t>(
+ HashString(aSpec.BeginReading(), maxLenToHash) & 0x0000FFFF)
+ << 32;
+ // Make this a prefix upper bound by filling the lowest 32 bits.
+ *_hash += 0xFFFFFFFF;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool GetHiddenState(bool aIsRedirect, uint32_t aTransitionType) {
+ return aTransitionType == nsINavHistoryService::TRANSITION_FRAMED_LINK ||
+ aTransitionType == nsINavHistoryService::TRANSITION_EMBED ||
+ aIsRedirect;
+}
+
+nsresult TokenizeQueryString(const nsACString& aQuery,
+ nsTArray<QueryKeyValuePair>* aTokens) {
+ // Strip off the "place:" prefix
+ const uint32_t prefixlen = 6; // = strlen("place:");
+ nsCString query;
+ if (aQuery.Length() >= prefixlen &&
+ Substring(aQuery, 0, prefixlen).EqualsLiteral("place:"))
+ query = Substring(aQuery, prefixlen);
+ else
+ query = aQuery;
+
+ int32_t keyFirstIndex = 0;
+ int32_t equalsIndex = 0;
+ for (uint32_t i = 0; i < query.Length(); i++) {
+ if (query[i] == '&') {
+ // new clause, save last one
+ if (i - keyFirstIndex > 1) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ aTokens->AppendElement(
+ QueryKeyValuePair(query, keyFirstIndex, equalsIndex, i));
+ }
+ keyFirstIndex = equalsIndex = i + 1;
+ } else if (query[i] == '=') {
+ equalsIndex = i;
+ }
+ }
+
+ // handle last pair, if any
+ if (query.Length() - keyFirstIndex > 1) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ aTokens->AppendElement(
+ QueryKeyValuePair(query, keyFirstIndex, equalsIndex, query.Length()));
+ }
+ return NS_OK;
+}
+
+void TokensToQueryString(const nsTArray<QueryKeyValuePair>& aTokens,
+ nsACString& aQuery) {
+ aQuery = "place:"_ns;
+ StringJoinAppend(aQuery, "&"_ns, aTokens,
+ [](nsACString& dst, const QueryKeyValuePair& token) {
+ dst.Append(token.key);
+ dst.AppendLiteral("=");
+ dst.Append(token.value);
+ });
+}
+
+nsresult BackupDatabaseFile(nsIFile* aDBFile, const nsAString& aBackupFileName,
+ nsIFile* aBackupParentDirectory, nsIFile** backup) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
+ if (!parentDir) {
+ // This argument is optional, and defaults to the same parent directory
+ // as the current file.
+ rv = aDBFile->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIFile> backupDB;
+ rv = parentDir->Clone(getter_AddRefs(backupDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDB->Append(aBackupFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString fileName;
+ rv = backupDB->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDB->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ backupDB.forget(backup);
+ return aDBFile->CopyTo(parentDir, fileName);
+}
+
+} // namespace places
+} // namespace mozilla