Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/cache/nsDiskCacheDeviceSQL.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim:set ts=2 sw=2 sts=2 et cin: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include <inttypes.h>
8
9
#include "mozilla/ArrayUtils.h"
10
#include "mozilla/Attributes.h"
11
#include "mozilla/Sprintf.h"
12
#include "mozilla/ThreadLocal.h"
13
14
#include "nsCache.h"
15
#include "nsDiskCache.h"
16
#include "nsDiskCacheDeviceSQL.h"
17
#include "nsCacheService.h"
18
#include "nsApplicationCache.h"
19
20
#include "nsNetCID.h"
21
#include "nsNetUtil.h"
22
#include "nsIURI.h"
23
#include "nsAutoPtr.h"
24
#include "nsEscape.h"
25
#include "nsIPrefBranch.h"
26
#include "nsIPrefService.h"
27
#include "nsString.h"
28
#include "nsPrintfCString.h"
29
#include "nsCRT.h"
30
#include "nsArrayUtils.h"
31
#include "nsIArray.h"
32
#include "nsIVariant.h"
33
#include "nsILoadContextInfo.h"
34
#include "nsThreadUtils.h"
35
#include "nsISerializable.h"
36
#include "nsIInputStream.h"
37
#include "nsIOutputStream.h"
38
#include "nsSerializationHelper.h"
39
#include "nsMemory.h"
40
41
#include "mozIStorageService.h"
42
#include "mozIStorageStatement.h"
43
#include "mozIStorageFunction.h"
44
#include "mozStorageHelper.h"
45
46
#include "nsICacheVisitor.h"
47
#include "nsISeekableStream.h"
48
49
#include "mozilla/Telemetry.h"
50
51
#include "mozilla/storage.h"
52
#include "nsVariant.h"
53
#include "mozilla/BasePrincipal.h"
54
55
using namespace mozilla;
56
using namespace mozilla::storage;
57
using mozilla::OriginAttributes;
58
59
static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
60
61
0
#define LOG(args) CACHE_LOG_DEBUG(args)
62
63
static uint32_t gNextTemporaryClientID = 0;
64
65
/*****************************************************************************
66
 * helpers
67
 */
68
69
static nsresult
70
EnsureDir(nsIFile *dir)
71
0
{
72
0
  bool exists;
73
0
  nsresult rv = dir->Exists(&exists);
74
0
  if (NS_SUCCEEDED(rv) && !exists)
75
0
    rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
76
0
  return rv;
77
0
}
78
79
static bool
80
DecomposeCacheEntryKey(const nsCString *fullKey,
81
                       const char **cid,
82
                       const char **key,
83
                       nsCString &buf)
84
0
{
85
0
  buf = *fullKey;
86
0
87
0
  int32_t colon = buf.FindChar(':');
88
0
  if (colon == kNotFound)
89
0
  {
90
0
    NS_ERROR("Invalid key");
91
0
    return false;
92
0
  }
93
0
  buf.SetCharAt('\0', colon);
94
0
95
0
  *cid = buf.get();
96
0
  *key = buf.get() + colon + 1;
97
0
98
0
  return true;
99
0
}
100
101
class AutoResetStatement
102
{
103
  public:
104
    explicit AutoResetStatement(mozIStorageStatement *s)
105
0
      : mStatement(s) {}
106
0
    ~AutoResetStatement() { mStatement->Reset(); }
107
0
    mozIStorageStatement *operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mStatement; }
108
  private:
109
    mozIStorageStatement *mStatement;
110
};
111
112
class EvictionObserver
113
{
114
  public:
115
  EvictionObserver(mozIStorageConnection *db,
116
                   nsOfflineCacheEvictionFunction *evictionFunction)
117
    : mDB(db), mEvictionFunction(evictionFunction)
118
0
    {
119
0
      mEvictionFunction->Init();
120
0
      mDB->ExecuteSimpleSQL(
121
0
          NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE"
122
0
                             " ON moz_cache FOR EACH ROW BEGIN SELECT"
123
0
                             " cache_eviction_observer("
124
0
                             "  OLD.ClientID, OLD.key, OLD.generation);"
125
0
                             " END;"));
126
0
    }
127
128
    ~EvictionObserver()
129
0
    {
130
0
      mDB->ExecuteSimpleSQL(
131
0
        NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
132
0
      mEvictionFunction->Reset();
133
0
    }
134
135
0
    void Apply() { return mEvictionFunction->Apply(); }
136
137
  private:
138
    mozIStorageConnection *mDB;
139
    RefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
140
};
141
142
#define DCACHE_HASH_MAX  INT64_MAX
143
#define DCACHE_HASH_BITS 64
144
145
/**
146
 *  nsOfflineCache::Hash(const char * key)
147
 *
148
 *  This algorithm of this method implies nsOfflineCacheRecords will be stored
149
 *  in a certain order on disk.  If the algorithm changes, existing cache
150
 *  map files may become invalid, and therefore the kCurrentVersion needs
151
 *  to be revised.
152
 */
153
static uint64_t
154
DCacheHash(const char * key)
155
0
{
156
0
  // initval 0x7416f295 was chosen randomly
157
0
  return (uint64_t(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295);
158
0
}
159
160
/******************************************************************************
161
 * nsOfflineCacheEvictionFunction
162
 */
163
164
NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction, mozIStorageFunction)
165
166
// helper function for directly exposing the same data file binding
167
// path algorithm used in nsOfflineCacheBinding::Create
168
static nsresult
169
GetCacheDataFile(nsIFile *cacheDir, const char *key,
170
                 int generation, nsCOMPtr<nsIFile> &file)
171
0
{
172
0
  cacheDir->Clone(getter_AddRefs(file));
173
0
  if (!file)
174
0
    return NS_ERROR_OUT_OF_MEMORY;
175
0
176
0
  uint64_t hash = DCacheHash(key);
177
0
178
0
  uint32_t dir1 = (uint32_t) (hash & 0x0F);
179
0
  uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
180
0
181
0
  hash >>= 8;
182
0
183
0
  file->AppendNative(nsPrintfCString("%X", dir1));
184
0
  file->AppendNative(nsPrintfCString("%X", dir2));
185
0
186
0
  char leaf[64];
187
0
  SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
188
0
  return file->AppendNative(nsDependentCString(leaf));
189
0
}
190
191
namespace appcachedetail {
192
193
typedef nsCOMArray<nsIFile> FileArray;
194
static MOZ_THREAD_LOCAL(FileArray*) tlsEvictionItems;
195
196
} // appcachedetail
197
198
NS_IMETHODIMP
199
nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval)
200
0
{
201
0
  LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
202
0
203
0
  *_retval = nullptr;
204
0
205
0
  uint32_t numEntries;
206
0
  nsresult rv = values->GetNumEntries(&numEntries);
207
0
  NS_ENSURE_SUCCESS(rv, rv);
208
0
  NS_ASSERTION(numEntries == 3, "unexpected number of arguments");
209
0
210
0
  uint32_t valueLen;
211
0
  const char *clientID = values->AsSharedUTF8String(0, &valueLen);
212
0
  const char *key = values->AsSharedUTF8String(1, &valueLen);
213
0
  nsAutoCString fullKey(clientID);
214
0
  fullKey.Append(':');
215
0
  fullKey.Append(key);
216
0
  int generation  = values->AsInt32(2);
217
0
218
0
  // If the key is currently locked, refuse to delete this row.
219
0
  if (mDevice->IsLocked(fullKey)) {
220
0
    // This code thought it was performing the equivalent of invoking the SQL
221
0
    // "RAISE(IGNORE)" function.  It was not.  Please see bug 1470961 and any
222
0
    // follow-ups to understand the plan for correcting this bug.
223
0
    return NS_OK;
224
0
  }
225
0
226
0
  nsCOMPtr<nsIFile> file;
227
0
  rv = GetCacheDataFile(mDevice->CacheDirectory(), key,
228
0
                        generation, file);
229
0
  if (NS_FAILED(rv))
230
0
  {
231
0
    LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%" PRIx32 "]!\n",
232
0
         key, generation, static_cast<uint32_t>(rv)));
233
0
    return rv;
234
0
  }
235
0
236
0
  appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
237
0
  MOZ_ASSERT(items);
238
0
  if (items) {
239
0
    items->AppendObject(file);
240
0
  }
241
0
242
0
  return NS_OK;
243
0
}
244
245
nsOfflineCacheEvictionFunction::nsOfflineCacheEvictionFunction(nsOfflineCacheDevice * device)
246
  : mDevice(device)
247
0
{
248
0
  mTLSInited = appcachedetail::tlsEvictionItems.init();
249
0
}
250
251
void nsOfflineCacheEvictionFunction::Init()
252
0
{
253
0
  if (mTLSInited) {
254
0
    appcachedetail::tlsEvictionItems.set(new appcachedetail::FileArray());
255
0
  }
256
0
}
257
258
void nsOfflineCacheEvictionFunction::Reset()
259
0
{
260
0
  if (!mTLSInited) {
261
0
    return;
262
0
  }
263
0
264
0
  appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
265
0
  if (!items) {
266
0
    return;
267
0
  }
268
0
269
0
  appcachedetail::tlsEvictionItems.set(nullptr);
270
0
  delete items;
271
0
}
272
273
void
274
nsOfflineCacheEvictionFunction::Apply()
275
0
{
276
0
  LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
277
0
278
0
  if (!mTLSInited) {
279
0
    return;
280
0
  }
281
0
282
0
  appcachedetail::FileArray* pitems = appcachedetail::tlsEvictionItems.get();
283
0
  if (!pitems) {
284
0
    return;
285
0
  }
286
0
287
0
  appcachedetail::FileArray items;
288
0
  items.SwapElements(*pitems);
289
0
290
0
  for (int32_t i = 0; i < items.Count(); i++) {
291
0
    if (MOZ_LOG_TEST(gCacheLog, LogLevel::Debug)) {
292
0
      LOG(("  removing %s\n", items[i]->HumanReadablePath().get()));
293
0
    }
294
0
295
0
    items[i]->Remove(false);
296
0
  }
297
0
}
298
299
class nsOfflineCacheDiscardCache : public Runnable
300
{
301
public:
302
  nsOfflineCacheDiscardCache(nsOfflineCacheDevice* device,
303
                             nsCString& group,
304
                             nsCString& clientID)
305
    : mozilla::Runnable("nsOfflineCacheDiscardCache")
306
    , mDevice(device)
307
    , mGroup(group)
308
    , mClientID(clientID)
309
0
  {
310
0
  }
311
312
  NS_IMETHOD Run() override
313
0
  {
314
0
    if (mDevice->IsActiveCache(mGroup, mClientID))
315
0
    {
316
0
      mDevice->DeactivateGroup(mGroup);
317
0
    }
318
0
319
0
    return mDevice->EvictEntries(mClientID.get());
320
0
  }
321
322
private:
323
  RefPtr<nsOfflineCacheDevice> mDevice;
324
  nsCString mGroup;
325
  nsCString mClientID;
326
};
327
328
/******************************************************************************
329
 * nsOfflineCacheDeviceInfo
330
 */
331
332
class nsOfflineCacheDeviceInfo final : public nsICacheDeviceInfo
333
{
334
public:
335
  NS_DECL_ISUPPORTS
336
  NS_DECL_NSICACHEDEVICEINFO
337
338
  explicit nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
339
    : mDevice(device)
340
0
  {}
341
342
private:
343
  ~nsOfflineCacheDeviceInfo() = default;
344
345
  nsOfflineCacheDevice* mDevice;
346
};
347
348
NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
349
350
NS_IMETHODIMP
351
nsOfflineCacheDeviceInfo::GetDescription(nsACString& aDescription)
352
0
{
353
0
  aDescription.AssignLiteral("Offline cache device");
354
0
  return NS_OK;
355
0
}
356
357
NS_IMETHODIMP
358
nsOfflineCacheDeviceInfo::GetUsageReport(nsACString& aUsageReport)
359
0
{
360
0
  nsAutoCString buffer;
361
0
  buffer.AssignLiteral("  <tr>\n"
362
0
                       "    <th>Cache Directory:</th>\n"
363
0
                       "    <td>");
364
0
  nsIFile *cacheDir = mDevice->CacheDirectory();
365
0
  if (!cacheDir)
366
0
    return NS_OK;
367
0
368
0
  nsAutoString path;
369
0
  nsresult rv = cacheDir->GetPath(path);
370
0
  if (NS_SUCCEEDED(rv))
371
0
    AppendUTF16toUTF8(path, buffer);
372
0
  else
373
0
    buffer.AppendLiteral("directory unavailable");
374
0
375
0
  buffer.AppendLiteral("</td>\n"
376
0
                       "  </tr>\n");
377
0
378
0
  aUsageReport.Assign(buffer);
379
0
  return NS_OK;
380
0
}
381
382
NS_IMETHODIMP
383
nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
384
0
{
385
0
  *aEntryCount = mDevice->EntryCount();
386
0
  return NS_OK;
387
0
}
388
389
NS_IMETHODIMP
390
nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
391
0
{
392
0
  *aTotalSize = mDevice->CacheSize();
393
0
  return NS_OK;
394
0
}
395
396
NS_IMETHODIMP
397
nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
398
0
{
399
0
  *aMaximumSize = mDevice->CacheCapacity();
400
0
  return NS_OK;
401
0
}
402
403
/******************************************************************************
404
 * nsOfflineCacheBinding
405
 */
