summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/local/src/nsMsgLocalStoreUtils.cpp
blob: eef6694a676cde9f3642ad71c77c6db8025bb8c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
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;
}