summaryrefslogtreecommitdiffstats
path: root/mailnews/local/src/nsMsgLocalStoreUtils.cpp
blob: 642694d3b9ac3d6c6d90e4e6fbd6e46850b87553 (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
/* -*- 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 "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)
{
  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, NS_LITERAL_STRING(".snm")) ||
      name.LowerCaseEqualsLiteral("popstate.dat") ||
      name.LowerCaseEqualsLiteral("sort.dat") ||
      name.LowerCaseEqualsLiteral("mailfilt.log") ||
      name.LowerCaseEqualsLiteral("filters.js") ||
      StringEndsWith(name, NS_LITERAL_STRING(".toc")))
    return true;

  // ignore RSS data source files
  if (name.LowerCaseEqualsLiteral("feeds.rdf") ||
      name.LowerCaseEqualsLiteral("feeditems.rdf") ||
      StringBeginsWith(name, NS_LITERAL_STRING("feeditems_error")))
    return true;

  // The .mozmsgs dir is for spotlight support
    return (StringEndsWith(name, NS_LITERAL_STRING(".mozmsgs")) ||
            StringEndsWith(name, NS_LITERAL_STRING(".sbd")) ||
            StringEndsWith(name, NS_LITERAL_STRING(SUMMARY_SUFFIX)));
}

/**
 * We're passed a stream positioned at the start of the message.
 * We start reading lines, looking for x-mozilla-keys: headers; If we're
 * adding the keyword and we find a header with the desired keyword already
 * in it, we don't need to do anything. Likewise, if removing keyword and we
 * don't find it,we don't need to do anything. Otherwise, if adding, we need
 * to see if there's an x-mozilla-keys header with room for the new keyword.
 *  If so, we replace the corresponding number of spaces with the keyword.
 * If no room, we can't do anything until the folder is compacted and another
 * x-mozilla-keys header is added. In that case, we set a property
 * on the header, which the compaction code will check.
 * This is not true for maildir, however, since it won't require compaction.
 */

void
nsMsgLocalStoreUtils::ChangeKeywordsHelper(nsIMsgDBHdr *message,
                                           uint64_t desiredOffset,
                                           nsLineBuffer<char> *lineBuffer,
                                           nsTArray<nsCString> &keywordArray,
                                           bool aAdd,
                                           nsIOutputStream *outputStream,
                                           nsISeekableStream *seekableStream,
                                           nsIInputStream *inputStream)
{
  uint32_t bytesWritten;

  for (uint32_t i = 0; i < keywordArray.Length(); i++)
  {
    nsAutoCString header;
    nsAutoCString keywords;
    bool done = false;
    uint32_t len = 0;
    nsAutoCString keywordToWrite(" ");

    keywordToWrite.Append(keywordArray[i]);
    seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, desiredOffset);
    // need to reset lineBuffer, which is cheaper than creating a new one.
    lineBuffer->start = lineBuffer->end = lineBuffer->buf;
    bool inKeywordHeader = false;
    bool foundKeyword = false;
    int64_t offsetToAddKeyword = 0;
    bool more;
    message->GetMessageSize(&len);
    // loop through
    while (!done)
    {
      int64_t lineStartPos;
      seekableStream->Tell(&lineStartPos);
      // we need to adjust the linestart pos by how much extra the line
      // buffer has read from the stream.
      lineStartPos -= (lineBuffer->end - lineBuffer->start);
      // NS_ReadLine doesn't return line termination chars.
      nsCString keywordHeaders;
      nsresult rv = NS_ReadLine(inputStream, lineBuffer, keywordHeaders, &more);
      if (NS_SUCCEEDED(rv))
      {
        if (keywordHeaders.IsEmpty())
          break; // passed headers; no x-mozilla-keywords header; give up.
        if (StringBeginsWith(keywordHeaders,
                             NS_LITERAL_CSTRING(HEADER_X_MOZILLA_KEYWORDS)))
          inKeywordHeader = true;
        else if (inKeywordHeader && (keywordHeaders.CharAt(0) == ' ' ||
                                     keywordHeaders.CharAt(0) == '\t'))
          ; // continuation header line
        else if (inKeywordHeader)
          break;
        else
          continue;
        uint32_t keywordHdrLength = keywordHeaders.Length();
        int32_t startOffset, keywordLength;
        // check if we have the keyword
        if (MsgFindKeyword(keywordArray[i], keywordHeaders, &startOffset,
                           &keywordLength))
        {
          foundKeyword = true;
          if (!aAdd) // if we're removing, remove it, and break;
          {
            keywordHeaders.Cut(startOffset, keywordLength);
            for (int32_t j = keywordLength; j > 0; j--)
              keywordHeaders.Append(' ');
            seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, lineStartPos);
            outputStream->Write(keywordHeaders.get(), keywordHeaders.Length(),
                                &bytesWritten);
          }
          offsetToAddKeyword = 0;
          // if adding and we already have the keyword, done
          done = true;
          break;
        }
        // argh, we need to check all the lines to see if we already have the
        // keyword, but if we don't find it, we want to remember the line and
        // position where we have room to add the keyword.
        if (aAdd)
        {
          nsAutoCString curKeywordHdr(keywordHeaders);
          // strip off line ending spaces.
          curKeywordHdr.Trim(" ", false, true);
          if (!offsetToAddKeyword && curKeywordHdr.Length() +
                keywordToWrite.Length() < keywordHdrLength)
            offsetToAddKeyword = lineStartPos + curKeywordHdr.Length();
        }
      }
    }
    if (aAdd && !foundKeyword)
    {
      if (!offsetToAddKeyword)
        message->SetUint32Property("growKeywords", 1);
      else
      {
        seekableStream->Seek(nsISeekableStream::NS_SEEK_SET,
                             offsetToAddKeyword);
        outputStream->Write(keywordToWrite.get(), keywordToWrite.Length(),
                            &bytesWritten);
      }
    }
  }
}