406
407
class nsOfflineCacheBinding final : public nsISupports
408
{
409
0
  ~nsOfflineCacheBinding() = default;
410
411
public:
412
  NS_DECL_THREADSAFE_ISUPPORTS
413
414
  static nsOfflineCacheBinding *
415
      Create(nsIFile *cacheDir, const nsCString *key, int generation);
416
417
  enum { FLAG_NEW_ENTRY = 1 };
418
419
  nsCOMPtr<nsIFile> mDataFile;
420
  int               mGeneration;
421
  int       mFlags;
422
423
0
  bool IsNewEntry() { return mFlags & FLAG_NEW_ENTRY; }
424
0
  void MarkNewEntry() { mFlags |= FLAG_NEW_ENTRY; }
425
0
  void ClearNewEntry() { mFlags &= ~FLAG_NEW_ENTRY; }
426
};
427
428
NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding)
429
430
nsOfflineCacheBinding *
431
nsOfflineCacheBinding::Create(nsIFile *cacheDir,
432
                              const nsCString *fullKey,
433
                              int generation)
434
0
{
435
0
  nsCOMPtr<nsIFile> file;
436
0
  cacheDir->Clone(getter_AddRefs(file));
437
0
  if (!file)
438
0
    return nullptr;
439
0
440
0
  nsAutoCString keyBuf;
441
0
  const char *cid, *key;
442
0
  if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
443
0
    return nullptr;
444
0
445
0
  uint64_t hash = DCacheHash(key);
446
0
447
0
  uint32_t dir1 = (uint32_t) (hash & 0x0F);
448
0
  uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
449
0
450
0
  hash >>= 8;
451
0
452
0
  // XXX we might want to create these directories up-front
453
0
454
0
  file->AppendNative(nsPrintfCString("%X", dir1));
455
0
  Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
456
0
457
0
  file->AppendNative(nsPrintfCString("%X", dir2));
458
0
  Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
459
0
460
0
  nsresult rv;
461
0
  char leaf[64];
462
0
463
0
  if (generation == -1)
464
0
  {
465
0
    file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
466
0
467
0
    for (generation = 0; ; ++generation)
468
0
    {
469
0
      SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
470
0
471
0
      rv = file->SetNativeLeafName(nsDependentCString(leaf));
472
0
      if (NS_FAILED(rv))
473
0
        return nullptr;
474
0
      rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
475
0
      if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
476
0
        return nullptr;
477
0
      if (NS_SUCCEEDED(rv))
478
0
        break;
479
0
    }
480
0
  }
481
0
  else
482
0
  {
483
0
    SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
484
0
    rv = file->AppendNative(nsDependentCString(leaf));
485
0
    if (NS_FAILED(rv))
486
0
      return nullptr;
487
0
  }
488
0
489
0
  nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
490
0
  if (!binding)
491
0
    return nullptr;
492
0
493
0
  binding->mDataFile.swap(file);
494
0
  binding->mGeneration = generation;
495
0
  binding->mFlags = 0;
496
0
  return binding;
497
0
}
498
499
/******************************************************************************
500
 * nsOfflineCacheRecord
501
 */
502
503
struct nsOfflineCacheRecord
504
{
505
  const char    *clientID;
506
  const char    *key;
507
  const uint8_t *metaData;
508
  uint32_t       metaDataLen;
509
  int32_t        generation;
510
  int32_t        dataSize;
511
  int32_t        fetchCount;
512
  int64_t        lastFetched;
513
  int64_t        lastModified;
514
  int64_t        expirationTime;
515
};
516
517
static nsCacheEntry *
518
CreateCacheEntry(nsOfflineCacheDevice *device,
519
                 const nsCString *fullKey,
520
                 const nsOfflineCacheRecord &rec)
521
0
{
522
0
  nsCacheEntry *entry;
523
0
524
0
  if (device->IsLocked(*fullKey)) {
525
0
      return nullptr;
526
0
  }
527
0
528
0
  nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
529
0
                                     nsICache::STREAM_BASED,
530
0
                                     nsICache::STORE_OFFLINE,
531
0
                                     device, &entry);
532
0
  if (NS_FAILED(rv))
533
0
    return nullptr;
534
0
535
0
  entry->SetFetchCount((uint32_t) rec.fetchCount);
536
0
  entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
537
0
  entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
538
0
  entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
539
0
  entry->SetDataSize((uint32_t) rec.dataSize);
540
0
541
0
  entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
542
0
543
0
  // Restore security info, if present
544
0
  const char* info = entry->GetMetaDataElement("security-info");
545
0
  if (info) {
546
0
    nsCOMPtr<nsISupports> infoObj;
547
0
    rv = NS_DeserializeObject(nsDependentCString(info),
548
0
                              getter_AddRefs(infoObj));
549
0
    if (NS_FAILED(rv)) {
550
0
      delete entry;
551
0
      return nullptr;
552
0
    }
553
0
    entry->SetSecurityInfo(infoObj);
554
0
  }
555
0
556
0
  // create a binding object for this entry
557
0
  nsOfflineCacheBinding *binding =
558
0
      nsOfflineCacheBinding::Create(device->CacheDirectory(),
559
0
                                    fullKey,
560
0
                                    rec.generation);
561
0
  if (!binding)
562
0
  {
563
0
    delete entry;
564
0
    return nullptr;
565
0
  }
566
0
  entry->SetData(binding);
567
0
568
0
  return entry;
569
0
}
570
571
572
/******************************************************************************
573
 * nsOfflineCacheEntryInfo
574
 */
575
576
class nsOfflineCacheEntryInfo final : public nsICacheEntryInfo
577
{
578
  ~nsOfflineCacheEntryInfo() = default;
579
580
public:
581
  NS_DECL_ISUPPORTS
582
  NS_DECL_NSICACHEENTRYINFO
583
584
  nsOfflineCacheRecord *mRec;
585
};
586
587
NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
588
589
NS_IMETHODIMP
590
nsOfflineCacheEntryInfo::GetClientID(nsACString& aClientID)
591
0
{
592
0
  aClientID.Assign(mRec->clientID);
593
0
  return NS_OK;
594
0
}
595
596
NS_IMETHODIMP
597
nsOfflineCacheEntryInfo::GetDeviceID(nsACString& aDeviceID)
598
0
{
599
0
  aDeviceID.Assign(OFFLINE_CACHE_DEVICE_ID);
600
0
  return NS_OK;
601
0
}
602
603
NS_IMETHODIMP
604
nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey)
605
0
{
606
0
  clientKey.Assign(mRec->key);
607
0
  return NS_OK;
608
0
}
609
610
NS_IMETHODIMP
611
nsOfflineCacheEntryInfo::GetFetchCount(int32_t *aFetchCount)
612
0
{
613
0
  *aFetchCount = mRec->fetchCount;
614
0
  return NS_OK;
615
0
}
616
617
NS_IMETHODIMP
618
nsOfflineCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched)
619
0
{
620
0
  *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
621
0
  return NS_OK;
622
0
}
623
624
NS_IMETHODIMP
625
nsOfflineCacheEntryInfo::GetLastModified(uint32_t *aLastModified)
626
0
{
627
0
  *aLastModified = SecondsFromPRTime(mRec->lastModified);
628
0
  return NS_OK;
629
0
}
630
631
NS_IMETHODIMP
632
nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime)
633
0
{
634
0
  *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
635
0
  return NS_OK;
636
0
}
637
638
NS_IMETHODIMP
639
nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased)
640
0
{
641
0
  *aStreamBased = true;
642
0
  return NS_OK;
643
0
}
644
645
NS_IMETHODIMP
646
nsOfflineCacheEntryInfo::GetDataSize(uint32_t *aDataSize)
647
0
{
648
0
  *aDataSize = mRec->dataSize;
649
0
  return NS_OK;
650
0
}
651
652
653
/******************************************************************************
654
 * nsApplicationCacheNamespace
655
 */
656
657
NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
658
659
NS_IMETHODIMP
660
nsApplicationCacheNamespace::Init(uint32_t itemType,
661
                                  const nsACString &namespaceSpec,
662
                                  const nsACString &data)
663
0
{
664
0
  mItemType = itemType;
665
0
  mNamespaceSpec = namespaceSpec;
666
0
  mData = data;
667
0
  return NS_OK;
668
0
}
669
670
NS_IMETHODIMP
671
nsApplicationCacheNamespace::GetItemType(uint32_t *out)
672
0
{
673
0
  *out = mItemType;
674
0
  return NS_OK;
675
0
}
676
677
NS_IMETHODIMP
678
nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
679
0
{
680
0
  out = mNamespaceSpec;
681
0
  return NS_OK;
682
0
}
683
684
NS_IMETHODIMP
685
nsApplicationCacheNamespace::GetData(nsACString &out)
686
0
{
687
0
  out = mData;
688
0
  return NS_OK;
689
0
}
690
691
/******************************************************************************
692
 * nsApplicationCache
693
 */
694
695
NS_IMPL_ISUPPORTS(nsApplicationCache,
696
                  nsIApplicationCache,
697
                  nsISupportsWeakReference)
698
699
nsApplicationCache::nsApplicationCache()
700
  : mDevice(nullptr)
701
  , mValid(true)
702
0
{
703
0
}
704
705
nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
706
                                       const nsACString &group,
707
                                       const nsACString &clientID)
708
  : mDevice(device)
709
  , mGroup(group)
710
  , mClientID(clientID)
711
  , mValid(true)
712
0
{
713
0
}
714
715
nsApplicationCache::~nsApplicationCache()
716
0
{
717
0
  if (!mDevice)
718
0
    return;
719
0
720
0
  {
721
0
    MutexAutoLock lock(mDevice->mLock);
722
0
    mDevice->mCaches.Remove(mClientID);
723
0
  }
724
0
725
0
  // If this isn't an active cache anymore, it can be destroyed.
726
0
  if (mValid && !mDevice->IsActiveCache(mGroup, mClientID))
727
0
    Discard();
728
0
}
729
730
void
731
nsApplicationCache::MarkInvalid()
732
0
{
733
0
  mValid = false;
734
0
}
735
736
NS_IMETHODIMP
737
nsApplicationCache::InitAsHandle(const nsACString &groupId,
738
                                 const nsACString &clientId)
739
0
{
740
0
  NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED);
741
0
  NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
742
0
743
0
  mGroup = groupId;
744
0
  mClientID = clientId;
745
0
  return NS_OK;
746
0
}
747
748
NS_IMETHODIMP
749
nsApplicationCache::GetManifestURI(nsIURI **out)
750
0
{
751
0
  nsCOMPtr<nsIURI> uri;
752
0
  nsresult rv = NS_NewURI(getter_AddRefs(uri), mGroup);
753
0
  NS_ENSURE_SUCCESS(rv, rv);
754
0
755
0
  rv = NS_GetURIWithNewRef(uri, EmptyCString(), out);
756
0
  NS_ENSURE_SUCCESS(rv, rv);
757
0
758
0
  return NS_OK;
759
0
}
760
761
NS_IMETHODIMP
762
nsApplicationCache::GetGroupID(nsACString &out)
763
0
{
764
0
  out = mGroup;
765
0
  return NS_OK;
766
0
}
767
768
NS_IMETHODIMP
769
nsApplicationCache::GetClientID(nsACString &out)
770
0
{
771
0
  out = mClientID;
772
0
  return NS_OK;
773
0
}
774
775
NS_IMETHODIMP
776
nsApplicationCache::GetProfileDirectory(nsIFile **out)
777
0
{
778
0
  if (mDevice->BaseDirectory())
779
0
      NS_ADDREF(*out = mDevice->BaseDirectory());
780
0
  else
781
0
      *out = nullptr;
782
0
783
0
  return NS_OK;
784
0
}
785
786
NS_IMETHODIMP
787
nsApplicationCache::GetActive(bool *out)
788
0
{
789
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
790
0
791
0
  *out = mDevice->IsActiveCache(mGroup, mClientID);
792
0
  return NS_OK;
793
0
}
794
795
NS_IMETHODIMP
796
nsApplicationCache::Activate()
797
0
{
798
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
799
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
800
0
801
0
  mDevice->ActivateCache(mGroup, mClientID);
802
0
803
0
  if (mDevice->AutoShutdown(this))
804
0
    mDevice = nullptr;
805
0
806
0
  return NS_OK;
807
0
}
808
809
NS_IMETHODIMP
810
nsApplicationCache::Discard()
811
0
{
812
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
813
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
814
0
815
0
  mValid = false;
816
0
817
0
  nsCOMPtr<nsIRunnable> ev =
818
0
    new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
819
0
  nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
820
0
  return rv;
821
0
}
822
823
NS_IMETHODIMP
824
nsApplicationCache::MarkEntry(const nsACString &key,
825
                              uint32_t typeBits)
826
0
{
827
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
828
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
829
0
830
0
  return mDevice->MarkEntry(mClientID, key, typeBits);
831
0
}
832
833
834
NS_IMETHODIMP
835
nsApplicationCache::UnmarkEntry(const nsACString &key,
836
                                uint32_t typeBits)
