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 
6 #include "StreamFunctions.h"
7 #include "nsZipHeader.h"
8 #include "nsMemory.h"
9 #include "prtime.h"
10 
11 #define ZIP_FILE_HEADER_SIGNATURE 0x04034b50
12 #define ZIP_FILE_HEADER_SIZE 30
13 #define ZIP_CDS_HEADER_SIGNATURE 0x02014b50
14 #define ZIP_CDS_HEADER_SIZE 46
15 
16 #define FLAGS_IS_UTF8 0x800
17 
18 #define ZIP_EXTENDED_TIMESTAMP_FIELD 0x5455
19 #define ZIP_EXTENDED_TIMESTAMP_MODTIME 0x01
20 
21 using namespace mozilla;
22 
23 /**
24  * nsZipHeader represents an entry from a zip file.
25  */
NS_IMPL_ISUPPORTS(nsZipHeader,nsIZipEntry)26 NS_IMPL_ISUPPORTS(nsZipHeader, nsIZipEntry)
27 
28 NS_IMETHODIMP nsZipHeader::GetCompression(uint16_t* aCompression) {
29   NS_ASSERTION(mInited, "Not initalised");
30 
31   *aCompression = mMethod;
32   return NS_OK;
33 }
34 
GetSize(uint32_t * aSize)35 NS_IMETHODIMP nsZipHeader::GetSize(uint32_t* aSize) {
36   NS_ASSERTION(mInited, "Not initalised");
37 
38   *aSize = mCSize;
39   return NS_OK;
40 }
41 
GetRealSize(uint32_t * aRealSize)42 NS_IMETHODIMP nsZipHeader::GetRealSize(uint32_t* aRealSize) {
43   NS_ASSERTION(mInited, "Not initalised");
44 
45   *aRealSize = mUSize;
46   return NS_OK;
47 }
48 
GetCRC32(uint32_t * aCRC32)49 NS_IMETHODIMP nsZipHeader::GetCRC32(uint32_t* aCRC32) {
50   NS_ASSERTION(mInited, "Not initalised");
51 
52   *aCRC32 = mCRC;
53   return NS_OK;
54 }
55 
GetIsDirectory(bool * aIsDirectory)56 NS_IMETHODIMP nsZipHeader::GetIsDirectory(bool* aIsDirectory) {
57   NS_ASSERTION(mInited, "Not initalised");
58 
59   if (mName.Last() == '/')
60     *aIsDirectory = true;
61   else
62     *aIsDirectory = false;
63   return NS_OK;
64 }
65 
GetLastModifiedTime(PRTime * aLastModifiedTime)66 NS_IMETHODIMP nsZipHeader::GetLastModifiedTime(PRTime* aLastModifiedTime) {
67   NS_ASSERTION(mInited, "Not initalised");
68 
69   // Try to read timestamp from extra field
70   uint16_t blocksize;
71   const uint8_t* tsField =
72       GetExtraField(ZIP_EXTENDED_TIMESTAMP_FIELD, false, &blocksize);
73   if (tsField && blocksize >= 5) {
74     uint32_t pos = 4;
75     uint8_t flags;
76     flags = READ8(tsField, &pos);
77     if (flags & ZIP_EXTENDED_TIMESTAMP_MODTIME) {
78       *aLastModifiedTime = (PRTime)(READ32(tsField, &pos)) * PR_USEC_PER_SEC;
79       return NS_OK;
80     }
81   }
82 
83   // Use DOS date/time fields
84   // Note that on DST shift we can't handle correctly the hour that is valid
85   // in both DST zones
86   PRExplodedTime time;
87 
88   time.tm_usec = 0;
89 
90   time.tm_hour = (mTime >> 11) & 0x1F;
91   time.tm_min = (mTime >> 5) & 0x3F;
92   time.tm_sec = (mTime & 0x1F) * 2;
93 
94   time.tm_year = (mDate >> 9) + 1980;
95   time.tm_month = ((mDate >> 5) & 0x0F) - 1;
96   time.tm_mday = mDate & 0x1F;
97 
98   time.tm_params.tp_gmt_offset = 0;
99   time.tm_params.tp_dst_offset = 0;
100 
101   PR_NormalizeTime(&time, PR_GMTParameters);
102   time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset;
103   PR_NormalizeTime(&time, PR_GMTParameters);
104   time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset;
105 
106   *aLastModifiedTime = PR_ImplodeTime(&time);
107 
108   return NS_OK;
109 }
110 
GetIsSynthetic(bool * aIsSynthetic)111 NS_IMETHODIMP nsZipHeader::GetIsSynthetic(bool* aIsSynthetic) {
112   NS_ASSERTION(mInited, "Not initalised");
113 
114   *aIsSynthetic = false;
115   return NS_OK;
116 }
117 
GetPermissions(uint32_t * aPermissions)118 NS_IMETHODIMP nsZipHeader::GetPermissions(uint32_t* aPermissions) {
119   NS_ASSERTION(mInited, "Not initalised");
120 
121   // Always give user read access at least, this matches nsIZipReader's
122   // behaviour
123   *aPermissions = ((mEAttr >> 16) & 0xfff) | 0x100;
124   return NS_OK;
125 }
126 
Init(const nsACString & aPath,PRTime aDate,uint32_t aAttr,uint32_t aOffset)127 nsresult nsZipHeader::Init(const nsACString& aPath, PRTime aDate,
128                            uint32_t aAttr, uint32_t aOffset) {
129   NS_ASSERTION(!mInited, "Already initalised");
130 
131   PRExplodedTime time;
132   PR_ExplodeTime(aDate, PR_LocalTimeParameters, &time);
133   if (time.tm_year < 1980) {
134     return NS_ERROR_INVALID_ARG;
135   }
136 
137   mTime = time.tm_sec / 2 + (time.tm_min << 5) + (time.tm_hour << 11);
138   mDate =
139       time.tm_mday + ((time.tm_month + 1) << 5) + ((time.tm_year - 1980) << 9);
140 
141   // Store modification timestamp as extra field
142   // First fill CDS extra field
143   mFieldLength = 9;
144   mExtraField = MakeUnique<uint8_t[]>(mFieldLength);
145   if (!mExtraField) {
146     mFieldLength = 0;
147   } else {
148     uint32_t pos = 0;
149     WRITE16(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_FIELD);
150     WRITE16(mExtraField.get(), &pos, 5);
151     WRITE8(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_MODTIME);
152     WRITE32(mExtraField.get(), &pos, aDate / PR_USEC_PER_SEC);
153 
154     // Fill local extra field
155     mLocalExtraField = MakeUnique<uint8_t[]>(mFieldLength);
156     if (mLocalExtraField) {
157       mLocalFieldLength = mFieldLength;
158       memcpy(mLocalExtraField.get(), mExtraField.get(), mLocalFieldLength);
159     }
160   }
161 
162   mEAttr = aAttr;
163   mOffset = aOffset;
164   mName = aPath;
165   mComment = NS_LITERAL_CSTRING("");
166   // Claim a UTF-8 path in case it needs it.
167   mFlags |= FLAGS_IS_UTF8;
168   mInited = true;
169 
170   return NS_OK;
171 }
172 
GetFileHeaderLength()173 uint32_t nsZipHeader::GetFileHeaderLength() {
174   return ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength;
175 }
176 
WriteFileHeader(nsIOutputStream * aStream)177 nsresult nsZipHeader::WriteFileHeader(nsIOutputStream* aStream) {
178   NS_ASSERTION(mInited, "Not initalised");
179 
180   uint8_t buf[ZIP_FILE_HEADER_SIZE];
181   uint32_t pos = 0;
182   WRITE32(buf, &pos, ZIP_FILE_HEADER_SIGNATURE);
183   WRITE16(buf, &pos, mVersionNeeded);
184   WRITE16(buf, &pos, mFlags);
185   WRITE16(buf, &pos, mMethod);
186   WRITE16(buf, &pos, mTime);
187   WRITE16(buf, &pos, mDate);
188   WRITE32(buf, &pos, mCRC);
189   WRITE32(buf, &pos, mCSize);
190   WRITE32(buf, &pos, mUSize);
191   WRITE16(buf, &pos, mName.Length());
192   WRITE16(buf, &pos, mLocalFieldLength);
193 
194   nsresult rv = ZW_WriteData(aStream, (const char*)buf, pos);
195   NS_ENSURE_SUCCESS(rv, rv);
196 
197   rv = ZW_WriteData(aStream, mName.get(), mName.Length());
198   NS_ENSURE_SUCCESS(rv, rv);
199 
200   if (mLocalFieldLength) {
201     rv = ZW_WriteData(aStream, (const char*)mLocalExtraField.get(),
202                       mLocalFieldLength);
203     NS_ENSURE_SUCCESS(rv, rv);
204   }
205 
206   return NS_OK;
207 }
208 
GetCDSHeaderLength()209 uint32_t nsZipHeader::GetCDSHeaderLength() {
210   return ZIP_CDS_HEADER_SIZE + mName.Length() + mComment.Length() +
211          mFieldLength;
212 }
213 
WriteCDSHeader(nsIOutputStream * aStream)214 nsresult nsZipHeader::WriteCDSHeader(nsIOutputStream* aStream) {
215   NS_ASSERTION(mInited, "Not initalised");
216 
217   uint8_t buf[ZIP_CDS_HEADER_SIZE];
218   uint32_t pos = 0;
219   WRITE32(buf, &pos, ZIP_CDS_HEADER_SIGNATURE);
220   WRITE16(buf, &pos, mVersionMade);
221   WRITE16(buf, &pos, mVersionNeeded);
222   WRITE16(buf, &pos, mFlags);
223   WRITE16(buf, &pos, mMethod);
224   WRITE16(buf, &pos, mTime);
225   WRITE16(buf, &pos, mDate);
226   WRITE32(buf, &pos, mCRC);
227   WRITE32(buf, &pos, mCSize);
228   WRITE32(buf, &pos, mUSize);
229   WRITE16(buf, &pos, mName.Length());
230   WRITE16(buf, &pos, mFieldLength);
231   WRITE16(buf, &pos, mComment.Length());
232   WRITE16(buf, &pos, mDisk);
233   WRITE16(buf, &pos, mIAttr);
234   WRITE32(buf, &pos, mEAttr);
235   WRITE32(buf, &pos, mOffset);
236 
237   nsresult rv = ZW_WriteData(aStream, (const char*)buf, pos);
238   NS_ENSURE_SUCCESS(rv, rv);
239 
240   rv = ZW_WriteData(aStream, mName.get(), mName.Length());
241   NS_ENSURE_SUCCESS(rv, rv);
242   if (mExtraField) {
243     rv = ZW_WriteData(aStream, (const char*)mExtraField.get(), mFieldLength);
244     NS_ENSURE_SUCCESS(rv, rv);
245   }
246   return ZW_WriteData(aStream, mComment.get(), mComment.Length());
247 }
248 
ReadCDSHeader(nsIInputStream * stream)249 nsresult nsZipHeader::ReadCDSHeader(nsIInputStream* stream) {
250   NS_ASSERTION(!mInited, "Already initalised");
251 
252   uint8_t buf[ZIP_CDS_HEADER_SIZE];
253 
254   nsresult rv = ZW_ReadData(stream, (char*)buf, ZIP_CDS_HEADER_SIZE);
255   NS_ENSURE_SUCCESS(rv, rv);
256 
257   uint32_t pos = 0;
258   uint32_t signature = READ32(buf, &pos);
259   if (signature != ZIP_CDS_HEADER_SIGNATURE) return NS_ERROR_FILE_CORRUPTED;
260 
261   mVersionMade = READ16(buf, &pos);
262   mVersionNeeded = READ16(buf, &pos);
263   mFlags = READ16(buf, &pos);
264   mMethod = READ16(buf, &pos);
265   mTime = READ16(buf, &pos);
266   mDate = READ16(buf, &pos);
267   mCRC = READ32(buf, &pos);
268   mCSize = READ32(buf, &pos);
269   mUSize = READ32(buf, &pos);
270   uint16_t namelength = READ16(buf, &pos);
271   mFieldLength = READ16(buf, &pos);
272   uint16_t commentlength = READ16(buf, &pos);
273   mDisk = READ16(buf, &pos);
274   mIAttr = READ16(buf, &pos);
275   mEAttr = READ32(buf, &pos);
276   mOffset = READ32(buf, &pos);
277 
278   if (namelength > 0) {
279     auto field = MakeUnique<char[]>(namelength);
280     NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
281     rv = ZW_ReadData(stream, field.get(), namelength);
282     NS_ENSURE_SUCCESS(rv, rv);
283     mName.Assign(field.get(), namelength);
284   } else
285     mName = NS_LITERAL_CSTRING("");
286 
287   if (mFieldLength > 0) {
288     mExtraField = MakeUnique<uint8_t[]>(mFieldLength);
289     NS_ENSURE_TRUE(mExtraField, NS_ERROR_OUT_OF_MEMORY);
290     rv = ZW_ReadData(stream, (char*)mExtraField.get(), mFieldLength);
291     NS_ENSURE_SUCCESS(rv, rv);
292   }
293 
294   if (commentlength > 0) {
295     auto field = MakeUnique<char[]>(commentlength);
296     NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
297     rv = ZW_ReadData(stream, field.get(), commentlength);
298     NS_ENSURE_SUCCESS(rv, rv);
299     mComment.Assign(field.get(), commentlength);
300   } else
301     mComment = NS_LITERAL_CSTRING("");
302 
303   mInited = true;
304   return NS_OK;
305 }
306 
GetExtraField(uint16_t aTag,bool aLocal,uint16_t * aBlockSize)307 const uint8_t* nsZipHeader::GetExtraField(uint16_t aTag, bool aLocal,
308                                           uint16_t* aBlockSize) {
309   const uint8_t* buf = aLocal ? mLocalExtraField.get() : mExtraField.get();
310   uint32_t buflen = aLocal ? mLocalFieldLength : mFieldLength;
311   uint32_t pos = 0;
312   uint16_t tag, blocksize;
313 
314   while (buf && (pos + 4) <= buflen) {
315     tag = READ16(buf, &pos);
316     blocksize = READ16(buf, &pos);
317 
318     if (aTag == tag && (pos + blocksize) <= buflen) {
319       *aBlockSize = blocksize;
320       return buf + pos - 4;
321     }
322 
323     pos += blocksize;
324   }
325 
326   return nullptr;
327 }
328 
329 /*
330  * Pad extra field to align data starting position to specified size.
331  */
PadExtraField(uint32_t aOffset,uint16_t aAlignSize)332 nsresult nsZipHeader::PadExtraField(uint32_t aOffset, uint16_t aAlignSize) {
333   uint32_t pad_size;
334   uint32_t pa_offset;
335   uint32_t pa_end;
336 
337   // Check for range and power of 2.
338   if (aAlignSize < 2 || aAlignSize > 32768 ||
339       (aAlignSize & (aAlignSize - 1)) != 0) {
340     return NS_ERROR_INVALID_ARG;
341   }
342 
343   // Point to current starting data position.
344   aOffset += ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength;
345 
346   // Calculate aligned offset.
347   pa_offset = aOffset & ~(aAlignSize - 1);
348   pa_end = pa_offset + aAlignSize;
349   pad_size = pa_end - aOffset;
350   if (pad_size == 0) {
351     return NS_OK;
352   }
353 
354   // Leave enough room(at least 4 bytes) for valid values in extra field.
355   while (pad_size < 4) {
356     pad_size += aAlignSize;
357   }
358   // Extra field length is 2 bytes.
359   if (mLocalFieldLength + pad_size > 65535) {
360     return NS_ERROR_FAILURE;
361   }
362 
363   UniquePtr<uint8_t[]> field = std::move(mLocalExtraField);
364   uint32_t pos = mLocalFieldLength;
365 
366   mLocalExtraField = MakeUnique<uint8_t[]>(mLocalFieldLength + pad_size);
367   memcpy(mLocalExtraField.get(), field.get(), mLocalFieldLength);
368   // Use 0xFFFF as tag ID to avoid conflict with other IDs.
369   // For more information, please read "Extensible data fields" section in:
370   // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
371   WRITE16(mLocalExtraField.get(), &pos, 0xFFFF);
372   WRITE16(mLocalExtraField.get(), &pos, pad_size - 4);
373   memset(mLocalExtraField.get() + pos, 0, pad_size - 4);
374   mLocalFieldLength += pad_size;
375 
376   return NS_OK;
377 }
378