summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp')
-rw-r--r--comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp380
1 files changed, 380 insertions, 0 deletions
diff --git a/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp b/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp
new file mode 100644
index 0000000000..eef6694a67
--- /dev/null
+++ b/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp
@@ -0,0 +1,380 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "msgCore.h" // precompiled header...
+#include "nsMsgLocalStoreUtils.h"
+#include "nsIFile.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "HeaderReader.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "mozilla/Buffer.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "prprf.h"
+
+#define EXTRA_SAFETY_SPACE 0x400000 // (4MiB)
+
+nsMsgLocalStoreUtils::nsMsgLocalStoreUtils() {}
+
+nsresult nsMsgLocalStoreUtils::AddDirectorySeparator(nsIFile* path) {
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+bool nsMsgLocalStoreUtils::nsShouldIgnoreFile(nsAString& name, nsIFile* path) {
+ if (name.IsEmpty()) return true;
+
+ char16_t firstChar = name.First();
+ if (firstChar == '.' || firstChar == '#' ||
+ name.CharAt(name.Length() - 1) == '~')
+ return true;
+
+ if (name.LowerCaseEqualsLiteral("msgfilterrules.dat") ||
+ name.LowerCaseEqualsLiteral("rules.dat") ||
+ name.LowerCaseEqualsLiteral("filterlog.html") ||
+ name.LowerCaseEqualsLiteral("junklog.html") ||
+ name.LowerCaseEqualsLiteral("rulesbackup.dat"))
+ return true;
+
+ // don't add summary files to the list of folders;
+ // don't add popstate files to the list either, or rules (sort.dat).
+ if (StringEndsWith(name, u".snm"_ns) ||
+ name.LowerCaseEqualsLiteral("popstate.dat") ||
+ name.LowerCaseEqualsLiteral("sort.dat") ||
+ name.LowerCaseEqualsLiteral("mailfilt.log") ||
+ name.LowerCaseEqualsLiteral("filters.js") ||
+ StringEndsWith(name, u".toc"_ns))
+ return true;
+
+ // ignore RSS data source files (see FeedUtils.jsm)
+ if (name.LowerCaseEqualsLiteral("feeds.json") ||
+ name.LowerCaseEqualsLiteral("feeds.json.tmp") ||
+ name.LowerCaseEqualsLiteral("feeds.json.backup") ||
+ name.LowerCaseEqualsLiteral("feeds.json.corrupt") ||
+ name.LowerCaseEqualsLiteral("feeditems.json") ||
+ name.LowerCaseEqualsLiteral("feeditems.json.tmp") ||
+ name.LowerCaseEqualsLiteral("feeditems.json.backup") ||
+ name.LowerCaseEqualsLiteral("feeditems.json.corrupt") ||
+ name.LowerCaseEqualsLiteral("feeds.rdf") ||
+ name.LowerCaseEqualsLiteral("feeditems.rdf") ||
+ StringBeginsWith(name, u"feeditems_error"_ns))
+ return true;
+
+ // Ignore hidden and other special system files.
+ bool specialFile = false;
+ path->IsHidden(&specialFile);
+ if (specialFile) return true;
+ specialFile = false;
+ path->IsSpecial(&specialFile);
+ if (specialFile) return true;
+
+ // The .mozmsgs dir is for spotlight support
+ return (StringEndsWith(name, u".mozmsgs"_ns) ||
+ StringEndsWith(name, NS_LITERAL_STRING_FROM_CSTRING(FOLDER_SUFFIX)) ||
+ StringEndsWith(name, NS_LITERAL_STRING_FROM_CSTRING(SUMMARY_SUFFIX)));
+}
+
+// Attempts to fill a buffer. Returns a span holding the data read.
+// Might be less than buffer size, if EOF was encountered.
+// Upon error, an empty span is returned.
+static mozilla::Span<char> readBuf(nsIInputStream* readable,
+ mozilla::Buffer<char>& buf) {
+ uint32_t total = 0;
+ while (total < buf.Length()) {
+ uint32_t n;
+ nsresult rv =
+ readable->Read(buf.Elements() + total, buf.Length() - total, &n);
+ if (NS_FAILED(rv)) {
+ total = 0;
+ break;
+ }
+ if (n == 0) {
+ break; // EOF
+ }
+ total += n;
+ }
+ return mozilla::Span<char>(buf.Elements(), total);
+}
+
+// Write data to outputstream, until complete or error.
+static nsresult writeBuf(nsIOutputStream* writeable, const char* data,
+ size_t dataSize) {
+ uint32_t written = 0;
+ while (written < dataSize) {
+ uint32_t n;
+ nsresult rv = writeable->Write(data + written, dataSize - written, &n);
+ NS_ENSURE_SUCCESS(rv, rv);
+ written += n;
+ }
+ return NS_OK;
+}
+
+/**
+ * Attempt to update X-Mozilla-Status and X-Mozilla-Status2 headers with
+ * new message flags by rewriting them in place.
+ */
+nsresult nsMsgLocalStoreUtils::RewriteMsgFlags(nsISeekableStream* seekable,
+ uint32_t msgFlags) {
+ nsresult rv;
+
+ // Remember where we started.
+ int64_t msgStart;
+ rv = seekable->Tell(&msgStart);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We edit the file in-place, so need to be able to read and write too.
+ nsCOMPtr<nsIInputStream> readable(do_QueryInterface(seekable, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIOutputStream> writable = do_QueryInterface(seekable, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read in the first chunk of the header and search for the X-Mozilla-Status
+ // headers. We know that those headers always appear at the beginning, so
+ // don't need to look too far in.
+ mozilla::Buffer<char> buf(512);
+ mozilla::Span<const char> data = readBuf(readable, buf);
+
+ // If there's a "From " line, consume it.
+ mozilla::Span<const char> fromLine;
+ if (data.Length() >= 5 &&
+ nsDependentCSubstring(data.First(5)).EqualsLiteral("From ")) {
+ fromLine = FirstLine(data);
+ data = data.From(fromLine.Length());
+ }
+
+ HeaderReader::Hdr statusHdr;
+ HeaderReader::Hdr status2Hdr;
+ auto findHeadersFn = [&](auto const& hdr) {
+ if (hdr.Name(data).EqualsLiteral(X_MOZILLA_STATUS)) {
+ statusHdr = hdr;
+ } else if (hdr.Name(data).EqualsLiteral(X_MOZILLA_STATUS2)) {
+ status2Hdr = hdr;
+ } else {
+ return true; // Keep looking.
+ }
+ // Keep looking until we find both.
+ return statusHdr.IsEmpty() || status2Hdr.IsEmpty();
+ };
+ HeaderReader rdr;
+ rdr.Parse(data, findHeadersFn);
+
+ // Update X-Mozilla-Status (holds the lower 16bits worth of flags).
+ if (!statusHdr.IsEmpty()) {
+ uint32_t oldFlags = statusHdr.Value(data).ToInteger(&rv, 16);
+ if (NS_SUCCEEDED(rv)) {
+ // Preserve the Queued flag from existing X-Mozilla-Status header.
+ // (Note: not sure why we do this, but keeping it in for now. - BenC)
+ msgFlags |= oldFlags & nsMsgMessageFlags::Queued;
+
+ if ((msgFlags & 0xFFFF) != oldFlags) {
+ auto out = nsPrintfCString("%4.4x", msgFlags & 0xFFFF);
+ if (out.Length() <= statusHdr.rawValLen) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ msgStart + fromLine.Length() + statusHdr.pos +
+ statusHdr.rawValOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Should be an exact fit already, but just in case...
+ while (out.Length() < statusHdr.rawValLen) {
+ out.Append(' ');
+ }
+ rv = writeBuf(writable, out.BeginReading(), out.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+
+ // Update X-Mozilla-Status2 (holds the upper 16bit flags only(!)).
+ if (!status2Hdr.IsEmpty()) {
+ uint32_t oldFlags = status2Hdr.Value(data).ToInteger(&rv, 16);
+ if (NS_SUCCEEDED(rv)) {
+ if ((msgFlags & 0xFFFF0000) != oldFlags) {
+ auto out = nsPrintfCString("%8.8x", msgFlags & 0xFFFF0000);
+ if (out.Length() <= status2Hdr.rawValLen) {
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ msgStart + fromLine.Length() + status2Hdr.pos +
+ status2Hdr.rawValOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (out.Length() < status2Hdr.rawValLen) {
+ out.Append(' ');
+ }
+ rv = writeBuf(writable, out.BeginReading(), out.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Returns true if there is enough space on disk.
+ *
+ * @param aFile Any file in the message store that is on a logical
+ * disk volume so that it can be queried for disk space.
+ * @param aSpaceRequested The size of free space there must be on the disk
+ * to return true.
+ */
+bool nsMsgLocalStoreUtils::DiskSpaceAvailableInStore(nsIFile* aFile,
+ uint64_t aSpaceRequested) {
+ int64_t diskFree;
+ nsresult rv = aFile->GetDiskSpaceAvailable(&diskFree);
+ if (NS_SUCCEEDED(rv)) {
+#ifdef DEBUG
+ printf("GetDiskSpaceAvailable returned: %lld bytes\n", (long long)diskFree);
+#endif
+ // When checking for disk space available, take into consideration
+ // possible database changes, therefore ask for a little more
+ // (EXTRA_SAFETY_SPACE) than what the requested size is. Also, due to disk
+ // sector sizes, allocation blocks, etc. The space "available" may be
+ // greater than the actual space usable.
+ return ((aSpaceRequested + EXTRA_SAFETY_SPACE) < (uint64_t)diskFree);
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // The call to GetDiskSpaceAvailable is not implemented!
+ // This will happen on certain platforms where GetDiskSpaceAvailable
+ // is not implemented. Since people on those platforms still need
+ // to download mail, we will simply bypass the disk-space check.
+ //
+ // We'll leave a debug message to warn people.
+#ifdef DEBUG
+ printf(
+ "Call to GetDiskSpaceAvailable FAILED because it is not "
+ "implemented!\n");
+#endif
+ return true;
+ } else {
+ printf("Call to GetDiskSpaceAvailable FAILED!\n");
+ return false;
+ }
+}
+
+/**
+ * Resets forceReparse in the database.
+ *
+ * @param aMsgDb The database to reset.
+ */
+void nsMsgLocalStoreUtils::ResetForceReparse(nsIMsgDatabase* aMsgDB) {
+ if (aMsgDB) {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ aMsgDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (folderInfo) folderInfo->SetBooleanProperty("forceReparse", false);
+ }
+}
+
+/**
+ * Update the value of an X-Mozilla-Keys header in place.
+ *
+ * @param seekable The stream containing the message, positioned at the
+ * beginning of the message (must also be readable and
+ * writable).
+ * @param keywordsToAdd The list of keywords to add.
+ * @param keywordsToRemove The list of keywords to remove.
+ * @param notEnoughRoom Upon return, this will be set if the header is missing
+ * or too small to contain the new keywords.
+ *
+ */
+nsresult nsMsgLocalStoreUtils::ChangeKeywordsHelper(
+ nsISeekableStream* seekable, nsTArray<nsCString> const& keywordsToAdd,
+ nsTArray<nsCString> const& keywordsToRemove, bool& notEnoughRoom) {
+ notEnoughRoom = false;
+ nsresult rv;
+
+ // Remember where we started.
+ int64_t msgStart;
+ rv = seekable->Tell(&msgStart);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We edit the file in-place, so need to be able to read and write too.
+ nsCOMPtr<nsIInputStream> readable(do_QueryInterface(seekable, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIOutputStream> writable = do_QueryInterface(seekable, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read in the first chunk of the header and search for X-Mozilla-Keys.
+ // We know that it always appears near the beginning, so don't need to look
+ // too far in.
+ mozilla::Buffer<char> buf(512);
+ mozilla::Span<const char> data = readBuf(readable, buf);
+
+ // If there's a "From " line, consume it.
+ mozilla::Span<const char> fromLine;
+ if (data.Length() >= 5 &&
+ nsDependentCSubstring(data.First(5)).EqualsLiteral("From ")) {
+ fromLine = FirstLine(data);
+ data = data.From(fromLine.Length());
+ }
+
+ HeaderReader::Hdr kwHdr;
+ auto findHeaderFn = [&](auto const& hdr) {
+ if (hdr.Name(data).EqualsLiteral(HEADER_X_MOZILLA_KEYWORDS)) {
+ kwHdr = hdr;
+ return false;
+ }
+ return true; // Keep looking.
+ };
+ HeaderReader rdr;
+ rdr.Parse(data, findHeaderFn);
+
+ if (kwHdr.IsEmpty()) {
+ NS_WARNING("X-Mozilla-Keys header not found.");
+ notEnoughRoom = true;
+ return NS_OK;
+ }
+
+ // Get existing keywords.
+ nsTArray<nsCString> keywords;
+ nsAutoCString old(kwHdr.Value(data));
+ old.CompressWhitespace();
+ for (nsACString const& kw : old.Split(' ')) {
+ keywords.AppendElement(kw);
+ }
+
+ bool altered = false;
+ // Add missing keywords.
+ for (auto const& add : keywordsToAdd) {
+ if (!keywords.Contains(add)) {
+ keywords.AppendElement(add);
+ altered = true;
+ }
+ }
+
+ // Remove any keywords we want gone.
+ for (auto const& remove : keywordsToRemove) {
+ auto idx = keywords.IndexOf(remove);
+ if (idx != keywords.NoIndex) {
+ keywords.RemoveElementAt(idx);
+ altered = true;
+ }
+ }
+
+ if (!altered) {
+ return NS_OK;
+ }
+
+ // Write updated keywords over existing value.
+ auto out = StringJoin(" "_ns, keywords);
+ if (out.Length() > kwHdr.rawValLen) {
+ NS_WARNING("X-Mozilla-Keys too small for new value.");
+ notEnoughRoom = true;
+ return NS_OK;
+ }
+ while (out.Length() < kwHdr.rawValLen) {
+ out.Append(' ');
+ }
+
+ rv = seekable->Seek(
+ nsISeekableStream::NS_SEEK_SET,
+ msgStart + fromLine.Length() + kwHdr.pos + kwHdr.rawValOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = writeBuf(writable, out.BeginReading(), out.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}