837
0
{
838
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
839
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
840
0
841
0
  return mDevice->UnmarkEntry(mClientID, key, typeBits);
842
0
}
843
844
NS_IMETHODIMP
845
nsApplicationCache::GetTypes(const nsACString &key,
846
                             uint32_t *typeBits)
847
0
{
848
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
849
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
850
0
851
0
  return mDevice->GetTypes(mClientID, key, typeBits);
852
0
}
853
854
NS_IMETHODIMP
855
nsApplicationCache::GatherEntries(uint32_t typeBits,
856
                                  uint32_t * count,
857
                                  char *** keys)
858
0
{
859
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
860
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
861
0
862
0
  return mDevice->GatherEntries(mClientID, typeBits, count, keys);
863
0
}
864
865
NS_IMETHODIMP
866
nsApplicationCache::AddNamespaces(nsIArray *namespaces)
867
0
{
868
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
869
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
870
0
871
0
  if (!namespaces)
872
0
    return NS_OK;
873
0
874
0
  mozStorageTransaction transaction(mDevice->mDB, false);
875
0
876
0
  uint32_t length;
877
0
  nsresult rv = namespaces->GetLength(&length);
878
0
  NS_ENSURE_SUCCESS(rv, rv);
879
0
880
0
  for (uint32_t i = 0; i < length; i++) {
881
0
    nsCOMPtr<nsIApplicationCacheNamespace> ns =
882
0
      do_QueryElementAt(namespaces, i);
883
0
    if (ns) {
884
0
      rv = mDevice->AddNamespace(mClientID, ns);
885
0
      NS_ENSURE_SUCCESS(rv, rv);
886
0
    }
887
0
  }
888
0
889
0
  rv = transaction.Commit();
890
0
  NS_ENSURE_SUCCESS(rv, rv);
891
0
892
0
  return NS_OK;
893
0
}
894
895
NS_IMETHODIMP
896
nsApplicationCache::GetMatchingNamespace(const nsACString &key,
897
                                         nsIApplicationCacheNamespace **out)
898
899
0
{
900
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
901
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
902
0
903
0
  return mDevice->GetMatchingNamespace(mClientID, key, out);
904
0
}
905
906
NS_IMETHODIMP
907
nsApplicationCache::GetUsage(uint32_t *usage)
908
0
{
909
0
  NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
910
0
  NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
911
0
912
0
  return mDevice->GetUsage(mClientID, usage);
913
0
}
914
915
/******************************************************************************
916
 * nsCloseDBEvent
917
 *****************************************************************************/
918
919
class nsCloseDBEvent : public Runnable {
920
public:
921
  explicit nsCloseDBEvent(mozIStorageConnection* aDB)
922
    : mozilla::Runnable("nsCloseDBEvent")
923
0
  {
924
0
    mDB = aDB;
925
0
  }
926
927
  NS_IMETHOD Run() override
928
0
  {
929
0
    mDB->Close();
930
0
    return NS_OK;
931
0
  }
932
933
protected:
934
0
  virtual ~nsCloseDBEvent() = default;
935
936
private:
937
  nsCOMPtr<mozIStorageConnection> mDB;
938
};
939
940
941
942
/******************************************************************************
943
 * nsOfflineCacheDevice
944
 */
945
946
NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice)
947
948
nsOfflineCacheDevice::nsOfflineCacheDevice()
949
  : mDB(nullptr)
950
  , mCacheCapacity(0)
951
  , mDeltaCounter(0)
952
  , mAutoShutdown(false)
953
  , mLock("nsOfflineCacheDevice.lock")
954
  , mActiveCaches(4)
955
  , mLockedEntries(32)
956
0
{
957
0
}
958
959
/* static */
960
bool
961
nsOfflineCacheDevice::GetStrictFileOriginPolicy()
962
0
{
963
0
    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
964
0
965
0
    bool retval;
966
0
    if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
967
0
        return retval;
968
0
969
0
    // As default value use true (be more strict)
970
0
    return true;
971
0
}
972
973
uint32_t
974
nsOfflineCacheDevice::CacheSize()
975
0
{
976
0
  NS_ENSURE_TRUE(Initialized(), 0);
977
0
978
0
  AutoResetStatement statement(mStatement_CacheSize);
979
0
980
0
  bool hasRows;
981
0
  nsresult rv = statement->ExecuteStep(&hasRows);
982
0
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
983
0
984
0
  return (uint32_t) statement->AsInt32(0);
985
0
}
986
987
uint32_t
988
nsOfflineCacheDevice::EntryCount()
989
0
{
990
0
  NS_ENSURE_TRUE(Initialized(), 0);
991
0
992
0
  AutoResetStatement statement(mStatement_EntryCount);
993
0
994
0
  bool hasRows;
995
0
  nsresult rv = statement->ExecuteStep(&hasRows);
996
0
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
997
0
998
0
  return (uint32_t) statement->AsInt32(0);
999
0
}
1000
1001
nsresult
1002
nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry)
1003
0
{
1004
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1005
0
1006
0
  // Decompose the key into "ClientID" and "Key"
1007
0
  nsAutoCString keyBuf;
1008
0
  const char *cid, *key;
1009
0
1010
0
  if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1011
0
    return NS_ERROR_UNEXPECTED;
1012
0
1013
0
  // Store security info, if it is serializable
1014
0
  nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
1015
0
  nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
1016
0
  if (infoObj && !serializable)
1017
0
    return NS_ERROR_UNEXPECTED;
1018
0
1019
0
  if (serializable) {
1020
0
    nsCString info;
1021
0
    nsresult rv = NS_SerializeToString(serializable, info);
1022
0
    NS_ENSURE_SUCCESS(rv, rv);
1023
0
1024
0
    rv = entry->SetMetaDataElement("security-info", info.get());
1025
0
    NS_ENSURE_SUCCESS(rv, rv);
1026
0
  }
1027
0
1028
0
  nsCString metaDataBuf;
1029
0
  uint32_t mdSize = entry->MetaDataSize();
1030
0
  if (!metaDataBuf.SetLength(mdSize, fallible))
1031
0
    return NS_ERROR_OUT_OF_MEMORY;
1032
0
  char *md = metaDataBuf.BeginWriting();
1033
0
  entry->FlattenMetaData(md, mdSize);
1034
0
1035
0
  nsOfflineCacheRecord rec;
1036
0
  rec.metaData = (const uint8_t *) md;
1037
0
  rec.metaDataLen = mdSize;
1038
0
  rec.dataSize = entry->DataSize();
1039
0
  rec.fetchCount = entry->FetchCount();
1040
0
  rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
1041
0
  rec.lastModified = PRTimeFromSeconds(entry->LastModified());
1042
0
  rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
1043
0
1044
0
  AutoResetStatement statement(mStatement_UpdateEntry);
1045
0
1046
0
  nsresult rv;
1047
0
  rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
1048
0
  nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize);
1049
0
  if (NS_FAILED(tmp)) {
1050
0
    rv = tmp;
1051
0
  }
1052
0
  tmp = statement->BindInt32ByIndex(2, rec.fetchCount);
1053
0
  if (NS_FAILED(tmp)) {
1054
0
    rv = tmp;
1055
0
  }
1056
0
  tmp = statement->BindInt64ByIndex(3, rec.lastFetched);
1057
0
  if (NS_FAILED(tmp)) {
1058
0
    rv = tmp;
1059
0
  }
1060
0
  tmp = statement->BindInt64ByIndex(4, rec.lastModified);
1061
0
  if (NS_FAILED(tmp)) {
1062
0
    rv = tmp;
1063
0
  }
1064
0
  tmp = statement->BindInt64ByIndex(5, rec.expirationTime);
1065
0
  if (NS_FAILED(tmp)) {
1066
0
    rv = tmp;
1067
0
  }
1068
0
  tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid));
1069
0
  if (NS_FAILED(tmp)) {
1070
0
    rv = tmp;
1071
0
  }
1072
0
  tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key));
1073
0
  if (NS_FAILED(tmp)) {
1074
0
    rv = tmp;
1075
0
  }
1076
0
  NS_ENSURE_SUCCESS(rv, rv);
1077
0
1078
0
  bool hasRows;
1079
0
  rv = statement->ExecuteStep(&hasRows);
1080
0
  NS_ENSURE_SUCCESS(rv, rv);
1081
0
1082
0
  NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1083
0
  return rv;
1084
0
}
1085
1086
nsresult
1087
nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry *entry, uint32_t newSize)
1088
0
{
1089
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1090
0
1091
0
  // Decompose the key into "ClientID" and "Key"
1092
0
  nsAutoCString keyBuf;
1093
0
  const char *cid, *key;
1094
0
  if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1095
0
    return NS_ERROR_UNEXPECTED;
1096
0
1097
0
  AutoResetStatement statement(mStatement_UpdateEntrySize);
1098
0
1099
0
  nsresult rv = statement->BindInt32ByIndex(0, newSize);
1100
0
  nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
1101
0
  if (NS_FAILED(tmp)) {
1102
0
    rv = tmp;
1103
0
  }
1104
0
  tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key));
1105
0
  if (NS_FAILED(tmp)) {
1106
0
    rv = tmp;
1107
0
  }
1108
0
  NS_ENSURE_SUCCESS(rv, rv);
1109
0
1110
0
  bool hasRows;
1111
0
  rv = statement->ExecuteStep(&hasRows);
1112
0
  NS_ENSURE_SUCCESS(rv, rv);
1113
0
1114
0
  NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1115
0
  return rv;
1116
0
}
1117
1118
nsresult
1119
nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, bool deleteData)
1120
0
{
1121
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1122
0
1123
0
  if (deleteData)
1124
0
  {
1125
0
    nsresult rv = DeleteData(entry);
1126
0
    if (NS_FAILED(rv))
1127
0
      return rv;
1128
0
  }
1129
0
1130
0
  // Decompose the key into "ClientID" and "Key"
1131
0
  nsAutoCString keyBuf;
1132
0
  const char *cid, *key;
1133
0
  if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1134
0
    return NS_ERROR_UNEXPECTED;
1135
0
1136
0
  AutoResetStatement statement(mStatement_DeleteEntry);
1137
0
1138
0
  nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
1139
0
  nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
1140
0
  NS_ENSURE_SUCCESS(rv, rv);
1141
0
  NS_ENSURE_SUCCESS(rv2, rv2);
1142
0
1143
0
  bool hasRows;
1144
0
  rv = statement->ExecuteStep(&hasRows);
1145
0
  NS_ENSURE_SUCCESS(rv, rv);
1146
0
1147
0
  NS_ASSERTION(!hasRows, "DELETE should not result in output");
1148
0
  return rv;
1149
0
}
1150
1151
nsresult
1152
nsOfflineCacheDevice::DeleteData(nsCacheEntry *entry)
1153
0
{
1154
0
  nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1155
0
  NS_ENSURE_STATE(binding);
1156
0
1157
0
  return binding->mDataFile->Remove(false);
1158
0
}
1159
1160
/**
1161
 * nsCacheDevice implementation
1162
 */
1163
1164
// This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
1165
// allow a template (mozilla::ArrayLength) to be instantiated based on a local
1166
// type.  Boo-urns!
1167
struct StatementSql {
1168
    nsCOMPtr<mozIStorageStatement> &statement;
1169
    const char *sql;
1170
    StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
1171
0
      statement (aStatement), sql (aSql) {}
1172
};
1173
1174
nsresult
1175
nsOfflineCacheDevice::Init()
1176
0
{
1177
0
  MOZ_ASSERT(false, "Need to be initialized with sqlite");
1178
0
  return NS_ERROR_NOT_IMPLEMENTED;
1179
0
}
1180
1181
nsresult
1182
nsOfflineCacheDevice::InitWithSqlite(mozIStorageService * ss)
1183
0
{
1184
0
  NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
1185
0
1186
0
  // SetCacheParentDirectory must have been called
1187
0
  NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
1188
0
1189
0
  // make sure the cache directory exists
1190
0
  nsresult rv = EnsureDir(mCacheDirectory);
1191
0
  NS_ENSURE_SUCCESS(rv, rv);
1192
0
1193
0
  // build path to index file
1194
0
  nsCOMPtr<nsIFile> indexFile;
1195
0
  rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
1196
0
  NS_ENSURE_SUCCESS(rv, rv);
1197
0
  rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
1198
0
  NS_ENSURE_SUCCESS(rv, rv);
1199
0
1200
0
  MOZ_ASSERT(ss, "nsOfflineCacheDevice::InitWithSqlite called before nsCacheService::Init() ?");
1201
0
  NS_ENSURE_TRUE(ss, NS_ERROR_UNEXPECTED);
1202
0
1203
0
  rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
1204
0
  NS_ENSURE_SUCCESS(rv, rv);
1205
0
1206
0
  mInitEventTarget = GetCurrentThreadEventTarget();
1207
0
1208
0
  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
1209
0
1210
0
  // XXX ... other initialization steps
1211
0
1212
0
  // XXX in the future we may wish to verify the schema for moz_cache
1213
0
  //     perhaps using "PRAGMA table_info" ?
1214
0
1215
0
  // build the table
1216
0
  //
1217
0
  //  "Generation" is the data file generation number.
1218
0
  //
1219
0
  rv = mDB->ExecuteSimpleSQL(
1220
0
      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
1221
0
                         "  ClientID        TEXT,\n"
1222
0
                         "  Key             TEXT,\n"
1223
0
                         "  MetaData        BLOB,\n"
1224
0
                         "  Generation      INTEGER,\n"
1225
0
                         "  DataSize        INTEGER,\n"
1226
0
                         "  FetchCount      INTEGER,\n"
1227
0
                         "  LastFetched     INTEGER,\n"
1228
0
                         "  LastModified    INTEGER,\n"
1229
0
                         "  ExpirationTime  INTEGER,\n"
1230
0
                         "  ItemType        INTEGER DEFAULT 0\n"
1231
0
                         ");\n"));