nsresult
nsMsgLocalStoreUtils::UpdateFolderFlag(nsIMsgDBHdr *mailHdr, bool bSet,
                                       nsMsgMessageFlagType flag,
                                       nsIOutputStream *fileStream)
{
  uint32_t statusOffset;
  uint64_t msgOffset;
  nsresult rv = mailHdr->GetStatusOffset(&statusOffset);
  // This probably means there's no x-mozilla-status header, so
  // we just ignore this.
  if (NS_FAILED(rv) || (statusOffset == 0))
    return NS_OK;
  rv = mailHdr->GetMessageOffset(&msgOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  uint64_t statusPos = msgOffset + statusOffset;
  nsCOMPtr<nsISeekableStream> seekableStream(do_QueryInterface(fileStream, &rv));
  NS_ENSURE_SUCCESS(rv, rv);
  rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, statusPos);
  NS_ENSURE_SUCCESS(rv, rv);
  char buf[50];
  buf[0] = '\0';
  nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(fileStream, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  uint32_t bytesRead;
  if (NS_SUCCEEDED(inputStream->Read(buf, X_MOZILLA_STATUS_LEN + 6,
                                     &bytesRead)))
  {
    buf[bytesRead] = '\0';
    if (strncmp(buf, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN) == 0 &&
      strncmp(buf + X_MOZILLA_STATUS_LEN, ": ", 2) == 0 &&
      strlen(buf) >= X_MOZILLA_STATUS_LEN + 6)
    {
      uint32_t flags;
      uint32_t bytesWritten;
      (void)mailHdr->GetFlags(&flags);
      if (!(flags & nsMsgMessageFlags::Expunged))
      {
        char *p = buf + X_MOZILLA_STATUS_LEN + 2;

        nsresult errorCode = NS_OK;
        flags = nsDependentCString(p).ToInteger(&errorCode, 16);

        uint32_t curFlags;
        (void)mailHdr->GetFlags(&curFlags);
        flags = (flags & nsMsgMessageFlags::Queued) |
          (curFlags & ~nsMsgMessageFlags::RuntimeOnly);
        if (bSet)
          flags |= flag;
        else
          flags &= ~flag;
      }
      else
      {
        flags &= ~nsMsgMessageFlags::RuntimeOnly;
      }
      seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, statusPos);
      // We are filing out x-mozilla-status flags here
      PR_snprintf(buf, sizeof(buf), X_MOZILLA_STATUS_FORMAT,
        flags & 0x0000FFFF);
      int32_t lineLen = PL_strlen(buf);
      uint64_t status2Pos = statusPos + lineLen;
      fileStream->Write(buf, lineLen, &bytesWritten);

      if (flag & 0xFFFF0000)
      {
        // Time to update x-mozilla-status2,
        // first find it by finding end of previous line, see bug 234935.
        seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);
        do
        {
          rv = inputStream->Read(buf, 1, &bytesRead);
          status2Pos++;
        } while (NS_SUCCEEDED(rv) && (*buf == '\n' || *buf == '\r'));
        status2Pos--;
        seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);
        if (NS_SUCCEEDED(inputStream->Read(buf, X_MOZILLA_STATUS2_LEN + 10,
                                           &bytesRead)))
        {
          if (strncmp(buf, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN) == 0 &&
            strncmp(buf + X_MOZILLA_STATUS2_LEN, ": ", 2) == 0 &&
            strlen(buf) >= X_MOZILLA_STATUS2_LEN + 10)
          {
            uint32_t dbFlags;
            (void)mailHdr->GetFlags(&dbFlags);
            dbFlags &= 0xFFFF0000;
            seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, status2Pos);
            PR_snprintf(buf, sizeof(buf), X_MOZILLA_STATUS2_FORMAT, dbFlags);
            fileStream->Write(buf, PL_strlen(buf), &bytesWritten);
          }
        }
      }
    }
    else
    {
#ifdef DEBUG
      printf("Didn't find %s where expected at position %ld\n"
        "instead, found %s.\n",
        X_MOZILLA_STATUS, (long) statusPos, buf);
#endif
      rv = NS_ERROR_FAILURE;
    }
  }
  else
    rv = NS_ERROR_FAILURE;
  return rv;
}

/**
 * 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);
  }
}