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/.
6 #include "StreamFunctions.h"
7 #include "nsZipHeader.h"
10 #define ZIP_FILE_HEADER_SIGNATURE 0x04034b50
11 #define ZIP_FILE_HEADER_SIZE 30
12 #define ZIP_CDS_HEADER_SIGNATURE 0x02014b50
13 #define ZIP_CDS_HEADER_SIZE 46
15 #define FLAGS_IS_UTF8 0x800
17 #define ZIP_EXTENDED_TIMESTAMP_FIELD 0x5455
18 #define ZIP_EXTENDED_TIMESTAMP_MODTIME 0x01
20 using namespace mozilla
;
23 * nsZipHeader represents an entry from a zip file.
25 NS_IMPL_ISUPPORTS(nsZipHeader
, nsIZipEntry
)
27 NS_IMETHODIMP
nsZipHeader::GetCompression(uint16_t* aCompression
) {
28 NS_ASSERTION(mInited
, "Not initalised");
30 *aCompression
= mMethod
;
34 NS_IMETHODIMP
nsZipHeader::GetSize(uint32_t* aSize
) {
35 NS_ASSERTION(mInited
, "Not initalised");
41 NS_IMETHODIMP
nsZipHeader::GetRealSize(uint32_t* aRealSize
) {
42 NS_ASSERTION(mInited
, "Not initalised");
48 NS_IMETHODIMP
nsZipHeader::GetCRC32(uint32_t* aCRC32
) {
49 NS_ASSERTION(mInited
, "Not initalised");
55 NS_IMETHODIMP
nsZipHeader::GetIsDirectory(bool* aIsDirectory
) {
56 NS_ASSERTION(mInited
, "Not initalised");
58 if (mName
.Last() == '/')
61 *aIsDirectory
= false;
65 NS_IMETHODIMP
nsZipHeader::GetLastModifiedTime(PRTime
* aLastModifiedTime
) {
66 NS_ASSERTION(mInited
, "Not initalised");
68 // Try to read timestamp from extra field
70 const uint8_t* tsField
=
71 GetExtraField(ZIP_EXTENDED_TIMESTAMP_FIELD
, false, &blocksize
);
72 if (tsField
&& blocksize
>= 5) {
75 flags
= READ8(tsField
, &pos
);
76 if (flags
& ZIP_EXTENDED_TIMESTAMP_MODTIME
) {
77 *aLastModifiedTime
= (PRTime
)(READ32(tsField
, &pos
)) * PR_USEC_PER_SEC
;
82 // Use DOS date/time fields
83 // Note that on DST shift we can't handle correctly the hour that is valid
89 time
.tm_hour
= (mTime
>> 11) & 0x1F;
90 time
.tm_min
= (mTime
>> 5) & 0x3F;
91 time
.tm_sec
= (mTime
& 0x1F) * 2;
93 time
.tm_year
= (mDate
>> 9) + 1980;
94 time
.tm_month
= ((mDate
>> 5) & 0x0F) - 1;
95 time
.tm_mday
= mDate
& 0x1F;
97 time
.tm_params
.tp_gmt_offset
= 0;
98 time
.tm_params
.tp_dst_offset
= 0;
100 PR_NormalizeTime(&time
, PR_GMTParameters
);
101 time
.tm_params
.tp_gmt_offset
= PR_LocalTimeParameters(&time
).tp_gmt_offset
;
102 PR_NormalizeTime(&time
, PR_GMTParameters
);
103 time
.tm_params
.tp_dst_offset
= PR_LocalTimeParameters(&time
).tp_dst_offset
;
105 *aLastModifiedTime
= PR_ImplodeTime(&time
);
110 NS_IMETHODIMP
nsZipHeader::GetIsSynthetic(bool* aIsSynthetic
) {
111 NS_ASSERTION(mInited
, "Not initalised");
113 *aIsSynthetic
= false;
117 NS_IMETHODIMP
nsZipHeader::GetPermissions(uint32_t* aPermissions
) {
118 NS_ASSERTION(mInited
, "Not initalised");
120 // Always give user read access at least, this matches nsIZipReader's
122 *aPermissions
= ((mEAttr
>> 16) & 0xfff) | 0x100;
126 nsresult
nsZipHeader::Init(const nsACString
& aPath
, PRTime aDate
,
127 uint32_t aAttr
, uint32_t aOffset
) {
128 NS_ASSERTION(!mInited
, "Already initalised");
131 PR_ExplodeTime(aDate
, PR_LocalTimeParameters
, &time
);
132 if (time
.tm_year
< 1980) {
133 return NS_ERROR_INVALID_ARG
;
136 mTime
= time
.tm_sec
/ 2 + (time
.tm_min
<< 5) + (time
.tm_hour
<< 11);
138 time
.tm_mday
+ ((time
.tm_month
+ 1) << 5) + ((time
.tm_year
- 1980) << 9);
140 // Store modification timestamp as extra field
141 // First fill CDS extra field
143 mExtraField
= MakeUnique
<uint8_t[]>(mFieldLength
);
148 WRITE16(mExtraField
.get(), &pos
, ZIP_EXTENDED_TIMESTAMP_FIELD
);
149 WRITE16(mExtraField
.get(), &pos
, 5);
150 WRITE8(mExtraField
.get(), &pos
, ZIP_EXTENDED_TIMESTAMP_MODTIME
);
151 WRITE32(mExtraField
.get(), &pos
, aDate
/ PR_USEC_PER_SEC
);
153 // Fill local extra field
154 mLocalExtraField
= MakeUnique
<uint8_t[]>(mFieldLength
);
155 if (mLocalExtraField
) {
156 mLocalFieldLength
= mFieldLength
;
157 memcpy(mLocalExtraField
.get(), mExtraField
.get(), mLocalFieldLength
);
165 // Claim a UTF-8 path in case it needs it.
166 mFlags
|= FLAGS_IS_UTF8
;
172 uint32_t nsZipHeader::GetFileHeaderLength() {
173 return ZIP_FILE_HEADER_SIZE
+ mName
.Length() + mLocalFieldLength
;
176 nsresult
nsZipHeader::WriteFileHeader(nsIOutputStream
* aStream
) {
177 NS_ASSERTION(mInited
, "Not initalised");
179 uint8_t buf
[ZIP_FILE_HEADER_SIZE
];
181 WRITE32(buf
, &pos
, ZIP_FILE_HEADER_SIGNATURE
);
182 WRITE16(buf
, &pos
, mVersionNeeded
);
183 WRITE16(buf
, &pos
, mFlags
);
184 WRITE16(buf
, &pos
, mMethod
);
185 WRITE16(buf
, &pos
, mTime
);
186 WRITE16(buf
, &pos
, mDate
);
187 WRITE32(buf
, &pos
, mCRC
);
188 WRITE32(buf
, &pos
, mCSize
);
189 WRITE32(buf
, &pos
, mUSize
);
190 WRITE16(buf
, &pos
, mName
.Length());
191 WRITE16(buf
, &pos
, mLocalFieldLength
);
193 nsresult rv
= ZW_WriteData(aStream
, (const char*)buf
, pos
);
194 NS_ENSURE_SUCCESS(rv
, rv
);
196 rv
= ZW_WriteData(aStream
, mName
.get(), mName
.Length());
197 NS_ENSURE_SUCCESS(rv
, rv
);
199 if (mLocalFieldLength
) {
200 rv
= ZW_WriteData(aStream
, (const char*)mLocalExtraField
.get(),
202 NS_ENSURE_SUCCESS(rv
, rv
);
208 uint32_t nsZipHeader::GetCDSHeaderLength() {
209 return ZIP_CDS_HEADER_SIZE
+ mName
.Length() + mComment
.Length() +
213 nsresult
nsZipHeader::WriteCDSHeader(nsIOutputStream
* aStream
) {
214 NS_ASSERTION(mInited
, "Not initalised");
216 uint8_t buf
[ZIP_CDS_HEADER_SIZE
];
218 WRITE32(buf
, &pos
, ZIP_CDS_HEADER_SIGNATURE
);
219 WRITE16(buf
, &pos
, mVersionMade
);
220 WRITE16(buf
, &pos
, mVersionNeeded
);
221 WRITE16(buf
, &pos
, mFlags
);
222 WRITE16(buf
, &pos
, mMethod
);
223 WRITE16(buf
, &pos
, mTime
);
224 WRITE16(buf
, &pos
, mDate
);
225 WRITE32(buf
, &pos
, mCRC
);
226 WRITE32(buf
, &pos
, mCSize
);
227 WRITE32(buf
, &pos
, mUSize
);
228 WRITE16(buf
, &pos
, mName
.Length());
229 WRITE16(buf
, &pos
, mFieldLength
);
230 WRITE16(buf
, &pos
, mComment
.Length());
231 WRITE16(buf
, &pos
, mDisk
);
232 WRITE16(buf
, &pos
, mIAttr
);
233 WRITE32(buf
, &pos
, mEAttr
);
234 WRITE32(buf
, &pos
, mOffset
);
236 nsresult rv
= ZW_WriteData(aStream
, (const char*)buf
, pos
);
237 NS_ENSURE_SUCCESS(rv
, rv
);
239 rv
= ZW_WriteData(aStream
, mName
.get(), mName
.Length());
240 NS_ENSURE_SUCCESS(rv
, rv
);
242 rv
= ZW_WriteData(aStream
, (const char*)mExtraField
.get(), mFieldLength
);
243 NS_ENSURE_SUCCESS(rv
, rv
);
245 return ZW_WriteData(aStream
, mComment
.get(), mComment
.Length());
248 nsresult
nsZipHeader::ReadCDSHeader(nsIInputStream
* stream
) {
249 NS_ASSERTION(!mInited
, "Already initalised");
251 uint8_t buf
[ZIP_CDS_HEADER_SIZE
];
253 nsresult rv
= ZW_ReadData(stream
, (char*)buf
, ZIP_CDS_HEADER_SIZE
);
254 NS_ENSURE_SUCCESS(rv
, rv
);
257 uint32_t signature
= READ32(buf
, &pos
);
258 if (signature
!= ZIP_CDS_HEADER_SIGNATURE
) return NS_ERROR_FILE_CORRUPTED
;
260 mVersionMade
= READ16(buf
, &pos
);
261 mVersionNeeded
= READ16(buf
, &pos
);
262 mFlags
= READ16(buf
, &pos
);
263 mMethod
= READ16(buf
, &pos
);
264 mTime
= READ16(buf
, &pos
);
265 mDate
= READ16(buf
, &pos
);
266 mCRC
= READ32(buf
, &pos
);
267 mCSize
= READ32(buf
, &pos
);
268 mUSize
= READ32(buf
, &pos
);
269 uint16_t namelength
= READ16(buf
, &pos
);
270 mFieldLength
= READ16(buf
, &pos
);
271 uint16_t commentlength
= READ16(buf
, &pos
);
272 mDisk
= READ16(buf
, &pos
);
273 mIAttr
= READ16(buf
, &pos
);
274 mEAttr
= READ32(buf
, &pos
);
275 mOffset
= READ32(buf
, &pos
);
277 if (namelength
> 0) {
278 auto field
= MakeUnique
<char[]>(namelength
);
279 NS_ENSURE_TRUE(field
, NS_ERROR_OUT_OF_MEMORY
);
280 rv
= ZW_ReadData(stream
, field
.get(), namelength
);
281 NS_ENSURE_SUCCESS(rv
, rv
);
282 mName
.Assign(field
.get(), namelength
);
286 if (mFieldLength
> 0) {
287 mExtraField
= MakeUnique
<uint8_t[]>(mFieldLength
);
288 NS_ENSURE_TRUE(mExtraField
, NS_ERROR_OUT_OF_MEMORY
);
289 rv
= ZW_ReadData(stream
, (char*)mExtraField
.get(), mFieldLength
);
290 NS_ENSURE_SUCCESS(rv
, rv
);
293 if (commentlength
> 0) {
294 auto field
= MakeUnique
<char[]>(commentlength
);
295 NS_ENSURE_TRUE(field
, NS_ERROR_OUT_OF_MEMORY
);
296 rv
= ZW_ReadData(stream
, field
.get(), commentlength
);
297 NS_ENSURE_SUCCESS(rv
, rv
);
298 mComment
.Assign(field
.get(), commentlength
);
306 const uint8_t* nsZipHeader::GetExtraField(uint16_t aTag
, bool aLocal
,
307 uint16_t* aBlockSize
) {
308 const uint8_t* buf
= aLocal
? mLocalExtraField
.get() : mExtraField
.get();
309 uint32_t buflen
= aLocal
? mLocalFieldLength
: mFieldLength
;
311 uint16_t tag
, blocksize
;
313 while (buf
&& (pos
+ 4) <= buflen
) {
314 tag
= READ16(buf
, &pos
);
315 blocksize
= READ16(buf
, &pos
);
317 if (aTag
== tag
&& (pos
+ blocksize
) <= buflen
) {
318 *aBlockSize
= blocksize
;
319 return buf
+ pos
- 4;
329 * Pad extra field to align data starting position to specified size.
331 nsresult
nsZipHeader::PadExtraField(uint32_t aOffset
, uint16_t aAlignSize
) {
336 // Check for range and power of 2.
337 if (aAlignSize
< 2 || aAlignSize
> 32768 ||
338 (aAlignSize
& (aAlignSize
- 1)) != 0) {
339 return NS_ERROR_INVALID_ARG
;
342 // Point to current starting data position.
343 aOffset
+= ZIP_FILE_HEADER_SIZE
+ mName
.Length() + mLocalFieldLength
;
345 // Calculate aligned offset.
346 pa_offset
= aOffset
& ~(aAlignSize
- 1);
347 pa_end
= pa_offset
+ aAlignSize
;
348 pad_size
= pa_end
- aOffset
;
353 // Leave enough room(at least 4 bytes) for valid values in extra field.
354 while (pad_size
< 4) {
355 pad_size
+= aAlignSize
;
357 // Extra field length is 2 bytes.
358 if (mLocalFieldLength
+ pad_size
> 65535) {
359 return NS_ERROR_FAILURE
;
362 UniquePtr
<uint8_t[]> field
= std::move(mLocalExtraField
);
363 uint32_t pos
= mLocalFieldLength
;
365 mLocalExtraField
= MakeUnique
<uint8_t[]>(mLocalFieldLength
+ pad_size
);
366 memcpy(mLocalExtraField
.get(), field
.get(), mLocalFieldLength
);
367 // Use 0xFFFF as tag ID to avoid conflict with other IDs.
368 // For more information, please read "Extensible data fields" section in:
369 // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
370 WRITE16(mLocalExtraField
.get(), &pos
, 0xFFFF);
371 WRITE16(mLocalExtraField
.get(), &pos
, pad_size
- 4);
372 memset(mLocalExtraField
.get() + pos
, 0, pad_size
- 4);
373 mLocalFieldLength
+= pad_size
;