1232
0
  NS_ENSURE_SUCCESS(rv, rv);
1233
0
1234
0
  // Databases from 1.9.0 don't have the ItemType column.  Add the column
1235
0
  // here, but don't worry about failures (the column probably already exists)
1236
0
  mDB->ExecuteSimpleSQL(
1237
0
    NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
1238
0
1239
0
  // Create the table for storing cache groups.  All actions on
1240
0
  // moz_cache_groups use the GroupID, so use it as the primary key.
1241
0
  rv = mDB->ExecuteSimpleSQL(
1242
0
      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
1243
0
                         " GroupID TEXT PRIMARY KEY,\n"
1244
0
                         " ActiveClientID TEXT\n"
1245
0
                         ");\n"));
1246
0
  NS_ENSURE_SUCCESS(rv, rv);
1247
0
1248
0
  mDB->ExecuteSimpleSQL(
1249
0
    NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
1250
0
                       "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
1251
0
1252
0
  // ClientID: clientID joining moz_cache and moz_cache_namespaces
1253
0
  // tables.
1254
0
  // Data: Data associated with this namespace (e.g. a fallback URI
1255
0
  // for fallback entries).
1256
0
  // ItemType: the type of namespace.
1257
0
  rv = mDB->ExecuteSimpleSQL(
1258
0
      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
1259
0
                         " moz_cache_namespaces (\n"
1260
0
                         " ClientID TEXT,\n"
1261
0
                         " NameSpace TEXT,\n"
1262
0
                         " Data TEXT,\n"
1263
0
                         " ItemType INTEGER\n"
1264
0
                          ");\n"));
1265
0
   NS_ENSURE_SUCCESS(rv, rv);
1266
0
1267
0
  // Databases from 1.9.0 have a moz_cache_index that should be dropped
1268
0
  rv = mDB->ExecuteSimpleSQL(
1269
0
      NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
1270
0
  NS_ENSURE_SUCCESS(rv, rv);
1271
0
1272
0
  // Key/ClientID pairs should be unique in the database.  All queries
1273
0
  // against moz_cache use the Key (which is also the most unique), so
1274
0
  // use it as the primary key for this index.
1275
0
  rv = mDB->ExecuteSimpleSQL(
1276
0
      NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
1277
0
                         " moz_cache_key_clientid_index"
1278
0
                         " ON moz_cache (Key, ClientID);"));
1279
0
  NS_ENSURE_SUCCESS(rv, rv);
1280
0
1281
0
  // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
1282
0
  rv = mDB->ExecuteSimpleSQL(
1283
0
      NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
1284
0
                         " moz_cache_namespaces_clientid_index"
1285
0
                         " ON moz_cache_namespaces (ClientID, NameSpace);"));
1286
0
  NS_ENSURE_SUCCESS(rv, rv);
1287
0
1288
0
  // Used for namespace lookups.
1289
0
  rv = mDB->ExecuteSimpleSQL(
1290
0
      NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
1291
0
                         " moz_cache_namespaces_namespace_index"
1292
0
                         " ON moz_cache_namespaces (NameSpace);"));
1293
0
  NS_ENSURE_SUCCESS(rv, rv);
1294
0
1295
0
1296
0
  mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
1297
0
  if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
1298
0
1299
0
  rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, mEvictionFunction);
1300
0
  NS_ENSURE_SUCCESS(rv, rv);
1301
0
1302
0
  // create all (most) of our statements up front
