1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "CacheLog.h"
6 #include "CacheFileMetadata.h"
7 
8 #include "CacheFileIOManager.h"
9 #include "nsICacheEntry.h"
10 #include "CacheHashUtils.h"
11 #include "CacheFileChunk.h"
12 #include "CacheFileUtils.h"
13 #include "nsILoadContextInfo.h"
14 #include "nsICacheEntry.h"  // for nsICacheEntryMetaDataVisitor
15 #include "../cache/nsCacheUtils.h"
16 #include "nsIFile.h"
17 #include "mozilla/Telemetry.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/IntegerPrintfMacros.h"
20 #include "prnetdb.h"
21 
22 namespace mozilla::net {
23 
24 #define kMinMetadataRead 1024  // TODO find optimal value from telemetry
25 #define kAlignSize 4096
26 
27 // Most of the cache entries fit into one chunk due to current chunk size. Make
28 // sure to tweak this value if kChunkSize is going to change.
29 #define kInitialHashArraySize 1
30 
31 // Initial elements buffer size.
32 #define kInitialBufSize 64
33 
34 // Max size of elements in bytes.
35 #define kMaxElementsSize (64 * 1024)
36 
37 #define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
38 
NS_IMPL_ISUPPORTS(CacheFileMetadata,CacheFileIOListener)39 NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
40 
41 CacheFileMetadata::CacheFileMetadata(CacheFileHandle* aHandle,
42                                      const nsACString& aKey)
43     : CacheMemoryConsumer(NORMAL),
44       mHandle(aHandle),
45       mOffset(-1),
46       mIsDirty(false),
47       mAnonymous(false),
48       mAllocExactSize(false),
49       mFirstRead(true) {
50   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
51        this, aHandle, PromiseFlatCString(aKey).get()));
52 
53   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
54   mMetaHdr.mVersion = kCacheEntryVersion;
55   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
56   mKey = aKey;
57 
58   DebugOnly<nsresult> rv{};
59   rv = ParseKey(aKey);
60   MOZ_ASSERT(NS_SUCCEEDED(rv));
61 }
62 
CacheFileMetadata(bool aMemoryOnly,bool aPinned,const nsACString & aKey)63 CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, bool aPinned,
64                                      const nsACString& aKey)
65     : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL),
66       mIsDirty(true),
67       mAnonymous(false),
68       mAllocExactSize(false),
69       mFirstRead(true) {
70   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]", this,
71        PromiseFlatCString(aKey).get()));
72 
73   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
74   mMetaHdr.mVersion = kCacheEntryVersion;
75   if (aPinned) {
76     AddFlags(kCacheEntryIsPinned);
77   }
78   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
79   mKey = aKey;
80   mMetaHdr.mKeySize = mKey.Length();
81 
82   DebugOnly<nsresult> rv{};
83   rv = ParseKey(aKey);
84   MOZ_ASSERT(NS_SUCCEEDED(rv));
85 }
86 
CacheFileMetadata()87 CacheFileMetadata::CacheFileMetadata()
88     : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */),
89       mIsDirty(false),
90       mAnonymous(false),
91       mAllocExactSize(false),
92       mFirstRead(true) {
93   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]", this));
94 
95   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
96 }
97 
~CacheFileMetadata()98 CacheFileMetadata::~CacheFileMetadata() {
99   LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
100 
101   MOZ_ASSERT(!mListener);
102 
103   if (mHashArray) {
104     CacheFileUtils::FreeBuffer(mHashArray);
105     mHashArray = nullptr;
106     mHashArraySize = 0;
107   }
108 
109   if (mBuf) {
110     CacheFileUtils::FreeBuffer(mBuf);
111     mBuf = nullptr;
112     mBufSize = 0;
113   }
114 }
115 
SetHandle(CacheFileHandle * aHandle)116 void CacheFileMetadata::SetHandle(CacheFileHandle* aHandle) {
117   LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle));
118 
119   MOZ_ASSERT(!mHandle);
120 
121   mHandle = aHandle;
122 }
123 
ReadMetadata(CacheFileMetadataListener * aListener)124 void CacheFileMetadata::ReadMetadata(CacheFileMetadataListener* aListener) {
125   LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this,
126        aListener));
127 
128   MOZ_ASSERT(!mListener);
129   MOZ_ASSERT(!mHashArray);
130   MOZ_ASSERT(!mBuf);
131   MOZ_ASSERT(!mWriteBuf);
132 
133   nsresult rv;
134 
135   int64_t size = mHandle->FileSize();
136   MOZ_ASSERT(size != -1);
137 
138   if (size == 0) {
139     // this is a new entry
140     LOG(
141         ("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
142          "metadata. [this=%p]",
143          this));
144 
145     InitEmptyMetadata();
146     aListener->OnMetadataRead(NS_OK);
147     return;
148   }
149 
150   if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2 * sizeof(uint32_t))) {
151     // there must be at least checksum, header and offset
152     LOG(
153         ("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
154          "empty metadata. [this=%p, filesize=%" PRId64 "]",
155          this, size));
156 
157     InitEmptyMetadata();
158     aListener->OnMetadataRead(NS_OK);
159     return;
160   }
161 
162   // Set offset so that we read at least kMinMetadataRead if the file is big
163   // enough.
164   int64_t offset;
165   if (size < kMinMetadataRead) {
166     offset = 0;
167   } else {
168     offset = size - kMinMetadataRead;
169   }
170 
171   // round offset to kAlignSize blocks
172   offset = (offset / kAlignSize) * kAlignSize;
173 
174   mBufSize = size - offset;
175   mBuf = static_cast<char*>(moz_xmalloc(mBufSize));
176 
177   DoMemoryReport(MemoryUsage());
178 
179   LOG(
180       ("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
181        "offset=%" PRId64 ", filesize=%" PRId64 " [this=%p]",
182        offset, size, this));
183 
184   mReadStart = mozilla::TimeStamp::Now();
185   mListener = aListener;
186   rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
187   if (NS_FAILED(rv)) {
188     LOG(
189         ("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
190          " synchronously, creating empty metadata. [this=%p, rv=0x%08" PRIx32
191          "]",
192          this, static_cast<uint32_t>(rv)));
193 
194     mListener = nullptr;
195     InitEmptyMetadata();
196     aListener->OnMetadataRead(NS_OK);
197   }
198 }
199 
CalcMetadataSize(uint32_t aElementsSize,uint32_t aHashCount)200 uint32_t CacheFileMetadata::CalcMetadataSize(uint32_t aElementsSize,
201                                              uint32_t aHashCount) {
202   return sizeof(uint32_t) +                          // hash of the metadata
203          aHashCount * sizeof(CacheHash::Hash16_t) +  // array of chunk hashes
204          sizeof(CacheFileMetadataHeader) +           // metadata header
205          mKey.Length() + 1 +                         // key with trailing null
206          aElementsSize +                             // elements
207          sizeof(uint32_t);                           // offset
208 }
209 
WriteMetadata(uint32_t aOffset,CacheFileMetadataListener * aListener)210 nsresult CacheFileMetadata::WriteMetadata(
211     uint32_t aOffset, CacheFileMetadataListener* aListener) {
212   LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
213        this, aOffset, aListener));
214 
215   MOZ_ASSERT(!mListener);
216   MOZ_ASSERT(!mWriteBuf);
217 
218   nsresult rv;
219 
220   mIsDirty = false;
221 
222   mWriteBuf =
223       static_cast<char*>(malloc(CalcMetadataSize(mElementsSize, mHashCount)));
224   if (!mWriteBuf) {
225     return NS_ERROR_OUT_OF_MEMORY;
226   }
227 
228   char* p = mWriteBuf + sizeof(uint32_t);
229   if (mHashCount) {
230     memcpy(p, mHashArray, mHashCount * sizeof(CacheHash::Hash16_t));
231     p += mHashCount * sizeof(CacheHash::Hash16_t);
232   }
233   mMetaHdr.WriteToBuf(p);
234   p += sizeof(CacheFileMetadataHeader);
235   memcpy(p, mKey.get(), mKey.Length());
236   p += mKey.Length();
237   *p = 0;
238   p++;
239   if (mElementsSize) {
240     memcpy(p, mBuf, mElementsSize);
241     p += mElementsSize;
242   }
243 
244   CacheHash::Hash32_t hash;
245   hash = CacheHash::Hash(mWriteBuf + sizeof(uint32_t),
246                          p - mWriteBuf - sizeof(uint32_t));
247   NetworkEndian::writeUint32(mWriteBuf, hash);
248 
249   NetworkEndian::writeUint32(p, aOffset);
250   p += sizeof(uint32_t);
251 
252   char* writeBuffer = mWriteBuf;
253   if (aListener) {
254     mListener = aListener;
255   } else {
256     // We are not going to pass |this| as a callback so the buffer will be
257     // released by CacheFileIOManager. Just null out mWriteBuf here.
258     mWriteBuf = nullptr;
259   }
260 
261   rv = CacheFileIOManager::Write(mHandle, aOffset, writeBuffer, p - writeBuffer,
262                                  true, true, aListener ? this : nullptr);
263   if (NS_FAILED(rv)) {
264     LOG(
265         ("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
266          "failed synchronously. [this=%p, rv=0x%08" PRIx32 "]",
267          this, static_cast<uint32_t>(rv)));
268 
269     mListener = nullptr;
270     if (mWriteBuf) {
271       CacheFileUtils::FreeBuffer(mWriteBuf);
272       mWriteBuf = nullptr;
273     }
274     NS_ENSURE_SUCCESS(rv, rv);
275   }
276 
277   DoMemoryReport(MemoryUsage());
278 
279   return NS_OK;
280 }
281 
SyncReadMetadata(nsIFile * aFile)282 nsresult CacheFileMetadata::SyncReadMetadata(nsIFile* aFile) {
283   LOG(("CacheFileMetadata::SyncReadMetadata() [this=%p]", this));
284 
285   MOZ_ASSERT(!mListener);
286   MOZ_ASSERT(!mHandle);
287   MOZ_ASSERT(!mHashArray);
288   MOZ_ASSERT(!mBuf);
289   MOZ_ASSERT(!mWriteBuf);
290   MOZ_ASSERT(mKey.IsEmpty());
291 
292   nsresult rv;
293 
294   int64_t fileSize;
295   rv = aFile->GetFileSize(&fileSize);
296   if (NS_FAILED(rv)) {
297     // Don't bloat the console
298     return rv;
299   }
300 
301   PRFileDesc* fd;
302   rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
303   NS_ENSURE_SUCCESS(rv, rv);
304 
305   int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET);
306   if (offset == -1) {
307     PR_Close(fd);
308     return NS_ERROR_FAILURE;
309   }
310 
311   uint32_t metaOffset;
312   int32_t bytesRead = PR_Read(fd, &metaOffset, sizeof(uint32_t));
313   if (bytesRead != sizeof(uint32_t)) {
314     PR_Close(fd);
315     return NS_ERROR_FAILURE;
316   }
317 
318   metaOffset = NetworkEndian::readUint32(&metaOffset);
319   if (metaOffset > fileSize) {
320     PR_Close(fd);
321     return NS_ERROR_FAILURE;
322   }
323 
324   mBuf = static_cast<char*>(malloc(fileSize - metaOffset));
325   if (!mBuf) {
326     return NS_ERROR_OUT_OF_MEMORY;
327   }
328   mBufSize = fileSize - metaOffset;
329 
330   DoMemoryReport(MemoryUsage());
331 
332   offset = PR_Seek64(fd, metaOffset, PR_SEEK_SET);
333   if (offset == -1) {
334     PR_Close(fd);
335     return NS_ERROR_FAILURE;
336   }
337 
338   bytesRead = PR_Read(fd, mBuf, mBufSize);
339   PR_Close(fd);
340   if (bytesRead != static_cast<int32_t>(mBufSize)) {
341     return NS_ERROR_FAILURE;
342   }
343 
344   rv = ParseMetadata(metaOffset, 0, false);
345   NS_ENSURE_SUCCESS(rv, rv);
346 
347   return NS_OK;
348 }
349 
GetElement(const char * aKey)350 const char* CacheFileMetadata::GetElement(const char* aKey) {
351   const char* data = mBuf;
352   const char* limit = mBuf + mElementsSize;
353 
354   while (data != limit) {
355     size_t maxLen = limit - data;
356     size_t keyLen = strnlen(data, maxLen);
357     MOZ_RELEASE_ASSERT(keyLen != maxLen,
358                        "Metadata elements corrupted. Key "
359                        "isn't null terminated!");
360     MOZ_RELEASE_ASSERT(keyLen + 1 != maxLen,
361                        "Metadata elements corrupted. "
362                        "There is no value for the key!");
363 
364     const char* value = data + keyLen + 1;
365     maxLen = limit - value;
366     size_t valueLen = strnlen(value, maxLen);
367     MOZ_RELEASE_ASSERT(valueLen != maxLen,
368                        "Metadata elements corrupted. Value "
369                        "isn't null terminated!");
370 
371     if (strcmp(data, aKey) == 0) {
372       LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
373            this, aKey));
374       return value;
375     }
376 
377     // point to next pair
378     data += keyLen + valueLen + 2;
379   }
380   LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
381        this, aKey));
382   return nullptr;
383 }
384 
SetElement(const char * aKey,const char * aValue)385 nsresult CacheFileMetadata::SetElement(const char* aKey, const char* aValue) {
386   LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]", this,
387        aKey, aValue));
388 
389   MarkDirty();
390 
391   nsresult rv;
392 
393   const uint32_t keySize = strlen(aKey) + 1;
394   char* pos = const_cast<char*>(GetElement(aKey));
395 
396   if (!aValue) {
397     // No value means remove the key/value pair completely, if existing
398     if (pos) {
399       uint32_t oldValueSize = strlen(pos) + 1;
400       uint32_t offset = pos - mBuf;
401       uint32_t remainder = mElementsSize - (offset + oldValueSize);
402 
403       memmove(pos - keySize, pos + oldValueSize, remainder);
404       mElementsSize -= keySize + oldValueSize;
405     }
406     return NS_OK;
407   }
408 
409   const uint32_t valueSize = strlen(aValue) + 1;
410   uint32_t newSize = mElementsSize + valueSize;
411   if (pos) {
412     const uint32_t oldValueSize = strlen(pos) + 1;
413     const uint32_t offset = pos - mBuf;
414     const uint32_t remainder = mElementsSize - (offset + oldValueSize);
415 
416     // Update the value in place
417     newSize -= oldValueSize;
418     rv = EnsureBuffer(newSize);
419     if (NS_FAILED(rv)) {
420       return rv;
421     }
422 
423     // Move the remainder to the right place
424     pos = mBuf + offset;
425     memmove(pos + valueSize, pos + oldValueSize, remainder);
426   } else {
427     // allocate new meta data element
428     newSize += keySize;
429     rv = EnsureBuffer(newSize);
430     if (NS_FAILED(rv)) {
431       return rv;
432     }
433 
434     // Add after last element
435     pos = mBuf + mElementsSize;
436     memcpy(pos, aKey, keySize);
437     pos += keySize;
438   }
439 
440   // Update value
441   memcpy(pos, aValue, valueSize);
442   mElementsSize = newSize;
443 
444   return NS_OK;
445 }
446 
Visit(nsICacheEntryMetaDataVisitor * aVisitor)447 void CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor* aVisitor) {
448   const char* data = mBuf;
449   const char* limit = mBuf + mElementsSize;
450 
451   while (data < limit) {
452     // Point to the value part
453     const char* value = data + strlen(data) + 1;
454     MOZ_ASSERT(value < limit, "Metadata elements corrupted");
455 
456     aVisitor->OnMetaDataElement(data, value);
457 
458     // Skip value part
459     data = value + strlen(value) + 1;
460   }
461 
462   MOZ_ASSERT(data == limit, "Metadata elements corrupted");
463 }
464 
GetHash(uint32_t aIndex)465 CacheHash::Hash16_t CacheFileMetadata::GetHash(uint32_t aIndex) {
466   MOZ_ASSERT(aIndex < mHashCount);
467   return NetworkEndian::readUint16(&mHashArray[aIndex]);
468 }
469 
SetHash(uint32_t aIndex,CacheHash::Hash16_t aHash)470 nsresult CacheFileMetadata::SetHash(uint32_t aIndex,
471                                     CacheHash::Hash16_t aHash) {
472   LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]", this, aIndex,
473        aHash));
474 
475   MarkDirty();
476 
477   MOZ_ASSERT(aIndex <= mHashCount);
478 
479   if (aIndex > mHashCount) {
480     return NS_ERROR_INVALID_ARG;
481   }
482   if (aIndex == mHashCount) {
483     if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) {
484       // reallocate hash array buffer
485       if (mHashArraySize == 0) {
486         mHashArraySize = kInitialHashArraySize * sizeof(CacheHash::Hash16_t);
487       } else {
488         mHashArraySize *= 2;
489       }
490       mHashArray = static_cast<CacheHash::Hash16_t*>(
491           moz_xrealloc(mHashArray, mHashArraySize));
492     }
493 
494     mHashCount++;
495   }
496 
497   NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
498 
499   DoMemoryReport(MemoryUsage());
500 
501   return NS_OK;
502 }
503 
RemoveHash(uint32_t aIndex)504 nsresult CacheFileMetadata::RemoveHash(uint32_t aIndex) {
505   LOG(("CacheFileMetadata::RemoveHash() [this=%p, idx=%d]", this, aIndex));
506 
507   MarkDirty();
508 
509   MOZ_ASSERT((aIndex + 1) == mHashCount, "Can remove only last hash!");
510 
511   if (aIndex + 1 != mHashCount) {
512     return NS_ERROR_INVALID_ARG;
513   }
514 
515   mHashCount--;
516   return NS_OK;
517 }
518 
AddFlags(uint32_t aFlags)519 void CacheFileMetadata::AddFlags(uint32_t aFlags) {
520   MarkDirty(false);
521   mMetaHdr.mFlags |= aFlags;
522 }
523 
RemoveFlags(uint32_t aFlags)524 void CacheFileMetadata::RemoveFlags(uint32_t aFlags) {
525   MarkDirty(false);
526   mMetaHdr.mFlags &= ~aFlags;
527 }
528 
SetExpirationTime(uint32_t aExpirationTime)529 void CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime) {
530   LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
531        this, aExpirationTime));
532 
533   MarkDirty(false);
534   mMetaHdr.mExpirationTime = aExpirationTime;
535 }
536 
SetFrecency(uint32_t aFrecency)537 void CacheFileMetadata::SetFrecency(uint32_t aFrecency) {
538   LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]", this,
539        (double)aFrecency));
540 
541   MarkDirty(false);
542   mMetaHdr.mFrecency = aFrecency;
543 }
544 
OnFetched()545 void CacheFileMetadata::OnFetched() {
546   MarkDirty(false);
547 
548   mMetaHdr.mLastFetched = NOW_SECONDS();
549   ++mMetaHdr.mFetchCount;
550 }
551 
MarkDirty(bool aUpdateLastModified)552 void CacheFileMetadata::MarkDirty(bool aUpdateLastModified) {
553   mIsDirty = true;
554   if (aUpdateLastModified) {
555     mMetaHdr.mLastModified = NOW_SECONDS();
556   }
557 }
558 
OnFileOpened(CacheFileHandle * aHandle,nsresult aResult)559 nsresult CacheFileMetadata::OnFileOpened(CacheFileHandle* aHandle,
560                                          nsresult aResult) {
561   MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
562   return NS_ERROR_UNEXPECTED;
563 }
564 
OnDataWritten(CacheFileHandle * aHandle,const char * aBuf,nsresult aResult)565 nsresult CacheFileMetadata::OnDataWritten(CacheFileHandle* aHandle,
566                                           const char* aBuf, nsresult aResult) {
567   LOG(
568       ("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, "
569        "result=0x%08" PRIx32 "]",
570        this, aHandle, static_cast<uint32_t>(aResult)));
571 
572   MOZ_ASSERT(mListener);
573   MOZ_ASSERT(mWriteBuf);
574 
575   CacheFileUtils::FreeBuffer(mWriteBuf);
576   mWriteBuf = nullptr;
577 
578   nsCOMPtr<CacheFileMetadataListener> listener;
579 
580   mListener.swap(listener);
581   listener->OnMetadataWritten(aResult);
582 
583   DoMemoryReport(MemoryUsage());
584 
585   return NS_OK;
586 }
587 
OnDataRead(CacheFileHandle * aHandle,char * aBuf,nsresult aResult)588 nsresult CacheFileMetadata::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
589                                        nsresult aResult) {
590   LOG((
591       "CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32
592       "]",
593       this, aHandle, static_cast<uint32_t>(aResult)));
594 
595   MOZ_ASSERT(mListener);
596 
597   nsresult rv;
598   nsCOMPtr<CacheFileMetadataListener> listener;
599 
600   if (NS_FAILED(aResult)) {
601     LOG(
602         ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
603          ", creating empty metadata. [this=%p, rv=0x%08" PRIx32 "]",
604          this, static_cast<uint32_t>(aResult)));
605 
606     InitEmptyMetadata();
607 
608     mListener.swap(listener);
609     listener->OnMetadataRead(NS_OK);
610     return NS_OK;
611   }
612 
613   if (mFirstRead) {
614     Telemetry::AccumulateTimeDelta(
615         Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_TIME_MS, mReadStart);
616   } else {
617     Telemetry::AccumulateTimeDelta(
618         Telemetry::NETWORK_CACHE_METADATA_SECOND_READ_TIME_MS, mReadStart);
619   }
620 
621   // check whether we have read all necessary data
622   uint32_t realOffset =
623       NetworkEndian::readUint32(mBuf + mBufSize - sizeof(uint32_t));
624 
625   int64_t size = mHandle->FileSize();
626   MOZ_ASSERT(size != -1);
627 
628   if (realOffset >= size) {
629     LOG(
630         ("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating "
631          "empty metadata. [this=%p, realOffset=%u, size=%" PRId64 "]",
632          this, realOffset, size));
633 
634     InitEmptyMetadata();
635 
636     mListener.swap(listener);
637     listener->OnMetadataRead(NS_OK);
638     return NS_OK;
639   }
640 
641   uint32_t maxHashCount = size / kChunkSize;
642   uint32_t maxMetadataSize = CalcMetadataSize(kMaxElementsSize, maxHashCount);
643   if (size - realOffset > maxMetadataSize) {
644     LOG(
645         ("CacheFileMetadata::OnDataRead() - Invalid realOffset, metadata would "
646          "be too big, creating empty metadata. [this=%p, realOffset=%u, "
647          "maxMetadataSize=%u, size=%" PRId64 "]",
648          this, realOffset, maxMetadataSize, size));
649 
650     InitEmptyMetadata();
651 
652     mListener.swap(listener);
653     listener->OnMetadataRead(NS_OK);
654     return NS_OK;
655   }
656 
657   uint32_t usedOffset = size - mBufSize;
658 
659   if (realOffset < usedOffset) {
660     uint32_t missing = usedOffset - realOffset;
661     // we need to read more data
662     char* newBuf = static_cast<char*>(realloc(mBuf, mBufSize + missing));
663     if (!newBuf) {
664       LOG(
665           ("CacheFileMetadata::OnDataRead() - Error allocating %d more bytes "
666            "for the missing part of the metadata, creating empty metadata. "
667            "[this=%p]",
668            missing, this));
669 
670       InitEmptyMetadata();
671 
672       mListener.swap(listener);
673       listener->OnMetadataRead(NS_OK);
674       return NS_OK;
675     }
676 
677     mBuf = newBuf;
678     memmove(mBuf + missing, mBuf, mBufSize);
679     mBufSize += missing;
680 
681     DoMemoryReport(MemoryUsage());
682 
683     LOG(
684         ("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
685          "have full metadata. [this=%p]",
686          missing, this));
687 
688     mFirstRead = false;
689     mReadStart = mozilla::TimeStamp::Now();
690     rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
691     if (NS_FAILED(rv)) {
692       LOG(
693           ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() "
694            "failed synchronously, creating empty metadata. [this=%p, "
695            "rv=0x%08" PRIx32 "]",
696            this, static_cast<uint32_t>(rv)));
697 
698       InitEmptyMetadata();
699 
700       mListener.swap(listener);
701       listener->OnMetadataRead(NS_OK);
702       return NS_OK;
703     }
704 
705     return NS_OK;
706   }
707 
708   Telemetry::Accumulate(Telemetry::NETWORK_CACHE_METADATA_SIZE_2,
709                         size - realOffset);
710 
711   // We have all data according to offset information at the end of the entry.
712   // Try to parse it.
713   rv = ParseMetadata(realOffset, realOffset - usedOffset, true);
714   if (NS_FAILED(rv)) {
715     LOG(
716         ("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
717          "empty metadata. [this=%p]",
718          this));
719     InitEmptyMetadata();
720   } else {
721     // Shrink elements buffer.
722     mBuf = static_cast<char*>(moz_xrealloc(mBuf, mElementsSize));
723     mBufSize = mElementsSize;
724 
725     // There is usually no or just one call to SetMetadataElement() when the
726     // metadata is parsed from disk. Avoid allocating power of two sized buffer
727     // which we do in case of newly created metadata.
728     mAllocExactSize = true;
729   }
730 
731   mListener.swap(listener);
732   listener->OnMetadataRead(NS_OK);
733 
734   return NS_OK;
735 }
736 
OnFileDoomed(CacheFileHandle * aHandle,nsresult aResult)737 nsresult CacheFileMetadata::OnFileDoomed(CacheFileHandle* aHandle,
738                                          nsresult aResult) {
739   MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
740   return NS_ERROR_UNEXPECTED;
741 }
742 
OnEOFSet(CacheFileHandle * aHandle,nsresult aResult)743 nsresult CacheFileMetadata::OnEOFSet(CacheFileHandle* aHandle,
744                                      nsresult aResult) {
745   MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
746   return NS_ERROR_UNEXPECTED;
747 }
748 
OnFileRenamed(CacheFileHandle * aHandle,nsresult aResult)749 nsresult CacheFileMetadata::OnFileRenamed(CacheFileHandle* aHandle,
750                                           nsresult aResult) {
751   MOZ_CRASH("CacheFileMetadata::OnFileRenamed should not be called!");
752   return NS_ERROR_UNEXPECTED;
753 }
754 
InitEmptyMetadata()755 void CacheFileMetadata::InitEmptyMetadata() {
756   if (mBuf) {
757     CacheFileUtils::FreeBuffer(mBuf);
758     mBuf = nullptr;
759     mBufSize = 0;
760   }
761   mAllocExactSize = false;
762   mOffset = 0;
763   mMetaHdr.mVersion = kCacheEntryVersion;
764   mMetaHdr.mFetchCount = 0;
765   mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
766   mMetaHdr.mKeySize = mKey.Length();
767 
768   // Deliberately not touching the "kCacheEntryIsPinned" flag.
769 
770   DoMemoryReport(MemoryUsage());
771 
772   // We're creating a new entry. If there is any old data truncate it.
773   if (mHandle) {
774     mHandle->SetPinned(Pinned());
775     // We can pronounce the handle as invalid now, because it simply
776     // doesn't have the correct metadata.  This will cause IO operations
777     // be bypassed during shutdown (mainly dooming it, when a channel
778     // is canceled by closing the window.)
779     mHandle->SetInvalid();
780     if (mHandle->FileExists() && mHandle->FileSize()) {
781       CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
782     }
783   }
784 }
785 
ParseMetadata(uint32_t aMetaOffset,uint32_t aBufOffset,bool aHaveKey)786 nsresult CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset,
787                                           uint32_t aBufOffset, bool aHaveKey) {
788   LOG(
789       ("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
790        "bufOffset=%d, haveKey=%u]",
791        this, aMetaOffset, aBufOffset, aHaveKey));
792 
793   nsresult rv;
794 
795   uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
796   uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
797   uint32_t hashCount = aMetaOffset / kChunkSize;
798   if (aMetaOffset % kChunkSize) hashCount++;
799   uint32_t hashesLen = hashCount * sizeof(CacheHash::Hash16_t);
800   uint32_t hdrOffset = hashesOffset + hashesLen;
801   uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);
802 
803   LOG(
804       ("CacheFileMetadata::ParseMetadata() [this=%p]\n  metaposOffset=%d\n  "
805        "hashesOffset=%d\n  hashCount=%d\n  hashesLen=%d\n  hdfOffset=%d\n  "
806        "keyOffset=%d\n",
807        this, metaposOffset, hashesOffset, hashCount, hashesLen, hdrOffset,
808        keyOffset));
809 
810   if (keyOffset > metaposOffset) {
811     LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
812          this));
813     return NS_ERROR_FILE_CORRUPTED;
814   }
815 
816   mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
817 
818   if (mMetaHdr.mVersion == 1) {
819     // Backward compatibility before we've added flags to the header
820     keyOffset -= sizeof(uint32_t);
821   } else if (mMetaHdr.mVersion == 2) {
822     // Version 2 just lacks the ability to store alternative data. Nothing to do
823     // here.
824   } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
825     LOG(
826         ("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
827          "[version=0x%x, this=%p]",
828          mMetaHdr.mVersion, this));
829     return NS_ERROR_UNEXPECTED;
830   }
831 
832   // Update the version stored in the header to make writes
833   // store the header in the current version form.
834   mMetaHdr.mVersion = kCacheEntryVersion;
835 
836   uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
837 
838   if (elementsOffset > metaposOffset) {
839     LOG(
840         ("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
841          "[this=%p]",
842          elementsOffset, this));
843     return NS_ERROR_FILE_CORRUPTED;
844   }
845 
846   // check that key ends with \0
847   if (mBuf[elementsOffset - 1] != 0) {
848     LOG(
849         ("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
850          "[this=%p]",
851          this));
852     return NS_ERROR_FILE_CORRUPTED;
853   }
854 
855   if (!aHaveKey) {
856     // get the key form metadata
857     mKey.Assign(mBuf + keyOffset, mMetaHdr.mKeySize);
858 
859     rv = ParseKey(mKey);
860     if (NS_FAILED(rv)) return rv;
861   } else {
862     if (mMetaHdr.mKeySize != mKey.Length()) {
863       LOG(
864           ("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s "
865            "[this=%p]",
866            nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
867       return NS_ERROR_FILE_CORRUPTED;
868     }
869 
870     if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
871       LOG(
872           ("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s "
873            "[this=%p]",
874            nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
875       return NS_ERROR_FILE_CORRUPTED;
876     }
877   }
878 
879   // check metadata hash (data from hashesOffset to metaposOffset)
880   CacheHash::Hash32_t hashComputed, hashExpected;
881   hashComputed =
882       CacheHash::Hash(mBuf + hashesOffset, metaposOffset - hashesOffset);
883   hashExpected = NetworkEndian::readUint32(mBuf + aBufOffset);
884 
885   if (hashComputed != hashExpected) {
886     LOG(
887         ("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
888          "the metadata is %x, hash in file is %x [this=%p]",
889          hashComputed, hashExpected, this));
890     return NS_ERROR_FILE_CORRUPTED;
891   }
892 
893   // check elements
894   rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
895   if (NS_FAILED(rv)) return rv;
896 
897   if (mHandle) {
898     if (!mHandle->SetPinned(Pinned())) {
899       LOG(
900           ("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
901            "pinning state, truncate the file [this=%p, pinned=%d]",
902            this, Pinned()));
903       return NS_ERROR_FILE_CORRUPTED;
904     }
905   }
906 
907   mHashArraySize = hashesLen;
908   mHashCount = hashCount;
909   if (mHashArraySize) {
910     mHashArray = static_cast<CacheHash::Hash16_t*>(moz_xmalloc(mHashArraySize));
911     memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
912   }
913 
914   MarkDirty();
915 
916   mElementsSize = metaposOffset - elementsOffset;
917   memmove(mBuf, mBuf + elementsOffset, mElementsSize);
918   mOffset = aMetaOffset;
919 
920   DoMemoryReport(MemoryUsage());
921 
922   return NS_OK;
923 }
924 
CheckElements(const char * aBuf,uint32_t aSize)925 nsresult CacheFileMetadata::CheckElements(const char* aBuf, uint32_t aSize) {
926   if (aSize) {
927     // Check if the metadata ends with a zero byte.
928     if (aBuf[aSize - 1] != 0) {
929       NS_ERROR("Metadata elements are not null terminated");
930       LOG(
931           ("CacheFileMetadata::CheckElements() - Elements are not null "
932            "terminated. [this=%p]",
933            this));
934       return NS_ERROR_FILE_CORRUPTED;
935     }
936     // Check that there are an even number of zero bytes
937     // to match the pattern { key \0 value \0 }
938     bool odd = false;
939     for (uint32_t i = 0; i < aSize; i++) {
940       if (aBuf[i] == 0) odd = !odd;
941     }
942     if (odd) {
943       NS_ERROR("Metadata elements are malformed");
944       LOG(
945           ("CacheFileMetadata::CheckElements() - Elements are malformed. "
946            "[this=%p]",
947            this));
948       return NS_ERROR_FILE_CORRUPTED;
949     }
950   }
951   return NS_OK;
952 }
953 
EnsureBuffer(uint32_t aSize)954 nsresult CacheFileMetadata::EnsureBuffer(uint32_t aSize) {
955   if (aSize > kMaxElementsSize) {
956     return NS_ERROR_FAILURE;
957   }
958 
959   if (mBufSize < aSize) {
960     if (mAllocExactSize) {
961       // If this is not the only allocation, use power of two for following
962       // allocations.
963       mAllocExactSize = false;
964     } else {
965       // find smallest power of 2 greater than or equal to aSize
966       --aSize;
967       aSize |= aSize >> 1;
968       aSize |= aSize >> 2;
969       aSize |= aSize >> 4;
970       aSize |= aSize >> 8;
971       aSize |= aSize >> 16;
972       ++aSize;
973     }
974 
975     if (aSize < kInitialBufSize) {
976       aSize = kInitialBufSize;
977     }
978 
979     char* newBuf = static_cast<char*>(realloc(mBuf, aSize));
980     if (!newBuf) {
981       return NS_ERROR_OUT_OF_MEMORY;
982     }
983     mBufSize = aSize;
984     mBuf = newBuf;
985 
986     DoMemoryReport(MemoryUsage());
987   }
988 
989   return NS_OK;
990 }
991 
ParseKey(const nsACString & aKey)992 nsresult CacheFileMetadata::ParseKey(const nsACString& aKey) {
993   nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
994   NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);
995 
996   mAnonymous = info->IsAnonymous();
997   mOriginAttributes = *info->OriginAttributesPtr();
998 
999   return NS_OK;
1000 }
1001 
1002 // Memory reporting
1003 
SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const1004 size_t CacheFileMetadata::SizeOfExcludingThis(
1005     mozilla::MallocSizeOf mallocSizeOf) const {
1006   size_t n = 0;
1007   // mHandle reported via CacheFileIOManager.
1008   n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1009   n += mallocSizeOf(mHashArray);
1010   n += mallocSizeOf(mBuf);
1011   // Ignore mWriteBuf, it's not safe to access it when metadata is being
1012   // written and it's null otherwise.
1013   // mListener is usually the owning CacheFile.
1014 
1015   return n;
1016 }
1017 
SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const1018 size_t CacheFileMetadata::SizeOfIncludingThis(
1019     mozilla::MallocSizeOf mallocSizeOf) const {
1020   return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1021 }
1022 
1023 }  // namespace mozilla::net
1024