1303
0
  StatementSql prepared[] = {
1304
0
    StatementSql ( mStatement_CacheSize,         "SELECT Sum(DataSize) from moz_cache;" ),
1305
0
    StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
1306
0
    StatementSql ( mStatement_EntryCount,        "SELECT count(*) from moz_cache;" ),
1307
0
    StatementSql ( mStatement_UpdateEntry,       "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
1308
0
    StatementSql ( mStatement_UpdateEntrySize,   "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
1309
0
    StatementSql ( mStatement_DeleteEntry,       "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1310
0
    StatementSql ( mStatement_FindEntry,         "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1311
0
    StatementSql ( mStatement_BindEntry,         "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ),
1312
0
1313
0
    StatementSql ( mStatement_MarkEntry,         "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
1314
0
    StatementSql ( mStatement_UnmarkEntry,       "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
1315
0
    StatementSql ( mStatement_GetTypes,          "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
1316
0
    StatementSql ( mStatement_CleanupUnmarked,   "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
1317
0
    StatementSql ( mStatement_GatherEntries,     "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
1318
0
1319
0
    StatementSql ( mStatement_ActivateClient,    "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
1320
0
    StatementSql ( mStatement_DeactivateGroup,   "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
1321
0
    StatementSql ( mStatement_FindClient,        "/* do not warn (bug 1293375) */ SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
1322
0
1323
0
    // Search for namespaces that match the URI.  Use the <= operator
1324
0
    // to ensure that we use the index on moz_cache_namespaces.
1325
0
    StatementSql ( mStatement_FindClientByNamespace, "/* do not warn (bug 1293375) */ SELECT ns.ClientID, ns.ItemType FROM"
1326
0
                                                     "  moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
1327
0
                                                     "  ON ns.ClientID = groups.ActiveClientID"
1328
0
                                                     " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
1329
0
                                                     " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
1330
0
    StatementSql ( mStatement_FindNamespaceEntry,    "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
1331
0
                                                     " WHERE ClientID = ?1"
1332
0
                                                     " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
1333
0
                                                     " ORDER BY NameSpace DESC;"),
1334
0
    StatementSql ( mStatement_InsertNamespaceEntry,  "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
1335
0
    StatementSql ( mStatement_EnumerateApps,         "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"),
1336
0
    StatementSql ( mStatement_EnumerateGroups,       "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
1337
0
    StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
1338
0
  };
1339
0
  for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i)
1340
0
  {
1341
0
    LOG(("Creating statement: %s\n", prepared[i].sql));
1342
0
1343
0
    rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
1344
0
                              getter_AddRefs(prepared[i].statement));
1345
0
    NS_ENSURE_SUCCESS(rv, rv);
1346
0
  }
1347
0
1348
0
  rv = InitActiveCaches();
1349
0
  NS_ENSURE_SUCCESS(rv, rv);
1350
0
1351
0
  return NS_OK;
1352
0
}
1353
1354
namespace {
1355
1356
nsresult
1357
GetGroupForCache(const nsACString& clientID, nsCString& group)
1358
0
{
1359
0
  group.Assign(clientID);
1360
0
  group.Truncate(group.FindChar('|'));
1361
0
  NS_UnescapeURL(group);
1362
0
1363
0
  return NS_OK;
1364
0
}
1365
1366
} // namespace
1367
1368
// static
1369
nsresult
1370
nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL,
1371
                                                   nsACString const &aOriginSuffix,
1372
                                                   nsACString &_result)
1373
0
{
1374
0
  nsCOMPtr<nsIURI> newURI;
1375
0
  nsresult rv = NS_GetURIWithNewRef(aManifestURL, EmptyCString(), getter_AddRefs(newURI));
1376
0
  NS_ENSURE_SUCCESS(rv, rv);
1377
0
1378
0
  nsAutoCString manifestSpec;
1379
0
  rv = newURI->GetAsciiSpec(manifestSpec);
1380
0
  NS_ENSURE_SUCCESS(rv, rv);
1381
0
1382
0
  _result.Assign(manifestSpec);
1383
0
  _result.Append('#');
1384
0
  _result.Append(aOriginSuffix);
1385
0
1386
0
  return NS_OK;
1387
0
}
1388
1389
nsresult
1390
nsOfflineCacheDevice::InitActiveCaches()
1391
0
{
1392
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1393
0
1394
0
  MutexAutoLock lock(mLock);
1395
0
1396
0
  AutoResetStatement statement(mStatement_EnumerateGroups);
1397
0
1398
0
  bool hasRows;
1399
0
  nsresult rv = statement->ExecuteStep(&hasRows);
1400
0
  NS_ENSURE_SUCCESS(rv, rv);
1401
0
1402
0
  while (hasRows)
1403
0
  {
1404
0
    nsAutoCString group;
1405
0
    statement->GetUTF8String(0, group);
1406
0
    nsCString clientID;
1407
0
    statement->GetUTF8String(1, clientID);
1408
0
1409
0
    mActiveCaches.PutEntry(clientID);
1410
0
    mActiveCachesByGroup.Put(group, new nsCString(clientID));
1411
0
1412
0
    rv = statement->ExecuteStep(&hasRows);
1413
0
    NS_ENSURE_SUCCESS(rv, rv);
1414
0
  }
1415
0
1416
0
  return NS_OK;
1417
0
}
1418
1419
nsresult
1420
nsOfflineCacheDevice::Shutdown()
1421
0
{
1422
0
  NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
1423
0
1424
0
  {
1425
0
    MutexAutoLock lock(mLock);
1426
0
    for (auto iter = mCaches.Iter(); !iter.Done(); iter.Next()) {
1427
0
      nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(iter.UserData());
1428
0
      if (obj) {
1429
0
        auto appCache = static_cast<nsApplicationCache*>(obj.get());
1430
0
        appCache->MarkInvalid();
1431
0
      }
1432
0
    }
1433
0
  }
1434
0
1435
0
  {
1436
0
  EvictionObserver evictionObserver(mDB, mEvictionFunction);
1437
0
1438
0
  // Delete all rows whose clientID is not an active clientID.
1439
0
  nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1440
0
    "DELETE FROM moz_cache WHERE rowid IN"
1441
0
    "  (SELECT moz_cache.rowid FROM"
1442
0
    "    moz_cache LEFT OUTER JOIN moz_cache_groups ON"
1443
0
    "      (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
1444
0
    "   WHERE moz_cache_groups.GroupID ISNULL)"));
1445
0
1446
0
  if (NS_FAILED(rv))
1447
0
    NS_WARNING("Failed to clean up unused application caches.");
1448
0
  else
1449
0
    evictionObserver.Apply();
1450
0
1451
0
  // Delete all namespaces whose clientID is not an active clientID.
1452
0
  rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1453
0
    "DELETE FROM moz_cache_namespaces WHERE rowid IN"
1454
0
    "  (SELECT moz_cache_namespaces.rowid FROM"
1455
0
    "    moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
1456
0
    "      (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
1457
0
    "   WHERE moz_cache_groups.GroupID ISNULL)"));
1458
0
1459
0
  if (NS_FAILED(rv))
1460
0
    NS_WARNING("Failed to clean up namespaces.");
1461
0
1462
0
  mEvictionFunction = nullptr;
1463
0
1464
0
  mStatement_CacheSize = nullptr;
1465
0
  mStatement_ApplicationCacheSize = nullptr;
1466
0
  mStatement_EntryCount = nullptr;
1467
0
  mStatement_UpdateEntry = nullptr;
1468
0
  mStatement_UpdateEntrySize = nullptr;
1469
0
  mStatement_DeleteEntry = nullptr;
1470
0
  mStatement_FindEntry = nullptr;
1471
0
  mStatement_BindEntry = nullptr;
1472
0
  mStatement_ClearDomain = nullptr;
1473
0
  mStatement_MarkEntry = nullptr;
1474
0
  mStatement_UnmarkEntry = nullptr;
1475
0
  mStatement_GetTypes = nullptr;
1476
0
  mStatement_FindNamespaceEntry = nullptr;
1477
0
  mStatement_InsertNamespaceEntry = nullptr;
1478
0
  mStatement_CleanupUnmarked = nullptr;
1479
0
  mStatement_GatherEntries = nullptr;
1480
0
  mStatement_ActivateClient = nullptr;
1481
0
  mStatement_DeactivateGroup = nullptr;
1482
0
  mStatement_FindClient = nullptr;
1483
0
  mStatement_FindClientByNamespace = nullptr;
1484
0
  mStatement_EnumerateApps = nullptr;
1485
0
  mStatement_EnumerateGroups = nullptr;
1486
0
  mStatement_EnumerateGroupsTimeOrder = nullptr;
1487
0
  }
1488
0
1489
0
  // Close Database on the correct thread
1490
0
  bool isOnCurrentThread = true;
1491
0
  if (mInitEventTarget)
1492
0
    isOnCurrentThread = mInitEventTarget->IsOnCurrentThread();
1493
0
1494
0
  if (!isOnCurrentThread) {
1495
0
    nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
1496
0
1497
0
    if (ev) {
1498
0
      mInitEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
1499
0
    }
1500
0
  }
1501
0
  else {
1502
0
    mDB->Close();
1503
0
  }
1504
0
1505
0
  mDB = nullptr;
1506
0
  mInitEventTarget = nullptr;
1507
0
1508
0
  return NS_OK;
1509
0
}
1510
1511
const char *
1512
nsOfflineCacheDevice::GetDeviceID()
1513
0
{
1514
0
  return OFFLINE_CACHE_DEVICE_ID;
1515
0
}
1516
1517
nsCacheEntry *
1518
nsOfflineCacheDevice::FindEntry(nsCString *fullKey, bool *collision)
1519
0
{
1520
0
  NS_ENSURE_TRUE(Initialized(), nullptr);
1521
0
1522
0
  mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH_2> timer;
1523
0
  LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
1524
0
1525
0
  // SELECT * FROM moz_cache WHERE key = ?
1526
0
1527
0
  // Decompose the key into "ClientID" and "Key"
1528
0
  nsAutoCString keyBuf;
1529
0
  const char *cid, *key;
1530
0
  if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
1531
0
    return nullptr;
1532
0
1533
0
  AutoResetStatement statement(mStatement_FindEntry);
1534
0
1535
0
  nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
1536
0
  nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
1537
0
  NS_ENSURE_SUCCESS(rv, nullptr);
1538
0
  NS_ENSURE_SUCCESS(rv2, nullptr);
1539
0
1540
0
  bool hasRows;
1541
0
  rv = statement->ExecuteStep(&hasRows);
1542
0
  if (NS_FAILED(rv) || !hasRows)
1543
0
    return nullptr; // entry not found
1544
0
1545
0
  nsOfflineCacheRecord rec;
1546
0
  statement->GetSharedBlob(0, &rec.metaDataLen,
1547
0
                           (const uint8_t **) &rec.metaData);
1548
0
  rec.generation     = statement->AsInt32(1);
1549
0
  rec.dataSize       = statement->AsInt32(2);
1550
0
  rec.fetchCount     = statement->AsInt32(3);
1551
0
  rec.lastFetched    = statement->AsInt64(4);
1552
0
  rec.lastModified   = statement->AsInt64(5);
1553
0
  rec.expirationTime = statement->AsInt64(6);
1554
0
1555
0
  LOG(("entry: [%u %d %d %d %" PRId64 " %" PRId64 " %" PRId64 "]\n",
1556
0
        rec.metaDataLen,
1557
0
        rec.generation,
1558
0
        rec.dataSize,
1559
0
        rec.fetchCount,
1560
0
        rec.lastFetched,
1561
0
        rec.lastModified,
1562
0
        rec.expirationTime));
1563
0
1564
0
  nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec);
1565
0
1566
0
  if (entry)
1567
0
  {
1568
0
    // make sure that the data file exists
1569
0
    nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data();
1570
0
    bool isFile;
1571
0
    rv = binding->mDataFile->IsFile(&isFile);
1572
0
    if (NS_FAILED(rv) || !isFile)
1573
0
    {
1574
0
      DeleteEntry(entry, false);
1575
0
      delete entry;
1576
0
      return nullptr;
1577
0
    }
1578
0
1579
0
    // lock the entry
1580
0
    Lock(*fullKey);
1581
0
  }
1582
0
1583
0
  return entry;
1584
0
}
1585
1586
nsresult
1587
nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry *entry)
1588
0
{
1589
0
  LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
1590
0
       entry->Key()->get()));
1591
0
1592
0
  // This method is called to inform us that the nsCacheEntry object is going
1593
0
  // away.  We should persist anything that needs to be persisted, or if the
1594
0
  // entry is doomed, we can go ahead and clear its storage.
1595
0
1596
0
  if (entry->IsDoomed())
1597
0
  {
1598
0
    // remove corresponding row and file if they exist
1599
0
1600
0
    // the row should have been removed in DoomEntry... we could assert that
1601
0
    // that happened.  otherwise, all we have to do here is delete the file
1602
0
    // on disk.
1603
0
    DeleteData(entry);
1604
0
  }
1605
0
  else if (((nsOfflineCacheBinding *)entry->Data())->IsNewEntry())
1606
0
  {
1607
0
    // UPDATE the database row
1608
0
1609
0
    // Only new entries are updated, since offline cache is updated in
1610
0
    // transactions.  New entries are those who is returned from
1611
0
    // BindEntry().
1612
0
1613
0
    LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
1614
0
    UpdateEntry(entry);
1615
0
  } else {
1616
0
    LOG(("nsOfflineCacheDevice::DeactivateEntry "
1617
0
   "skipping update since entry is not dirty\n"));
1618
0
  }
1619
0
1620
0
  // Unlock the entry
1621
0
  Unlock(*entry->Key());
1622
0
1623
0
  delete entry;
1624
0
1625
0
  return NS_OK;
1626
0
}
1627
1628
nsresult
1629
nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry)
1630
0
{
1631
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1632
0
1633
0
  LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
1634
0
1635
0
  NS_ENSURE_STATE(!entry->Data());
1636
0
1637
0
  // This method is called to inform us that we have a new entry.  The entry
1638
0
  // may collide with an existing entry in our DB, but if that happens we can
1639
0
  // assume that the entry is not being used.
1640
0
1641
0
  // INSERT the database row
1642
0
1643
0
  // XXX Assumption: if the row already exists, then FindEntry would have
1644
0
  // returned it.  if that entry was doomed, then DoomEntry would have removed
1645
0
  // it from the table.  so, we should always have to insert at this point.
1646
0
1647
0
  // Decompose the key into "ClientID" and "Key"
1648
0
  nsAutoCString keyBuf;
1649
0
  const char *cid, *key;
1650
0
  if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1651
0
    return NS_ERROR_UNEXPECTED;
1652
0
1653
0
  // create binding, pick best generation number
1654
0
  RefPtr<nsOfflineCacheBinding> binding =
1655
0
      nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
1656
0
  if (!binding)
1657
0
    return NS_ERROR_OUT_OF_MEMORY;
1658
0
  binding->MarkNewEntry();
1659
0
1660
0
  nsOfflineCacheRecord rec;
1661
0
  rec.clientID = cid;
1662
0
  rec.key = key;
1663
0
  rec.metaData = nullptr; // don't write any metadata now.
1664
0
  rec.metaDataLen = 0;
1665
0
  rec.generation = binding->mGeneration;
1666
0
  rec.dataSize = 0;
1667
0
  rec.fetchCount = entry->FetchCount();
1668
0
  rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
1669
0
  rec.lastModified = PRTimeFromSeconds(entry->LastModified());
1670
0
  rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
1671
0
1672
0
  AutoResetStatement statement(mStatement_BindEntry);
1673
0
1674
0
  nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
1675
0
  nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
1676
0
  if (NS_FAILED(tmp)) {
1677
0
    rv = tmp;
1678
0
  }
1679
0
  tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
1680
0
  if (NS_FAILED(tmp)) {
1681
0
    rv = tmp;
1682
0
  }
1683
0
  tmp = statement->BindInt32ByIndex(3, rec.generation);
1684
0
  if (NS_FAILED(tmp)) {
1685
0
    rv = tmp;
1686
0
  }
1687
0
  tmp = statement->BindInt32ByIndex(4, rec.dataSize);
1688
0
  if (NS_FAILED(tmp)) {
1689
0
    rv = tmp;
1690
0
  }
1691
0
  tmp = statement->BindInt32ByIndex(5, rec.fetchCount);
1692
0
  if (NS_FAILED(tmp)) {
1693
0
    rv = tmp;
1694
0
  }
1695
0
  tmp = statement->BindInt64ByIndex(6, rec.lastFetched);
1696
0
  if (NS_FAILED(tmp)) {
1697
0
    rv = tmp;
1698
0
  }
1699
0
  tmp = statement->BindInt64ByIndex(7, rec.lastModified);
1700
0
  if (NS_FAILED(tmp)) {
1701
0
    rv = tmp;
1702
0
  }
1703
0
  tmp = statement->BindInt64ByIndex(8, rec.expirationTime);
1704
0
  if (NS_FAILED(tmp)) {
1705
0
    rv = tmp;
1706
0
  }
1707
0
  NS_ENSURE_SUCCESS(rv, rv);
1708
0
1709
0
  bool hasRows;
1710
0
  rv = statement->ExecuteStep(&hasRows);
1711
0
  NS_ENSURE_SUCCESS(rv, rv);
1712
0
  NS_ASSERTION(!hasRows, "INSERT should not result in output");
1713
0
1714
0
  entry->SetData(binding);
1715
0
1716
0
  // lock the entry
1717
0
  Lock(*entry->Key());
1718
0
1719
0
  return NS_OK;
1720
0
}
1721
1722
void
1723
nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry)
1724
0
{
1725
0
  LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
1726
0
1727
0
  // This method is called to inform us that we should mark the entry to be
1728
0
  // deleted when it is no longer in use.
1729
0
1730
0
  // We can go ahead and delete the corresponding row in our table,
1731
0
  // but we must not delete the file on disk until we are deactivated.
1732
0
  // In another word, the file should be deleted if the entry had been
1733
0
  // deactivated.
1734
0
1735
0
  DeleteEntry(entry, !entry->IsActive());
1736
0
}
1737
1738
nsresult
1739
nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry      *entry,
1740
                                              nsCacheAccessMode  mode,
1741
                                              uint32_t           offset,
1742
                                              nsIInputStream   **result)
1743
0
{
1744
0
  LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
1745
0
       entry->Key()->get()));
1746
0
1747
0
  *result = nullptr;
1748
0
1749
0
  NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG);
1750
0
1751
0
  // return an input stream to the entry's data file.  the stream
1752
0
  // may be read on a background thread.
1753
0
1754
0
  nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1755
0
  NS_ENSURE_STATE(binding);
1756
0
1757
0
  nsCOMPtr<nsIInputStream> in;
1758
0
  NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
1759
0
  if (!in)
1760
0
    return NS_ERROR_UNEXPECTED;
1761
0
1762
0
  // respect |offset| param
1763
0
  if (offset != 0)
1764
0
  {
1765
0
    nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
1766
0
    NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1767
0
1768
0
    seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1769
0
  }
1770
0
1771
0
  in.swap(*result);
1772
0
  return NS_OK;
1773
0
}
1774
1775
nsresult
1776
nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry       *entry,
1777
                                               nsCacheAccessMode   mode,
1778
                                               uint32_t            offset,
1779
                                               nsIOutputStream   **result)
1780
0
{
1781
0
  LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
1782
0
       entry->Key()->get()));
1783
0
1784
0
  *result = nullptr;
1785
0
1786
0
  NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
1787
0
1788
0
  // return an output stream to the entry's data file.  we can assume
1789
0
  // that the output stream will only be used on the main thread.
1790
0
1791
0
  nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1792
0
  NS_ENSURE_STATE(binding);
1793
0
1794
0
  nsCOMPtr<nsIOutputStream> out;
1795
0
  NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
1796
0
                              PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
1797
0
                              00600);
1798
0
  if (!out)
1799
0
    return NS_ERROR_UNEXPECTED;
1800
0
1801
0
  // respect |offset| param
1802
0
  nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
1803
0
  NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1804
0
  if (offset != 0)
1805
0
    seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1806
0
1807
0
  // truncate the file at the given offset
1808
0
  seekable->SetEOF();
1809
0
1810
0
  nsCOMPtr<nsIOutputStream> bufferedOut;
1811
0
  nsresult rv =
1812
0
    NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out.forget(),
1813
0
                               16 * 1024);
1814
0
  NS_ENSURE_SUCCESS(rv, rv);
1815
0
1816
0
  bufferedOut.forget(result);
1817
0
  return NS_OK;
1818
0
}
1819
1820
nsresult
1821
nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result)
1822
0
{
1823
0
  LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
1824
0
       entry->Key()->get()));
1825
0
1826
0
  nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1827
0
  NS_ENSURE_STATE(binding);
1828
0
1829
0
  NS_IF_ADDREF(*result = binding->mDataFile);
1830
0
  return NS_OK;
1831
0
}
1832
1833
nsresult
1834
nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry *entry, int32_t deltaSize)
1835
0
{
1836
0
  LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
1837
0
      entry->Key()->get(), deltaSize));
1838
0
1839
0
  const int32_t DELTA_THRESHOLD = 1<<14; // 16k
1840
0
1841
0
  // called to notify us of an impending change in the total size of the
1842
0
  // specified entry.
1843
0
1844
0
  uint32_t oldSize = entry->DataSize();
1845
0
  NS_ASSERTION(deltaSize >= 0 || int32_t(oldSize) + deltaSize >= 0, "oops");
1846
0
  uint32_t newSize = int32_t(oldSize) + deltaSize;
1847
0
  UpdateEntrySize(entry, newSize);
1848
0
1849
0
  mDeltaCounter += deltaSize; // this may go negative
1850
0
1851
0
  if (mDeltaCounter >= DELTA_THRESHOLD)
1852
0
  {
1853
0
    if (CacheSize() > mCacheCapacity) {
1854
0
      // the entry will overrun the cache capacity, doom the entry
1855
0
      // and abort
1856
#ifdef DEBUG
1857
      nsresult rv =
1858
#endif
1859
        nsCacheService::DoomEntry(entry);
1860
0
      NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
1861
0
      return NS_ERROR_ABORT;
1862
0
    }
1863
0
1864
0
    mDeltaCounter = 0; // reset counter
1865
0
  }
1866
0
1867
0
  return NS_OK;
1868
0
}
1869
1870
nsresult
1871
nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor)
1872
0
{
1873
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1874
0
1875
0
  // called to enumerate the offline cache.
1876
0
1877
0
  nsCOMPtr<nsICacheDeviceInfo> deviceInfo =
1878
0
      new nsOfflineCacheDeviceInfo(this);
1879
0
1880
0
  bool keepGoing;
1881
0
  nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo,
1882
0
                                     &keepGoing);
1883
0
  if (NS_FAILED(rv))
1884
0
    return rv;
1885
0
1886
0
  if (!keepGoing)
1887
0
    return NS_OK;
1888
0
1889
0
  // SELECT * from moz_cache;
1890
0
1891
0
  nsOfflineCacheRecord rec;
1892
0
  RefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
1893
0
  if (!info)
1894
0
    return NS_ERROR_OUT_OF_MEMORY;
1895
0
  info->mRec = &rec;
1896
0
1897
0
  // XXX may want to list columns explicitly
1898
0
  nsCOMPtr<mozIStorageStatement> statement;
1899
0
  rv = mDB->CreateStatement(
1900
0
      NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
1901
0
      getter_AddRefs(statement));
1902
0
  NS_ENSURE_SUCCESS(rv, rv);
1903
0
1904
0
  bool hasRows;
1905
0
  for (;;)
1906
0
  {
1907
0
    rv = statement->ExecuteStep(&hasRows);
1908
0
    if (NS_FAILED(rv) || !hasRows)
1909
0
      break;
1910
0
1911
0
    statement->GetSharedUTF8String(0, nullptr, &rec.clientID);
1912
0
    statement->GetSharedUTF8String(1, nullptr, &rec.key);
1913
0
    statement->GetSharedBlob(2, &rec.metaDataLen,
1914
0
                             (const uint8_t **) &rec.metaData);
1915
0
    rec.generation     = statement->AsInt32(3);
1916
0
    rec.dataSize       = statement->AsInt32(4);
1917
0
    rec.fetchCount     = statement->AsInt32(5);
1918
0
    rec.lastFetched    = statement->AsInt64(6);
1919
0
    rec.lastModified   = statement->AsInt64(7);
1920
0
    rec.expirationTime = statement->AsInt64(8);
1921
0
1922
0
    bool keepGoing;
1923
0
    rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
1924
0
    if (NS_FAILED(rv) || !keepGoing)
1925
0
      break;
1926
0
  }
1927
0
1928
0
  info->mRec = nullptr;
1929
0
  return NS_OK;
1930
0
}
1931
1932
nsresult
1933
nsOfflineCacheDevice::EvictEntries(const char *clientID)
1934
0
{
1935
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1936
0
1937
0
  LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
1938
0
       clientID ? clientID : ""));
1939
0
1940
0
  // called to evict all entries matching the given clientID.
1941
0
1942
0
  // need trigger to fire user defined function after a row is deleted
1943
0
  // so we can delete the corresponding data file.
1944
0
  EvictionObserver evictionObserver(mDB, mEvictionFunction);
1945
0
1946
0
  nsCOMPtr<mozIStorageStatement> statement;
1947
0
  nsresult rv;
1948
0
  if (clientID)
1949
0
  {
1950
0
    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"),
1951
0
                              getter_AddRefs(statement));
1952
0
    NS_ENSURE_SUCCESS(rv, rv);
1953
0
1954
0
    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1955
0
    NS_ENSURE_SUCCESS(rv, rv);
1956
0
1957
0
    rv = statement->Execute();
1958
0
    NS_ENSURE_SUCCESS(rv, rv);
1959
0
1960
0
    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
1961
0
                              getter_AddRefs(statement));
1962
0
    NS_ENSURE_SUCCESS(rv, rv);
1963
0
1964
0
    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1965
0
    NS_ENSURE_SUCCESS(rv, rv);
1966
0
1967
0
    rv = statement->Execute();
1968
0
    NS_ENSURE_SUCCESS(rv, rv);
1969
0
1970
0
    // TODO - Should update internal hashtables.
1971
0
    // Low priority, since this API is not widely used.
1972
0
  }
1973
0
  else
1974
0
  {
1975
0
    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"),
1976
0
                              getter_AddRefs(statement));
1977
0
    NS_ENSURE_SUCCESS(rv, rv);
1978
0
1979
0
    rv = statement->Execute();
1980
0
    NS_ENSURE_SUCCESS(rv, rv);
1981
0
1982
0
    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
1983
0
                              getter_AddRefs(statement));
1984
0
    NS_ENSURE_SUCCESS(rv, rv);
1985
0
1986
0
    rv = statement->Execute();
1987
0
    NS_ENSURE_SUCCESS(rv, rv);
1988
0
1989
0
    MutexAutoLock lock(mLock);
1990
0
    mCaches.Clear();
1991
0
    mActiveCaches.Clear();
1992
0
    mActiveCachesByGroup.Clear();
1993
0
  }
1994
0
1995
0
  evictionObserver.Apply();
1996
0
1997
0
  statement = nullptr;
1998
0
  // Also evict any namespaces associated with this clientID.
1999
0
  if (clientID)
2000
0
  {
2001
0
    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
2002
0
                              getter_AddRefs(statement));
2003
0
    NS_ENSURE_SUCCESS(rv, rv);
2004
0
2005
0
    rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
2006
0
    NS_ENSURE_SUCCESS(rv, rv);
2007
0
  }
2008
0
  else
2009
0
  {
2010
0
    rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
2011
0
                              getter_AddRefs(statement));
2012
0
    NS_ENSURE_SUCCESS(rv, rv);
2013
0
  }
2014
0
2015
0
  rv = statement->Execute();
2016
0
  NS_ENSURE_SUCCESS(rv, rv);
2017
0
2018
0
  return NS_OK;
2019
0
}
2020
2021
nsresult
2022
nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
2023
                                const nsACString &key,
2024
                                uint32_t typeBits)
2025
0
{
2026
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2027
0
2028
0
  LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
2029
0
       clientID.get(), PromiseFlatCString(key).get(), typeBits));
2030
0
2031
0
  AutoResetStatement statement(mStatement_MarkEntry);
2032
0
  nsresult rv = statement->BindInt32ByIndex(0, typeBits);
2033
0
  NS_ENSURE_SUCCESS(rv, rv);
2034
0
  rv = statement->BindUTF8StringByIndex(1, clientID);
2035
0
  NS_ENSURE_SUCCESS(rv, rv);
2036
0
  rv = statement->BindUTF8StringByIndex(2, key);
2037
0
  NS_ENSURE_SUCCESS(rv, rv);
2038
0
2039
0
  rv = statement->Execute();
2040
0
  NS_ENSURE_SUCCESS(rv, rv);
2041
0
2042
0
  return NS_OK;
2043
0
}
2044
2045
nsresult
2046
nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
2047
                                  const nsACString &key,
2048
                                  uint32_t typeBits)
2049
0
{
2050
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2051
0
2052
0
  LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
2053
0
       clientID.get(), PromiseFlatCString(key).get(), typeBits));
2054
0
2055
0
  AutoResetStatement statement(mStatement_UnmarkEntry);
2056
0
  nsresult rv = statement->BindInt32ByIndex(0, typeBits);
2057
0
  NS_ENSURE_SUCCESS(rv, rv);
2058
0
  rv = statement->BindUTF8StringByIndex(1, clientID);
2059
0
  NS_ENSURE_SUCCESS(rv, rv);
2060
0
  rv = statement->BindUTF8StringByIndex(2, key);
2061
0
  NS_ENSURE_SUCCESS(rv, rv);
2062
0
2063
0
  rv = statement->Execute();
2064
0
  NS_ENSURE_SUCCESS(rv, rv);
2065
0
2066
0
  // Remove the entry if it is now empty.
2067
0
2068
0
  EvictionObserver evictionObserver(mDB, mEvictionFunction);
2069
0
2070
0
  AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
2071
0
  rv = cleanupStatement->BindUTF8StringByIndex(0, clientID);
2072
0
  NS_ENSURE_SUCCESS(rv, rv);
2073
0
  rv = cleanupStatement->BindUTF8StringByIndex(1, key);
2074
0
  NS_ENSURE_SUCCESS(rv, rv);
2075
0
2076
0
  rv = cleanupStatement->Execute();
2077
0
  NS_ENSURE_SUCCESS(rv, rv);
2078
0
2079
0
  evictionObserver.Apply();
2080
0
2081
0
  return NS_OK;
2082
0
}
2083
2084
nsresult
2085
nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID,
2086
                                           const nsACString &key,
2087
                                           nsIApplicationCacheNamespace **out)
2088
0
{
2089
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2090
0
2091
0
  LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
2092
0
       clientID.get(), PromiseFlatCString(key).get()));
2093
0
2094
0
  nsresult rv;
2095
0
2096
0
  AutoResetStatement statement(mStatement_FindNamespaceEntry);
2097
0
2098
0
  rv = statement->BindUTF8StringByIndex(0, clientID);
2099
0
  NS_ENSURE_SUCCESS(rv, rv);
2100
0
  rv = statement->BindUTF8StringByIndex(1, key);
2101
0
  NS_ENSURE_SUCCESS(rv, rv);
2102
0
2103
0
  bool hasRows;
2104
0
  rv = statement->ExecuteStep(&hasRows);
2105
0
  NS_ENSURE_SUCCESS(rv, rv);
2106
0
2107
0
  *out = nullptr;
2108
0
2109
0
  bool found = false;
2110
0
  nsCString nsSpec;
2111
0
  int32_t nsType = 0;
2112
0
  nsCString nsData;
2113
0
2114
0
  while (hasRows)
2115
0
  {
2116
0
    int32_t itemType;
2117
0
    rv = statement->GetInt32(2, &itemType);
2118
0
    NS_ENSURE_SUCCESS(rv, rv);
2119
0
2120
0
    if (!found || itemType > nsType)
2121
0
    {
2122
0
      nsType = itemType;
2123
0
2124
0
      rv = statement->GetUTF8String(0, nsSpec);
2125
0
      NS_ENSURE_SUCCESS(rv, rv);
2126
0
2127
0
      rv = statement->GetUTF8String(1, nsData);
2128
0
      NS_ENSURE_SUCCESS(rv, rv);
2129
0
2130
0
      found = true;
2131
0
    }
2132
0
2133
0
    rv = statement->ExecuteStep(&hasRows);
2134
0
    NS_ENSURE_SUCCESS(rv, rv);
2135
0
  }
2136
0
2137
0
  if (found) {
2138
0
    nsCOMPtr<nsIApplicationCacheNamespace> ns =
2139
0
      new nsApplicationCacheNamespace();
2140
0
    if (!ns)
2141
0
      return NS_ERROR_OUT_OF_MEMORY;
2142
0
    rv = ns->Init(nsType, nsSpec, nsData);
2143
0
    NS_ENSURE_SUCCESS(rv, rv);
2144
0
2145
0
    ns.swap(*out);
2146
0
  }
2147
0
2148
0
  return NS_OK;
2149
0
}
2150
2151
nsresult
2152
nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID,
2153
                                             const nsACString &key)
2154
0
{
2155
0
  // XXX: We should also be propagating this cache entry to other matching
2156
0
  // caches.  See bug 444807.
2157
0
2158
0
  return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
2159
0
}
2160
2161
nsresult
2162
nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
2163
                               const nsACString &key,
2164
                               uint32_t *typeBits)
2165
0
{
2166
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2167
0
2168
0
  LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
2169
0
       clientID.get(), PromiseFlatCString(key).get()));
2170
0
2171
0
  AutoResetStatement statement(mStatement_GetTypes);
2172
0
  nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2173
0
  NS_ENSURE_SUCCESS(rv, rv);
2174
0
  rv = statement->BindUTF8StringByIndex(1, key);
2175
0
  NS_ENSURE_SUCCESS(rv, rv);
2176
0
2177
0
  bool hasRows;
2178
0
  rv = statement->ExecuteStep(&hasRows);
2179
0
  NS_ENSURE_SUCCESS(rv, rv);
2180
0
2181
0
  if (!hasRows)
2182
0
    return NS_ERROR_CACHE_KEY_NOT_FOUND;
2183
0
2184
0
  *typeBits = statement->AsInt32(0);
2185
0
2186
0
  return NS_OK;
2187
0
}
2188
2189
nsresult
2190
nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
2191
                                    uint32_t typeBits,
2192
                                    uint32_t *count,
2193
                                    char ***keys)
2194
0
{
2195
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2196
0
2197
0
  LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
2198
0
       clientID.get(), typeBits));
2199
0
2200
0
  AutoResetStatement statement(mStatement_GatherEntries);
2201
0
  nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2202
0
  NS_ENSURE_SUCCESS(rv, rv);
2203
0
2204
0
  rv = statement->BindInt32ByIndex(1, typeBits);
2205
0
  NS_ENSURE_SUCCESS(rv, rv);
2206
0
2207
0
  return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
2208
0
}
2209
2210
nsresult
2211
nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
2212
                                   nsIApplicationCacheNamespace *ns)
2213
0
{
2214
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2215
0
2216
0
  nsCString namespaceSpec;
2217
0
  nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
2218
0
  NS_ENSURE_SUCCESS(rv, rv);
2219
0
2220
0
  nsCString data;
2221
0
  rv = ns->GetData(data);
2222
0
  NS_ENSURE_SUCCESS(rv, rv);
2223
0
2224
0
  uint32_t itemType;
2225
0
  rv = ns->GetItemType(&itemType);
2226
0
  NS_ENSURE_SUCCESS(rv, rv);
2227
0
2228
0
  LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
2229
0
       clientID.get(), namespaceSpec.get(), data.get(), itemType));
2230
0
2231
0
  AutoResetStatement statement(mStatement_InsertNamespaceEntry);
2232
0
2233
0
  rv = statement->BindUTF8StringByIndex(0, clientID);
2234
0
  NS_ENSURE_SUCCESS(rv, rv);
2235
0
2236
0
  rv = statement->BindUTF8StringByIndex(1, namespaceSpec);
2237
0
  NS_ENSURE_SUCCESS(rv, rv);
2238
0
2239
0
  rv = statement->BindUTF8StringByIndex(2, data);
2240
0
  NS_ENSURE_SUCCESS(rv, rv);
2241
0
2242
0
  rv = statement->BindInt32ByIndex(3, itemType);
2243
0
  NS_ENSURE_SUCCESS(rv, rv);
2244
0
2245
0
  rv = statement->Execute();
2246
0
  NS_ENSURE_SUCCESS(rv, rv);
2247
0
2248
0
  return NS_OK;
2249
0
}
2250
2251
nsresult
2252
nsOfflineCacheDevice::GetUsage(const nsACString &clientID,
2253
                               uint32_t *usage)
2254
0
{
2255
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2256
0
2257
0
  LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
2258
0
       PromiseFlatCString(clientID).get()));
2259
0
2260
0
  *usage = 0;
2261
0
2262
0
  AutoResetStatement statement(mStatement_ApplicationCacheSize);
2263
0
2264
0
  nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2265
0
  NS_ENSURE_SUCCESS(rv, rv);
2266
0
2267
0
  bool hasRows;
2268
0
  rv = statement->ExecuteStep(&hasRows);
2269
0
  NS_ENSURE_SUCCESS(rv, rv);
2270
0
2271
0
  if (!hasRows)
2272
0
    return NS_OK;
2273
0
2274
0
  *usage = static_cast<uint32_t>(statement->AsInt32(0));
2275
0
2276
0
  return NS_OK;
2277
0
}
2278
2279
nsresult
2280
nsOfflineCacheDevice::GetGroups(uint32_t *count,
2281
                                 char ***keys)
2282
0
{
2283
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2284
0
2285
0
  LOG(("nsOfflineCacheDevice::GetGroups"));
2286
0
2287
0
  return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
2288
0
}
2289
2290
nsresult
2291
nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count,
2292
             char ***keys)
2293
0
{
2294
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2295
0
2296
0
  LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
2297
0
2298
0
  return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys);
2299
0
}
2300
2301
bool
2302
nsOfflineCacheDevice::IsLocked(const nsACString &key)
2303
0
{
2304
0
  MutexAutoLock lock(mLock);
2305
0
  return mLockedEntries.GetEntry(key);
2306
0
}
2307
2308
void
2309
nsOfflineCacheDevice::Lock(const nsACString &key)
2310
0
{
2311
0
  MutexAutoLock lock(mLock);
2312
0
  mLockedEntries.PutEntry(key);
2313
0
}
2314
2315
void
2316
nsOfflineCacheDevice::Unlock(const nsACString &key)
2317
0
{
2318
0
  MutexAutoLock lock(mLock);
2319
0
  mLockedEntries.RemoveEntry(key);
2320
0
}
2321
2322
nsresult
2323
nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
2324
                                     uint32_t resultIndex,
2325
                                     uint32_t * count,
2326
                                     char *** values)
2327
0
{
2328
0
  bool hasRows;
2329
0
  nsresult rv = statement->ExecuteStep(&hasRows);
2330
0
  NS_ENSURE_SUCCESS(rv, rv);
2331
0
2332
0
  nsTArray<nsCString> valArray;
2333
0
  while (hasRows)
2334
0
  {
2335
0
    uint32_t length;
2336
0
    valArray.AppendElement(
2337
0
      nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length)));
2338
0
2339
0
    rv = statement->ExecuteStep(&hasRows);
2340
0
    NS_ENSURE_SUCCESS(rv, rv);
2341
0
  }
2342
0
2343
0
  *count = valArray.Length();
2344
0
  char **ret = static_cast<char **>(moz_xmalloc(*count * sizeof(char*)));
2345
0
2346
0
  for (uint32_t i = 0; i <  *count; i++) {
2347
0
    ret[i] = NS_xstrdup(valArray[i].get());
2348
0
  }
2349
0
2350
0
  *values = ret;
2351
0
2352
0
  return NS_OK;
2353
0
}
2354
2355
nsresult
2356
nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
2357
                                             nsIApplicationCache **out)
2358
0
{
2359
0
  *out = nullptr;
2360
0
2361
0
  nsCString clientID;
2362
0
  // Some characters are special in the clientID.  Escape the groupID
2363
0
  // before putting it in to the client key.
2364
0
  if (!NS_Escape(nsCString(group), clientID, url_Path)) {
2365
0
    return NS_ERROR_OUT_OF_MEMORY;
2366
0
  }
2367
0
2368
0
  PRTime now = PR_Now();
2369
0
2370
0
  // Include the timestamp to guarantee uniqueness across runs, and
2371
0
  // the gNextTemporaryClientID for uniqueness within a second.
2372
0
  clientID.Append(nsPrintfCString("|%016" PRId64 "|%d",
2373
0
                                  now / PR_USEC_PER_SEC,
2374
0
                                  gNextTemporaryClientID++));
2375
0
2376
0
  nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
2377
0
                                                               group,
2378
0
                                                               clientID);
2379
0
  if (!cache)
2380
0
    return NS_ERROR_OUT_OF_MEMORY;
2381
0
2382
0
  nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
2383
0
  if (!weak)
2384
0
    return NS_ERROR_OUT_OF_MEMORY;
2385
0
2386
0
  MutexAutoLock lock(mLock);
2387
0
  mCaches.Put(clientID, weak);
2388
0
2389
0
  cache.swap(*out);
2390
0
2391
0
  return NS_OK;
2392
0
}
2393
2394
nsresult
2395
nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
2396
                                          nsIApplicationCache **out)
2397
0
{
2398
0
  MutexAutoLock lock(mLock);
2399
0
  return GetApplicationCache_Unlocked(clientID, out);
2400
0
}
2401
2402
nsresult
2403
nsOfflineCacheDevice::GetApplicationCache_Unlocked(const nsACString &clientID,
2404
                                                   nsIApplicationCache **out)
2405
0
{
2406
0
  *out = nullptr;
2407
0
2408
0
  nsCOMPtr<nsIApplicationCache> cache;
2409
0
2410
0
  nsWeakPtr weak;
2411
0
  if (mCaches.Get(clientID, getter_AddRefs(weak)))
2412
0
    cache = do_QueryReferent(weak);
2413
0
2414
0
  if (!cache)
2415
0
  {
2416
0
    nsCString group;
2417
0
    nsresult rv = GetGroupForCache(clientID, group);
2418
0
    NS_ENSURE_SUCCESS(rv, rv);
2419
0
2420
0
    if (group.IsEmpty()) {
2421
0
      return NS_OK;
2422
0
    }
2423
0
2424
0
    cache = new nsApplicationCache(this, group, clientID);
2425
0
    weak = do_GetWeakReference(cache);
2426
0
    if (!weak)
2427
0
      return NS_ERROR_OUT_OF_MEMORY;
2428
0
2429
0
    mCaches.Put(clientID, weak);
2430
0
  }
2431
0
2432
0
  cache.swap(*out);
2433
0
2434
0
  return NS_OK;
2435
0
}
2436
2437
nsresult
2438
nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
2439
                                     nsIApplicationCache **out)
2440
0
{
2441
0
  *out = nullptr;
2442
0
2443
0
  MutexAutoLock lock(mLock);
2444
0
2445
0
  nsCString *clientID;
2446
0
  if (mActiveCachesByGroup.Get(group, &clientID))
2447
0
    return GetApplicationCache_Unlocked(*clientID, out);
2448
0
2449
0
  return NS_OK;
2450
0
}
2451
2452
nsresult
2453
nsOfflineCacheDevice::DeactivateGroup(const nsACString &group)
2454
0
{
2455
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2456
0
2457
0
  nsCString *active = nullptr;
2458
0
2459
0
  AutoResetStatement statement(mStatement_DeactivateGroup);
2460
0
  nsresult rv = statement->BindUTF8StringByIndex(0, group);
2461
0
  NS_ENSURE_SUCCESS(rv, rv);
2462
0
2463
0
  rv = statement->Execute();
2464
0
  NS_ENSURE_SUCCESS(rv, rv);
2465
0
2466
0
  MutexAutoLock lock(mLock);
2467
0
2468
0
  if (mActiveCachesByGroup.Get(group, &active))
2469
0
  {
2470
0
    mActiveCaches.RemoveEntry(*active);
2471
0
    mActiveCachesByGroup.Remove(group);
2472
0
    active = nullptr;
2473
0
  }
2474
0
2475
0
  return NS_OK;
2476
0
}
2477
2478
nsresult
2479
nsOfflineCacheDevice::Evict(nsILoadContextInfo *aInfo)
2480
0
{
2481
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2482
0
2483
0
  NS_ENSURE_ARG(aInfo);
2484
0
2485
0
  nsresult rv;
2486
0
2487
0
  mozilla::OriginAttributes const *oa = aInfo->OriginAttributesPtr();
2488
0
2489
0
  if (oa->mInIsolatedMozBrowser == false) {
2490
0
    nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
2491
0
    NS_ENSURE_SUCCESS(rv, rv);
2492
0
2493
0
    return nsCacheService::GlobalInstance()->EvictEntriesInternal(nsICache::STORE_OFFLINE);
2494
0
  }
2495
0
2496
0
  nsAutoCString jaridsuffix;
2497
0
  jaridsuffix.Append('%');
2498
0
2499
0
  nsAutoCString suffix;
2500
0
  oa->CreateSuffix(suffix);
2501
0
  jaridsuffix.Append('#');
2502
0
  jaridsuffix.Append(suffix);
2503
0
2504
0
  AutoResetStatement statement(mStatement_EnumerateApps);
2505
0
  rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
2506
0
  NS_ENSURE_SUCCESS(rv, rv);
2507
0
2508
0
  bool hasRows;
2509
0
  rv = statement->ExecuteStep(&hasRows);
2510
0
  NS_ENSURE_SUCCESS(rv, rv);
2511
0
2512
0
  while (hasRows) {
2513
0
    nsAutoCString group;
2514
0
    rv = statement->GetUTF8String(0, group);
2515
0
    NS_ENSURE_SUCCESS(rv, rv);
2516
0
2517
0
    nsCString clientID;
2518
0
    rv = statement->GetUTF8String(1, clientID);
2519
0
    NS_ENSURE_SUCCESS(rv, rv);
2520
0
2521
0
    nsCOMPtr<nsIRunnable> ev =
2522
0
      new nsOfflineCacheDiscardCache(this, group, clientID);
2523
0
2524
0
    rv = nsCacheService::DispatchToCacheIOThread(ev);
2525
0
    NS_ENSURE_SUCCESS(rv, rv);
2526
0
2527
0
    rv = statement->ExecuteStep(&hasRows);
2528
0
    NS_ENSURE_SUCCESS(rv, rv);
2529
0
  }
2530
0
2531
0
  return NS_OK;
2532
0
}
2533
2534
namespace { // anon
2535
2536
class OriginMatch final : public mozIStorageFunction
2537
{
2538
0
  ~OriginMatch() = default;
2539
  mozilla::OriginAttributesPattern const mPattern;
2540
2541
  NS_DECL_ISUPPORTS
2542
  NS_DECL_MOZISTORAGEFUNCTION
2543
  explicit OriginMatch(mozilla::OriginAttributesPattern const &aPattern)
2544
0
    : mPattern(aPattern) {}
2545
};
2546
2547
NS_IMPL_ISUPPORTS(OriginMatch, mozIStorageFunction)
2548
2549
NS_IMETHODIMP
2550
OriginMatch::OnFunctionCall(mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
2551
0
{
2552
0
  nsresult rv;
2553
0
2554
0
  nsAutoCString groupId;
2555
0
  rv = aFunctionArguments->GetUTF8String(0, groupId);
2556
0
  NS_ENSURE_SUCCESS(rv, rv);
2557
0
2558
0
  int32_t hash = groupId.Find(NS_LITERAL_CSTRING("#"));
2559
0
  if (hash == kNotFound) {
2560
0
    // Just ignore...
2561
0
    return NS_OK;
2562
0
  }
2563
0
2564
0
  ++hash;
2565
0
2566
0
  nsDependentCSubstring suffix(groupId.BeginReading() + hash, groupId.Length() - hash);
2567
0
2568
0
  mozilla::OriginAttributes oa;
2569
0
  bool ok = oa.PopulateFromSuffix(suffix);
2570
0
  NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
2571
0
2572
0
  bool match = mPattern.Matches(oa);
2573
0
2574
0
  RefPtr<nsVariant> outVar(new nsVariant());
2575
0
  rv = outVar->SetAsUint32(match ? 1 : 0);
2576
0
  NS_ENSURE_SUCCESS(rv, rv);
2577
0
2578
0
  outVar.forget(aResult);
2579
0
  return NS_OK;
2580
0
}
2581
2582
} // anon
2583
2584
nsresult
2585
nsOfflineCacheDevice::Evict(mozilla::OriginAttributesPattern const &aPattern)
2586
0
{
2587
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2588
0
2589
0
  nsresult rv;
2590
0
2591
0
  nsCOMPtr<mozIStorageFunction> function1(new OriginMatch(aPattern));
2592
0
  rv = mDB->CreateFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"), 1, function1);
2593
0
  NS_ENSURE_SUCCESS(rv, rv);
2594
0
2595
0
  class AutoRemoveFunc {
2596
0
  public:
2597
0
    mozIStorageConnection* mDB;
2598
0
    explicit AutoRemoveFunc(mozIStorageConnection* aDB) : mDB(aDB) {}
2599
0
    ~AutoRemoveFunc() {
2600
0
      mDB->RemoveFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"));
2601
0
    }
2602
0
  };
2603
0
  AutoRemoveFunc autoRemove(mDB);
2604
0
2605
0
  nsCOMPtr<mozIStorageStatement> statement;
2606
0
  rv = mDB->CreateStatement(
2607
0
    NS_LITERAL_CSTRING("SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE ORIGIN_MATCH(GroupID);"),
2608
0
    getter_AddRefs(statement));
2609
0
  NS_ENSURE_SUCCESS(rv, rv);
2610
0
2611
0
  AutoResetStatement statementScope(statement);
2612
0
2613
0
  bool hasRows;
2614
0
  rv = statement->ExecuteStep(&hasRows);
2615
0
  NS_ENSURE_SUCCESS(rv, rv);
2616
0
2617
0
  while (hasRows) {
2618
0
    nsAutoCString group;
2619
0
    rv = statement->GetUTF8String(0, group);
2620
0
    NS_ENSURE_SUCCESS(rv, rv);
2621
0
2622
0
    nsCString clientID;
2623
0
    rv = statement->GetUTF8String(1, clientID);
2624
0
    NS_ENSURE_SUCCESS(rv, rv);
2625
0
2626
0
    nsCOMPtr<nsIRunnable> ev =
2627
0
      new nsOfflineCacheDiscardCache(this, group, clientID);
2628
0
2629
0
    rv = nsCacheService::DispatchToCacheIOThread(ev);
2630
0
    NS_ENSURE_SUCCESS(rv, rv);
2631
0
2632
0
    rv = statement->ExecuteStep(&hasRows);
2633
0
    NS_ENSURE_SUCCESS(rv, rv);
2634
0
  }
2635
0
2636
0
  return NS_OK;
2637
0
}
2638
2639
bool
2640
nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI,
2641
                                  const nsACString &clientID,
2642
                                  nsILoadContextInfo *loadContextInfo)
2643
0
{
2644
0
  {
2645
0
    MutexAutoLock lock(mLock);
2646
0
    if (!mActiveCaches.Contains(clientID))
2647
0
      return false;
2648
0
  }
2649
0
2650
0
  nsAutoCString groupID;
2651
0
  nsresult rv = GetGroupForCache(clientID, groupID);
2652
0
  NS_ENSURE_SUCCESS(rv, false);
2653
0
2654
0
  nsCOMPtr<nsIURI> groupURI;
2655
0
  rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
2656
0
  if (NS_FAILED(rv)) {
2657
0
    return false;
2658
0
  }
2659
0
2660
0
  // When we are choosing an initial cache to load the top
2661
0
  // level document from, the URL of that document must have
2662
0
  // the same origin as the manifest, according to the spec.
2663
0
  // The following check is here because explicit, fallback
2664
0
  // and dynamic entries might have origin different from the
2665
0
  // manifest origin.
2666
0
  if (!NS_SecurityCompareURIs(keyURI, groupURI,
2667
0
                              GetStrictFileOriginPolicy())) {
2668
0
    return false;
2669
0
  }
2670
0
2671
0
  // Check the groupID we found is equal to groupID based
2672
0
  // on the load context demanding load from app cache.
2673
0
  // This is check of extended origin.
2674
0
2675
0
  nsAutoCString originSuffix;
2676
0
  loadContextInfo->OriginAttributesPtr()->CreateSuffix(originSuffix);
2677
0
2678
0
  nsAutoCString demandedGroupID;
2679
0
  rv = BuildApplicationCacheGroupID(groupURI, originSuffix, demandedGroupID);
2680
0
  NS_ENSURE_SUCCESS(rv, false);
2681
0
2682
0
  if (groupID != demandedGroupID) {
2683
0
    return false;
2684
0
  }
2685
0
2686
0
  return true;
2687
0
}
2688
2689
2690
nsresult
2691
nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
2692
                                             nsILoadContextInfo *loadContextInfo,
2693
                                             nsIApplicationCache **out)
2694
0
{
2695
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2696
0
2697
0
  NS_ENSURE_ARG(loadContextInfo);
2698
0
2699
0
  nsresult rv;
2700
0
2701
0
  *out = nullptr;
2702
0
2703
0
  nsCOMPtr<nsIURI> keyURI;
2704
0
  rv = NS_NewURI(getter_AddRefs(keyURI), key);
2705
0
  NS_ENSURE_SUCCESS(rv, rv);
2706
0
2707
0
  // First try to find a matching cache entry.
2708
0
  AutoResetStatement statement(mStatement_FindClient);
2709
0
  rv = statement->BindUTF8StringByIndex(0, key);
2710
0
  NS_ENSURE_SUCCESS(rv, rv);
2711
0
2712
0
  bool hasRows;
2713
0
  rv = statement->ExecuteStep(&hasRows);
2714
0
  NS_ENSURE_SUCCESS(rv, rv);
2715
0
2716
0
  while (hasRows) {
2717
0
    int32_t itemType;
2718
0
    rv = statement->GetInt32(1, &itemType);
2719
0
    NS_ENSURE_SUCCESS(rv, rv);
2720
0
2721
0
    if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
2722
0
      nsAutoCString clientID;
2723
0
      rv = statement->GetUTF8String(0, clientID);
2724
0
      NS_ENSURE_SUCCESS(rv, rv);
2725
0
2726
0
      if (CanUseCache(keyURI, clientID, loadContextInfo)) {
2727
0
        return GetApplicationCache(clientID, out);
2728
0
      }
2729
0
    }
2730
0
2731
0
    rv = statement->ExecuteStep(&hasRows);
2732
0
    NS_ENSURE_SUCCESS(rv, rv);
2733
0
  }
2734
0
2735
0
  // OK, we didn't find an exact match.  Search for a client with a
2736
0
  // matching namespace.
2737
0
2738
0
  AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
2739
0
2740
0
  rv = nsstatement->BindUTF8StringByIndex(0, key);
2741
0
  NS_ENSURE_SUCCESS(rv, rv);
2742
0
2743
0
  rv = nsstatement->ExecuteStep(&hasRows);
2744
0
  NS_ENSURE_SUCCESS(rv, rv);
2745
0
2746
0
  while (hasRows)
2747
0
  {
2748
0
    int32_t itemType;
2749
0
    rv = nsstatement->GetInt32(1, &itemType);
2750
0
    NS_ENSURE_SUCCESS(rv, rv);
2751
0
2752
0
    // Don't associate with a cache based solely on a whitelist entry
2753
0
    if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
2754
0
      nsAutoCString clientID;
2755
0
      rv = nsstatement->GetUTF8String(0, clientID);
2756
0
      NS_ENSURE_SUCCESS(rv, rv);
2757
0
2758
0
      if (CanUseCache(keyURI, clientID, loadContextInfo)) {
2759
0
        return GetApplicationCache(clientID, out);
2760
0
      }
2761
0
    }
2762
0
2763
0
    rv = nsstatement->ExecuteStep(&hasRows);
2764
0
    NS_ENSURE_SUCCESS(rv, rv);
2765
0
  }
2766
0
2767
0
  return NS_OK;
2768
0
}
2769
2770
nsresult
2771
nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
2772
                                             const nsACString &key)
2773
0
{
2774
0
  NS_ENSURE_ARG_POINTER(cache);
2775
0
2776
0
  nsresult rv;
2777
0
2778
0
  nsAutoCString clientID;
2779
0
  rv = cache->GetClientID(clientID);
2780
0
  NS_ENSURE_SUCCESS(rv, rv);
2781
0
2782
0
  return CacheOpportunistically(clientID, key);
2783
0
}
2784
2785
nsresult
2786
nsOfflineCacheDevice::ActivateCache(const nsACString& group,
2787
                                    const nsACString& clientID)
2788
0
{
2789
0
  NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2790
0
2791
0
  AutoResetStatement statement(mStatement_ActivateClient);
2792
0
  nsresult rv = statement->BindUTF8StringByIndex(0, group);
2793
0
  NS_ENSURE_SUCCESS(rv, rv);
2794
0
  rv = statement->BindUTF8StringByIndex(1, clientID);
2795
0
  NS_ENSURE_SUCCESS(rv, rv);
2796
0
  rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
2797
0
  NS_ENSURE_SUCCESS(rv, rv);
2798
0
2799
0
  rv = statement->Execute();
2800
0
  NS_ENSURE_SUCCESS(rv, rv);
2801
0
2802
0
  MutexAutoLock lock(mLock);
2803
0
2804
0
  nsCString *active;
2805
0
  if (mActiveCachesByGroup.Get(group, &active))
2806
0
  {
2807
0
    mActiveCaches.RemoveEntry(*active);
2808
0
    mActiveCachesByGroup.Remove(group);
2809
0
    active = nullptr;
2810
0
  }
2811
0
2812
0
  if (!clientID.IsEmpty())
2813
0
  {
2814
0
    mActiveCaches.PutEntry(clientID);
2815
0
    mActiveCachesByGroup.Put(group, new nsCString(clientID));
2816
0
  }
2817
0
2818
0
  return NS_OK;
2819
0
}
2820
2821
bool
2822
nsOfflineCacheDevice::IsActiveCache(const nsACString& group,
2823
                                    const nsACString& clientID)
2824
0
{
2825
0
  nsCString *active = nullptr;
2826
0
  MutexAutoLock lock(mLock);
2827
0
  return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
2828
0
}
2829
2830
/**
2831
 * Preference accessors
2832
 */
2833
2834
void
2835
nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile *parentDir)
2836
0
{
2837
0
  if (Initialized())
2838
0
  {
2839
0
    NS_ERROR("cannot switch cache directory once initialized");
2840
0
    return;
2841
0
  }
2842
0
2843
0
  if (!parentDir)
2844
0
  {
2845
0
    mCacheDirectory = nullptr;
2846
0
    return;
2847
0
  }
2848
0
2849
0
  // ensure parent directory exists
2850
0
  nsresult rv = EnsureDir(parentDir);
2851
0
  if (NS_FAILED(rv))
2852
0
  {
2853
0
    NS_WARNING("unable to create parent directory");
2854
0
    return;
2855
0
  }
2856
0
2857
0
  mBaseDirectory = parentDir;
2858
0
2859
0
  // cache dir may not exist, but that's ok
2860
0
  nsCOMPtr<nsIFile> dir;
2861
0
  rv = parentDir->Clone(getter_AddRefs(dir));
2862
0
  if (NS_FAILED(rv))
2863
0
    return;
2864
0
  rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
2865
0
  if (NS_FAILED(rv))
2866
0
    return;
2867
0
2868
0
  mCacheDirectory = do_QueryInterface(dir);
2869
0
}
2870
2871
void
2872
nsOfflineCacheDevice::SetCapacity(uint32_t capacity)
2873
0
{
2874
0
  mCacheCapacity = capacity * 1024;
2875
0
}
2876
2877
bool
2878
nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache * aAppCache)
2879
0
{
2880
0
  if (!mAutoShutdown)
2881
0
    return false;
2882
0
2883
0
  mAutoShutdown = false;
2884
0
2885
0
  Shutdown();
2886
0
2887
0
  nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
2888
0
  RefPtr<nsCacheService> cacheService = nsCacheService::GlobalInstance();
2889
0
  cacheService->RemoveCustomOfflineDevice(this);
2890
0
2891
0
  nsAutoCString clientID;
2892
0
  aAppCache->GetClientID(clientID);
2893
0
2894
0
  MutexAutoLock lock(mLock);
2895
0
  mCaches.Remove(clientID);
2896
0
2897
0
  return true;
2898
0
}