summaryrefslogtreecommitdiff
path: root/modules/libjar
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /modules/libjar
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'modules/libjar')
-rw-r--r--modules/libjar/appnote.txt1192
-rw-r--r--modules/libjar/moz.build47
-rw-r--r--modules/libjar/nsIJARChannel.idl32
-rw-r--r--modules/libjar/nsIJARFactory.h10
-rw-r--r--modules/libjar/nsIJARProtocolHandler.idl17
-rw-r--r--modules/libjar/nsIJARURI.idl38
-rw-r--r--modules/libjar/nsIZipReader.idl273
-rw-r--r--modules/libjar/nsJAR.cpp1401
-rw-r--r--modules/libjar/nsJAR.h224
-rw-r--r--modules/libjar/nsJARChannel.cpp1096
-rw-r--r--modules/libjar/nsJARChannel.h109
-rw-r--r--modules/libjar/nsJARFactory.cpp64
-rw-r--r--modules/libjar/nsJARInputStream.cpp375
-rw-r--r--modules/libjar/nsJARInputStream.h73
-rw-r--r--modules/libjar/nsJARProtocolHandler.cpp188
-rw-r--r--modules/libjar/nsJARProtocolHandler.h52
-rw-r--r--modules/libjar/nsJARURI.cpp913
-rw-r--r--modules/libjar/nsJARURI.h97
-rw-r--r--modules/libjar/nsZipArchive.cpp1294
-rw-r--r--modules/libjar/nsZipArchive.h436
-rw-r--r--modules/libjar/test/chrome/chrome.ini9
-rw-r--r--modules/libjar/test/chrome/signed-added.zipbin0 -> 2141 bytes
-rw-r--r--modules/libjar/test/chrome/signed-badca.zipbin0 -> 1964 bytes
-rw-r--r--modules/libjar/test/chrome/signed-tampered.zipbin0 -> 1979 bytes
-rw-r--r--modules/libjar/test/chrome/signed.zipbin0 -> 1957 bytes
-rw-r--r--modules/libjar/test/chrome/test_bug386153.html97
-rw-r--r--modules/libjar/test/chrome/unsigned.zipbin0 -> 174 bytes
-rw-r--r--modules/libjar/test/mochitest/bug403331.zipbin0 -> 239 bytes
-rw-r--r--modules/libjar/test/mochitest/bug403331.zip^headers^1
-rw-r--r--modules/libjar/test/mochitest/mochitest.ini11
-rw-r--r--modules/libjar/test/mochitest/openredirect.sjs5
-rw-r--r--modules/libjar/test/mochitest/test_bug1034143_mapped.html53
-rw-r--r--modules/libjar/test/mochitest/test_bug1173171.html64
-rw-r--r--modules/libjar/test/mochitest/test_bug403331.html47
-rw-r--r--modules/libjar/test/unit/data/empty0
-rw-r--r--modules/libjar/test/unit/data/test_bug333423.zipbin0 -> 1086 bytes
-rw-r--r--modules/libjar/test/unit/data/test_bug336691.zipbin0 -> 789 bytes
-rw-r--r--modules/libjar/test/unit/data/test_bug370103.jarbin0 -> 128 bytes
-rw-r--r--modules/libjar/test/unit/data/test_bug379841.zipbin0 -> 140 bytes
-rw-r--r--modules/libjar/test/unit/data/test_bug589292.zipbin0 -> 168 bytes
-rw-r--r--modules/libjar/test/unit/data/test_bug597702.zipbin0 -> 253 bytes
-rw-r--r--modules/libjar/test/unit/data/test_bug637286.zipbin0 -> 12958 bytes
-rw-r--r--modules/libjar/test/unit/data/test_bug658093.zipbin0 -> 4096 bytes
-rw-r--r--modules/libjar/test/unit/data/test_corrupt.zipbin0 -> 142 bytes
-rw-r--r--modules/libjar/test/unit/data/test_corrupt2.zip1
-rw-r--r--modules/libjar/test/unit/data/test_corrupt3.zipbin0 -> 121 bytes
-rw-r--r--modules/libjar/test/unit/data/test_crx_dummy.crxbin0 -> 1102 bytes
-rw-r--r--modules/libjar/test/unit/data/test_umlaute.zipbin0 -> 179 bytes
-rw-r--r--modules/libjar/test/unit/data/uncompressed.zipbin0 -> 142 bytes
-rw-r--r--modules/libjar/test/unit/test_bug278262.js39
-rw-r--r--modules/libjar/test/unit/test_bug333423.js22
-rw-r--r--modules/libjar/test/unit/test_bug336691.js12
-rw-r--r--modules/libjar/test/unit/test_bug370103.js28
-rw-r--r--modules/libjar/test/unit/test_bug379841.js23
-rw-r--r--modules/libjar/test/unit/test_bug407303.js42
-rw-r--r--modules/libjar/test/unit/test_bug453254.js12
-rw-r--r--modules/libjar/test/unit/test_bug458158.js11
-rw-r--r--modules/libjar/test/unit/test_bug589292.js25
-rw-r--r--modules/libjar/test/unit/test_bug597702.js35
-rw-r--r--modules/libjar/test/unit/test_bug637286.js29
-rw-r--r--modules/libjar/test/unit/test_bug658093.js25
-rw-r--r--modules/libjar/test/unit/test_corrupt_1211262.js34
-rw-r--r--modules/libjar/test/unit/test_corrupt_536911.js36
-rw-r--r--modules/libjar/test/unit/test_corrupt_541828.js24
-rw-r--r--modules/libjar/test/unit/test_crx.js43
-rw-r--r--modules/libjar/test/unit/test_dirjar_bug525755.js25
-rw-r--r--modules/libjar/test/unit/test_jarchannel.js216
-rw-r--r--modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js42
-rw-r--r--modules/libjar/test/unit/test_not_found.js24
-rw-r--r--modules/libjar/test/unit/test_umlaute.js40
-rw-r--r--modules/libjar/test/unit/test_uncompressed.js12
-rw-r--r--modules/libjar/test/unit/xpcshell.ini44
-rw-r--r--modules/libjar/zipstruct.h107
-rw-r--r--modules/libjar/zipwriter/StreamFunctions.cpp48
-rw-r--r--modules/libjar/zipwriter/StreamFunctions.h68
-rw-r--r--modules/libjar/zipwriter/ZipWriterModule.cpp37
-rw-r--r--modules/libjar/zipwriter/moz.build24
-rw-r--r--modules/libjar/zipwriter/nsDeflateConverter.cpp188
-rw-r--r--modules/libjar/zipwriter/nsDeflateConverter.h63
-rw-r--r--modules/libjar/zipwriter/nsIZipWriter.idl220
-rw-r--r--modules/libjar/zipwriter/nsZipDataStream.cpp181
-rw-r--r--modules/libjar/zipwriter/nsZipDataStream.h45
-rw-r--r--modules/libjar/zipwriter/nsZipHeader.cpp389
-rw-r--r--modules/libjar/zipwriter/nsZipHeader.h94
-rw-r--r--modules/libjar/zipwriter/nsZipWriter.cpp1133
-rw-r--r--modules/libjar/zipwriter/nsZipWriter.h80
-rw-r--r--modules/libjar/zipwriter/test/unit/data/emptyfile.txt0
-rw-r--r--modules/libjar/zipwriter/test/unit/data/smallfile.txt1
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test.pngbin0 -> 3402 bytes
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test.txt5
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test.zipbin0 -> 3824 bytes
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test_bug399727.html160
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test_bug399727.zlibbin0 -> 1959 bytes
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test_bug446708/thumbs/st14-1.tiffbin0 -> 4940 bytes
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test_bug717061.gzbin0 -> 1275 bytes
-rw-r--r--modules/libjar/zipwriter/test/unit/data/test_bug717061.html16
-rw-r--r--modules/libjar/zipwriter/test/unit/head_zipwriter.js46
-rw-r--r--modules/libjar/zipwriter/test/unit/tail_zipwriter.js14
-rw-r--r--modules/libjar/zipwriter/test/unit/test_alignment.js113
-rw-r--r--modules/libjar/zipwriter/test/unit/test_asyncadd.js107
-rw-r--r--modules/libjar/zipwriter/test/unit/test_asyncbadadd.js35
-rw-r--r--modules/libjar/zipwriter/test/unit/test_asyncbadremove.js31
-rw-r--r--modules/libjar/zipwriter/test/unit/test_asyncremove.js47
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug399727.js86
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug419769_1.js70
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug419769_2.js64
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug425768.js31
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug433248.js73
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug446708.js37
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug467740.js29
-rw-r--r--modules/libjar/zipwriter/test/unit/test_bug717061.js92
-rw-r--r--modules/libjar/zipwriter/test/unit/test_createempty.js16
-rw-r--r--modules/libjar/zipwriter/test/unit/test_deflatedata.js53
-rw-r--r--modules/libjar/zipwriter/test/unit/test_directory.js27
-rw-r--r--modules/libjar/zipwriter/test/unit/test_editexisting.js62
-rw-r--r--modules/libjar/zipwriter/test/unit/test_storedata.js78
-rw-r--r--modules/libjar/zipwriter/test/unit/test_sync.js57
-rw-r--r--modules/libjar/zipwriter/test/unit/test_undochange.js42
-rw-r--r--modules/libjar/zipwriter/test/unit/test_zipcomment.js34
-rw-r--r--modules/libjar/zipwriter/test/unit/test_zippermissions.js83
-rw-r--r--modules/libjar/zipwriter/test/unit/xpcshell.ini37
121 files changed, 13285 insertions, 0 deletions
diff --git a/modules/libjar/appnote.txt b/modules/libjar/appnote.txt
new file mode 100644
index 0000000000..7b96643cad
--- /dev/null
+++ b/modules/libjar/appnote.txt
@@ -0,0 +1,1192 @@
+Revised: 03/01/1999
+
+Disclaimer
+----------
+
+Although PKWARE will attempt to supply current and accurate
+information relating to its file formats, algorithms, and the
+subject programs, the possibility of error can not be eliminated.
+PKWARE therefore expressly disclaims any warranty that the
+information contained in the associated materials relating to the
+subject programs and/or the format of the files created or
+accessed by the subject programs and/or the algorithms used by
+the subject programs, or any other matter, is current, correct or
+accurate as delivered. Any risk of damage due to any possible
+inaccurate information is assumed by the user of the information.
+Furthermore, the information relating to the subject programs
+and/or the file formats created or accessed by the subject
+programs and/or the algorithms used by the subject programs is
+subject to change without notice.
+
+General Format of a ZIP file
+----------------------------
+
+ Files stored in arbitrary order. Large zipfiles can span multiple
+ diskette media.
+
+ Overall zipfile format:
+
+ [local file header + file data + data_descriptor] . . .
+ [central directory] end of central directory record
+
+
+ A. Local file header:
+
+ local file header signature 4 bytes (0x04034b50)
+ version needed to extract 2 bytes
+ general purpose bit flag 2 bytes
+ compression method 2 bytes
+ last mod file time 2 bytes
+ last mod file date 2 bytes
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+ filename length 2 bytes
+ extra field length 2 bytes
+
+ filename (variable size)
+ extra field (variable size)
+
+ B. Data descriptor:
+
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+
+ This descriptor exists only if bit 3 of the general
+ purpose bit flag is set (see below). It is byte aligned
+ and immediately follows the last byte of compressed data.
+ This descriptor is used only when it was not possible to
+ seek in the output zip file, e.g., when the output zip file
+ was standard output or a non seekable device.
+
+ C. Central directory structure:
+
+ [file header] . . . end of central dir record
+
+ File header:
+
+ central file header signature 4 bytes (0x02014b50)
+ version made by 2 bytes
+ version needed to extract 2 bytes
+ general purpose bit flag 2 bytes
+ compression method 2 bytes
+ last mod file time 2 bytes
+ last mod file date 2 bytes
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+ filename length 2 bytes
+ extra field length 2 bytes
+ file comment length 2 bytes
+ disk number start 2 bytes
+ internal file attributes 2 bytes
+ external file attributes 4 bytes
+ relative offset of local header 4 bytes
+
+ filename (variable size)
+ extra field (variable size)
+ file comment (variable size)
+
+ End of central dir record:
+
+ end of central dir signature 4 bytes (0x06054b50)
+ number of this disk 2 bytes
+ number of the disk with the
+ start of the central directory 2 bytes
+ total number of entries in
+ the central dir on this disk 2 bytes
+ total number of entries in
+ the central dir 2 bytes
+ size of the central directory 4 bytes
+ offset of start of central
+ directory with respect to
+ the starting disk number 4 bytes
+ zipfile comment length 2 bytes
+ zipfile comment (variable size)
+
+ D. Explanation of fields:
+
+ version made by (2 bytes)
+
+ The upper byte indicates the compatibility of the file
+ attribute information. If the external file attributes
+ are compatible with MS-DOS and can be read by PKZIP for
+ DOS version 2.04g then this value will be zero. If these
+ attributes are not compatible, then this value will
+ identify the host system on which the attributes are
+ compatible. Software can use this information to determine
+ the line record format for text files etc. The current
+ mappings are:
+
+ 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)
+ 1 - Amiga 2 - VAX/VMS
+ 3 - Unix 4 - VM/CMS
+ 5 - Atari ST 6 - OS/2 H.P.F.S.
+ 7 - Macintosh 8 - Z-System
+ 9 - CP/M 10 - Windows NTFS
+ 11 thru 255 - unused
+
+ The lower byte indicates the version number of the
+ software used to encode the file. The value/10
+ indicates the major version number, and the value
+ mod 10 is the minor version number.
+
+ version needed to extract (2 bytes)
+
+ The minimum software version needed to extract the
+ file, mapped as above.
+
+ general purpose bit flag: (2 bytes)
+
+ Bit 0: If set, indicates that the file is encrypted.
+
+ (For Method 6 - Imploding)
+ Bit 1: If the compression method used was type 6,
+ Imploding, then this bit, if set, indicates
+ an 8K sliding dictionary was used. If clear,
+ then a 4K sliding dictionary was used.
+ Bit 2: If the compression method used was type 6,
+ Imploding, then this bit, if set, indicates
+ 3 Shannon-Fano trees were used to encode the
+ sliding dictionary output. If clear, then 2
+ Shannon-Fano trees were used.
+
+ (For Method 8 - Deflating)
+ Bit 2 Bit 1
+ 0 0 Normal (-en) compression option was used.
+ 0 1 Maximum (-ex) compression option was used.
+ 1 0 Fast (-ef) compression option was used.
+ 1 1 Super Fast (-es) compression option was used.
+
+ Note: Bits 1 and 2 are undefined if the compression
+ method is any other.
+
+ Bit 3: If this bit is set, the fields crc-32, compressed
+ size and uncompressed size are set to zero in the
+ local header. The correct values are put in the
+ data descriptor immediately following the compressed
+ data. (Note: PKZIP version 2.04g for DOS only
+ recognizes this bit for method 8 compression, newer
+ versions of PKZIP recognize this bit for any
+ compression method.)
+
+ Bit 4: Reserved for use with method 8, for enhanced
+ deflating.
+
+ Bit 5: If this bit is set, this indicates that the file is
+ compressed patched data. (Note: Requires PKZIP
+ version 2.70 or greater)
+
+ Bit 6: Currently unused.
+
+ Bit 7: Currently unused.
+
+ Bit 8: Currently unused.
+
+ Bit 9: Currently unused.
+
+ Bit 10: Currently unused.
+
+ Bit 11: Currently unused.
+
+ Bit 12: Reserved by PKWARE for enhanced compression.
+
+ Bit 13: Reserved by PKWARE.
+
+ Bit 14: Reserved by PKWARE.
+
+ Bit 15: Reserved by PKWARE.
+
+ compression method: (2 bytes)
+
+ (see accompanying documentation for algorithm
+ descriptions)
+
+ 0 - The file is stored (no compression)
+ 1 - The file is Shrunk
+ 2 - The file is Reduced with compression factor 1
+ 3 - The file is Reduced with compression factor 2
+ 4 - The file is Reduced with compression factor 3
+ 5 - The file is Reduced with compression factor 4
+ 6 - The file is Imploded
+ 7 - Reserved for Tokenizing compression algorithm
+ 8 - The file is Deflated
+ 9 - Reserved for enhanced Deflating
+ 10 - PKWARE Date Compression Library Imploding
+
+ date and time fields: (2 bytes each)
+
+ The date and time are encoded in standard MS-DOS format.
+ If input came from standard input, the date and time are
+ those at which compression was started for this data.
+
+ CRC-32: (4 bytes)
+
+ The CRC-32 algorithm was generously contributed by
+ David Schwaderer and can be found in his excellent
+ book "C Programmers Guide to NetBIOS" published by
+ Howard W. Sams & Co. Inc. The 'magic number' for
+ the CRC is 0xdebb20e3. The proper CRC pre and post
+ conditioning is used, meaning that the CRC register
+ is pre-conditioned with all ones (a starting value
+ of 0xffffffff) and the value is post-conditioned by
+ taking the one's complement of the CRC residual.
+ If bit 3 of the general purpose flag is set, this
+ field is set to zero in the local header and the correct
+ value is put in the data descriptor and in the central
+ directory.
+
+ compressed size: (4 bytes)
+ uncompressed size: (4 bytes)
+
+ The size of the file compressed and uncompressed,
+ respectively. If bit 3 of the general purpose bit flag
+ is set, these fields are set to zero in the local header
+ and the correct values are put in the data descriptor and
+ in the central directory.
+
+ filename length: (2 bytes)
+ extra field length: (2 bytes)
+ file comment length: (2 bytes)
+
+ The length of the filename, extra field, and comment
+ fields respectively. The combined length of any
+ directory record and these three fields should not
+ generally exceed 65,535 bytes. If input came from standard
+ input, the filename length is set to zero.
+
+ disk number start: (2 bytes)
+
+ The number of the disk on which this file begins.
+
+ internal file attributes: (2 bytes)
+
+ The lowest bit of this field indicates, if set, that
+ the file is apparently an ASCII or text file. If not
+ set, that the file apparently contains binary data.
+ The remaining bits are unused in version 1.0.
+
+ Bits 1 and 2 are reserved for use by PKWARE.
+
+ external file attributes: (4 bytes)
+
+ The mapping of the external attributes is
+ host-system dependent (see 'version made by'). For
+ MS-DOS, the low order byte is the MS-DOS directory
+ attribute byte. If input came from standard input, this
+ field is set to zero.
+
+ relative offset of local header: (4 bytes)
+
+ This is the offset from the start of the first disk on
+ which this file appears, to where the local header should
+ be found.
+
+ filename: (Variable)
+
+ The name of the file, with optional relative path.
+ The path stored should not contain a drive or
+ device letter, or a leading slash. All slashes
+ should be forward slashes '/' as opposed to
+ backwards slashes '\' for compatibility with Amiga
+ and Unix file systems etc. If input came from standard
+ input, there is no filename field.
+
+ extra field: (Variable)
+
+ This is for future expansion. If additional information
+ needs to be stored in the future, it should be stored
+ here. Earlier versions of the software can then safely
+ skip this file, and find the next file or header. This
+ field will be 0 length in version 1.0.
+
+ In order to allow different programs and different types
+ of information to be stored in the 'extra' field in .ZIP
+ files, the following structure should be used for all
+ programs storing data in this field:
+
+ header1+data1 + header2+data2 . . .
+
+ Each header should consist of:
+
+ Header ID - 2 bytes
+ Data Size - 2 bytes
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ The Header ID field indicates the type of data that is in
+ the following data block.
+
+ Header ID's of 0 thru 31 are reserved for use by PKWARE.
+ The remaining ID's can be used by third party vendors for
+ proprietary usage.
+
+ The current Header ID mappings defined by PKWARE are:
+
+ 0x0007 AV Info
+ 0x0009 OS/2
+ 0x000a NTFS
+ 0x000c VAX/VMS
+ 0x000d Unix
+ 0x000f Patch Descriptor
+
+ Several third party mappings commonly used are:
+
+ 0x4b46 FWKCS MD5 (see below)
+ 0x07c8 Macintosh
+ 0x4341 Acorn/SparkFS
+ 0x4453 Windows NT security descriptor (binary ACL)
+ 0x4704 VM/CMS
+ 0x470f MVS
+ 0x4c41 OS/2 access control list (text ACL)
+ 0x4d49 Info-ZIP VMS (VAX or Alpha)
+ 0x5455 extended timestamp
+ 0x5855 Info-ZIP Unix (original, also OS/2, NT, etc)
+ 0x6542 BeOS/BeBox
+ 0x756e ASi Unix
+ 0x7855 Info-ZIP Unix (new)
+ 0xfd4a SMS/QDOS
+
+ The Data Size field indicates the size of the following
+ data block. Programs can use this value to skip to the
+ next header block, passing over any data blocks that are
+ not of interest.
+
+ Note: As stated above, the size of the entire .ZIP file
+ header, including the filename, comment, and extra
+ field should not exceed 64K in size.
+
+ In case two different programs should appropriate the same
+ Header ID value, it is strongly recommended that each
+ program place a unique signature of at least two bytes in
+ size (and preferably 4 bytes or bigger) at the start of
+ each data area. Every program should verify that its
+ unique signature is present, in addition to the Header ID
+ value being correct, before assuming that it is a block of
+ known type.
+
+ -OS/2 Extra Field:
+
+ The following is the layout of the OS/2 attributes "extra"
+ block. (Last Revision 09/05/95)
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (OS/2) 0x0009 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ BSize 4 bytes Uncompressed Block Size
+ CType 2 bytes Compression type
+ EACRC 4 bytes CRC value for uncompress block
+ (var) variable Compressed block
+
+ The OS/2 extended attribute structure (FEA2LIST) is
+ compressed and then stored in it's entirety within this
+ structure. There will only ever be one "block" of data in
+ VarFields[].
+
+ -UNIX Extra Field:
+
+ The following is the layout of the Unix "extra" block.
+ Note: all fields are stored in Intel low-byte/high-byte
+ order.
+
+ Value Size Description
+ ----- ---- -----------
+ (UNIX) 0x000d 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ Atime 4 bytes File last access time
+ Mtime 4 bytes File last modification time
+ Uid 2 bytes File user ID
+ Gid 2 bytes File group ID
+ (var) variable Variable length data field
+
+ The variable length data field will contain file type
+ specific data. Currently the only values allowed are
+ the original "linked to" file names for hard or symbolic
+ links.
+
+ -VAX/VMS Extra Field:
+
+ The following is the layout of the VAX/VMS attributes
+ "extra" block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (VMS) 0x000c 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ CRC 4 bytes 32-bit CRC for remainder of the block
+ Tag1 2 bytes VMS attribute tag value #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ (var.) Size1 Attribute #1 data
+ .
+ .
+ .
+ TagN 2 bytes VMS attribute tage value #N
+ SizeN 2 bytes Size of attribute #N, in bytes
+ (var.) SizeN Attribute #N data
+
+ Rules:
+
+ 1. There will be one or more of attributes present, which
+ will each be preceded by the above TagX & SizeX values.
+ These values are identical to the ATR$C_XXXX and
+ ATR$S_XXXX constants which are defined in ATR.H under
+ VMS C. Neither of these values will ever be zero.
+
+ 2. No word alignment or padding is performed.
+
+ 3. A well-behaved PKZIP/VMS program should never produce
+ more than one sub-block with the same TagX value. Also,
+ there will never be more than one "extra" block of type
+ 0x000c in a particular directory record.
+
+ -NTFS Extra Field:
+
+ The following is the layout of the NTFS attributes
+ "extra" block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (NTFS) 0x000a 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ Reserved 4 bytes Reserved for future use
+ Tag1 2 bytes NTFS attribute tag value #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ (var.) Size1 Attribute #1 data
+ .
+ .
+ .
+ TagN 2 bytes NTFS attribute tage value #N
+ SizeN 2 bytes Size of attribute #N, in bytes
+ (var.) SizeN Attribute #N data
+
+ For NTFS, values for Tag1 through TagN are as follows:
+ (currently only one set of attributes is defined for NTFS)
+
+ Tag Size Description
+ ----- ---- -----------
+ 0x0001 2 bytes Tag for attribute #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ Mtime 8 bytes File last modification time
+ Atime 8 bytes File last access time
+ Ctime 8 bytes File creation time
+
+ -PATCH Descriptor Extra Field:
+
+ The following is the layout of the Patch Descriptor "extra"
+ block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (Patch) 0x000f 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ Version 2 bytes Version of the descriptor
+ Flags 4 bytes Actions and reactions (see below)
+ OldSize 4 bytes Size of the file about to be patched
+ OldCRC 4 bytes 32-bit CRC of the file to be patched
+ NewSize 4 bytes Size of the resulting file
+ NewCRC 4 bytes 32-bit CRC of the resulting file
+
+ Actions and reactions
+
+ Bits Description
+ ---- ----------------
+ 0 Use for autodetection
+ 1 Treat as selfpatch
+ 2-3 RESERVED
+ 4-5 Action (see below)
+ 6-7 RESERVED
+ 8-9 Reaction (see below) to absent file
+ 10-11 Reaction (see below) to newer file
+ 12-13 Reaction (see below) to unknown file
+ 14-15 RESERVED
+ 16-31 RESERVED
+
+ Actions
+
+ Action Value
+ ------ -----
+ none 0
+ add 1
+ delete 2
+ patch 3
+
+ Reactions
+
+ Reaction Value
+ -------- -----
+ ask 0
+ skip 1
+ ignore 2
+ fail 3
+
+ - FWKCS MD5 Extra Field:
+
+ The FWKCS Contents_Signature System, used in
+ automatically identifying files independent of filename,
+ optionally adds and uses an extra field to support the
+ rapid creation of an enhanced contents_signature:
+
+ Header ID = 0x4b46
+ Data Size = 0x0013
+ Preface = 'M','D','5'
+ followed by 16 bytes containing the uncompressed file's
+ 128_bit MD5 hash(1), low byte first.
+
+ When FWKCS revises a zipfile central directory to add
+ this extra field for a file, it also replaces the
+ central directory entry for that file's uncompressed
+ filelength with a measured value.
+
+ FWKCS provides an option to strip this extra field, if
+ present, from a zipfile central directory. In adding
+ this extra field, FWKCS preserves Zipfile Authenticity
+ Verification; if stripping this extra field, FWKCS
+ preserves all versions of AV through PKZIP version 2.04g.
+
+ FWKCS, and FWKCS Contents_Signature System, are
+ trademarks of Frederick W. Kantor.
+
+ (1) R. Rivest, RFC1321.TXT, MIT Laboratory for Computer
+ Science and RSA Data Security, Inc., April 1992.
+ ll.76-77: "The MD5 algorithm is being placed in the
+ public domain for review and possible adoption as a
+ standard."
+
+ file comment: (Variable)
+
+ The comment for this file.
+
+ number of this disk: (2 bytes)
+
+ The number of this disk, which contains central
+ directory end record.
+
+ number of the disk with the start of the central
+ directory: (2 bytes)
+
+ The number of the disk on which the central
+ directory starts.
+
+ total number of entries in the central dir on
+ this disk: (2 bytes)
+
+ The number of central directory entries on this disk.
+
+ total number of entries in the central dir: (2 bytes)
+
+ The total number of files in the zipfile.
+
+ size of the central directory: (4 bytes)
+
+ The size (in bytes) of the entire central directory.
+
+ offset of start of central directory with respect to
+ the starting disk number: (4 bytes)
+
+ Offset of the start of the central directory on the
+ disk on which the central directory starts.
+
+ zipfile comment length: (2 bytes)
+
+ The length of the comment for this zipfile.
+
+ zipfile comment: (Variable)
+
+ The comment for this zipfile.
+
+ D. General notes:
+
+ 1) All fields unless otherwise noted are unsigned and stored
+ in Intel low-byte:high-byte, low-word:high-word order.
+
+ 2) String fields are not null terminated, since the
+ length is given explicitly.
+
+ 3) Local headers should not span disk boundaries. Also, even
+ though the central directory can span disk boundaries, no
+ single record in the central directory should be split
+ across disks.
+
+ 4) The entries in the central directory may not necessarily
+ be in the same order that files appear in the zipfile.
+
+UnShrinking - Method 1
+----------------------
+
+Shrinking is a Dynamic Ziv-Lempel-Welch compression algorithm
+with partial clearing. The initial code size is 9 bits, and
+the maximum code size is 13 bits. Shrinking differs from
+conventional Dynamic Ziv-Lempel-Welch implementations in several
+respects:
+
+1) The code size is controlled by the compressor, and is not
+ automatically increased when codes larger than the current
+ code size are created (but not necessarily used). When
+ the decompressor encounters the code sequence 256
+ (decimal) followed by 1, it should increase the code size
+ read from the input stream to the next bit size. No
+ blocking of the codes is performed, so the next code at
+ the increased size should be read from the input stream
+ immediately after where the previous code at the smaller
+ bit size was read. Again, the decompressor should not
+ increase the code size used until the sequence 256,1 is
+ encountered.
+
+2) When the table becomes full, total clearing is not
+ performed. Rather, when the compressor emits the code
+ sequence 256,2 (decimal), the decompressor should clear
+ all leaf nodes from the Ziv-Lempel tree, and continue to
+ use the current code size. The nodes that are cleared
+ from the Ziv-Lempel tree are then re-used, with the lowest
+ code value re-used first, and the highest code value
+ re-used last. The compressor can emit the sequence 256,2
+ at any time.
+
+Expanding - Methods 2-5
+-----------------------
+
+The Reducing algorithm is actually a combination of two
+distinct algorithms. The first algorithm compresses repeated
+byte sequences, and the second algorithm takes the compressed
+stream from the first algorithm and applies a probabilistic
+compression method.
+
+The probabilistic compression stores an array of 'follower
+sets' S(j), for j=0 to 255, corresponding to each possible
+ASCII character. Each set contains between 0 and 32
+characters, to be denoted as S(j)[0],...,S(j)[m], where m<32.
+The sets are stored at the beginning of the data area for a
+Reduced file, in reverse order, with S(255) first, and S(0)
+last.
+
+The sets are encoded as { N(j), S(j)[0],...,S(j)[N(j)-1] },
+where N(j) is the size of set S(j). N(j) can be 0, in which
+case the follower set for S(j) is empty. Each N(j) value is
+encoded in 6 bits, followed by N(j) eight bit character values
+corresponding to S(j)[0] to S(j)[N(j)-1] respectively. If
+N(j) is 0, then no values for S(j) are stored, and the value
+for N(j-1) immediately follows.
+
+Immediately after the follower sets, is the compressed data
+stream. The compressed data stream can be interpreted for the
+probabilistic decompression as follows:
+
+let Last-Character <- 0.
+loop until done
+ if the follower set S(Last-Character) is empty then
+ read 8 bits from the input stream, and copy this
+ value to the output stream.
+ otherwise if the follower set S(Last-Character) is non-empty then
+ read 1 bit from the input stream.
+ if this bit is not zero then
+ read 8 bits from the input stream, and copy this
+ value to the output stream.
+ otherwise if this bit is zero then
+ read B(N(Last-Character)) bits from the input
+ stream, and assign this value to I.
+ Copy the value of S(Last-Character)[I] to the
+ output stream.
+
+ assign the last value placed on the output stream to
+ Last-Character.
+end loop
+
+B(N(j)) is defined as the minimal number of bits required to
+encode the value N(j)-1.
+
+The decompressed stream from above can then be expanded to
+re-create the original file as follows:
+
+let State <- 0.
+
+loop until done
+ read 8 bits from the input stream into C.
+ case State of
+ 0: if C is not equal to DLE (144 decimal) then
+ copy C to the output stream.
+ otherwise if C is equal to DLE then
+ let State <- 1.
+
+ 1: if C is non-zero then
+ let V <- C.
+ let Len <- L(V)
+ let State <- F(Len).
+ otherwise if C is zero then
+ copy the value 144 (decimal) to the output stream.
+ let State <- 0
+
+ 2: let Len <- Len + C
+ let State <- 3.
+
+ 3: move backwards D(V,C) bytes in the output stream
+ (if this position is before the start of the output
+ stream, then assume that all the data before the
+ start of the output stream is filled with zeros).
+ copy Len+3 bytes from this position to the output stream.
+ let State <- 0.
+ end case
+end loop
+
+The functions F,L, and D are dependent on the 'compression
+factor', 1 through 4, and are defined as follows:
+
+For compression factor 1:
+ L(X) equals the lower 7 bits of X.
+ F(X) equals 2 if X equals 127 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 1 bit of X) * 256 + Y + 1.
+For compression factor 2:
+ L(X) equals the lower 6 bits of X.
+ F(X) equals 2 if X equals 63 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 2 bits of X) * 256 + Y + 1.
+For compression factor 3:
+ L(X) equals the lower 5 bits of X.
+ F(X) equals 2 if X equals 31 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 3 bits of X) * 256 + Y + 1.
+For compression factor 4:
+ L(X) equals the lower 4 bits of X.
+ F(X) equals 2 if X equals 15 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 4 bits of X) * 256 + Y + 1.
+
+Imploding - Method 6
+--------------------
+
+The Imploding algorithm is actually a combination of two distinct
+algorithms. The first algorithm compresses repeated byte
+sequences using a sliding dictionary. The second algorithm is
+used to compress the encoding of the sliding dictionary output,
+using multiple Shannon-Fano trees.
+
+The Imploding algorithm can use a 4K or 8K sliding dictionary
+size. The dictionary size used can be determined by bit 1 in the
+general purpose flag word; a 0 bit indicates a 4K dictionary
+while a 1 bit indicates an 8K dictionary.
+
+The Shannon-Fano trees are stored at the start of the compressed
+file. The number of trees stored is defined by bit 2 in the
+general purpose flag word; a 0 bit indicates two trees stored, a
+1 bit indicates three trees are stored. If 3 trees are stored,
+the first Shannon-Fano tree represents the encoding of the
+Literal characters, the second tree represents the encoding of
+the Length information, the third represents the encoding of the
+Distance information. When 2 Shannon-Fano trees are stored, the
+Length tree is stored first, followed by the Distance tree.
+
+The Literal Shannon-Fano tree, if present is used to represent
+the entire ASCII character set, and contains 256 values. This
+tree is used to compress any data not compressed by the sliding
+dictionary algorithm. When this tree is present, the Minimum
+Match Length for the sliding dictionary is 3. If this tree is
+not present, the Minimum Match Length is 2.
+
+The Length Shannon-Fano tree is used to compress the Length part
+of the (length,distance) pairs from the sliding dictionary
+output. The Length tree contains 64 values, ranging from the
+Minimum Match Length, to 63 plus the Minimum Match Length.
+
+The Distance Shannon-Fano tree is used to compress the Distance
+part of the (length,distance) pairs from the sliding dictionary
+output. The Distance tree contains 64 values, ranging from 0 to
+63, representing the upper 6 bits of the distance value. The
+distance values themselves will be between 0 and the sliding
+dictionary size, either 4K or 8K.
+
+The Shannon-Fano trees themselves are stored in a compressed
+format. The first byte of the tree data represents the number of
+bytes of data representing the (compressed) Shannon-Fano tree
+minus 1. The remaining bytes represent the Shannon-Fano tree
+data encoded as:
+
+ High 4 bits: Number of values at this bit length + 1. (1 - 16)
+ Low 4 bits: Bit Length needed to represent value + 1. (1 - 16)
+
+The Shannon-Fano codes can be constructed from the bit lengths
+using the following algorithm:
+
+1) Sort the Bit Lengths in ascending order, while retaining the
+ order of the original lengths stored in the file.
+
+2) Generate the Shannon-Fano trees:
+
+ Code <- 0
+ CodeIncrement <- 0
+ LastBitLength <- 0
+ i <- number of Shannon-Fano codes - 1 (either 255 or 63)
+
+ loop while i >= 0
+ Code = Code + CodeIncrement
+ if BitLength(i) <> LastBitLength then
+ LastBitLength=BitLength(i)
+ CodeIncrement = 1 shifted left (16 - LastBitLength)
+ ShannonCode(i) = Code
+ i <- i - 1
+ end loop
+
+3) Reverse the order of all the bits in the above ShannonCode()
+ vector, so that the most significant bit becomes the least
+ significant bit. For example, the value 0x1234 (hex) would
+ become 0x2C48 (hex).
+
+4) Restore the order of Shannon-Fano codes as originally stored
+ within the file.
+
+Example:
+
+ This example will show the encoding of a Shannon-Fano tree
+ of size 8. Notice that the actual Shannon-Fano trees used
+ for Imploding are either 64 or 256 entries in size.
+
+Example: 0x02, 0x42, 0x01, 0x13
+
+ The first byte indicates 3 values in this table. Decoding the
+ bytes:
+ 0x42 = 5 codes of 3 bits long
+ 0x01 = 1 code of 2 bits long
+ 0x13 = 2 codes of 4 bits long
+
+ This would generate the original bit length array of:
+ (3, 3, 3, 3, 3, 2, 4, 4)
+
+ There are 8 codes in this table for the values 0 thru 7. Using
+ the algorithm to obtain the Shannon-Fano codes produces:
+
+ Reversed Order Original
+Val Sorted Constructed Code Value Restored Length
+--- ------ ----------------- -------- -------- ------
+0: 2 1100000000000000 11 101 3
+1: 3 1010000000000000 101 001 3
+2: 3 1000000000000000 001 110 3
+3: 3 0110000000000000 110 010 3
+4: 3 0100000000000000 010 100 3
+5: 3 0010000000000000 100 11 2
+6: 4 0001000000000000 1000 1000 4
+7: 4 0000000000000000 0000 0000 4
+
+The values in the Val, Order Restored and Original Length columns
+now represent the Shannon-Fano encoding tree that can be used for
+decoding the Shannon-Fano encoded data. How to parse the
+variable length Shannon-Fano values from the data stream is beyond
+the scope of this document. (See the references listed at the end of
+this document for more information.) However, traditional decoding
+schemes used for Huffman variable length decoding, such as the
+Greenlaw algorithm, can be successfully applied.
+
+The compressed data stream begins immediately after the
+compressed Shannon-Fano data. The compressed data stream can be
+interpreted as follows:
+
+loop until done
+ read 1 bit from input stream.
+
+ if this bit is non-zero then (encoded data is literal data)
+ if Literal Shannon-Fano tree is present
+ read and decode character using Literal Shannon-Fano tree.
+ otherwise
+ read 8 bits from input stream.
+ copy character to the output stream.
+ otherwise (encoded data is sliding dictionary match)
+ if 8K dictionary size
+ read 7 bits for offset Distance (lower 7 bits of offset).
+ otherwise
+ read 6 bits for offset Distance (lower 6 bits of offset).
+
+ using the Distance Shannon-Fano tree, read and decode the
+ upper 6 bits of the Distance value.
+
+ using the Length Shannon-Fano tree, read and decode
+ the Length value.
+
+ Length <- Length + Minimum Match Length
+
+ if Length = 63 + Minimum Match Length
+ read 8 bits from the input stream,
+ add this value to Length.
+
+ move backwards Distance+1 bytes in the output stream, and
+ copy Length characters from this position to the output
+ stream. (if this position is before the start of the output
+ stream, then assume that all the data before the start of
+ the output stream is filled with zeros).
+end loop
+
+Tokenizing - Method 7
+--------------------
+
+This method is not used by PKZIP.
+
+Deflating - Method 8
+-----------------
+
+The Deflate algorithm is similar to the Implode algorithm using
+a sliding dictionary of up to 32K with secondary compression
+from Huffman/Shannon-Fano codes.
+
+The compressed data is stored in blocks with a header describing
+the block and the Huffman codes used in the data block. The header
+format is as follows:
+
+ Bit 0: Last Block bit This bit is set to 1 if this is the last
+ compressed block in the data.
+ Bits 1-2: Block type
+ 00 (0) - Block is stored - All stored data is byte aligned.
+ Skip bits until next byte, then next word = block
+ length, followed by the ones compliment of the block
+ length word. Remaining data in block is the stored
+ data.
+
+ 01 (1) - Use fixed Huffman codes for literal and distance codes.
+ Lit Code Bits Dist Code Bits
+ --------- ---- --------- ----
+ 0 - 143 8 0 - 31 5
+ 144 - 255 9
+ 256 - 279 7
+ 280 - 287 8
+
+ Literal codes 286-287 and distance codes 30-31 are
+ never used but participate in the huffman construction.
+
+ 10 (2) - Dynamic Huffman codes. (See expanding Huffman codes)
+
+ 11 (3) - Reserved - Flag a "Error in compressed data" if seen.
+
+Expanding Huffman Codes
+-----------------------
+If the data block is stored with dynamic Huffman codes, the Huffman
+codes are sent in the following compressed format:
+
+ 5 Bits: # of Literal codes sent - 256 (256 - 286)
+ All other codes are never sent.
+ 5 Bits: # of Dist codes - 1 (1 - 32)
+ 4 Bits: # of Bit Length codes - 3 (3 - 19)
+
+The Huffman codes are sent as bit lengths and the codes are built as
+described in the implode algorithm. The bit lengths themselves are
+compressed with Huffman codes. There are 19 bit length codes:
+
+ 0 - 15: Represent bit lengths of 0 - 15
+ 16: Copy the previous bit length 3 - 6 times.
+ The next 2 bits indicate repeat length (0 = 3, ... ,3 = 6)
+ Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will
+ expand to 12 bit lengths of 8 (1 + 6 + 5)
+ 17: Repeat a bit length of 0 for 3 - 10 times. (3 bits of length)
+ 18: Repeat a bit length of 0 for 11 - 138 times (7 bits of length)
+
+The lengths of the bit length codes are sent packed 3 bits per value
+(0 - 7) in the following order:
+
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+
+The Huffman codes should be built as described in the Implode algorithm
+except codes are assigned starting at the shortest bit length, i.e. the
+shortest code should be all 0's rather than all 1's. Also, codes with
+a bit length of zero do not participate in the tree construction. The
+codes are then used to decode the bit lengths for the literal and
+distance tables.
+
+The bit lengths for the literal tables are sent first with the number
+of entries sent described by the 5 bits sent earlier. There are up
+to 286 literal characters; the first 256 represent the respective 8
+bit character, code 256 represents the End-Of-Block code, the remaining
+29 codes represent copy lengths of 3 thru 258. There are up to 30
+distance codes representing distances from 1 thru 32k as described
+below.
+
+ Length Codes
+ ------------
+ Extra Extra Extra Extra
+ Code Bits Length Code Bits Lengths Code Bits Lengths Code Bits Length(s)
+ ---- ---- ------ ---- ---- ------- ---- ---- ------- ---- ---- ---------
+ 257 0 3 265 1 11,12 273 3 35-42 281 5 131-162
+ 258 0 4 266 1 13,14 274 3 43-50 282 5 163-194
+ 259 0 5 267 1 15,16 275 3 51-58 283 5 195-226
+ 260 0 6 268 1 17,18 276 3 59-66 284 5 227-257
+ 261 0 7 269 2 19-22 277 4 67-82 285 0 258
+ 262 0 8 270 2 23-26 278 4 83-98
+ 263 0 9 271 2 27-30 279 4 99-114
+ 264 0 10 272 2 31-34 280 4 115-130
+
+ Distance Codes
+ --------------
+ Extra Extra Extra Extra
+ Code Bits Dist Code Bits Dist Code Bits Distance Code Bits Distance
+ ---- ---- ---- ---- ---- ------ ---- ---- -------- ---- ---- --------
+ 0 0 1 8 3 17-24 16 7 257-384 24 11 4097-6144
+ 1 0 2 9 3 25-32 17 7 385-512 25 11 6145-8192
+ 2 0 3 10 4 33-48 18 8 513-768 26 12 8193-12288
+ 3 0 4 11 4 49-64 19 8 769-1024 27 12 12289-16384
+ 4 1 5,6 12 5 65-96 20 9 1025-1536 28 13 16385-24576
+ 5 1 7,8 13 5 97-128 21 9 1537-2048 29 13 24577-32768
+ 6 2 9-12 14 6 129-192 22 10 2049-3072
+ 7 2 13-16 15 6 193-256 23 10 3073-4096
+
+The compressed data stream begins immediately after the
+compressed header data. The compressed data stream can be
+interpreted as follows:
+
+do
+ read header from input stream.
+
+ if stored block
+ skip bits until byte aligned
+ read count and 1's compliment of count
+ copy count bytes data block
+ otherwise
+ loop until end of block code sent
+ decode literal character from input stream
+ if literal < 256
+ copy character to the output stream
+ otherwise
+ if literal = end of block
+ break from loop
+ otherwise
+ decode distance from input stream
+
+ move backwards distance bytes in the output stream, and
+ copy length characters from this position to the output
+ stream.
+ end loop
+while not last block
+
+if data descriptor exists
+ skip bits until byte aligned
+ read crc and sizes
+endif
+
+Decryption
+----------
+
+The encryption used in PKZIP was generously supplied by Roger
+Schlafly. PKWARE is grateful to Mr. Schlafly for his expert
+help and advice in the field of data encryption.
+
+PKZIP encrypts the compressed data stream. Encrypted files must
+be decrypted before they can be extracted.
+
+Each encrypted file has an extra 12 bytes stored at the start of
+the data area defining the encryption header for that file. The
+encryption header is originally set to random values, and then
+itself encrypted, using three, 32-bit keys. The key values are
+initialized using the supplied encryption password. After each byte
+is encrypted, the keys are then updated using pseudo-random number
+generation techniques in combination with the same CRC-32 algorithm
+used in PKZIP and described elsewhere in this document.
+
+The following is the basic steps required to decrypt a file:
+
+1) Initialize the three 32-bit keys with the password.
+2) Read and decrypt the 12-byte encryption header, further
+ initializing the encryption keys.
+3) Read and decrypt the compressed data stream using the
+ encryption keys.
+
+Step 1 - Initializing the encryption keys
+-----------------------------------------
+
+Key(0) <- 305419896
+Key(1) <- 591751049
+Key(2) <- 878082192
+
+loop for i <- 0 to length(password)-1
+ update_keys(password(i))
+end loop
+
+Where update_keys() is defined as:
+
+update_keys(char):
+ Key(0) <- crc32(key(0),char)
+ Key(1) <- Key(1) + (Key(0) & 000000ffH)
+ Key(1) <- Key(1) * 134775813 + 1
+ Key(2) <- crc32(key(2),key(1) >> 24)
+end update_keys
+
+Where crc32(old_crc,char) is a routine that given a CRC value and a
+character, returns an updated CRC value after applying the CRC-32
+algorithm described elsewhere in this document.
+
+Step 2 - Decrypting the encryption header
+-----------------------------------------
+
+The purpose of this step is to further initialize the encryption
+keys, based on random data, to render a plaintext attack on the
+data ineffective.
+
+Read the 12-byte encryption header into Buffer, in locations
+Buffer(0) thru Buffer(11).
+
+loop for i <- 0 to 11
+ C <- buffer(i) ^ decrypt_byte()
+ update_keys(C)
+ buffer(i) <- C
+end loop
+
+Where decrypt_byte() is defined as:
+
+unsigned char decrypt_byte()
+ local unsigned short temp
+ temp <- Key(2) | 2
+ decrypt_byte <- (temp * (temp ^ 1)) >> 8
+end decrypt_byte
+
+After the header is decrypted, the last 1 or 2 bytes in Buffer
+should be the high-order word/byte of the CRC for the file being
+decrypted, stored in Intel low-byte/high-byte order. Versions of
+PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC check is
+used on versions after 2.0. This can be used to test if the password
+supplied is correct or not.
+
+Step 3 - Decrypting the compressed data stream
+----------------------------------------------
+
+The compressed data stream can be decrypted as follows:
+
+loop until done
+ read a character into C
+ Temp <- C ^ decrypt_byte()
+ update_keys(temp)
+ output Temp
+end loop
+
+In addition to the above mentioned contributors to PKZIP and PKUNZIP,
+I would like to extend special thanks to Robert Mahoney for suggesting
+the extension .ZIP for this software.
+
+References:
+
+ Fiala, Edward R., and Greene, Daniel H., "Data compression with
+ finite windows", Communications of the ACM, Volume 32, Number 4,
+ April 1989, pages 490-505.
+
+ Held, Gilbert, "Data Compression, Techniques and Applications,
+ Hardware and Software Considerations", John Wiley & Sons, 1987.
+
+ Huffman, D.A., "A method for the construction of minimum-redundancy
+ codes", Proceedings of the IRE, Volume 40, Number 9, September 1952,
+ pages 1098-1101.
+
+ Nelson, Mark, "LZW Data Compression", Dr. Dobbs Journal, Volume 14,
+ Number 10, October 1989, pages 29-37.
+
+ Nelson, Mark, "The Data Compression Book", M&T Books, 1991.
+
+ Storer, James A., "Data Compression, Methods and Theory",
+ Computer Science Press, 1988
+
+ Welch, Terry, "A Technique for High-Performance Data Compression",
+ IEEE Computer, Volume 17, Number 6, June 1984, pages 8-19.
+
+ Ziv, J. and Lempel, A., "A universal algorithm for sequential data
+ compression", Communications of the ACM, Volume 30, Number 6,
+ June 1987, pages 520-540.
+
+ Ziv, J. and Lempel, A., "Compression of individual sequences via
+ variable-rate coding", IEEE Transactions on Information Theory,
+ Volume 24, Number 5, September 1978, pages 530-536.
diff --git a/modules/libjar/moz.build b/modules/libjar/moz.build
new file mode 100644
index 0000000000..b018d6486b
--- /dev/null
+++ b/modules/libjar/moz.build
@@ -0,0 +1,47 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_ZIPWRITER']:
+ DIRS += ['zipwriter']
+
+MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
+
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
+XPIDL_SOURCES += [
+ 'nsIJARChannel.idl',
+ 'nsIJARProtocolHandler.idl',
+ 'nsIJARURI.idl',
+ 'nsIZipReader.idl',
+]
+
+XPIDL_MODULE = 'jar'
+
+EXPORTS += [
+ 'nsJARURI.h',
+ 'nsZipArchive.h',
+ 'zipstruct.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsJARProtocolHandler.cpp',
+ 'nsJARURI.cpp',
+]
+
+# These files cannot be built in unified mode because they rely on plarena.h.
+SOURCES += [
+ 'nsJAR.cpp',
+ 'nsJARChannel.cpp',
+ 'nsJARFactory.cpp',
+ 'nsJARInputStream.cpp',
+ 'nsZipArchive.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/modules/libjar/nsIJARChannel.idl b/modules/libjar/nsIJARChannel.idl
new file mode 100644
index 0000000000..4348717d27
--- /dev/null
+++ b/modules/libjar/nsIJARChannel.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIChannel.idl"
+
+interface nsIFile;
+interface nsIZipEntry;
+
+[scriptable, builtinclass, uuid(e72b179b-d5df-4d87-b5de-fd73a65c60f6)]
+interface nsIJARChannel : nsIChannel
+{
+ /**
+ * Returns TRUE if the JAR file is not safe (if the content type reported
+ * by the server for a remote JAR is not of an expected type). Scripting,
+ * redirects, and plugins should be disabled when loading from this
+ * channel.
+ */
+ [infallible] readonly attribute boolean isUnsafe;
+
+ /**
+ * Returns the JAR file. May be null if the jar is remote.
+ */
+ readonly attribute nsIFile jarFile;
+
+ /**
+ * Returns the zip entry if the file is synchronously accessible.
+ * This will work even without opening the channel.
+ */
+ readonly attribute nsIZipEntry zipEntry;
+};
diff --git a/modules/libjar/nsIJARFactory.h b/modules/libjar/nsIJARFactory.h
new file mode 100644
index 0000000000..a5b48280e4
--- /dev/null
+++ b/modules/libjar/nsIJARFactory.h
@@ -0,0 +1,10 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsIJARFactory_h__
+#define nsIJARFactory_h__
+
+#endif // nsIJARFactory_h__
diff --git a/modules/libjar/nsIJARProtocolHandler.idl b/modules/libjar/nsIJARProtocolHandler.idl
new file mode 100644
index 0000000000..a00239a48b
--- /dev/null
+++ b/modules/libjar/nsIJARProtocolHandler.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+interface nsIZipReaderCache;
+
+[scriptable, uuid(92c3b42c-98c4-11d3-8cd9-0060b0fc14a3)]
+interface nsIJARProtocolHandler : nsIProtocolHandler {
+
+ /**
+ * JARCache contains the collection of open jar files.
+ */
+ readonly attribute nsIZipReaderCache JARCache;
+};
diff --git a/modules/libjar/nsIJARURI.idl b/modules/libjar/nsIJARURI.idl
new file mode 100644
index 0000000000..68aefa9db8
--- /dev/null
+++ b/modules/libjar/nsIJARURI.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIURL.idl"
+
+/**
+ * JAR URLs have the following syntax
+ *
+ * jar:<jar-file-uri>!/<jar-entry>
+ *
+ * EXAMPLE: jar:http://www.big.com/blue.jar!/ocean.html
+ *
+ * The nsIURL methods operate on the <jar-entry> part of the spec.
+ */
+[scriptable, uuid(646a508c-f786-4e14-be6d-8dda2a633c60)]
+interface nsIJARURI : nsIURL {
+
+ /**
+ * Returns the root URI (the one for the actual JAR file) for this JAR
+ * (e.g., http://www.big.com/blue.jar).
+ */
+ readonly attribute nsIURI JARFile;
+
+ /**
+ * Returns the entry specified for this JAR URI (e.g., "ocean.html"). This
+ * value may contain %-escaped byte sequences.
+ */
+ attribute AUTF8String JAREntry;
+
+ /**
+ * Create a clone of the JAR URI with a new root URI (the URI for the
+ * actual JAR file).
+ */
+ nsIJARURI cloneWithJARFile(in nsIURI jarFile);
+};
diff --git a/modules/libjar/nsIZipReader.idl b/modules/libjar/nsIZipReader.idl
new file mode 100644
index 0000000000..9ca111a3a2
--- /dev/null
+++ b/modules/libjar/nsIZipReader.idl
@@ -0,0 +1,273 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescStar(PRFileDesc);
+
+interface nsIUTF8StringEnumerator;
+interface nsIInputStream;
+interface nsIFile;
+interface nsIX509Cert;
+
+[scriptable, uuid(fad6f72f-13d8-4e26-9173-53007a4afe71)]
+interface nsIZipEntry : nsISupports
+{
+ /**
+ * The type of compression used for the item. The possible values and
+ * their meanings are defined in the zip file specification at
+ * http://www.pkware.com/business_and_developers/developer/appnote/
+ */
+ readonly attribute unsigned short compression;
+ /**
+ * The compressed size of the data in the item.
+ */
+ readonly attribute unsigned long size;
+ /**
+ * The uncompressed size of the data in the item.
+ */
+ readonly attribute unsigned long realSize;
+ /**
+ * The CRC-32 hash of the file in the entry.
+ */
+ readonly attribute unsigned long CRC32;
+ /**
+ * True if the name of the entry ends with '/' and false otherwise.
+ */
+ readonly attribute boolean isDirectory;
+ /**
+ * The time at which this item was last modified.
+ */
+ readonly attribute PRTime lastModifiedTime;
+ /**
+ * Use this attribute to determine whether this item is an actual zip entry
+ * or is one synthesized for part of a real entry's path. A synthesized
+ * entry represents a directory within the zip file which has no
+ * corresponding entry within the zip file. For example, the entry for the
+ * directory foo/ in a zip containing exactly one entry for foo/bar.txt
+ * is synthetic. If the zip file contains an actual entry for a directory,
+ * this attribute will be false for the nsIZipEntry for that directory.
+ * It is impossible for a file to be synthetic.
+ */
+ readonly attribute boolean isSynthetic;
+ /**
+ * The UNIX style file permissions of this item.
+ */
+ readonly attribute unsigned long permissions;
+};
+
+[scriptable, uuid(9ba4ef54-e0a0-4f65-9d23-128482448885)]
+interface nsIZipReader : nsISupports
+{
+ /**
+ * Opens a zip file for reading.
+ * It is allowed to open with another file,
+ * but it needs to be closed first with close().
+ */
+ void open(in nsIFile zipFile);
+
+ /**
+ * Opens a zip file inside a zip file for reading.
+ */
+ void openInner(in nsIZipReader zipReader, in AUTF8String zipEntry);
+
+ /**
+ * Opens a zip file stored in memory; the file attribute will be null.
+ *
+ * The ZipReader does not copy or take ownership of this memory; the
+ * caller must ensure that it is valid and unmodified until the
+ * ZipReader is closed or destroyed, and must free the memory as
+ * appropriate afterwards.
+ */
+ void openMemory(in voidPtr aData, in unsigned long aLength);
+
+ /**
+ * The file that represents the zip with which this zip reader was
+ * initialized. This will be null if there is no underlying file.
+ */
+ readonly attribute nsIFile file;
+
+ /**
+ * Closes a zip reader. Subsequent attempts to extract files or read from
+ * its input stream will result in an error.
+ *
+ * Subsequent attempts to access a nsIZipEntry obtained from this zip
+ * reader will cause unspecified behavior.
+ */
+ void close();
+
+ /**
+ * Tests the integrity of the archive by performing a CRC check
+ * on each item expanded into memory. If an entry is specified
+ * the integrity of only that item is tested. If null (javascript)
+ * or EmptyCString() (c++) is passed in the integrity of all items
+ * in the archive are tested.
+ */
+ void test(in AUTF8String aEntryName);
+
+ /**
+ * Extracts a zip entry into a local file specified by outFile.
+ * The entry must be stored in the zip in either uncompressed or
+ * DEFLATE-compressed format for the extraction to be successful.
+ * If the entry is a directory, the directory will be extracted
+ * non-recursively.
+ */
+ void extract(in AUTF8String zipEntry, in nsIFile outFile);
+
+ /**
+ * Returns a nsIZipEntry describing a specified zip entry.
+ */
+ nsIZipEntry getEntry(in AUTF8String zipEntry);
+
+ /**
+ * Checks whether the zipfile contains an entry specified by entryName.
+ */
+ boolean hasEntry(in AUTF8String zipEntry);
+
+ /**
+ * Returns a string enumerator containing the matching entry names.
+ *
+ * @param aPattern
+ * A regular expression used to find matching entries in the zip file.
+ * Set this parameter to null (javascript) or EmptyCString() (c++) or "*"
+ * to get all entries; otherwise, use the
+ * following syntax:
+ *
+ * o * matches anything
+ * o ? matches one character
+ * o $ matches the end of the string
+ * o [abc] matches one occurrence of a, b, or c. The only character that
+ * must be escaped inside the brackets is ]. ^ and - must never
+ * appear in the first and second positions within the brackets,
+ * respectively. (In the former case, the behavior specified for
+ * '[^az]' will happen.)
+ * o [a-z] matches any character between a and z. The characters a and z
+ * must either both be letters or both be numbers, with the
+ * character represented by 'a' having a lower ASCII value than
+ * the character represented by 'z'.
+ * o [^az] matches any character except a or z. If ] is to appear inside
+ * the brackets as a character to not match, it must be escaped.
+ * o pat~pat2 returns matches to the pattern 'pat' which do not also match
+ * the pattern 'pat2'. This may be used to perform filtering
+ * upon the results of one pattern to remove all matches which
+ * also match another pattern. For example, because '*'
+ * matches any string and '*z*' matches any string containing a
+ * 'z', '*~*z*' will match all strings except those containing
+ * a 'z'. Note that a pattern may not use '~' multiple times,
+ * so a string such as '*~*z*~*y*' is not a valid pattern.
+ * o (foo|bar) will match either the pattern foo or the pattern bar.
+ * Neither of the patterns foo or bar may use the 'pat~pat2'
+ * syntax described immediately above.
+ * o \ will escape a special character. Escaping is required for all
+ * special characters unless otherwise specified.
+ * o All other characters match case-sensitively.
+ *
+ * An aPattern not conforming to this syntax has undefined behavior.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE on many but not all invalid aPattern
+ * values.
+ */
+ nsIUTF8StringEnumerator findEntries(in AUTF8String aPattern);
+
+ /**
+ * Returns an input stream containing the contents of the specified zip
+ * entry.
+ * @param zipEntry the name of the entry to open the stream from
+ */
+ nsIInputStream getInputStream(in AUTF8String zipEntry);
+
+ /**
+ * Returns an input stream containing the contents of the specified zip
+ * entry. If the entry refers to a directory (ends with '/'), a directory stream
+ * is opened, otherwise the contents of the file entry is returned.
+ * @param aJarSpec the Spec of the URI for the JAR (only used for directory streams)
+ * @param zipEntry the name of the entry to open the stream from
+ */
+ nsIInputStream getInputStreamWithSpec(in AUTF8String aJarSpec, in AUTF8String zipEntry);
+
+ /**
+ * Returns an object describing the entity which signed
+ * an entry. parseManifest must be called first. If aEntryName is an
+ * entry in the jar, getInputStream must be called after parseManifest.
+ * If aEntryName is an external file which has meta-information
+ * stored in the jar, verifyExternalFile (not yet implemented) must
+ * be called before getPrincipal.
+ */
+ nsIX509Cert getSigningCert(in AUTF8String aEntryName);
+
+ readonly attribute uint32_t manifestEntriesCount;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIZipReaderCache
+
+[scriptable, uuid(31179807-9fcd-46c4-befa-2ade209a394b)]
+interface nsIZipReaderCache : nsISupports
+{
+ /**
+ * Initializes a new zip reader cache.
+ * @param cacheSize - the number of released entries to maintain before
+ * beginning to throw some out (note that the number of outstanding
+ * entries can be much greater than this number -- this is the count
+ * for those otherwise unused entries)
+ */
+ void init(in unsigned long cacheSize);
+
+ /**
+ * Returns a (possibly shared) nsIZipReader for an nsIFile.
+ *
+ * If the zip reader for given file is not in the cache, a new zip reader
+ * is created, initialized, and opened (see nsIZipReader::init and
+ * nsIZipReader::open). Otherwise the previously created zip reader is
+ * returned.
+ *
+ * @note If someone called close() on the shared nsIZipReader, this method
+ * will return the closed zip reader.
+ */
+ nsIZipReader getZip(in nsIFile zipFile);
+
+ /**
+ * returns true if this zipreader already has this file cached
+ */
+ bool isCached(in nsIFile zipFile);
+
+ /**
+ * Returns a (possibly shared) nsIZipReader for a zip inside another zip
+ *
+ * See getZip
+ */
+ nsIZipReader getInnerZip(in nsIFile zipFile, in AUTF8String zipEntry);
+
+ /**
+ * Returns the cached NSPR file descriptor of the file.
+ * Note: currently not supported on Windows platform.
+ */
+ PRFileDescStar getFd(in nsIFile zipFile);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+%{C++
+
+#define NS_ZIPREADER_CID \
+{ /* 88e2fd0b-f7f4-480c-9483-7846b00e8dad */ \
+ 0x88e2fd0b, 0xf7f4, 0x480c, \
+ { 0x94, 0x83, 0x78, 0x46, 0xb0, 0x0e, 0x8d, 0xad } \
+}
+
+#define NS_ZIPREADERCACHE_CID \
+{ /* 608b7f6f-4b60-40d6-87ed-d933bf53d8c1 */ \
+ 0x608b7f6f, 0x4b60, 0x40d6, \
+ { 0x87, 0xed, 0xd9, 0x33, 0xbf, 0x53, 0xd8, 0xc1 } \
+}
+
+%}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/modules/libjar/nsJAR.cpp b/modules/libjar/nsJAR.cpp
new file mode 100644
index 0000000000..0b0444a811
--- /dev/null
+++ b/modules/libjar/nsJAR.cpp
@@ -0,0 +1,1401 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <string.h>
+#include "nsJARInputStream.h"
+#include "nsJAR.h"
+#include "nsIFile.h"
+#include "nsIX509Cert.h"
+#include "nsIConsoleService.h"
+#include "nsICryptoHash.h"
+#include "nsIDataSignatureVerifier.h"
+#include "prprf.h"
+#include "mozilla/Omnijar.h"
+
+#ifdef XP_UNIX
+ #include <sys/stat.h>
+#elif defined (XP_WIN)
+ #include <io.h>
+#endif
+
+using namespace mozilla;
+
+//----------------------------------------------
+// nsJARManifestItem declaration
+//----------------------------------------------
+/*
+ * nsJARManifestItem contains meta-information pertaining
+ * to an individual JAR entry, taken from the
+ * META-INF/MANIFEST.MF and META-INF/ *.SF files.
+ * This is security-critical information, defined here so it is not
+ * accessible from anywhere else.
+ */
+typedef enum
+{
+ JAR_INVALID = 1,
+ JAR_INTERNAL = 2,
+ JAR_EXTERNAL = 3
+} JARManifestItemType;
+
+class nsJARManifestItem
+{
+public:
+ JARManifestItemType mType;
+
+ // True if the second step of verification (VerifyEntry)
+ // has taken place:
+ bool entryVerified;
+
+ // Not signed, valid, or failure code
+ int16_t status;
+
+ // Internal storage of digests
+ nsCString calculatedSectionDigest;
+ nsCString storedEntryDigest;
+
+ nsJARManifestItem();
+ virtual ~nsJARManifestItem();
+};
+
+//-------------------------------------------------
+// nsJARManifestItem constructors and destructor
+//-------------------------------------------------
+nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
+ entryVerified(false),
+ status(JAR_NOT_SIGNED)
+{
+}
+
+nsJARManifestItem::~nsJARManifestItem()
+{
+}
+
+//----------------------------------------------
+// nsJAR constructor/destructor
+//----------------------------------------------
+
+// The following initialization makes a guess of 10 entries per jarfile.
+nsJAR::nsJAR(): mZip(new nsZipArchive()),
+ mManifestData(8),
+ mParsedManifest(false),
+ mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
+ mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
+ mCache(nullptr),
+ mLock("nsJAR::mLock"),
+ mMtime(0),
+ mTotalItemsInManifest(0),
+ mOpened(false),
+ mIsOmnijar(false)
+{
+}
+
+nsJAR::~nsJAR()
+{
+ Close();
+}
+
+NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
+NS_IMPL_ADDREF(nsJAR)
+
+// Custom Release method works with nsZipReaderCache...
+// Release might be called from multi-thread, we have to
+// take this function carefully to avoid delete-after-use.
+MozExternalRefCountType nsJAR::Release(void)
+{
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+
+ RefPtr<nsZipReaderCache> cache;
+ if (mRefCnt == 2) { // don't use a lock too frequently
+ // Use a mutex here to guarantee mCache is not racing and the target instance
+ // is still valid to increase ref-count.
+ MutexAutoLock lock(mLock);
+ cache = mCache;
+ mCache = nullptr;
+ }
+ if (cache) {
+ DebugOnly<nsresult> rv = cache->ReleaseZip(this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to release zip file");
+ }
+
+ count = --mRefCnt; // don't access any member variable after this line
+ NS_LOG_RELEASE(this, count, "nsJAR");
+ if (0 == count) {
+ mRefCnt = 1; /* stabilize */
+ /* enable this to find non-threadsafe destructors: */
+ /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
+ delete this;
+ return 0;
+ }
+
+ return count;
+}
+
+//----------------------------------------------
+// nsIZipReader implementation
+//----------------------------------------------
+
+NS_IMETHODIMP
+nsJAR::Open(nsIFile* zipFile)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+ if (mOpened) return NS_ERROR_FAILURE; // Already open!
+
+ mZipFile = zipFile;
+ mOuterZipEntry.Truncate();
+ mOpened = true;
+
+ // The omnijar is special, it is opened early on and closed late
+ // this avoids reopening it
+ RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
+ if (zip) {
+ mZip = zip;
+ mIsOmnijar = true;
+ return NS_OK;
+ }
+ return mZip->OpenArchive(zipFile);
+}
+
+NS_IMETHODIMP
+nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry)
+{
+ NS_ENSURE_ARG_POINTER(aZipReader);
+ if (mOpened) return NS_ERROR_FAILURE; // Already open!
+
+ bool exist;
+ nsresult rv = aZipReader->HasEntry(aZipEntry, &exist);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
+
+ rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOpened = true;
+
+ mOuterZipEntry.Assign(aZipEntry);
+
+ RefPtr<nsZipHandle> handle;
+ rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(),
+ getter_AddRefs(handle));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mZip->OpenArchive(handle);
+}
+
+NS_IMETHODIMP
+nsJAR::OpenMemory(void* aData, uint32_t aLength)
+{
+ NS_ENSURE_ARG_POINTER(aData);
+ if (mOpened) return NS_ERROR_FAILURE; // Already open!
+
+ mOpened = true;
+
+ RefPtr<nsZipHandle> handle;
+ nsresult rv = nsZipHandle::Init(static_cast<uint8_t*>(aData), aLength,
+ getter_AddRefs(handle));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mZip->OpenArchive(handle);
+}
+
+NS_IMETHODIMP
+nsJAR::GetFile(nsIFile* *result)
+{
+ *result = mZipFile;
+ NS_IF_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::Close()
+{
+ if (!mOpened) {
+ return NS_ERROR_FAILURE; // Never opened or already closed.
+ }
+
+ mOpened = false;
+ mParsedManifest = false;
+ mManifestData.Clear();
+ mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
+ mTotalItemsInManifest = 0;
+
+ if (mIsOmnijar) {
+ // Reset state, but don't close the omnijar because we did not open it.
+ mIsOmnijar = false;
+ mZip = new nsZipArchive();
+ return NS_OK;
+ }
+
+ return mZip->CloseArchive();
+}
+
+NS_IMETHODIMP
+nsJAR::Test(const nsACString &aEntryName)
+{
+ return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get());
+}
+
+NS_IMETHODIMP
+nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile)
+{
+ // nsZipArchive and zlib are not thread safe
+ // we need to use a lock to prevent bug #51267
+ MutexAutoLock lock(mLock);
+
+ nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get());
+ NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
+
+ // Remove existing file or directory so we set permissions correctly.
+ // If it's a directory that already exists and contains files, throw
+ // an exception and return.
+
+ nsresult rv = outFile->Remove(false);
+ if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY ||
+ rv == NS_ERROR_FAILURE)
+ return rv;
+
+ if (item->IsDirectory())
+ {
+ rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
+ //XXX Do this in nsZipArchive? It would be nice to keep extraction
+ //XXX code completely there, but that would require a way to get a
+ //XXX PRDir from outFile.
+ }
+ else
+ {
+ PRFileDesc* fd;
+ rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd);
+ if (NS_FAILED(rv)) return rv;
+
+ // ExtractFile also closes the fd handle and resolves the symlink if needed
+ nsAutoCString path;
+ rv = outFile->GetNativePath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mZip->ExtractFile(item, path.get(), fd);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // nsIFile needs milliseconds, while prtime is in microseconds.
+ // non-fatal if this fails, ignore errors
+ outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result)
+{
+ nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get());
+ NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
+
+ nsJARItem* jarItem = new nsJARItem(zipItem);
+
+ NS_ADDREF(*result = jarItem);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::HasEntry(const nsACString &aEntryName, bool *result)
+{
+ *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsZipFind *find;
+ nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
+
+ NS_ADDREF(*result = zipEnum);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result)
+{
+ return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
+}
+
+NS_IMETHODIMP
+nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
+ const nsACString &aEntryName, nsIInputStream** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
+ nsZipItem *item = nullptr;
+ const nsCString& entry = PromiseFlatCString(aEntryName);
+ if (*entry.get()) {
+ // First check if item exists in jar
+ item = mZip->GetItem(entry.get());
+ if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ }
+ nsJARInputStream* jis = new nsJARInputStream();
+ // addref now so we can call InitFile/InitDirectory()
+ NS_ADDREF(*result = jis);
+
+ nsresult rv = NS_OK;
+ if (!item || item->IsDirectory()) {
+ rv = jis->InitDirectory(this, aJarDirSpec, entry.get());
+ } else {
+ rv = jis->InitFile(this, item);
+ }
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*result);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJAR::GetSigningCert(const nsACString& aFilename, nsIX509Cert** aSigningCert)
+{
+ //-- Parameter check
+ if (!aSigningCert) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aSigningCert = nullptr;
+
+ // Don't check signatures in the omnijar - this is only
+ // interesting for extensions/XPIs.
+ RefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
+ RefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
+
+ if (mZip == greOmni || mZip == appOmni)
+ return NS_OK;
+
+ //-- Parse the manifest
+ nsresult rv = ParseManifest();
+ if (NS_FAILED(rv)) return rv;
+ if (mGlobalStatus == JAR_NO_MANIFEST)
+ return NS_OK;
+
+ int16_t requestedStatus;
+ if (!aFilename.IsEmpty())
+ {
+ //-- Find the item
+ nsJARManifestItem* manItem = mManifestData.Get(aFilename);
+ if (!manItem)
+ return NS_OK;
+ //-- Verify the item against the manifest
+ if (!manItem->entryVerified)
+ {
+ nsXPIDLCString entryData;
+ uint32_t entryDataLen;
+ rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen);
+ if (NS_FAILED(rv)) return rv;
+ rv = VerifyEntry(manItem, entryData, entryDataLen);
+ if (NS_FAILED(rv)) return rv;
+ }
+ requestedStatus = manItem->status;
+ }
+ else // User wants identity of signer w/o verifying any entries
+ requestedStatus = mGlobalStatus;
+
+ if (requestedStatus != JAR_VALID_MANIFEST) {
+ ReportError(aFilename, requestedStatus);
+ } else { // Valid signature
+ *aSigningCert = mSigningCert;
+ NS_IF_ADDREF(*aSigningCert);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::GetManifestEntriesCount(uint32_t* count)
+{
+ *count = mTotalItemsInManifest;
+ return NS_OK;
+}
+
+nsresult
+nsJAR::GetJarPath(nsACString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(mZipFile);
+
+ return mZipFile->GetNativePath(aResult);
+}
+
+nsresult
+nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc)
+{
+ if (!aNSPRFileDesc) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ *aNSPRFileDesc = nullptr;
+
+ if (!mZip) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsZipHandle> handle = mZip->GetFD();
+ if (!handle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return handle->GetNSPRFileDesc(aNSPRFileDesc);
+}
+
+//----------------------------------------------
+// nsJAR private implementation
+//----------------------------------------------
+nsresult
+nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen)
+{
+ //-- Get a stream for reading the file
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> manifestStream;
+ rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
+ if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+
+ //-- Read the manifest file into memory
+ char* buf;
+ uint64_t len64;
+ rv = manifestStream->Available(&len64);
+ if (NS_FAILED(rv)) return rv;
+ if (len64 >= UINT32_MAX) { // bug 164695
+ nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest size";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ uint32_t len = (uint32_t)len64;
+ buf = (char*)malloc(len+1);
+ if (!buf) return NS_ERROR_OUT_OF_MEMORY;
+ uint32_t bytesRead;
+ rv = manifestStream->Read(buf, len, &bytesRead);
+ if (bytesRead != len) {
+ nsZipArchive::sFileCorruptedReason = "nsJAR: manifest too small";
+ rv = NS_ERROR_FILE_CORRUPTED;
+ }
+ if (NS_FAILED(rv)) {
+ free(buf);
+ return rv;
+ }
+ buf[len] = '\0'; //Null-terminate the buffer
+ *aBuf = buf;
+ if (aBufLen)
+ *aBufLen = len;
+ return NS_OK;
+}
+
+
+int32_t
+nsJAR::ReadLine(const char** src)
+{
+ if (!*src) {
+ return 0;
+ }
+
+ //--Moves pointer to beginning of next line and returns line length
+ // not including CR/LF.
+ int32_t length;
+ char* eol = PL_strpbrk(*src, "\r\n");
+
+ if (eol == nullptr) // Probably reached end of file before newline
+ {
+ length = strlen(*src);
+ if (length == 0) // immediate end-of-file
+ *src = nullptr;
+ else // some data left on this line
+ *src += length;
+ }
+ else
+ {
+ length = eol - *src;
+ if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
+ *src = eol+2;
+ else // Either CR or LF, so skip 1
+ *src = eol+1;
+ }
+ return length;
+}
+
+//-- The following #defines are used by ParseManifest()
+// and ParseOneFile(). The header strings are defined in the JAR specification.
+#define JAR_MF 1
+#define JAR_SF 2
+#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
+#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
+#define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
+#define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
+
+nsresult
+nsJAR::ParseManifest()
+{
+ //-- Verification Step 1
+ if (mParsedManifest)
+ return NS_OK;
+ //-- (1)Manifest (MF) file
+ nsCOMPtr<nsIUTF8StringEnumerator> files;
+ nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files));
+ if (!files) rv = NS_ERROR_FAILURE;
+ if (NS_FAILED(rv)) return rv;
+
+ //-- Load the file into memory
+ bool more;
+ rv = files->HasMore(&more);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!more)
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+
+ nsAutoCString manifestFilename;
+ rv = files->GetNext(manifestFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if there is more than one manifest, if so then error!
+ rv = files->HasMore(&more);
+ if (NS_FAILED(rv)) return rv;
+ if (more)
+ {
+ mParsedManifest = true;
+ nsZipArchive::sFileCorruptedReason = "nsJAR: duplicate manifests";
+ return NS_ERROR_FILE_CORRUPTED; // More than one MF file
+ }
+
+ nsXPIDLCString manifestBuffer;
+ uint32_t manifestLen;
+ rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
+ if (NS_FAILED(rv)) return rv;
+
+ //-- Parse it
+ rv = ParseOneFile(manifestBuffer, JAR_MF);
+ if (NS_FAILED(rv)) return rv;
+
+ //-- (2)Signature (SF) file
+ // If there are multiple signatures, we select one.
+ rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files));
+ if (!files) rv = NS_ERROR_FAILURE;
+ if (NS_FAILED(rv)) return rv;
+ //-- Get an SF file
+ rv = files->HasMore(&more);
+ if (NS_FAILED(rv)) return rv;
+ if (!more)
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+ rv = files->GetNext(manifestFilename);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
+ if (NS_FAILED(rv)) return rv;
+
+ //-- Get its corresponding signature file
+ nsAutoCString sigFilename(manifestFilename);
+ int32_t extension = sigFilename.RFindChar('.') + 1;
+ NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
+ (void)sigFilename.Cut(extension, 2);
+ nsXPIDLCString sigBuffer;
+ uint32_t sigLen;
+ {
+ nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
+ rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
+ }
+ if (NS_FAILED(rv))
+ {
+ nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
+ rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
+ }
+ if (NS_FAILED(rv))
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+
+ //-- Get the signature verifier service
+ nsCOMPtr<nsIDataSignatureVerifier> verifier(
+ do_GetService("@mozilla.org/security/datasignatureverifier;1", &rv));
+ if (NS_FAILED(rv)) // No signature verifier available
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+
+ //-- Verify that the signature file is a valid signature of the SF file
+ int32_t verifyError;
+ rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
+ &verifyError, getter_AddRefs(mSigningCert));
+ if (NS_FAILED(rv)) return rv;
+ if (mSigningCert && verifyError == nsIDataSignatureVerifier::VERIFY_OK) {
+ mGlobalStatus = JAR_VALID_MANIFEST;
+ } else if (verifyError == nsIDataSignatureVerifier::VERIFY_ERROR_UNKNOWN_ISSUER) {
+ mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
+ } else {
+ mGlobalStatus = JAR_INVALID_SIG;
+ }
+
+ //-- Parse the SF file. If the verification above failed, principal
+ // is null, and ParseOneFile will mark the relevant entries as invalid.
+ // if ParseOneFile fails, then it has no effect, and we can safely
+ // continue to the next SF file, or return.
+ ParseOneFile(manifestBuffer, JAR_SF);
+ mParsedManifest = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType)
+{
+ //-- Check file header
+ const char* nextLineStart = filebuf;
+ nsAutoCString curLine;
+ int32_t linelen;
+ linelen = ReadLine(&nextLineStart);
+ curLine.Assign(filebuf, linelen);
+
+ if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
+ ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) ) {
+ nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest header";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ //-- Skip header section
+ do {
+ linelen = ReadLine(&nextLineStart);
+ } while (linelen > 0);
+
+ //-- Set up parsing variables
+ const char* curPos;
+ const char* sectionStart = nextLineStart;
+
+ nsJARManifestItem* curItemMF = nullptr;
+ bool foundName = false;
+ if (aFileType == JAR_MF) {
+ curItemMF = new nsJARManifestItem();
+ }
+
+ nsAutoCString curItemName;
+ nsAutoCString storedSectionDigest;
+
+ for(;;)
+ {
+ curPos = nextLineStart;
+ linelen = ReadLine(&nextLineStart);
+ curLine.Assign(curPos, linelen);
+ if (linelen == 0)
+ // end of section (blank line or end-of-file)
+ {
+ if (aFileType == JAR_MF)
+ {
+ mTotalItemsInManifest++;
+ if (curItemMF->mType != JAR_INVALID)
+ {
+ //-- Did this section have a name: line?
+ if(!foundName)
+ curItemMF->mType = JAR_INVALID;
+ else
+ {
+ //-- If it's an internal item, it must correspond
+ // to a valid jar entry
+ if (curItemMF->mType == JAR_INTERNAL)
+ {
+ bool exists;
+ nsresult rv = HasEntry(curItemName, &exists);
+ if (NS_FAILED(rv) || !exists)
+ curItemMF->mType = JAR_INVALID;
+ }
+ //-- Check for duplicates
+ if (mManifestData.Contains(curItemName)) {
+ curItemMF->mType = JAR_INVALID;
+ }
+ }
+ }
+
+ if (curItemMF->mType == JAR_INVALID)
+ delete curItemMF;
+ else //-- calculate section digest
+ {
+ uint32_t sectionLength = curPos - sectionStart;
+ CalculateDigest(sectionStart, sectionLength,
+ curItemMF->calculatedSectionDigest);
+ //-- Save item in the hashtable
+ mManifestData.Put(curItemName, curItemMF);
+ }
+ if (nextLineStart == nullptr) // end-of-file
+ break;
+
+ sectionStart = nextLineStart;
+ curItemMF = new nsJARManifestItem();
+ } // (aFileType == JAR_MF)
+ else
+ //-- file type is SF, compare digest with calculated
+ // section digests from MF file.
+ {
+ if (foundName)
+ {
+ nsJARManifestItem* curItemSF = mManifestData.Get(curItemName);
+ if(curItemSF)
+ {
+ NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED,
+ "SECURITY ERROR: nsJARManifestItem not correctly initialized");
+ curItemSF->status = mGlobalStatus;
+ if (curItemSF->status == JAR_VALID_MANIFEST)
+ { // Compare digests
+ if (storedSectionDigest.IsEmpty())
+ curItemSF->status = JAR_NOT_SIGNED;
+ else
+ {
+ if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest))
+ curItemSF->status = JAR_INVALID_MANIFEST;
+ curItemSF->calculatedSectionDigest.Truncate();
+ storedSectionDigest.Truncate();
+ }
+ } // (aPrincipal != nullptr)
+ } // if(curItemSF)
+ } // if(foundName)
+
+ if(nextLineStart == nullptr) // end-of-file
+ break;
+ } // aFileType == JAR_SF
+ foundName = false;
+ continue;
+ } // if(linelen == 0)
+
+ //-- Look for continuations (beginning with a space) on subsequent lines
+ // and append them to the current line.
+ while(*nextLineStart == ' ')
+ {
+ curPos = nextLineStart;
+ int32_t continuationLen = ReadLine(&nextLineStart) - 1;
+ nsAutoCString continuation(curPos+1, continuationLen);
+ curLine += continuation;
+ linelen += continuationLen;
+ }
+
+ //-- Find colon in current line, this separates name from value
+ int32_t colonPos = curLine.FindChar(':');
+ if (colonPos == -1) // No colon on line, ignore line
+ continue;
+ //-- Break down the line
+ nsAutoCString lineName;
+ curLine.Left(lineName, colonPos);
+ nsAutoCString lineData;
+ curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
+
+ //-- Lines to look for:
+ // (1) Digest:
+ if (lineName.LowerCaseEqualsLiteral("sha1-digest"))
+ //-- This is a digest line, save the data in the appropriate place
+ {
+ if(aFileType == JAR_MF)
+ curItemMF->storedEntryDigest = lineData;
+ else
+ storedSectionDigest = lineData;
+ continue;
+ }
+
+ // (2) Name: associates this manifest section with a file in the jar.
+ if (!foundName && lineName.LowerCaseEqualsLiteral("name"))
+ {
+ curItemName = lineData;
+ foundName = true;
+ continue;
+ }
+
+ // (3) Magic: this may be an inline Javascript.
+ // We can't do any other kind of magic.
+ if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic"))
+ {
+ if (lineData.LowerCaseEqualsLiteral("javascript"))
+ curItemMF->mType = JAR_EXTERNAL;
+ else
+ curItemMF->mType = JAR_INVALID;
+ continue;
+ }
+
+ } // for (;;)
+ return NS_OK;
+} //ParseOneFile()
+
+nsresult
+nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
+ uint32_t aLen)
+{
+ if (aManItem->status == JAR_VALID_MANIFEST)
+ {
+ if (aManItem->storedEntryDigest.IsEmpty())
+ // No entry digests in manifest file. Entry is unsigned.
+ aManItem->status = JAR_NOT_SIGNED;
+ else
+ { //-- Calculate and compare digests
+ nsCString calculatedEntryDigest;
+ nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+ if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest))
+ aManItem->status = JAR_INVALID_ENTRY;
+ aManItem->storedEntryDigest.Truncate();
+ }
+ }
+ aManItem->entryVerified = true;
+ return NS_OK;
+}
+
+void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode)
+{
+ //-- Generate error message
+ nsAutoString message;
+ message.AssignLiteral("Signature Verification Error: the signature on ");
+ if (!aFilename.IsEmpty())
+ AppendASCIItoUTF16(aFilename, message);
+ else
+ message.AppendLiteral("this .jar archive");
+ message.AppendLiteral(" is invalid because ");
+ switch(errorCode)
+ {
+ case JAR_NOT_SIGNED:
+ message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
+ break;
+ case JAR_INVALID_SIG:
+ message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
+ break;
+ case JAR_INVALID_UNKNOWN_CA:
+ message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
+ break;
+ case JAR_INVALID_MANIFEST:
+ message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
+ break;
+ case JAR_INVALID_ENTRY:
+ message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
+ break;
+ case JAR_NO_MANIFEST:
+ message.AppendLiteral("the archive did not contain a manifest.");
+ break;
+ default:
+ message.AppendLiteral("of an unknown problem.");
+ }
+
+ // Report error in JS console
+ nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
+ if (console)
+ {
+ console->LogStringMessage(message.get());
+ }
+#ifdef DEBUG
+ char* messageCstr = ToNewCString(message);
+ if (!messageCstr) return;
+ fprintf(stderr, "%s\n", messageCstr);
+ free(messageCstr);
+#endif
+}
+
+
+nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen,
+ nsCString& digest)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = hasher->Update((const uint8_t*) aInBuf, aLen);
+ if (NS_FAILED(rv)) return rv;
+
+ return hasher->Finish(true, digest);
+}
+
+NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator)
+
+//----------------------------------------------
+// nsJAREnumerator::HasMore
+//----------------------------------------------
+NS_IMETHODIMP
+nsJAREnumerator::HasMore(bool* aResult)
+{
+ // try to get the next element
+ if (!mName) {
+ NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
+ nsresult rv = mFind->FindNext( &mName, &mNameLen );
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ *aResult = false; // No more matches available
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation
+ }
+
+ *aResult = true;
+ return NS_OK;
+}
+
+//----------------------------------------------
+// nsJAREnumerator::GetNext
+//----------------------------------------------
+NS_IMETHODIMP
+nsJAREnumerator::GetNext(nsACString& aResult)
+{
+ // check if the current item is "stale"
+ if (!mName) {
+ bool bMore;
+ nsresult rv = HasMore(&bMore);
+ if (NS_FAILED(rv) || !bMore)
+ return NS_ERROR_FAILURE; // no error translation
+ }
+ aResult.Assign(mName, mNameLen);
+ mName = 0; // we just gave this one away
+ return NS_OK;
+}
+
+
+NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
+
+nsJARItem::nsJARItem(nsZipItem* aZipItem)
+ : mSize(aZipItem->Size()),
+ mRealsize(aZipItem->RealSize()),
+ mCrc32(aZipItem->CRC32()),
+ mLastModTime(aZipItem->LastModTime()),
+ mCompression(aZipItem->Compression()),
+ mPermissions(aZipItem->Mode()),
+ mIsDirectory(aZipItem->IsDirectory()),
+ mIsSynthetic(aZipItem->isSynthetic)
+{
+}
+
+//------------------------------------------
+// nsJARItem::GetCompression
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetCompression(uint16_t *aCompression)
+{
+ NS_ENSURE_ARG_POINTER(aCompression);
+
+ *aCompression = mCompression;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetSize
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetSize(uint32_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ *aSize = mSize;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetRealSize
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetRealSize(uint32_t *aRealsize)
+{
+ NS_ENSURE_ARG_POINTER(aRealsize);
+
+ *aRealsize = mRealsize;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetCrc32
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetCRC32(uint32_t *aCrc32)
+{
+ NS_ENSURE_ARG_POINTER(aCrc32);
+
+ *aCrc32 = mCrc32;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetIsDirectory
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetIsDirectory(bool *aIsDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aIsDirectory);
+
+ *aIsDirectory = mIsDirectory;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetIsSynthetic
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetIsSynthetic(bool *aIsSynthetic)
+{
+ NS_ENSURE_ARG_POINTER(aIsSynthetic);
+
+ *aIsSynthetic = mIsSynthetic;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetLastModifiedTime
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
+{
+ NS_ENSURE_ARG_POINTER(aLastModTime);
+
+ *aLastModTime = mLastModTime;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetPermissions
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetPermissions(uint32_t* aPermissions)
+{
+ NS_ENSURE_ARG_POINTER(aPermissions);
+
+ *aPermissions = mPermissions;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIZipReaderCache
+
+NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
+
+nsZipReaderCache::nsZipReaderCache()
+ : mLock("nsZipReaderCache.mLock")
+ , mCacheSize(0)
+ , mZips()
+#ifdef ZIP_CACHE_HIT_RATE
+ ,
+ mZipCacheLookups(0),
+ mZipCacheHits(0),
+ mZipCacheFlushes(0),
+ mZipSyncMisses(0)
+#endif
+{
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::Init(uint32_t cacheSize)
+{
+ mCacheSize = cacheSize;
+
+// Register as a memory pressure observer
+ nsCOMPtr<nsIObserverService> os =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (os)
+ {
+ os->AddObserver(this, "memory-pressure", true);
+ os->AddObserver(this, "chrome-flush-caches", true);
+ os->AddObserver(this, "flush-cache-entry", true);
+ }
+// ignore failure of the observer registration.
+
+ return NS_OK;
+}
+
+nsZipReaderCache::~nsZipReaderCache()
+{
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->SetZipReaderCache(nullptr);
+ }
+
+#ifdef ZIP_CACHE_HIT_RATE
+ printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
+ mCacheSize, mZipCacheHits, mZipCacheLookups,
+ (float)mZipCacheHits / mZipCacheLookups,
+ mZipCacheFlushes, mZipSyncMisses);
+#endif
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+ nsresult rv;
+ MutexAutoLock lock(mLock);
+
+ nsAutoCString uri;
+ rv = zipFile->GetNativePath(uri);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ *aResult = mZips.Contains(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+ nsresult rv;
+ MutexAutoLock lock(mLock);
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheLookups++;
+#endif
+
+ nsAutoCString uri;
+ rv = zipFile->GetNativePath(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (zip) {
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheHits++;
+#endif
+ zip->ClearReleaseTime();
+ } else {
+ zip = new nsJAR();
+ zip->SetZipReaderCache(this);
+ rv = zip->Open(zipFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!mZips.Contains(uri));
+ mZips.Put(uri, zip);
+ }
+ zip.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry,
+ nsIZipReader* *result)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+
+ nsCOMPtr<nsIZipReader> outerZipReader;
+ nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheLookups++;
+#endif
+
+ nsAutoCString uri;
+ rv = zipFile->GetNativePath(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
+ uri.AppendLiteral("!/");
+ uri.Append(entry);
+
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (zip) {
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheHits++;
+#endif
+ zip->ClearReleaseTime();
+ } else {
+ zip = new nsJAR();
+ zip->SetZipReaderCache(this);
+
+ rv = zip->OpenInner(outerZipReader, entry);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!mZips.Contains(uri));
+ mZips.Put(uri, zip);
+ }
+ zip.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal)
+{
+#if defined(XP_WIN)
+ MOZ_CRASH("Not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ if (!zipFile) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ nsAutoCString uri;
+ rv = zipFile->GetNativePath(uri);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ MutexAutoLock lock(mLock);
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (!zip) {
+ return NS_ERROR_FAILURE;
+ }
+
+ zip->ClearReleaseTime();
+ rv = zip->GetNSPRFileDesc(aRetVal);
+ // Do this to avoid possible deadlock on mLock with ReleaseZip().
+ MutexAutoUnlock unlock(mLock);
+ RefPtr<nsJAR> zipTemp = zip.forget();
+ return rv;
+#endif /* XP_WIN */
+}
+
+nsresult
+nsZipReaderCache::ReleaseZip(nsJAR* zip)
+{
+ nsresult rv;
+ MutexAutoLock lock(mLock);
+
+ // It is possible that two thread compete for this zip. The dangerous
+ // case is where one thread Releases the zip and discovers that the ref
+ // count has gone to one. Before it can call this ReleaseZip method
+ // another thread calls our GetZip method. The ref count goes to two. That
+ // second thread then Releases the zip and the ref count goes to one. It
+ // then tries to enter this ReleaseZip method and blocks while the first
+ // thread is still here. The first thread continues and remove the zip from
+ // the cache and calls its Release method sending the ref count to 0 and
+ // deleting the zip. However, the second thread is still blocked at the
+ // start of ReleaseZip, but the 'zip' param now hold a reference to a
+ // deleted zip!
+ //
+ // So, we are going to try safeguarding here by searching our hashtable while
+ // locked here for the zip. We return fast if it is not found.
+
+ bool found = false;
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ if (zip == iter.UserData()) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipSyncMisses++;
+#endif
+ return NS_OK;
+ }
+
+ zip->SetReleaseTime();
+
+ if (mZips.Count() <= mCacheSize)
+ return NS_OK;
+
+ // Find the oldest zip.
+ nsJAR* oldest = nullptr;
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ nsJAR* current = iter.UserData();
+ PRIntervalTime currentReleaseTime = current->GetReleaseTime();
+ if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
+ if (oldest == nullptr ||
+ currentReleaseTime < oldest->GetReleaseTime()) {
+ oldest = current;
+ }
+ }
+ }
+
+ // Because of the craziness above it is possible that there is no zip that
+ // needs removing.
+ if (!oldest)
+ return NS_OK;
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheFlushes++;
+#endif
+
+ // remove from hashtable
+ nsAutoCString uri;
+ rv = oldest->GetJarPath(uri);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (oldest->mOuterZipEntry.IsEmpty()) {
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+ } else {
+ uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
+ uri.AppendLiteral("!/");
+ uri.Append(oldest->mOuterZipEntry);
+ }
+
+ // Retrieving and removing the JAR must be done without an extra AddRef
+ // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
+ // an extra time and trigger a deadlock.
+ RefPtr<nsJAR> removed;
+ mZips.Remove(uri, getter_AddRefs(removed));
+ NS_ASSERTION(removed, "botched");
+ NS_ASSERTION(oldest == removed, "removed wrong entry");
+
+ if (removed)
+ removed->SetZipReaderCache(nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aSomeData)
+{
+ if (strcmp(aTopic, "memory-pressure") == 0) {
+ MutexAutoLock lock(mLock);
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<nsJAR>& current = iter.Data();
+ if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
+ current->SetZipReaderCache(nullptr);
+ iter.Remove();
+ }
+ }
+ }
+ else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
+ MutexAutoLock lock(mLock);
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->SetZipReaderCache(nullptr);
+ }
+ mZips.Clear();
+ }
+ else if (strcmp(aTopic, "flush-cache-entry") == 0) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject);
+ if (!file)
+ return NS_OK;
+
+ nsAutoCString uri;
+ if (NS_FAILED(file->GetNativePath(uri)))
+ return NS_OK;
+
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ MutexAutoLock lock(mLock);
+
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (!zip)
+ return NS_OK;
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheFlushes++;
+#endif
+
+ zip->SetZipReaderCache(nullptr);
+
+ mZips.Remove(uri);
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/modules/libjar/nsJAR.h b/modules/libjar/nsJAR.h
new file mode 100644
index 0000000000..2a982a1b5c
--- /dev/null
+++ b/modules/libjar/nsJAR.h
@@ -0,0 +1,224 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsJAR_h_
+#define nsJAR_h_
+
+#include "nscore.h"
+#include "prio.h"
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "prinrval.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsStringEnumerator.h"
+#include "nsHashKeys.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashtable.h"
+#include "nsIZipReader.h"
+#include "nsZipArchive.h"
+#include "nsIObserverService.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+#include "mozilla/Attributes.h"
+
+class nsIX509Cert;
+class nsJARManifestItem;
+class nsZipReaderCache;
+
+/* For mManifestStatus */
+typedef enum
+{
+ JAR_MANIFEST_NOT_PARSED = 0,
+ JAR_VALID_MANIFEST = 1,
+ JAR_INVALID_SIG = 2,
+ JAR_INVALID_UNKNOWN_CA = 3,
+ JAR_INVALID_MANIFEST = 4,
+ JAR_INVALID_ENTRY = 5,
+ JAR_NO_MANIFEST = 6,
+ JAR_NOT_SIGNED = 7
+} JARManifestStatusType;
+
+/*-------------------------------------------------------------------------
+ * Class nsJAR declaration.
+ * nsJAR serves as an XPCOM wrapper for nsZipArchive with the addition of
+ * JAR manifest file parsing.
+ *------------------------------------------------------------------------*/
+class nsJAR final : public nsIZipReader
+{
+ // Allows nsJARInputStream to call the verification functions
+ friend class nsJARInputStream;
+ // Allows nsZipReaderCache to access mOuterZipEntry
+ friend class nsZipReaderCache;
+
+ private:
+
+ virtual ~nsJAR();
+
+ public:
+
+ nsJAR();
+
+ NS_DEFINE_STATIC_CID_ACCESSOR( NS_ZIPREADER_CID )
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIZIPREADER
+
+ nsresult GetJarPath(nsACString& aResult);
+
+ PRIntervalTime GetReleaseTime() {
+ return mReleaseTime;
+ }
+
+ bool IsReleased() {
+ return mReleaseTime != PR_INTERVAL_NO_TIMEOUT;
+ }
+
+ void SetReleaseTime() {
+ mReleaseTime = PR_IntervalNow();
+ }
+
+ void ClearReleaseTime() {
+ mReleaseTime = PR_INTERVAL_NO_TIMEOUT;
+ }
+
+ void SetZipReaderCache(nsZipReaderCache* aCache) {
+ mozilla::MutexAutoLock lock(mLock);
+ mCache = aCache;
+ }
+
+ nsresult GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc);
+
+ protected:
+ typedef nsClassHashtable<nsCStringHashKey, nsJARManifestItem> ManifestDataHashtable;
+
+ //-- Private data members
+ nsCOMPtr<nsIFile> mZipFile; // The zip/jar file on disk
+ nsCString mOuterZipEntry; // The entry in the zip this zip is reading from
+ RefPtr<nsZipArchive> mZip; // The underlying zip archive
+ ManifestDataHashtable mManifestData; // Stores metadata for each entry
+ bool mParsedManifest; // True if manifest has been parsed
+ nsCOMPtr<nsIX509Cert> mSigningCert; // The entity which signed this file
+ int16_t mGlobalStatus; // Global signature verification status
+ PRIntervalTime mReleaseTime; // used by nsZipReaderCache for flushing entries
+ nsZipReaderCache* mCache; // if cached, this points to the cache it's contained in
+ mozilla::Mutex mLock; // protect mCache and mZip
+ int64_t mMtime;
+ int32_t mTotalItemsInManifest;
+ bool mOpened;
+ bool mIsOmnijar;
+
+ nsresult ParseManifest();
+ void ReportError(const nsACString &aFilename, int16_t errorCode);
+ nsresult LoadEntry(const nsACString &aFilename, char** aBuf,
+ uint32_t* aBufLen = nullptr);
+ int32_t ReadLine(const char** src);
+ nsresult ParseOneFile(const char* filebuf, int16_t aFileType);
+ nsresult VerifyEntry(nsJARManifestItem* aEntry, const char* aEntryData,
+ uint32_t aLen);
+
+ nsresult CalculateDigest(const char* aInBuf, uint32_t aInBufLen,
+ nsCString& digest);
+};
+
+/**
+ * nsJARItem
+ *
+ * An individual JAR entry. A set of nsJARItems matching a
+ * supplied pattern are returned in a nsJAREnumerator.
+ */
+class nsJARItem : public nsIZipEntry
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIZIPENTRY
+
+ explicit nsJARItem(nsZipItem* aZipItem);
+
+private:
+ virtual ~nsJARItem() {}
+
+ uint32_t mSize; /* size in original file */
+ uint32_t mRealsize; /* inflated size */
+ uint32_t mCrc32;
+ PRTime mLastModTime;
+ uint16_t mCompression;
+ uint32_t mPermissions;
+ bool mIsDirectory;
+ bool mIsSynthetic;
+};
+
+/**
+ * nsJAREnumerator
+ *
+ * Enumerates a list of files in a zip archive
+ * (based on a pattern match in its member nsZipFind).
+ */
+class nsJAREnumerator final : public nsIUTF8StringEnumerator
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ explicit nsJAREnumerator(nsZipFind *aFind)
+ : mFind(aFind), mName(nullptr), mNameLen(0) {
+ NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
+ }
+
+private:
+ nsZipFind *mFind;
+ const char* mName; // pointer to an name owned by mArchive -- DON'T delete
+ uint16_t mNameLen;
+
+ ~nsJAREnumerator() { delete mFind; }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined(DEBUG_warren) || defined(DEBUG_jband)
+#define ZIP_CACHE_HIT_RATE
+#endif
+
+class nsZipReaderCache : public nsIZipReaderCache, public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIZIPREADERCACHE
+ NS_DECL_NSIOBSERVER
+
+ nsZipReaderCache();
+
+ nsresult ReleaseZip(nsJAR* reader);
+
+ typedef nsRefPtrHashtable<nsCStringHashKey, nsJAR> ZipsHashtable;
+
+protected:
+
+ virtual ~nsZipReaderCache();
+
+ mozilla::Mutex mLock;
+ uint32_t mCacheSize;
+ ZipsHashtable mZips;
+
+#ifdef ZIP_CACHE_HIT_RATE
+ uint32_t mZipCacheLookups;
+ uint32_t mZipCacheHits;
+ uint32_t mZipCacheFlushes;
+ uint32_t mZipSyncMisses;
+#endif
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif /* nsJAR_h_ */
diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp
new file mode 100644
index 0000000000..4b6b78c6d2
--- /dev/null
+++ b/modules/libjar/nsJARChannel.cpp
@@ -0,0 +1,1096 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsJAR.h"
+#include "nsJARChannel.h"
+#include "nsJARProtocolHandler.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsEscape.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIViewSourceChannel.h"
+#include "nsContentUtils.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrincipal.h"
+#include "nsIFileURL.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "nsITabChild.h"
+#include "private/pprio.h"
+#include "nsInputStreamPump.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
+
+// the entry for a directory will either be empty (in the case of the
+// top-level directory) or will end with a slash
+#define ENTRY_IS_DIRECTORY(_entry) \
+ ((_entry).IsEmpty() || '/' == (_entry).Last())
+
+//-----------------------------------------------------------------------------
+
+// Ignore any LOG macro that we inherit from arbitrary headers. (We define our
+// own LOG macro below.)
+#ifdef LOG
+#undef LOG
+#endif
+
+//
+// set NSPR_LOG_MODULES=nsJarProtocol:5
+//
+static LazyLogModule gJarProtocolLog("nsJarProtocol");
+
+#define LOG(args) MOZ_LOG(gJarProtocolLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(gJarProtocolLog, mozilla::LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+// nsJARInputThunk
+//
+// this class allows us to do some extra work on the stream transport thread.
+//-----------------------------------------------------------------------------
+
+class nsJARInputThunk : public nsIInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ nsJARInputThunk(nsIZipReader *zipReader,
+ nsIURI* fullJarURI,
+ const nsACString &jarEntry,
+ bool usingJarCache)
+ : mUsingJarCache(usingJarCache)
+ , mJarReader(zipReader)
+ , mJarEntry(jarEntry)
+ , mContentLength(-1)
+ {
+ if (fullJarURI) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ fullJarURI->GetAsciiSpec(mJarDirSpec);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "this shouldn't fail");
+ }
+ }
+
+ int64_t GetContentLength()
+ {
+ return mContentLength;
+ }
+
+ nsresult Init();
+
+private:
+
+ virtual ~nsJARInputThunk()
+ {
+ Close();
+ }
+
+ bool mUsingJarCache;
+ nsCOMPtr<nsIZipReader> mJarReader;
+ nsCString mJarDirSpec;
+ nsCOMPtr<nsIInputStream> mJarStream;
+ nsCString mJarEntry;
+ int64_t mContentLength;
+};
+
+NS_IMPL_ISUPPORTS(nsJARInputThunk, nsIInputStream)
+
+nsresult
+nsJARInputThunk::Init()
+{
+ nsresult rv;
+ if (ENTRY_IS_DIRECTORY(mJarEntry)) {
+ // A directory stream also needs the Spec of the FullJarURI
+ // because is included in the stream data itself.
+
+ NS_ENSURE_STATE(!mJarDirSpec.IsEmpty());
+
+ rv = mJarReader->GetInputStreamWithSpec(mJarDirSpec,
+ mJarEntry,
+ getter_AddRefs(mJarStream));
+ }
+ else {
+ rv = mJarReader->GetInputStream(mJarEntry,
+ getter_AddRefs(mJarStream));
+ }
+ if (NS_FAILED(rv)) {
+ // convert to the proper result if the entry wasn't found
+ // so that error pages work
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ return rv;
+ }
+
+ // ask the JarStream for the content length
+ uint64_t avail;
+ rv = mJarStream->Available((uint64_t *) &avail);
+ if (NS_FAILED(rv)) return rv;
+
+ mContentLength = avail < INT64_MAX ? (int64_t) avail : -1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::Close()
+{
+ nsresult rv = NS_OK;
+
+ if (mJarStream)
+ rv = mJarStream->Close();
+
+ if (!mUsingJarCache && mJarReader)
+ mJarReader->Close();
+
+ mJarReader = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::Available(uint64_t *avail)
+{
+ return mJarStream->Available(avail);
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::Read(char *buf, uint32_t count, uint32_t *countRead)
+{
+ return mJarStream->Read(buf, count, countRead);
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // stream transport does only calls Read()
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::IsNonBlocking(bool *nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsJARChannel
+//-----------------------------------------------------------------------------
+
+
+nsJARChannel::nsJARChannel()
+ : mOpened(false)
+ , mContentDisposition(0)
+ , mContentLength(-1)
+ , mLoadFlags(LOAD_NORMAL)
+ , mStatus(NS_OK)
+ , mIsPending(false)
+ , mIsUnsafe(true)
+ , mBlockRemoteFiles(false)
+{
+ mBlockRemoteFiles = Preferences::GetBool("network.jar.block-remote-files", false);
+
+ // hold an owning reference to the jar handler
+ NS_ADDREF(gJarHandler);
+}
+
+nsJARChannel::~nsJARChannel()
+{
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+
+ // release owning reference to the jar handler
+ nsJARProtocolHandler *handler = gJarHandler;
+ NS_RELEASE(handler); // nullptr parameter
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel,
+ nsHashPropertyBag,
+ nsIRequest,
+ nsIChannel,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIThreadRetargetableRequest,
+ nsIThreadRetargetableStreamListener,
+ nsIJARChannel)
+
+nsresult
+nsJARChannel::Init(nsIURI *uri)
+{
+ nsresult rv;
+ mJarURI = do_QueryInterface(uri, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mOriginalURI = mJarURI;
+
+ // Prevent loading jar:javascript URIs (see bug 290982).
+ nsCOMPtr<nsIURI> innerURI;
+ rv = mJarURI->GetJARFile(getter_AddRefs(innerURI));
+ if (NS_FAILED(rv))
+ return rv;
+ bool isJS;
+ rv = innerURI->SchemeIs("javascript", &isJS);
+ if (NS_FAILED(rv))
+ return rv;
+ if (isJS) {
+ NS_WARNING("blocking jar:javascript:");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mJarURI->GetSpec(mSpec);
+ return rv;
+}
+
+nsresult
+nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resultInput)
+{
+ MOZ_ASSERT(resultInput);
+ MOZ_ASSERT(mJarFile || mTempMem);
+
+ // important to pass a clone of the file since the nsIFile impl is not
+ // necessarily MT-safe
+ nsCOMPtr<nsIFile> clonedFile;
+ nsresult rv = NS_OK;
+ if (mJarFile) {
+ rv = mJarFile->Clone(getter_AddRefs(clonedFile));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ nsCOMPtr<nsIZipReader> reader;
+ if (jarCache) {
+ MOZ_ASSERT(mJarFile);
+ if (mInnerJarEntry.IsEmpty())
+ rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader));
+ else
+ rv = jarCache->GetInnerZip(clonedFile, mInnerJarEntry,
+ getter_AddRefs(reader));
+ } else {
+ // create an uncached jar reader
+ nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mJarFile) {
+ rv = outerReader->Open(clonedFile);
+ } else {
+ rv = outerReader->OpenMemory(mTempMem->Elements(),
+ mTempMem->Length());
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mInnerJarEntry.IsEmpty())
+ reader = outerReader;
+ else {
+ reader = do_CreateInstance(kZipReaderCID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = reader->OpenInner(outerReader, mInnerJarEntry);
+ }
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsJARInputThunk> input = new nsJARInputThunk(reader,
+ mJarURI,
+ mJarEntry,
+ jarCache != nullptr
+ );
+ rv = input->Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Make GetContentLength meaningful
+ mContentLength = input->GetContentLength();
+
+ input.forget(resultInput);
+ return NS_OK;
+}
+
+nsresult
+nsJARChannel::LookupFile(bool aAllowAsync)
+{
+ LOG(("nsJARChannel::LookupFile [this=%x %s]\n", this, mSpec.get()));
+
+ if (mJarFile)
+ return NS_OK;
+
+ nsresult rv;
+
+ rv = mJarURI->GetJARFile(getter_AddRefs(mJarBaseURI));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = mJarURI->GetJAREntry(mJarEntry);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // The name of the JAR entry must not contain URL-escaped characters:
+ // we're moving from URL domain to a filename domain here. nsStandardURL
+ // does basic escaping by default, which breaks reading zipped files which
+ // have e.g. spaces in their filenames.
+ NS_UnescapeURL(mJarEntry);
+
+ // try to get a nsIFile directly from the url, which will often succeed.
+ {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI);
+ if (fileURL)
+ fileURL->GetFile(getter_AddRefs(mJarFile));
+ }
+
+ // try to handle a nested jar
+ if (!mJarFile) {
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI);
+ if (jarURI) {
+ nsCOMPtr<nsIFileURL> fileURL;
+ nsCOMPtr<nsIURI> innerJarURI;
+ rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI));
+ if (NS_SUCCEEDED(rv))
+ fileURL = do_QueryInterface(innerJarURI);
+ if (fileURL) {
+ fileURL->GetFile(getter_AddRefs(mJarFile));
+ jarURI->GetJAREntry(mInnerJarEntry);
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsJARChannel::OpenLocalFile()
+{
+ MOZ_ASSERT(mIsPending);
+
+ // Local files are always considered safe.
+ mIsUnsafe = false;
+
+ RefPtr<nsJARInputThunk> input;
+ nsresult rv = CreateJarInput(gJarHandler->JarCache(),
+ getter_AddRefs(input));
+ if (NS_SUCCEEDED(rv)) {
+ // Create input stream pump and call AsyncRead as a block.
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
+ if (NS_SUCCEEDED(rv))
+ rv = mPump->AsyncRead(this, nullptr);
+ }
+
+ return rv;
+}
+
+void
+nsJARChannel::NotifyError(nsresult aError)
+{
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ mStatus = aError;
+
+ OnStartRequest(nullptr, nullptr);
+ OnStopRequest(nullptr, nullptr, aError);
+}
+
+void
+nsJARChannel::FireOnProgress(uint64_t aProgress)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mProgressSink);
+
+ mProgressSink->OnProgress(this, nullptr, aProgress, mContentLength);
+}
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsJARChannel::GetName(nsACString &result)
+{
+ return mJarURI->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsJARChannel::IsPending(bool *result)
+{
+ *result = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetStatus(nsresult *status)
+{
+ if (mPump && NS_SUCCEEDED(mStatus))
+ mPump->GetStatus(status);
+ else
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Cancel(nsresult status)
+{
+ mStatus = status;
+ if (mPump)
+ return mPump->Cancel(status);
+
+ NS_ASSERTION(!mIsPending, "need to implement cancel when downloading");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Suspend()
+{
+ if (mPump)
+ return mPump->Suspend();
+
+ NS_ASSERTION(!mIsPending, "need to implement suspend when downloading");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Resume()
+{
+ if (mPump)
+ return mPump->Resume();
+
+ NS_ASSERTION(!mIsPending, "need to implement resume when downloading");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsJARChannel::GetOriginalURI(nsIURI **aURI)
+{
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetOriginalURI(nsIURI *aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = mJarURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetOwner(nsISupports **aOwner)
+{
+ // JAR signatures are not processed to avoid main-thread network I/O (bug 726125)
+ *aOwner = mOwner;
+ NS_IF_ADDREF(*aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetOwner(nsISupports *aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ NS_IF_ADDREF(*aCallbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
+{
+ NS_PRECONDITION(aSecurityInfo, "Null out param");
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentType(nsACString &result)
+{
+ // If the Jar file has not been open yet,
+ // We return application/x-unknown-content-type
+ if (!mOpened) {
+ result.Assign(UNKNOWN_CONTENT_TYPE);
+ return NS_OK;
+ }
+
+ if (mContentType.IsEmpty()) {
+
+ //
+ // generate content type and set it
+ //
+ const char *ext = nullptr, *fileName = mJarEntry.get();
+ int32_t len = mJarEntry.Length();
+
+ // check if we're displaying a directory
+ // mJarEntry will be empty if we're trying to display
+ // the topmost directory in a zip, e.g. jar:foo.zip!/
+ if (ENTRY_IS_DIRECTORY(mJarEntry)) {
+ mContentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
+ }
+ else {
+ // not a directory, take a guess by its extension
+ for (int32_t i = len-1; i >= 0; i--) {
+ if (fileName[i] == '.') {
+ ext = &fileName[i + 1];
+ break;
+ }
+ }
+ if (ext) {
+ nsIMIMEService *mimeServ = gJarHandler->MimeService();
+ if (mimeServ)
+ mimeServ->GetTypeFromExtension(nsDependentCString(ext), mContentType);
+ }
+ if (mContentType.IsEmpty())
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ }
+ }
+ result = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentType(const nsACString &aContentType)
+{
+ // If someone gives us a type hint we should just use that type instead of
+ // doing our guessing. So we don't care when this is being called.
+
+ // mContentCharset is unchanged if not parsed
+ NS_ParseResponseContentType(aContentType, mContentType, mContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ // If someone gives us a charset hint we should just use that charset.
+ // So we don't care when this is being called.
+ aContentCharset = mContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ mContentCharset = aContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ if (mContentDispositionHeader.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aContentDisposition = mContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ if (mContentDispositionHeader.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionHeader = mContentDispositionHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentLength(int64_t *result)
+{
+ *result = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentLength(int64_t aContentLength)
+{
+ // XXX does this really make any sense at all?
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Open(nsIInputStream **stream)
+{
+ LOG(("nsJARChannel::Open [this=%x]\n", this));
+
+ NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+
+ mJarFile = nullptr;
+ mIsUnsafe = true;
+
+ nsresult rv = LookupFile(false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // If mJarInput was not set by LookupFile, the JAR is a remote jar.
+ if (!mJarFile) {
+ NS_NOTREACHED("need sync downloader");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<nsJARInputThunk> input;
+ rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
+ if (NS_FAILED(rv))
+ return rv;
+
+ input.forget(stream);
+ mOpened = true;
+ // local files are always considered safe
+ mIsUnsafe = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+
+ mJarFile = nullptr;
+ mIsUnsafe = true;
+
+ // Initialize mProgressSink
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
+
+ mListener = listener;
+ mListenerContext = ctx;
+ mIsPending = true;
+
+ nsresult rv = LookupFile(true);
+ if (NS_FAILED(rv)) {
+ mIsPending = false;
+ mListenerContext = nullptr;
+ mListener = nullptr;
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+
+ if (!mJarFile) {
+ // Not a local file...
+
+ // Check preferences to see if all remote jar support should be disabled
+ if (mBlockRemoteFiles) {
+ mIsUnsafe = true;
+ return NS_ERROR_UNSAFE_CONTENT_TYPE;
+ }
+
+ static bool reportedRemoteJAR = false;
+ if (!reportedRemoteJAR) {
+ reportedRemoteJAR = true;
+ Telemetry::Accumulate(Telemetry::REMOTE_JAR_PROTOCOL_USED, 1);
+ }
+
+ // kick off an async download of the base URI...
+ nsCOMPtr<nsIStreamListener> downloader = new MemoryDownloader(this);
+ uint32_t loadFlags =
+ mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS);
+ rv = NS_NewChannelInternal(getter_AddRefs(channel),
+ mJarBaseURI,
+ mLoadInfo,
+ mLoadGroup,
+ mCallbacks,
+ loadFlags);
+ if (NS_FAILED(rv)) {
+ mIsPending = false;
+ mListenerContext = nullptr;
+ mListener = nullptr;
+ return rv;
+ }
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ rv = channel->AsyncOpen2(downloader);
+ }
+ else {
+ rv = channel->AsyncOpen(downloader, nullptr);
+ }
+ }
+ else {
+ rv = OpenLocalFile();
+ }
+
+ if (NS_FAILED(rv)) {
+ mIsPending = false;
+ mListenerContext = nullptr;
+ mListener = nullptr;
+ return rv;
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ mOpened = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//-----------------------------------------------------------------------------
+// nsIJARChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsJARChannel::GetIsUnsafe(bool *isUnsafe)
+{
+ *isUnsafe = mIsUnsafe;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetJarFile(nsIFile **aFile)
+{
+ NS_IF_ADDREF(*aFile = mJarFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetZipEntry(nsIZipEntry **aZipEntry)
+{
+ nsresult rv = LookupFile(false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mJarFile)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIZipReader> reader;
+ rv = gJarHandler->JarCache()->GetZip(mJarFile, getter_AddRefs(reader));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return reader->GetEntry(mJarEntry, aZipEntry);
+}
+
+//-----------------------------------------------------------------------------
+// mozilla::net::MemoryDownloader::IObserver
+//-----------------------------------------------------------------------------
+
+void
+nsJARChannel::OnDownloadComplete(MemoryDownloader* aDownloader,
+ nsIRequest *request,
+ nsISupports *context,
+ nsresult status,
+ MemoryDownloader::Data aData)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+ if (channel) {
+ uint32_t loadFlags;
+ channel->GetLoadFlags(&loadFlags);
+ if (loadFlags & LOAD_REPLACE) {
+ mLoadFlags |= LOAD_REPLACE;
+
+ if (!mOriginalURI) {
+ SetOriginalURI(mJarURI);
+ }
+
+ nsCOMPtr<nsIURI> innerURI;
+ rv = channel->GetURI(getter_AddRefs(innerURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIJARURI> newURI;
+ rv = mJarURI->CloneWithJARFile(innerURI,
+ getter_AddRefs(newURI));
+ if (NS_SUCCEEDED(rv)) {
+ mJarURI = newURI;
+ }
+ }
+ if (NS_SUCCEEDED(status)) {
+ status = rv;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(status) && channel) {
+ // Grab the security info from our base channel
+ channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ // We only want to run scripts if the server really intended to
+ // send us a JAR file. Check the server-supplied content type for
+ // a JAR type.
+ nsAutoCString header;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
+ header);
+ nsAutoCString contentType;
+ nsAutoCString charset;
+ NS_ParseResponseContentType(header, contentType, charset);
+ nsAutoCString channelContentType;
+ channel->GetContentType(channelContentType);
+ mIsUnsafe = !(contentType.Equals(channelContentType) &&
+ (contentType.EqualsLiteral("application/java-archive") ||
+ contentType.EqualsLiteral("application/x-jar")));
+ } else {
+ nsCOMPtr<nsIJARChannel> innerJARChannel(do_QueryInterface(channel));
+ if (innerJARChannel) {
+ mIsUnsafe = innerJARChannel->GetIsUnsafe();
+ }
+ }
+
+ channel->GetContentDispositionHeader(mContentDispositionHeader);
+ mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
+ }
+
+ // This is a defense-in-depth check for the preferences to see if all remote jar
+ // support should be disabled. This check may not be needed.
+ MOZ_RELEASE_ASSERT(!mBlockRemoteFiles);
+
+ if (NS_SUCCEEDED(status) && mIsUnsafe &&
+ !Preferences::GetBool("network.jar.open-unsafe-types", false)) {
+ status = NS_ERROR_UNSAFE_CONTENT_TYPE;
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ // Refuse to unpack view-source: jars even if open-unsafe-types is set.
+ nsCOMPtr<nsIViewSourceChannel> viewSource = do_QueryInterface(channel);
+ if (viewSource) {
+ status = NS_ERROR_UNSAFE_CONTENT_TYPE;
+ }
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ mTempMem = Move(aData);
+
+ RefPtr<nsJARInputThunk> input;
+ rv = CreateJarInput(nullptr, getter_AddRefs(input));
+ if (NS_SUCCEEDED(rv)) {
+ // create input stream pump
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
+ if (NS_SUCCEEDED(rv))
+ rv = mPump->AsyncRead(this, nullptr);
+ }
+ status = rv;
+ }
+
+ if (NS_FAILED(status)) {
+ NotifyError(status);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx)
+{
+ LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get()));
+
+ mRequest = req;
+ nsresult rv = mListener->OnStartRequest(this, mListenerContext);
+ mRequest = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARChannel::OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status)
+{
+ LOG(("nsJARChannel::OnStopRequest [this=%x %s status=%x]\n",
+ this, mSpec.get(), status));
+
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ if (mListener) {
+ mListener->OnStopRequest(this, mListenerContext, status);
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, status);
+
+ mPump = nullptr;
+ mIsPending = false;
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+
+ #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
+ #else
+ // To deallocate file descriptor by RemoteOpenFileChild destructor.
+ mJarFile = nullptr;
+ #endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::OnDataAvailable(nsIRequest *req, nsISupports *ctx,
+ nsIInputStream *stream,
+ uint64_t offset, uint32_t count)
+{
+ LOG(("nsJARChannel::OnDataAvailable [this=%x %s]\n", this, mSpec.get()));
+
+ nsresult rv;
+
+ rv = mListener->OnDataAvailable(this, mListenerContext, stream, offset, count);
+
+ // simply report progress here instead of hooking ourselves up as a
+ // nsITransportEventSink implementation.
+ // XXX do the 64-bit stuff for real
+ if (mProgressSink && NS_SUCCEEDED(rv)) {
+ if (NS_IsMainThread()) {
+ FireOnProgress(offset + count);
+ } else {
+ NS_DispatchToMainThread(NewRunnableMethod
+ <uint64_t>(this,
+ &nsJARChannel::FireOnProgress,
+ offset + count));
+ }
+ }
+
+ return rv; // let the pump cancel on failure
+}
+
+NS_IMETHODIMP
+nsJARChannel::RetargetDeliveryTo(nsIEventTarget* aEventTarget)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableRequest> request = do_QueryInterface(mRequest);
+ if (!request) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return request->RetargetDeliveryTo(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsJARChannel::CheckListenerChain()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
diff --git a/modules/libjar/nsJARChannel.h b/modules/libjar/nsJARChannel.h
new file mode 100644
index 0000000000..5328d586ab
--- /dev/null
+++ b/modules/libjar/nsJARChannel.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsJARChannel_h__
+#define nsJARChannel_h__
+
+#include "mozilla/net/MemoryDownloader.h"
+#include "nsIJARChannel.h"
+#include "nsIJARURI.h"
+#include "nsIInputStreamPump.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStreamListener.h"
+#include "nsIZipReader.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsHashPropertyBag.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+
+class nsJARInputThunk;
+class nsInputStreamPump;
+
+//-----------------------------------------------------------------------------
+
+class nsJARChannel final : public nsIJARChannel
+ , public mozilla::net::MemoryDownloader::IObserver
+ , public nsIStreamListener
+ , public nsIThreadRetargetableRequest
+ , public nsIThreadRetargetableStreamListener
+ , public nsHashPropertyBag
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIJARCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsJARChannel();
+
+ nsresult Init(nsIURI *uri);
+
+private:
+ virtual ~nsJARChannel();
+
+ nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
+ nsresult LookupFile(bool aAllowAsync);
+ nsresult OpenLocalFile();
+ void NotifyError(nsresult aError);
+ void FireOnProgress(uint64_t aProgress);
+ virtual void OnDownloadComplete(mozilla::net::MemoryDownloader* aDownloader,
+ nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsresult aStatus,
+ mozilla::net::MemoryDownloader::Data aData)
+ override;
+
+ nsCString mSpec;
+
+ bool mOpened;
+
+ nsCOMPtr<nsIJARURI> mJarURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ nsCString mContentDispositionHeader;
+ /* mContentDisposition is uninitialized if mContentDispositionHeader is
+ * empty */
+ uint32_t mContentDisposition;
+ int64_t mContentLength;
+ uint32_t mLoadFlags;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mIsUnsafe;
+
+ mozilla::net::MemoryDownloader::Data mTempMem;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ // mRequest is only non-null during OnStartRequest, so we'll have a pointer
+ // to the request if we get called back via RetargetDeliveryTo.
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIFile> mJarFile;
+ nsCOMPtr<nsIURI> mJarBaseURI;
+ nsCString mJarEntry;
+ nsCString mInnerJarEntry;
+
+ // True if this channel should not download any remote files.
+ bool mBlockRemoteFiles;
+};
+
+#endif // nsJARChannel_h__
diff --git a/modules/libjar/nsJARFactory.cpp b/modules/libjar/nsJARFactory.cpp
new file mode 100644
index 0000000000..01d8d73f35
--- /dev/null
+++ b/modules/libjar/nsJARFactory.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "nscore.h"
+
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsIJARFactory.h"
+#include "nsJARProtocolHandler.h"
+#include "nsJARURI.h"
+#include "nsJAR.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsJAR)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsZipReaderCache)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsJARProtocolHandler,
+ nsJARProtocolHandler::GetSingleton)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsJARURI)
+
+NS_DEFINE_NAMED_CID(NS_ZIPREADER_CID);
+NS_DEFINE_NAMED_CID(NS_ZIPREADERCACHE_CID);
+NS_DEFINE_NAMED_CID(NS_JARPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_JARURI_CID);
+
+static const mozilla::Module::CIDEntry kJARCIDs[] = {
+ { &kNS_ZIPREADER_CID, false, nullptr, nsJARConstructor },
+ { &kNS_ZIPREADERCACHE_CID, false, nullptr, nsZipReaderCacheConstructor },
+ { &kNS_JARPROTOCOLHANDLER_CID, false, nullptr, nsJARProtocolHandlerConstructor },
+ { &kNS_JARURI_CID, false, nullptr, nsJARURIConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kJARContracts[] = {
+ { "@mozilla.org/libjar/zip-reader;1", &kNS_ZIPREADER_CID },
+ { "@mozilla.org/libjar/zip-reader-cache;1", &kNS_ZIPREADERCACHE_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar", &kNS_JARPROTOCOLHANDLER_CID },
+ { nullptr }
+};
+
+// Jar module shutdown hook
+static void nsJarShutdown()
+{
+ // Make sure to not null out gJarHandler here, because we may have
+ // still-live nsJARChannels that will want to release it.
+ nsJARProtocolHandler *handler = gJarHandler;
+ NS_IF_RELEASE(handler);
+}
+
+static const mozilla::Module kJARModule = {
+ mozilla::Module::kVersion,
+ kJARCIDs,
+ kJARContracts,
+ nullptr,
+ nullptr,
+ nullptr,
+ nsJarShutdown
+};
+
+NSMODULE_DEFN(nsJarModule) = &kJARModule;
diff --git a/modules/libjar/nsJARInputStream.cpp b/modules/libjar/nsJARInputStream.cpp
new file mode 100644
index 0000000000..6993dd578f
--- /dev/null
+++ b/modules/libjar/nsJARInputStream.cpp
@@ -0,0 +1,375 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* nsJARInputStream.cpp
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsJARInputStream.h"
+#include "zipstruct.h" // defines ZIP compression codes
+#include "nsZipArchive.h"
+
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsDebug.h"
+#include <algorithm>
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+/*---------------------------------------------
+ * nsISupports implementation
+ *--------------------------------------------*/
+
+NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream)
+
+/*----------------------------------------------------------
+ * nsJARInputStream implementation
+ *--------------------------------------------------------*/
+
+nsresult
+nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item)
+{
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(aJar, "Argument may not be null");
+ MOZ_ASSERT(item, "Argument may not be null");
+
+ // Mark it as closed, in case something fails in initialisation
+ mMode = MODE_CLOSED;
+ //-- prepare for the compression type
+ switch (item->Compression()) {
+ case STORED:
+ mMode = MODE_COPY;
+ break;
+
+ case DEFLATED:
+ rv = gZlibInit(&mZs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMode = MODE_INFLATE;
+ mInCrc = item->CRC32();
+ mOutCrc = crc32(0L, Z_NULL, 0);
+ break;
+
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data
+ mFd = aJar->mZip->GetFD();
+ mZs.next_in = (Bytef *)aJar->mZip->GetData(item);
+ if (!mZs.next_in) {
+ nsZipArchive::sFileCorruptedReason = "nsJARInputStream: !mZs.next_in";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ mZs.avail_in = item->Size();
+ mOutSize = item->RealSize();
+ mZs.total_out = 0;
+ return NS_OK;
+}
+
+nsresult
+nsJARInputStream::InitDirectory(nsJAR* aJar,
+ const nsACString& aJarDirSpec,
+ const char* aDir)
+{
+ MOZ_ASSERT(aJar, "Argument may not be null");
+ MOZ_ASSERT(aDir, "Argument may not be null");
+
+ // Mark it as closed, in case something fails in initialisation
+ mMode = MODE_CLOSED;
+
+ // Keep the zipReader for getting the actual zipItems
+ mJar = aJar;
+ nsZipFind *find;
+ nsresult rv;
+ // We can get aDir's contents as strings via FindEntries
+ // with the following pattern (see nsIZipReader.findEntries docs)
+ // assuming dirName is properly escaped:
+ //
+ // dirName + "?*~" + dirName + "?*/?*"
+ nsDependentCString dirName(aDir);
+ mNameLen = dirName.Length();
+
+ // iterate through dirName and copy it to escDirName, escaping chars
+ // which are special at the "top" level of the regexp so FindEntries
+ // works correctly
+ nsAutoCString escDirName;
+ const char* curr = dirName.BeginReading();
+ const char* end = dirName.EndReading();
+ while (curr != end) {
+ switch (*curr) {
+ case '*':
+ case '?':
+ case '$':
+ case '[':
+ case ']':
+ case '^':
+ case '~':
+ case '(':
+ case ')':
+ case '\\':
+ escDirName.Append('\\');
+ MOZ_FALLTHROUGH;
+ default:
+ escDirName.Append(*curr);
+ }
+ ++curr;
+ }
+ nsAutoCString pattern = escDirName + NS_LITERAL_CSTRING("?*~") +
+ escDirName + NS_LITERAL_CSTRING("?*/?*");
+ rv = mJar->mZip->FindInit(pattern.get(), &find);
+ if (NS_FAILED(rv)) return rv;
+
+ const char *name;
+ uint16_t nameLen;
+ while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) {
+ // Must copy, to make it zero-terminated
+ mArray.AppendElement(nsCString(name,nameLen));
+ }
+ delete find;
+
+ if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE; // no error translation
+ }
+
+ // Sort it
+ mArray.Sort();
+
+ mBuffer.AssignLiteral("300: ");
+ mBuffer.Append(aJarDirSpec);
+ mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n");
+
+ // Open for reading
+ mMode = MODE_DIRECTORY;
+ mZs.total_out = 0;
+ mArrPos = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::Available(uint64_t *_retval)
+{
+ // A lot of callers don't check the error code.
+ // They just use the _retval value.
+ *_retval = 0;
+
+ switch (mMode) {
+ case MODE_NOTINITED:
+ break;
+
+ case MODE_CLOSED:
+ return NS_BASE_STREAM_CLOSED;
+
+ case MODE_DIRECTORY:
+ *_retval = mBuffer.Length();
+ break;
+
+ case MODE_INFLATE:
+ case MODE_COPY:
+ *_retval = mOutSize - mZs.total_out;
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
+{
+ NS_ENSURE_ARG_POINTER(aBuffer);
+ NS_ENSURE_ARG_POINTER(aBytesRead);
+
+ *aBytesRead = 0;
+
+ nsresult rv = NS_OK;
+MOZ_WIN_MEM_TRY_BEGIN
+ switch (mMode) {
+ case MODE_NOTINITED:
+ return NS_OK;
+
+ case MODE_CLOSED:
+ return NS_BASE_STREAM_CLOSED;
+
+ case MODE_DIRECTORY:
+ return ReadDirectory(aBuffer, aCount, aBytesRead);
+
+ case MODE_INFLATE:
+ if (mFd) {
+ rv = ContinueInflate(aBuffer, aCount, aBytesRead);
+ }
+ // be aggressive about releasing the file!
+ // note that sometimes, we will release mFd before we've finished
+ // deflating - this is because zlib buffers the input
+ if (mZs.avail_in == 0) {
+ mFd = nullptr;
+ }
+ break;
+
+ case MODE_COPY:
+ if (mFd) {
+ uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out));
+ if (count) {
+ memcpy(aBuffer, mZs.next_in + mZs.total_out, count);
+ mZs.total_out += count;
+ }
+ *aBytesRead = count;
+ }
+ // be aggressive about releasing the file!
+ // note that sometimes, we will release mFd before we've finished copying.
+ if (mZs.total_out >= mOutSize) {
+ mFd = nullptr;
+ }
+ break;
+ }
+MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE)
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval)
+{
+ // don't have a buffer to read from, so this better not be called!
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::Close()
+{
+ if (mMode == MODE_INFLATE) {
+ inflateEnd(&mZs);
+ }
+ mMode = MODE_CLOSED;
+ mFd = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount,
+ uint32_t* aBytesRead)
+{
+ // No need to check the args, ::Read did that, but assert them at least
+ NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
+ NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
+
+ // Keep old total_out count
+ const uint32_t oldTotalOut = mZs.total_out;
+
+ // make sure we aren't reading too much
+ mZs.avail_out = std::min(aCount, (mOutSize-oldTotalOut));
+ mZs.next_out = (unsigned char*)aBuffer;
+
+ // now inflate
+ int zerr = inflate(&mZs, Z_SYNC_FLUSH);
+ if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) {
+ nsZipArchive::sFileCorruptedReason = "nsJARInputStream: error while inflating";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ *aBytesRead = (mZs.total_out - oldTotalOut);
+
+ // Calculate the CRC on the output
+ mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead);
+
+ // be aggressive about ending the inflation
+ // for some reason we don't always get Z_STREAM_END
+ if (zerr == Z_STREAM_END || mZs.total_out == mOutSize) {
+ inflateEnd(&mZs);
+
+ // stop returning valid data as soon as we know we have a bad CRC
+ if (mOutCrc != mInCrc) {
+ nsZipArchive::sFileCorruptedReason = "nsJARInputStream: crc mismatch";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
+{
+ // No need to check the args, ::Read did that, but assert them at least
+ NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
+ NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
+
+ // If the buffer contains data, copy what's there up to the desired amount
+ uint32_t numRead = CopyDataToBuffer(aBuffer, aCount);
+
+ if (aCount > 0) {
+ // empty the buffer and start writing directory entry lines to it
+ mBuffer.Truncate();
+ mCurPos = 0;
+ const uint32_t arrayLen = mArray.Length();
+
+ for ( ;aCount > mBuffer.Length(); mArrPos++) {
+ // have we consumed all the directory contents?
+ if (arrayLen <= mArrPos)
+ break;
+
+ const char * entryName = mArray[mArrPos].get();
+ uint32_t entryNameLen = mArray[mArrPos].Length();
+ nsZipItem* ze = mJar->mZip->GetItem(entryName);
+ NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
+
+ // Last Modified Time
+ PRExplodedTime tm;
+ PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm);
+ char itemLastModTime[65];
+ PR_FormatTimeUSEnglish(itemLastModTime,
+ sizeof(itemLastModTime),
+ " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
+ &tm);
+
+ // write a 201: line to the buffer for this item
+ // 200: filename content-length last-modified file-type
+ mBuffer.AppendLiteral("201: ");
+
+ // Names must be escaped and relative, so use the pre-calculated length
+ // of the directory name as the offset into the string
+ // NS_EscapeURL adds the escaped URL to the give string buffer
+ NS_EscapeURL(entryName + mNameLen,
+ entryNameLen - mNameLen,
+ esc_Minimal | esc_AlwaysCopy,
+ mBuffer);
+
+ mBuffer.Append(' ');
+ mBuffer.AppendInt(ze->RealSize(), 10);
+ mBuffer.Append(itemLastModTime); // starts/ends with ' '
+ if (ze->IsDirectory())
+ mBuffer.AppendLiteral("DIRECTORY\n");
+ else
+ mBuffer.AppendLiteral("FILE\n");
+ }
+
+ // Copy up to the desired amount of data to buffer
+ numRead += CopyDataToBuffer(aBuffer, aCount);
+ }
+
+ *aBytesRead = numRead;
+ return NS_OK;
+}
+
+uint32_t
+nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount)
+{
+ const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos);
+
+ if (writeLength > 0) {
+ memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength);
+ mCurPos += writeLength;
+ aCount -= writeLength;
+ aBuffer += writeLength;
+ }
+
+ // return number of bytes copied to the buffer so the
+ // Read method can return the number of bytes copied
+ return writeLength;
+}
diff --git a/modules/libjar/nsJARInputStream.h b/modules/libjar/nsJARInputStream.h
new file mode 100644
index 0000000000..1c396aa102
--- /dev/null
+++ b/modules/libjar/nsJARInputStream.h
@@ -0,0 +1,73 @@
+/* nsJARInputStream.h
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsJARINPUTSTREAM_h__
+#define nsJARINPUTSTREAM_h__
+
+#include "nsIInputStream.h"
+#include "nsJAR.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+/*-------------------------------------------------------------------------
+ * Class nsJARInputStream declaration. This class defines the type of the
+ * object returned by calls to nsJAR::GetInputStream(filename) for the
+ * purpose of reading a file item out of a JAR file.
+ *------------------------------------------------------------------------*/
+class nsJARInputStream final : public nsIInputStream
+{
+ public:
+ nsJARInputStream() :
+ mOutSize(0), mInCrc(0), mOutCrc(0), mNameLen(0),
+ mCurPos(0), mArrPos(0), mMode(MODE_NOTINITED)
+ {
+ memset(&mZs, 0, sizeof(z_stream));
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ // takes ownership of |fd|, even on failure
+ nsresult InitFile(nsJAR *aJar, nsZipItem *item);
+
+ nsresult InitDirectory(nsJAR *aJar,
+ const nsACString& aJarDirSpec,
+ const char* aDir);
+
+ private:
+ ~nsJARInputStream() { Close(); }
+
+ RefPtr<nsZipHandle> mFd; // handle for reading
+ uint32_t mOutSize; // inflated size
+ uint32_t mInCrc; // CRC as provided by the zipentry
+ uint32_t mOutCrc; // CRC as calculated by me
+ z_stream mZs; // zip data structure
+
+ /* For directory reading */
+ RefPtr<nsJAR> mJar; // string reference to zipreader
+ uint32_t mNameLen; // length of dirname
+ nsCString mBuffer; // storage for generated text of stream
+ uint32_t mCurPos; // Current position in buffer
+ uint32_t mArrPos; // current position within mArray
+ nsTArray<nsCString> mArray; // array of names in (zip) directory
+
+ typedef enum {
+ MODE_NOTINITED,
+ MODE_CLOSED,
+ MODE_DIRECTORY,
+ MODE_INFLATE,
+ MODE_COPY
+ } JISMode;
+
+ JISMode mMode; // Modus of the stream
+
+ nsresult ContinueInflate(char* aBuf, uint32_t aCount, uint32_t* aBytesRead);
+ nsresult ReadDirectory(char* aBuf, uint32_t aCount, uint32_t* aBytesRead);
+ uint32_t CopyDataToBuffer(char* &aBuffer, uint32_t &aCount);
+};
+
+#endif /* nsJARINPUTSTREAM_h__ */
+
diff --git a/modules/libjar/nsJARProtocolHandler.cpp b/modules/libjar/nsJARProtocolHandler.cpp
new file mode 100644
index 0000000000..38de73f142
--- /dev/null
+++ b/modules/libjar/nsJARProtocolHandler.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAutoPtr.h"
+#include "nsJARProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsCRT.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsJARURI.h"
+#include "nsIURL.h"
+#include "nsJARChannel.h"
+#include "nsXPIDLString.h"
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsIMIMEService.h"
+#include "nsMimeTypes.h"
+#include "nsThreadUtils.h"
+
+static NS_DEFINE_CID(kZipReaderCacheCID, NS_ZIPREADERCACHE_CID);
+
+#define NS_JAR_CACHE_SIZE 32
+
+//-----------------------------------------------------------------------------
+
+nsJARProtocolHandler *gJarHandler = nullptr;
+
+nsJARProtocolHandler::nsJARProtocolHandler()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+nsJARProtocolHandler::~nsJARProtocolHandler()
+{
+ MOZ_ASSERT(gJarHandler == this);
+ gJarHandler = nullptr;
+}
+
+nsresult
+nsJARProtocolHandler::Init()
+{
+ nsresult rv;
+
+ mJARCache = do_CreateInstance(kZipReaderCacheCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mJARCache->Init(NS_JAR_CACHE_SIZE);
+ return rv;
+}
+
+nsIMIMEService *
+nsJARProtocolHandler::MimeService()
+{
+ if (!mMimeService)
+ mMimeService = do_GetService("@mozilla.org/mime;1");
+
+ return mMimeService.get();
+}
+
+NS_IMPL_ISUPPORTS(nsJARProtocolHandler,
+ nsIJARProtocolHandler,
+ nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+nsJARProtocolHandler*
+nsJARProtocolHandler::GetSingleton()
+{
+ if (!gJarHandler) {
+ gJarHandler = new nsJARProtocolHandler();
+ if (!gJarHandler)
+ return nullptr;
+
+ NS_ADDREF(gJarHandler);
+ nsresult rv = gJarHandler->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(gJarHandler);
+ return nullptr;
+ }
+ }
+ NS_ADDREF(gJarHandler);
+ return gJarHandler;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetJARCache(nsIZipReaderCache* *result)
+{
+ *result = mJARCache;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("jar");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1; // no port for JAR: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ // URI_LOADABLE_BY_ANYONE, since it's our inner URI that will matter
+ // anyway.
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE;
+ /* Although jar uris have their own concept of relative urls
+ it is very different from the standard behaviour, so we
+ have to say norelative here! */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv = NS_OK;
+
+ RefPtr<nsJARURI> jarURI = new nsJARURI();
+ if (!jarURI)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = jarURI->Init(aCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = jarURI->SetSpecWithBase(aSpec, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ADDREF(*result = jarURI);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsJARChannel *chan = new nsJARChannel();
+ if (!chan)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(chan);
+
+ nsresult rv = chan->Init(uri);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = chan->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ *result = chan;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::NewChannel(nsIURI *uri, nsIChannel **result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+
+NS_IMETHODIMP
+nsJARProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/modules/libjar/nsJARProtocolHandler.h b/modules/libjar/nsJARProtocolHandler.h
new file mode 100644
index 0000000000..62a4f7ac2d
--- /dev/null
+++ b/modules/libjar/nsJARProtocolHandler.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsJARProtocolHandler_h__
+#define nsJARProtocolHandler_h__
+
+#include "nsIJARProtocolHandler.h"
+#include "nsIProtocolHandler.h"
+#include "nsIJARURI.h"
+#include "nsIZipReader.h"
+#include "nsIMIMEService.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+
+class nsJARProtocolHandler final : public nsIJARProtocolHandler
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIJARPROTOCOLHANDLER
+
+ // nsJARProtocolHandler methods:
+ nsJARProtocolHandler();
+
+ static nsJARProtocolHandler *GetSingleton();
+
+ nsresult Init();
+
+ // returns non addref'ed pointer.
+ nsIMIMEService *MimeService();
+ nsIZipReaderCache *JarCache() { return mJARCache; }
+protected:
+ virtual ~nsJARProtocolHandler();
+
+ nsCOMPtr<nsIZipReaderCache> mJARCache;
+ nsCOMPtr<nsIMIMEService> mMimeService;
+};
+
+extern nsJARProtocolHandler *gJarHandler;
+
+#define NS_JARPROTOCOLHANDLER_CID \
+{ /* 0xc7e410d4-0x85f2-11d3-9f63-006008a6efe9 */ \
+ 0xc7e410d4, \
+ 0x85f2, \
+ 0x11d3, \
+ {0x9f, 0x63, 0x00, 0x60, 0x08, 0xa6, 0xef, 0xe9} \
+}
+
+#endif // !nsJARProtocolHandler_h__
diff --git a/modules/libjar/nsJARURI.cpp b/modules/libjar/nsJARURI.cpp
new file mode 100644
index 0000000000..e46e514671
--- /dev/null
+++ b/modules/libjar/nsJARURI.cpp
@@ -0,0 +1,913 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "nsJARURI.h"
+#include "nsNetUtil.h"
+#include "nsIIOService.h"
+#include "nsIStandardURL.h"
+#include "nsCRT.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIZipReader.h"
+#include "nsReadableUtils.h"
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla::ipc;
+
+static NS_DEFINE_CID(kJARURICID, NS_JARURI_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsJARURI::nsJARURI()
+{
+}
+
+nsJARURI::~nsJARURI()
+{
+}
+
+// XXX Why is this threadsafe?
+NS_IMPL_ADDREF(nsJARURI)
+NS_IMPL_RELEASE(nsJARURI)
+NS_INTERFACE_MAP_BEGIN(nsJARURI)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJARURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURIWithQuery)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY(nsIJARURI)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+ NS_INTERFACE_MAP_ENTRY(nsINestedURI)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI)
+ // see nsJARURI::Equals
+ if (aIID.Equals(NS_GET_IID(nsJARURI)))
+ foundInterface = reinterpret_cast<nsISupports*>(this);
+ else
+NS_INTERFACE_MAP_END
+
+nsresult
+nsJARURI::Init(const char *charsetHint)
+{
+ mCharsetHint = charsetHint;
+ return NS_OK;
+}
+
+#define NS_JAR_SCHEME NS_LITERAL_CSTRING("jar:")
+#define NS_JAR_DELIMITER NS_LITERAL_CSTRING("!/")
+#define NS_BOGUS_ENTRY_SCHEME NS_LITERAL_CSTRING("x:///")
+
+// FormatSpec takes the entry spec (including the "x:///" at the
+// beginning) and gives us a full JAR spec.
+nsresult
+nsJARURI::FormatSpec(const nsACString &entrySpec, nsACString &result,
+ bool aIncludeScheme)
+{
+ // The entrySpec MUST start with "x:///"
+ NS_ASSERTION(StringBeginsWith(entrySpec, NS_BOGUS_ENTRY_SCHEME),
+ "bogus entry spec");
+
+ nsAutoCString fileSpec;
+ nsresult rv = mJARFile->GetSpec(fileSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aIncludeScheme)
+ result = NS_JAR_SCHEME;
+ else
+ result.Truncate();
+
+ result.Append(fileSpec + NS_JAR_DELIMITER +
+ Substring(entrySpec, 5, entrySpec.Length() - 5));
+ return NS_OK;
+}
+
+nsresult
+nsJARURI::CreateEntryURL(const nsACString& entryFilename,
+ const char* charset,
+ nsIURL** url)
+{
+ *url = nullptr;
+
+ nsCOMPtr<nsIStandardURL> stdURL(do_CreateInstance(NS_STANDARDURL_CONTRACTID));
+ if (!stdURL) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Flatten the concatenation, just in case. See bug 128288
+ nsAutoCString spec(NS_BOGUS_ENTRY_SCHEME + entryFilename);
+ nsresult rv = stdURL->Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1,
+ spec, charset, nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return CallQueryInterface(stdURL, url);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISerializable methods:
+
+NS_IMETHODIMP
+nsJARURI::Read(nsIObjectInputStream* aInputStream)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aInputStream->ReadObject(true, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mJARFile = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aInputStream->ReadObject(true, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mJAREntry = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aInputStream->ReadCString(mCharsetHint);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARURI::Write(nsIObjectOutputStream* aOutputStream)
+{
+ nsresult rv;
+
+ rv = aOutputStream->WriteCompoundObject(mJARFile, NS_GET_IID(nsIURI),
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aOutputStream->WriteCompoundObject(mJAREntry, NS_GET_IID(nsIURL),
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aOutputStream->WriteStringZ(mCharsetHint.get());
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIClassInfo methods:
+
+NS_IMETHODIMP
+nsJARURI::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ *count = 0;
+ *array = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetContractID(char * *aContractID)
+{
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetClassID(nsCID * *aClassID)
+{
+ *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
+ if (!*aClassID)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFlags(uint32_t *aFlags)
+{
+ // XXX We implement THREADSAFE addref/release, but probably shouldn't.
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kJARURICID;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURI methods:
+
+NS_IMETHODIMP
+nsJARURI::GetSpec(nsACString &aSpec)
+{
+ nsAutoCString entrySpec;
+ mJAREntry->GetSpec(entrySpec);
+ return FormatSpec(entrySpec, aSpec);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetSpecIgnoringRef(nsACString &aSpec)
+{
+ nsAutoCString entrySpec;
+ mJAREntry->GetSpecIgnoringRef(entrySpec);
+ return FormatSpec(entrySpec, aSpec);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetHasRef(bool *result)
+{
+ return mJAREntry->GetHasRef(result);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetSpec(const nsACString& aSpec)
+{
+ return SetSpecWithBase(aSpec, nullptr);
+}
+
+nsresult
+nsJARURI::SetSpecWithBase(const nsACString &aSpec, nsIURI* aBaseURL)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ioServ(do_GetIOService(&rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme;
+ rv = ioServ->ExtractScheme(aSpec, scheme);
+ if (NS_FAILED(rv)) {
+ // not an absolute URI
+ if (!aBaseURL)
+ return NS_ERROR_MALFORMED_URI;
+
+ RefPtr<nsJARURI> otherJAR;
+ aBaseURL->QueryInterface(NS_GET_IID(nsJARURI), getter_AddRefs(otherJAR));
+ NS_ENSURE_TRUE(otherJAR, NS_NOINTERFACE);
+
+ mJARFile = otherJAR->mJARFile;
+
+ nsCOMPtr<nsIStandardURL> entry(do_CreateInstance(NS_STANDARDURL_CONTRACTID));
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = entry->Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1,
+ aSpec, mCharsetHint.get(), otherJAR->mJAREntry);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mJAREntry = do_QueryInterface(entry);
+ if (!mJAREntry)
+ return NS_NOINTERFACE;
+
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(scheme.EqualsLiteral("jar"), NS_ERROR_MALFORMED_URI);
+
+ nsACString::const_iterator begin, end;
+ aSpec.BeginReading(begin);
+ aSpec.EndReading(end);
+
+ while (begin != end && *begin != ':')
+ ++begin;
+
+ ++begin; // now we're past the "jar:"
+
+ // Search backward from the end for the "!/" delimiter. Remember, jar URLs
+ // can nest, e.g.:
+ // jar:jar:http://www.foo.com/bar.jar!/a.jar!/b.html
+ // This gets the b.html document from out of the a.jar file, that's
+ // contained within the bar.jar file.
+ // Also, the outermost "inner" URI may be a relative URI:
+ // jar:../relative.jar!/a.html
+
+ nsACString::const_iterator delim_begin (begin),
+ delim_end (end);
+
+ if (!RFindInReadable(NS_JAR_DELIMITER, delim_begin, delim_end))
+ return NS_ERROR_MALFORMED_URI;
+
+ rv = ioServ->NewURI(Substring(begin, delim_begin), mCharsetHint.get(),
+ aBaseURL, getter_AddRefs(mJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_TryToSetImmutable(mJARFile);
+
+ // skip over any extra '/' chars
+ while (*delim_end == '/')
+ ++delim_end;
+
+ return SetJAREntry(Substring(delim_end, end));
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPrePath(nsACString &prePath)
+{
+ prePath = NS_JAR_SCHEME;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetScheme(nsACString &aScheme)
+{
+ aScheme = "jar";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetScheme(const nsACString &aScheme)
+{
+ // doesn't make sense to set the scheme of a jar: URL
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetUserPass(nsACString &aUserPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetUserPass(const nsACString &aUserPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetUsername(nsACString &aUsername)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetUsername(const nsACString &aUsername)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPassword(nsACString &aPassword)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetPassword(const nsACString &aPassword)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetHostPort(nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetHostPort(const nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetHostAndPort(const nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetHost(nsACString &aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetHost(const nsACString &aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPort(int32_t *aPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetPort(int32_t aPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPath(nsACString &aPath)
+{
+ nsAutoCString entrySpec;
+ mJAREntry->GetSpec(entrySpec);
+ return FormatSpec(entrySpec, aPath, false);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetPath(const nsACString &aPath)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetAsciiSpec(nsACString &aSpec)
+{
+ // XXX Shouldn't this like... make sure it returns ASCII or something?
+ return GetSpec(aSpec);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetAsciiHostPort(nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetAsciiHost(nsACString &aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetOriginCharset(nsACString &aOriginCharset)
+{
+ aOriginCharset = mCharsetHint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::Equals(nsIURI *other, bool *result)
+{
+ return EqualsInternal(other, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsJARURI::EqualsExceptRef(nsIURI *other, bool *result)
+{
+ return EqualsInternal(other, eIgnoreRef, result);
+}
+
+// Helper method:
+/* virtual */ nsresult
+nsJARURI::EqualsInternal(nsIURI *other,
+ nsJARURI::RefHandlingEnum refHandlingMode,
+ bool *result)
+{
+ *result = false;
+
+ if (!other)
+ return NS_OK; // not equal
+
+ RefPtr<nsJARURI> otherJAR;
+ other->QueryInterface(NS_GET_IID(nsJARURI), getter_AddRefs(otherJAR));
+ if (!otherJAR)
+ return NS_OK; // not equal
+
+ bool equal;
+ nsresult rv = mJARFile->Equals(otherJAR->mJARFile, &equal);
+ if (NS_FAILED(rv) || !equal) {
+ return rv; // not equal
+ }
+
+ return refHandlingMode == eHonorRef ?
+ mJAREntry->Equals(otherJAR->mJAREntry, result) :
+ mJAREntry->EqualsExceptRef(otherJAR->mJAREntry, result);
+}
+
+NS_IMETHODIMP
+nsJARURI::SchemeIs(const char *i_Scheme, bool *o_Equals)
+{
+ NS_ENSURE_ARG_POINTER(o_Equals);
+ if (!i_Scheme) return NS_ERROR_INVALID_ARG;
+
+ if (*i_Scheme == 'j' || *i_Scheme == 'J') {
+ *o_Equals = PL_strcasecmp("jar", i_Scheme) ? false : true;
+ } else {
+ *o_Equals = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::Clone(nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIJARURI> uri;
+ rv = CloneWithJARFileInternal(mJARFile, eHonorRef, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::CloneIgnoringRef(nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIJARURI> uri;
+ rv = CloneWithJARFileInternal(mJARFile, eIgnoreRef, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::CloneWithNewRef(const nsACString& newRef, nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIJARURI> uri;
+ rv = CloneWithJARFileInternal(mJARFile, eReplaceRef, newRef,
+ getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ioServ(do_GetIOService(&rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString scheme;
+ rv = ioServ->ExtractScheme(relativePath, scheme);
+ if (NS_SUCCEEDED(rv)) {
+ // then aSpec is absolute
+ result = relativePath;
+ return NS_OK;
+ }
+
+ nsAutoCString resolvedPath;
+ mJAREntry->Resolve(relativePath, resolvedPath);
+
+ return FormatSpec(resolvedPath, result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURL methods:
+
+NS_IMETHODIMP
+nsJARURI::GetFilePath(nsACString& filePath)
+{
+ return mJAREntry->GetFilePath(filePath);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFilePath(const nsACString& filePath)
+{
+ return mJAREntry->SetFilePath(filePath);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetQuery(nsACString& query)
+{
+ return mJAREntry->GetQuery(query);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetQuery(const nsACString& query)
+{
+ return mJAREntry->SetQuery(query);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetRef(nsACString& ref)
+{
+ return mJAREntry->GetRef(ref);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetRef(const nsACString& ref)
+{
+ return mJAREntry->SetRef(ref);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetDirectory(nsACString& directory)
+{
+ return mJAREntry->GetDirectory(directory);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetDirectory(const nsACString& directory)
+{
+ return mJAREntry->SetDirectory(directory);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFileName(nsACString& fileName)
+{
+ return mJAREntry->GetFileName(fileName);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFileName(const nsACString& fileName)
+{
+ return mJAREntry->SetFileName(fileName);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFileBaseName(nsACString& fileBaseName)
+{
+ return mJAREntry->GetFileBaseName(fileBaseName);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFileBaseName(const nsACString& fileBaseName)
+{
+ return mJAREntry->SetFileBaseName(fileBaseName);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFileExtension(nsACString& fileExtension)
+{
+ return mJAREntry->GetFileExtension(fileExtension);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFileExtension(const nsACString& fileExtension)
+{
+ return mJAREntry->SetFileExtension(fileExtension);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetCommonBaseSpec(nsIURI* uriToCompare, nsACString& commonSpec)
+{
+ commonSpec.Truncate();
+
+ NS_ENSURE_ARG_POINTER(uriToCompare);
+
+ commonSpec.Truncate();
+ nsCOMPtr<nsIJARURI> otherJARURI(do_QueryInterface(uriToCompare));
+ if (!otherJARURI) {
+ // Nothing in common
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> otherJARFile;
+ nsresult rv = otherJARURI->GetJARFile(getter_AddRefs(otherJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool equal;
+ rv = mJARFile->Equals(otherJARFile, &equal);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!equal) {
+ // See what the JAR file URIs have in common
+ nsCOMPtr<nsIURL> ourJARFileURL(do_QueryInterface(mJARFile));
+ if (!ourJARFileURL) {
+ // Not a URL, so nothing in common
+ return NS_OK;
+ }
+ nsAutoCString common;
+ rv = ourJARFileURL->GetCommonBaseSpec(otherJARFile, common);
+ if (NS_FAILED(rv)) return rv;
+
+ commonSpec = NS_JAR_SCHEME + common;
+ return NS_OK;
+
+ }
+
+ // At this point we have the same JAR file. Compare the JAREntrys
+ nsAutoCString otherEntry;
+ rv = otherJARURI->GetJAREntry(otherEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString otherCharset;
+ rv = uriToCompare->GetOriginCharset(otherCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURL> url;
+ rv = CreateEntryURL(otherEntry, otherCharset.get(), getter_AddRefs(url));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString common;
+ rv = mJAREntry->GetCommonBaseSpec(url, common);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = FormatSpec(common, commonSpec);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetRelativeSpec(nsIURI* uriToCompare, nsACString& relativeSpec)
+{
+ GetSpec(relativeSpec);
+
+ NS_ENSURE_ARG_POINTER(uriToCompare);
+
+ nsCOMPtr<nsIJARURI> otherJARURI(do_QueryInterface(uriToCompare));
+ if (!otherJARURI) {
+ // Nothing in common
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> otherJARFile;
+ nsresult rv = otherJARURI->GetJARFile(getter_AddRefs(otherJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool equal;
+ rv = mJARFile->Equals(otherJARFile, &equal);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!equal) {
+ // We live in different JAR files. Nothing in common.
+ return rv;
+ }
+
+ // Same JAR file. Compare the JAREntrys
+ nsAutoCString otherEntry;
+ rv = otherJARURI->GetJAREntry(otherEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString otherCharset;
+ rv = uriToCompare->GetOriginCharset(otherCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURL> url;
+ rv = CreateEntryURL(otherEntry, otherCharset.get(), getter_AddRefs(url));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString relativeEntrySpec;
+ rv = mJAREntry->GetRelativeSpec(url, relativeEntrySpec);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!StringBeginsWith(relativeEntrySpec, NS_BOGUS_ENTRY_SCHEME)) {
+ // An actual relative spec!
+ relativeSpec = relativeEntrySpec;
+ }
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIJARURI methods:
+
+NS_IMETHODIMP
+nsJARURI::GetJARFile(nsIURI* *jarFile)
+{
+ return GetInnerURI(jarFile);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetJAREntry(nsACString &entryPath)
+{
+ nsAutoCString filePath;
+ mJAREntry->GetFilePath(filePath);
+ NS_ASSERTION(filePath.Length() > 0, "path should never be empty!");
+ // Trim off the leading '/'
+ entryPath = Substring(filePath, 1, filePath.Length() - 1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetJAREntry(const nsACString &entryPath)
+{
+ return CreateEntryURL(entryPath, mCharsetHint.get(),
+ getter_AddRefs(mJAREntry));
+}
+
+NS_IMETHODIMP
+nsJARURI::CloneWithJARFile(nsIURI *jarFile, nsIJARURI **result)
+{
+ return CloneWithJARFileInternal(jarFile, eHonorRef, result);
+}
+
+nsresult
+nsJARURI::CloneWithJARFileInternal(nsIURI *jarFile,
+ nsJARURI::RefHandlingEnum refHandlingMode,
+ nsIJARURI **result)
+{
+ return CloneWithJARFileInternal(jarFile, refHandlingMode, EmptyCString(), result);
+}
+
+nsresult
+nsJARURI::CloneWithJARFileInternal(nsIURI *jarFile,
+ nsJARURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef,
+ nsIJARURI **result)
+{
+ if (!jarFile) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> newJARFile;
+ rv = jarFile->Clone(getter_AddRefs(newJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_TryToSetImmutable(newJARFile);
+
+ nsCOMPtr<nsIURI> newJAREntryURI;
+ if (refHandlingMode == eHonorRef) {
+ rv = mJAREntry->Clone(getter_AddRefs(newJAREntryURI));
+ } else if (refHandlingMode == eReplaceRef) {
+ rv = mJAREntry->CloneWithNewRef(newRef, getter_AddRefs(newJAREntryURI));
+ } else {
+ rv = mJAREntry->CloneIgnoringRef(getter_AddRefs(newJAREntryURI));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURL> newJAREntry(do_QueryInterface(newJAREntryURI));
+ NS_ASSERTION(newJAREntry, "This had better QI to nsIURL!");
+
+ nsJARURI* uri = new nsJARURI();
+ NS_ADDREF(uri);
+ uri->mJARFile = newJARFile;
+ uri->mJAREntry = newJAREntry;
+ *result = uri;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsJARURI::GetInnerURI(nsIURI **uri)
+{
+ return NS_EnsureSafeToReturn(mJARFile, uri);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetInnermostURI(nsIURI** uri)
+{
+ return NS_ImplGetInnermostURI(this, uri);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIIPCSerializableURI methods:
+
+void
+nsJARURI::Serialize(URIParams& aParams)
+{
+ JARURIParams params;
+
+ SerializeURI(mJARFile, params.jarFile());
+ SerializeURI(mJAREntry, params.jarEntry());
+ params.charset() = mCharsetHint;
+
+ aParams = params;
+}
+
+bool
+nsJARURI::Deserialize(const URIParams& aParams)
+{
+ if (aParams.type() != URIParams::TJARURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const JARURIParams& params = aParams.get_JARURIParams();
+
+ nsCOMPtr<nsIURI> file = DeserializeURI(params.jarFile());
+ if (!file) {
+ NS_ERROR("Couldn't deserialize jar file URI!");
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> entry = DeserializeURI(params.jarEntry());
+ if (!entry) {
+ NS_ERROR("Couldn't deserialize jar entry URI!");
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> entryURL = do_QueryInterface(entry);
+ if (!entryURL) {
+ NS_ERROR("Couldn't QI jar entry URI to nsIURL!");
+ return false;
+ }
+
+ mJARFile.swap(file);
+ mJAREntry.swap(entryURL);
+ mCharsetHint = params.charset();
+
+ return true;
+}
diff --git a/modules/libjar/nsJARURI.h b/modules/libjar/nsJARURI.h
new file mode 100644
index 0000000000..31271e4ac1
--- /dev/null
+++ b/modules/libjar/nsJARURI.h
@@ -0,0 +1,97 @@
+/* -*- Mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsJARURI_h__
+#define nsJARURI_h__
+
+#include "nsIJARURI.h"
+#include "nsISerializable.h"
+#include "nsIClassInfo.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsINestedURI.h"
+#include "nsIIPCSerializableURI.h"
+
+#define NS_THIS_JARURI_IMPL_CID \
+{ /* 9a55f629-730b-4d08-b75b-fa7d9570a691 */ \
+ 0x9a55f629, \
+ 0x730b, \
+ 0x4d08, \
+ {0xb7, 0x5b, 0xfa, 0x7d, 0x95, 0x70, 0xa6, 0x91} \
+}
+
+#define NS_JARURI_CID \
+{ /* 245abae2-b947-4ded-a46d-9829d3cca462 */ \
+ 0x245abae2, \
+ 0xb947, \
+ 0x4ded, \
+ {0xa4, 0x6d, 0x98, 0x29, 0xd3, 0xcc, 0xa4, 0x62} \
+}
+
+
+class nsJARURI final : public nsIJARURI,
+ public nsISerializable,
+ public nsIClassInfo,
+ public nsINestedURI,
+ public nsIIPCSerializableURI
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURIWITHQUERY
+ NS_DECL_NSIURL
+ NS_DECL_NSIJARURI
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSINESTEDURI
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_JARURI_IMPL_CID)
+
+ // nsJARURI
+ nsJARURI();
+
+ nsresult Init(const char *charsetHint);
+ nsresult FormatSpec(const nsACString &entryPath, nsACString &result,
+ bool aIncludeScheme = true);
+ nsresult CreateEntryURL(const nsACString& entryFilename,
+ const char* charset,
+ nsIURL** url);
+ nsresult SetSpecWithBase(const nsACString& aSpec, nsIURI* aBaseURL);
+
+protected:
+ virtual ~nsJARURI();
+
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ // Helpers to share code between Clone methods.
+ nsresult CloneWithJARFileInternal(nsIURI *jarFile,
+ RefHandlingEnum refHandlingMode,
+ nsIJARURI **result);
+ nsresult CloneWithJARFileInternal(nsIURI *jarFile,
+ RefHandlingEnum refHandlingMode,
+ const nsACString& newRef,
+ nsIJARURI **result);
+ nsCOMPtr<nsIURI> mJARFile;
+ // mJarEntry stored as a URL so that we can easily access things
+ // like extensions, refs, etc.
+ nsCOMPtr<nsIURL> mJAREntry;
+ nsCString mCharsetHint;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsJARURI, NS_THIS_JARURI_IMPL_CID)
+
+#endif // nsJARURI_h__
diff --git a/modules/libjar/nsZipArchive.cpp b/modules/libjar/nsZipArchive.cpp
new file mode 100644
index 0000000000..429de10111
--- /dev/null
+++ b/modules/libjar/nsZipArchive.cpp
@@ -0,0 +1,1294 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This module implements a simple archive extractor for the PKZIP format.
+ *
+ * The underlying nsZipArchive is NOT thread-safe. Do not pass references
+ * or pointers to it across thread boundaries.
+ */
+
+// This must be the first include in the file in order for the
+// PL_ARENA_CONST_ALIGN_MASK macro to be effective.
+#define PL_ARENA_CONST_ALIGN_MASK (sizeof(void*)-1)
+#include "plarena.h"
+
+#define READTYPE int32_t
+#include "zlib.h"
+#include "nsISupportsUtils.h"
+#include "prio.h"
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "stdlib.h"
+#include "nsWildCard.h"
+#include "nsZipArchive.h"
+#include "nsString.h"
+#include "prenv.h"
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+// For placement new used for arena allocations of zip file list
+#include <new>
+#define ZIP_ARENABLOCKSIZE (1*1024)
+
+#ifdef XP_UNIX
+ #include <sys/mman.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <limits.h>
+ #include <unistd.h>
+#elif defined(XP_WIN)
+ #include <io.h>
+#endif
+
+#ifdef __SYMBIAN32__
+ #include <sys/syslimits.h>
+#endif /*__SYMBIAN32__*/
+
+
+#ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */
+# ifndef S_IFMT
+# define S_IFMT 0170000
+# endif
+# ifndef S_IFLNK
+# define S_IFLNK 0120000
+# endif
+# ifndef PATH_MAX
+# define PATH_MAX 1024
+# endif
+#endif /* XP_UNIX */
+
+#ifdef XP_WIN
+#include "private/pprio.h" // To get PR_ImportFile
+#endif
+
+using namespace mozilla;
+
+static const uint32_t kMaxNameLength = PATH_MAX; /* Maximum name length */
+// For synthetic zip entries. Date/time corresponds to 1980-01-01 00:00.
+static const uint16_t kSyntheticTime = 0;
+static const uint16_t kSyntheticDate = (1 + (1 << 5) + (0 << 9));
+
+static uint16_t xtoint(const uint8_t *ii);
+static uint32_t xtolong(const uint8_t *ll);
+static uint32_t HashName(const char* aName, uint16_t nameLen);
+#ifdef XP_UNIX
+static nsresult ResolveSymlink(const char *path);
+#endif
+
+class ZipArchiveLogger {
+public:
+ void Write(const nsACString &zip, const char *entry) const {
+ if (!fd) {
+ char *env = PR_GetEnv("MOZ_JAR_LOG_FILE");
+ if (!env)
+ return;
+
+ nsCOMPtr<nsIFile> logFile;
+ nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(logFile));
+ if (NS_FAILED(rv))
+ return;
+
+ // Create the log file and its parent directory (in case it doesn't exist)
+ rv = logFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_FAILED(rv))
+ return;
+
+ PRFileDesc* file;
+#ifdef XP_WIN
+ // PR_APPEND is racy on Windows, so open a handle ourselves with flags that
+ // will work, and use PR_ImportFile to make it a PRFileDesc.
+ // This can go away when bug 840435 is fixed.
+ nsAutoString path;
+ logFile->GetPath(path);
+ if (path.IsEmpty())
+ return;
+ HANDLE handle = CreateFileW(path.get(), FILE_APPEND_DATA, FILE_SHARE_WRITE,
+ nullptr, OPEN_ALWAYS, 0, nullptr);
+ if (handle == INVALID_HANDLE_VALUE)
+ return;
+ file = PR_ImportFile((PROsfd)handle);
+ if (!file)
+ return;
+#else
+ rv = logFile->OpenNSPRFileDesc(PR_WRONLY|PR_CREATE_FILE|PR_APPEND, 0644, &file);
+ if (NS_FAILED(rv))
+ return;
+#endif
+ fd = file;
+ }
+ nsCString buf(zip);
+ buf.Append(' ');
+ buf.Append(entry);
+ buf.Append('\n');
+ PR_Write(fd, buf.get(), buf.Length());
+ }
+
+ void AddRef() {
+ MOZ_ASSERT(refCnt >= 0);
+ ++refCnt;
+ }
+
+ void Release() {
+ MOZ_ASSERT(refCnt > 0);
+ if ((0 == --refCnt) && fd) {
+ PR_Close(fd);
+ fd = nullptr;
+ }
+ }
+private:
+ int refCnt;
+ mutable PRFileDesc *fd;
+};
+
+static ZipArchiveLogger zipLog;
+
+//***********************************************************
+// For every inflation the following allocations are done:
+// malloc(1 * 9520)
+// malloc(32768 * 1)
+//***********************************************************
+
+nsresult gZlibInit(z_stream *zs)
+{
+ memset(zs, 0, sizeof(z_stream));
+ int zerr = inflateInit2(zs, -MAX_WBITS);
+ if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsZipHandle::nsZipHandle()
+ : mFileData(nullptr)
+ , mLen(0)
+ , mMap(nullptr)
+ , mRefCnt(0)
+ , mFileStart(nullptr)
+ , mTotalLen(0)
+{
+ MOZ_COUNT_CTOR(nsZipHandle);
+}
+
+NS_IMPL_ADDREF(nsZipHandle)
+NS_IMPL_RELEASE(nsZipHandle)
+
+nsresult nsZipHandle::Init(nsIFile *file, nsZipHandle **ret,
+ PRFileDesc **aFd)
+{
+ mozilla::AutoFDClose fd;
+ int32_t flags = PR_RDONLY;
+#if defined(XP_WIN)
+ flags |= nsIFile::OS_READAHEAD;
+#endif
+ nsresult rv = file->OpenNSPRFileDesc(flags, 0000, &fd.rwget());
+ if (NS_FAILED(rv))
+ return rv;
+
+ int64_t size = PR_Available64(fd);
+ if (size >= INT32_MAX)
+ return NS_ERROR_FILE_TOO_BIG;
+
+ PRFileMap *map = PR_CreateFileMap(fd, size, PR_PROT_READONLY);
+ if (!map)
+ return NS_ERROR_FAILURE;
+
+ uint8_t *buf = (uint8_t*) PR_MemMap(map, 0, (uint32_t) size);
+ // Bug 525755: PR_MemMap fails when fd points at something other than a normal file.
+ if (!buf) {
+ PR_CloseFileMap(map);
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsZipHandle> handle = new nsZipHandle();
+ if (!handle) {
+ PR_MemUnmap(buf, (uint32_t) size);
+ PR_CloseFileMap(map);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+#if defined(XP_WIN)
+ if (aFd) {
+ *aFd = fd.forget();
+ }
+#else
+ handle->mNSPRFileDesc = fd.forget();
+#endif
+ handle->mMap = map;
+ handle->mFile.Init(file);
+ handle->mTotalLen = (uint32_t) size;
+ handle->mFileStart = buf;
+ rv = handle->findDataStart();
+ if (NS_FAILED(rv)) {
+ PR_MemUnmap(buf, (uint32_t) size);
+ PR_CloseFileMap(map);
+ return rv;
+ }
+ handle.forget(ret);
+ return NS_OK;
+}
+
+nsresult nsZipHandle::Init(nsZipArchive *zip, const char *entry,
+ nsZipHandle **ret)
+{
+ RefPtr<nsZipHandle> handle = new nsZipHandle();
+ if (!handle)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ handle->mBuf = new nsZipItemPtr<uint8_t>(zip, entry);
+ if (!handle->mBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (!handle->mBuf->Buffer())
+ return NS_ERROR_UNEXPECTED;
+
+ handle->mMap = nullptr;
+ handle->mFile.Init(zip, entry);
+ handle->mTotalLen = handle->mBuf->Length();
+ handle->mFileStart = handle->mBuf->Buffer();
+ nsresult rv = handle->findDataStart();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ handle.forget(ret);
+ return NS_OK;
+}
+
+nsresult nsZipHandle::Init(const uint8_t* aData, uint32_t aLen,
+ nsZipHandle **aRet)
+{
+ RefPtr<nsZipHandle> handle = new nsZipHandle();
+
+ handle->mFileStart = aData;
+ handle->mTotalLen = aLen;
+ nsresult rv = handle->findDataStart();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ handle.forget(aRet);
+ return NS_OK;
+}
+
+// This function finds the start of the ZIP data. If the file is a regular ZIP,
+// this is just the start of the file. If the file is a CRX file, the start of
+// the data is after the CRX header.
+// CRX header reference: (CRX version 2)
+// Header requires little-endian byte ordering with 4-byte alignment.
+// 32 bits : magicNumber - Defined as a |char m[] = "Cr24"|.
+// Equivilant to |uint32_t m = 0x34327243|.
+// 32 bits : version - Unsigned integer representing the CRX file
+// format version. Currently equal to 2.
+// 32 bits : pubKeyLength - Unsigned integer representing the length
+// of the public key in bytes.
+// 32 bits : sigLength - Unsigned integer representing the length
+// of the signature in bytes.
+// pubKeyLength : publicKey - Contents of the author's public key.
+// sigLength : signature - Signature of the ZIP content.
+// Signature is created using the RSA
+// algorighm with the SHA-1 hash function.
+nsresult nsZipHandle::findDataStart()
+{
+ // In the CRX header, integers are 32 bits. Our pointer to the file is of
+ // type |uint8_t|, which is guaranteed to be 8 bits.
+ const uint32_t CRXIntSize = 4;
+
+MOZ_WIN_MEM_TRY_BEGIN
+ if (mTotalLen > CRXIntSize * 4 && xtolong(mFileStart) == kCRXMagic) {
+ const uint8_t* headerData = mFileStart;
+ headerData += CRXIntSize * 2; // Skip magic number and version number
+ uint32_t pubKeyLength = xtolong(headerData);
+ headerData += CRXIntSize;
+ uint32_t sigLength = xtolong(headerData);
+ uint32_t headerSize = CRXIntSize * 4 + pubKeyLength + sigLength;
+ if (mTotalLen > headerSize) {
+ mLen = mTotalLen - headerSize;
+ mFileData = mFileStart + headerSize;
+ return NS_OK;
+ }
+ }
+ mLen = mTotalLen;
+ mFileData = mFileStart;
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_OK;
+}
+
+int64_t nsZipHandle::SizeOfMapping()
+{
+ return mTotalLen;
+}
+
+nsresult nsZipHandle::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc)
+{
+ if (!aNSPRFileDesc) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ *aNSPRFileDesc = mNSPRFileDesc;
+ if (!mNSPRFileDesc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+nsZipHandle::~nsZipHandle()
+{
+ if (mMap) {
+ PR_MemUnmap((void *)mFileStart, mTotalLen);
+ PR_CloseFileMap(mMap);
+ }
+ mFileStart = nullptr;
+ mFileData = nullptr;
+ mMap = nullptr;
+ mBuf = nullptr;
+ MOZ_COUNT_DTOR(nsZipHandle);
+}
+
+//***********************************************************
+// nsZipArchive -- public methods
+//***********************************************************
+
+//---------------------------------------------
+// nsZipArchive::OpenArchive
+//---------------------------------------------
+nsresult nsZipArchive::OpenArchive(nsZipHandle *aZipHandle, PRFileDesc *aFd)
+{
+ mFd = aZipHandle;
+
+ // Initialize our arena
+ PL_INIT_ARENA_POOL(&mArena, "ZipArena", ZIP_ARENABLOCKSIZE);
+
+ //-- get table of contents for archive
+ nsresult rv = BuildFileList(aFd);
+ if (NS_SUCCEEDED(rv)) {
+ if (aZipHandle->mFile)
+ aZipHandle->mFile.GetURIString(mURI);
+ }
+ return rv;
+}
+
+nsresult nsZipArchive::OpenArchive(nsIFile *aFile)
+{
+ RefPtr<nsZipHandle> handle;
+#if defined(XP_WIN)
+ mozilla::AutoFDClose fd;
+ nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle),
+ &fd.rwget());
+#else
+ nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle));
+#endif
+ if (NS_FAILED(rv))
+ return rv;
+
+#if defined(XP_WIN)
+ return OpenArchive(handle, fd.get());
+#else
+ return OpenArchive(handle);
+#endif
+}
+
+//---------------------------------------------
+// nsZipArchive::Test
+//---------------------------------------------
+nsresult nsZipArchive::Test(const char *aEntryName)
+{
+ nsZipItem* currItem;
+
+ if (aEntryName) // only test specified item
+ {
+ currItem = GetItem(aEntryName);
+ if (!currItem)
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ //-- don't test (synthetic) directory items
+ if (currItem->IsDirectory())
+ return NS_OK;
+ return ExtractFile(currItem, 0, 0);
+ }
+
+ // test all items in archive
+ for (int i = 0; i < ZIP_TABSIZE; i++) {
+ for (currItem = mFiles[i]; currItem; currItem = currItem->next) {
+ //-- don't test (synthetic) directory items
+ if (currItem->IsDirectory())
+ continue;
+ nsresult rv = ExtractFile(currItem, 0, 0);
+ if (rv != NS_OK)
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//---------------------------------------------
+// nsZipArchive::CloseArchive
+//---------------------------------------------
+nsresult nsZipArchive::CloseArchive()
+{
+ if (mFd) {
+ PL_FinishArenaPool(&mArena);
+ mFd = nullptr;
+ }
+
+ // CAUTION:
+ // We don't need to delete each of the nsZipItem as the memory for
+ // the zip item and the filename it holds are both allocated from the Arena.
+ // Hence, destroying the Arena is like destroying all the memory
+ // for all the nsZipItem in one shot. But if the ~nsZipItem is doing
+ // anything more than cleaning up memory, we should start calling it.
+ // Let us also cleanup the mFiles table for re-use on the next 'open' call
+ memset(mFiles, 0, sizeof(mFiles));
+ mBuiltSynthetics = false;
+ return NS_OK;
+}
+
+//---------------------------------------------
+// nsZipArchive::GetItem
+//---------------------------------------------
+nsZipItem* nsZipArchive::GetItem(const char * aEntryName)
+{
+ if (aEntryName) {
+ uint32_t len = strlen(aEntryName);
+ //-- If the request is for a directory, make sure that synthetic entries
+ //-- are created for the directories without their own entry.
+ if (!mBuiltSynthetics) {
+ if ((len > 0) && (aEntryName[len-1] == '/')) {
+ if (BuildSynthetics() != NS_OK)
+ return 0;
+ }
+ }
+MOZ_WIN_MEM_TRY_BEGIN
+ nsZipItem* item = mFiles[ HashName(aEntryName, len) ];
+ while (item) {
+ if ((len == item->nameLength) &&
+ (!memcmp(aEntryName, item->Name(), len))) {
+
+ // Successful GetItem() is a good indicator that the file is about to be read
+ zipLog.Write(mURI, aEntryName);
+ return item; //-- found it
+ }
+ item = item->next;
+ }
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+ }
+ return nullptr;
+}
+
+//---------------------------------------------
+// nsZipArchive::ExtractFile
+// This extracts the item to the filehandle provided.
+// If 'aFd' is null, it only tests the extraction.
+// On extraction error(s) it removes the file.
+// When needed, it also resolves the symlink.
+//---------------------------------------------
+nsresult nsZipArchive::ExtractFile(nsZipItem *item, const char *outname,
+ PRFileDesc* aFd)
+{
+ if (!item)
+ return NS_ERROR_ILLEGAL_VALUE;
+ if (!mFd)
+ return NS_ERROR_FAILURE;
+
+ // Directory extraction is handled in nsJAR::Extract,
+ // so the item to be extracted should never be a directory
+ MOZ_ASSERT(!item->IsDirectory());
+
+ Bytef outbuf[ZIP_BUFLEN];
+
+ nsZipCursor cursor(item, this, outbuf, ZIP_BUFLEN, true);
+
+ nsresult rv = NS_OK;
+
+ while (true) {
+ uint32_t count = 0;
+ uint8_t* buf = cursor.Read(&count);
+ if (!buf) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: Read() failed to return a buffer";
+ rv = NS_ERROR_FILE_CORRUPTED;
+ break;
+ } else if (count == 0) {
+ break;
+ }
+
+ if (aFd && PR_Write(aFd, buf, count) < (READTYPE)count) {
+ rv = NS_ERROR_FILE_DISK_FULL;
+ break;
+ }
+ }
+
+ //-- delete the file on errors, or resolve symlink if needed
+ if (aFd) {
+ PR_Close(aFd);
+ if (rv != NS_OK)
+ PR_Delete(outname);
+#ifdef XP_UNIX
+ else if (item->IsSymlink())
+ rv = ResolveSymlink(outname);
+#endif
+ }
+
+ return rv;
+}
+
+//---------------------------------------------
+// nsZipArchive::FindInit
+//---------------------------------------------
+nsresult
+nsZipArchive::FindInit(const char * aPattern, nsZipFind **aFind)
+{
+ if (!aFind)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ // null out param in case an error happens
+ *aFind = nullptr;
+
+ bool regExp = false;
+ char* pattern = 0;
+
+ // Create synthetic directory entries on demand
+ nsresult rv = BuildSynthetics();
+ if (rv != NS_OK)
+ return rv;
+
+ // validate the pattern
+ if (aPattern)
+ {
+ switch (NS_WildCardValid((char*)aPattern))
+ {
+ case INVALID_SXP:
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ case NON_SXP:
+ regExp = false;
+ break;
+
+ case VALID_SXP:
+ regExp = true;
+ break;
+
+ default:
+ // undocumented return value from RegExpValid!
+ PR_ASSERT(false);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ pattern = PL_strdup(aPattern);
+ if (!pattern)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *aFind = new nsZipFind(this, pattern, regExp);
+ if (!*aFind) {
+ PL_strfree(pattern);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+
+
+//---------------------------------------------
+// nsZipFind::FindNext
+//---------------------------------------------
+nsresult nsZipFind::FindNext(const char ** aResult, uint16_t *aNameLen)
+{
+ if (!mArchive || !aResult || !aNameLen)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ *aResult = 0;
+ *aNameLen = 0;
+MOZ_WIN_MEM_TRY_BEGIN
+ // we start from last match, look for next
+ while (mSlot < ZIP_TABSIZE)
+ {
+ // move to next in current chain, or move to new slot
+ mItem = mItem ? mItem->next : mArchive->mFiles[mSlot];
+
+ bool found = false;
+ if (!mItem)
+ ++mSlot; // no more in this chain, move to next slot
+ else if (!mPattern)
+ found = true; // always match
+ else if (mRegExp)
+ {
+ char buf[kMaxNameLength+1];
+ memcpy(buf, mItem->Name(), mItem->nameLength);
+ buf[mItem->nameLength]='\0';
+ found = (NS_WildCardMatch(buf, mPattern, false) == MATCH);
+ }
+ else
+ found = ((mItem->nameLength == strlen(mPattern)) &&
+ (memcmp(mItem->Name(), mPattern, mItem->nameLength) == 0));
+ if (found) {
+ // Need also to return the name length, as it is NOT zero-terminatdd...
+ *aResult = mItem->Name();
+ *aNameLen = mItem->nameLength;
+ return NS_OK;
+ }
+ }
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+}
+
+#ifdef XP_UNIX
+//---------------------------------------------
+// ResolveSymlink
+//---------------------------------------------
+static nsresult ResolveSymlink(const char *path)
+{
+ PRFileDesc * fIn = PR_Open(path, PR_RDONLY, 0000);
+ if (!fIn)
+ return NS_ERROR_FILE_DISK_FULL;
+
+ char buf[PATH_MAX+1];
+ int32_t length = PR_Read(fIn, (void*)buf, PATH_MAX);
+ PR_Close(fIn);
+
+ if ( (length <= 0)
+ || ((buf[length] = 0, PR_Delete(path)) != 0)
+ || (symlink(buf, path) != 0))
+ {
+ return NS_ERROR_FILE_DISK_FULL;
+ }
+ return NS_OK;
+}
+#endif
+
+//***********************************************************
+// nsZipArchive -- private implementation
+//***********************************************************
+
+//---------------------------------------------
+// nsZipArchive::CreateZipItem
+//---------------------------------------------
+nsZipItem* nsZipArchive::CreateZipItem()
+{
+ // Arena allocate the nsZipItem
+ void *mem;
+ PL_ARENA_ALLOCATE(mem, &mArena, sizeof(nsZipItem));
+ return (nsZipItem*)mem;
+}
+
+//---------------------------------------------
+// nsZipArchive::BuildFileList
+//---------------------------------------------
+nsresult nsZipArchive::BuildFileList(PRFileDesc *aFd)
+{
+ // Get archive size using end pos
+ const uint8_t* buf;
+ const uint8_t* startp = mFd->mFileData;
+ const uint8_t* endp = startp + mFd->mLen;
+MOZ_WIN_MEM_TRY_BEGIN
+ uint32_t centralOffset = 4;
+ if (mFd->mLen > ZIPCENTRAL_SIZE && xtolong(startp + centralOffset) == CENTRALSIG) {
+ // Success means optimized jar layout from bug 559961 is in effect
+ uint32_t readaheadLength = xtolong(startp);
+ if (readaheadLength) {
+#if defined(XP_UNIX)
+ madvise(const_cast<uint8_t*>(startp), readaheadLength, MADV_WILLNEED);
+#elif defined(XP_WIN)
+ if (aFd) {
+ HANDLE hFile = (HANDLE) PR_FileDesc2NativeHandle(aFd);
+ mozilla::ReadAhead(hFile, 0, readaheadLength);
+ }
+#endif
+ }
+ } else {
+ for (buf = endp - ZIPEND_SIZE; buf > startp; buf--)
+ {
+ if (xtolong(buf) == ENDSIG) {
+ centralOffset = xtolong(((ZipEnd *)buf)->offset_central_dir);
+ break;
+ }
+ }
+ }
+
+ if (!centralOffset) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: no central offset";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ buf = startp + centralOffset;
+
+ // avoid overflow of startp + centralOffset.
+ if (buf < startp) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: overflow looking for central directory";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ //-- Read the central directory headers
+ uint32_t sig = 0;
+ while ((buf + int32_t(sizeof(uint32_t)) > buf) &&
+ (buf + int32_t(sizeof(uint32_t)) <= endp) &&
+ ((sig = xtolong(buf)) == CENTRALSIG)) {
+ // Make sure there is enough data available.
+ if ((buf > endp) || (endp - buf < ZIPCENTRAL_SIZE)) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: central directory too small";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Read the fixed-size data.
+ ZipCentral* central = (ZipCentral*)buf;
+
+ uint16_t namelen = xtoint(central->filename_len);
+ uint16_t extralen = xtoint(central->extrafield_len);
+ uint16_t commentlen = xtoint(central->commentfield_len);
+ uint32_t diff = ZIPCENTRAL_SIZE + namelen + extralen + commentlen;
+
+ // Sanity check variable sizes and refuse to deal with
+ // anything too big: it's likely a corrupt archive.
+ if (namelen < 1 ||
+ namelen > kMaxNameLength) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: namelen out of range";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ if (buf >= buf + diff || // No overflow
+ buf >= endp - diff) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: overflow looking for next item";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Point to the next item at the top of loop
+ buf += diff;
+
+ nsZipItem* item = CreateZipItem();
+ if (!item)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ item->central = central;
+ item->nameLength = namelen;
+ item->isSynthetic = false;
+
+ // Add item to file table
+ uint32_t hash = HashName(item->Name(), namelen);
+ item->next = mFiles[hash];
+ mFiles[hash] = item;
+
+ sig = 0;
+ } /* while reading central directory records */
+
+ if (sig != ENDSIG) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: unexpected sig";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Make the comment available for consumers.
+ if ((endp >= buf) && (endp - buf >= ZIPEND_SIZE)) {
+ ZipEnd *zipend = (ZipEnd *)buf;
+
+ buf += ZIPEND_SIZE;
+ uint16_t commentlen = xtoint(zipend->commentfield_len);
+ if (endp - buf >= commentlen) {
+ mCommentPtr = (const char *)buf;
+ mCommentLen = commentlen;
+ }
+ }
+
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_OK;
+}
+
+//---------------------------------------------
+// nsZipArchive::BuildSynthetics
+//---------------------------------------------
+nsresult nsZipArchive::BuildSynthetics()
+{
+ if (mBuiltSynthetics)
+ return NS_OK;
+ mBuiltSynthetics = true;
+
+MOZ_WIN_MEM_TRY_BEGIN
+ // Create synthetic entries for any missing directories.
+ // Do this when all ziptable has scanned to prevent double entries.
+ for (int i = 0; i < ZIP_TABSIZE; ++i)
+ {
+ for (nsZipItem* item = mFiles[i]; item != nullptr; item = item->next)
+ {
+ if (item->isSynthetic)
+ continue;
+
+ //-- add entries for directories in the current item's path
+ //-- go from end to beginning, because then we can stop trying
+ //-- to create diritems if we find that the diritem we want to
+ //-- create already exists
+ //-- start just before the last char so as to not add the item
+ //-- twice if it's a directory
+ uint16_t namelen = item->nameLength;
+ MOZ_ASSERT(namelen > 0, "Attempt to build synthetic for zero-length entry name!");
+ const char *name = item->Name();
+ for (uint16_t dirlen = namelen - 1; dirlen > 0; dirlen--)
+ {
+ if (name[dirlen-1] != '/')
+ continue;
+
+ // The character before this is '/', so if this is also '/' then we
+ // have an empty path component. Skip it.
+ if (name[dirlen] == '/')
+ continue;
+
+ // Is the directory already in the file table?
+ uint32_t hash = HashName(item->Name(), dirlen);
+ bool found = false;
+ for (nsZipItem* zi = mFiles[hash]; zi != nullptr; zi = zi->next)
+ {
+ if ((dirlen == zi->nameLength) &&
+ (0 == memcmp(item->Name(), zi->Name(), dirlen)))
+ {
+ // we've already added this dir and all its parents
+ found = true;
+ break;
+ }
+ }
+ // if the directory was found, break out of the directory
+ // creation loop now that we know all implicit directories
+ // are there -- otherwise, start creating the zip item
+ if (found)
+ break;
+
+ nsZipItem* diritem = CreateZipItem();
+ if (!diritem)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Point to the central record of the original item for the name part.
+ diritem->central = item->central;
+ diritem->nameLength = dirlen;
+ diritem->isSynthetic = true;
+
+ // add diritem to the file table
+ diritem->next = mFiles[hash];
+ mFiles[hash] = diritem;
+ } /* end processing of dirs in item's name */
+ }
+ }
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_OK;
+}
+
+nsZipHandle* nsZipArchive::GetFD()
+{
+ if (!mFd)
+ return nullptr;
+ return mFd.get();
+}
+
+//---------------------------------------------
+// nsZipArchive::GetDataOffset
+//---------------------------------------------
+uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem)
+{
+ MOZ_ASSERT(aItem);
+MOZ_WIN_MEM_TRY_BEGIN
+ //-- read local header to get variable length values and calculate
+ //-- the real data offset
+ uint32_t len = mFd->mLen;
+ const uint8_t* data = mFd->mFileData;
+ uint32_t offset = aItem->LocalOffset();
+ if (len < ZIPLOCAL_SIZE || offset > len - ZIPLOCAL_SIZE)
+ return 0;
+
+ // -- check signature before using the structure, in case the zip file is corrupt
+ ZipLocal* Local = (ZipLocal*)(data + offset);
+ if ((xtolong(Local->signature) != LOCALSIG))
+ return 0;
+
+ //-- NOTE: extralen is different in central header and local header
+ //-- for archives created using the Unix "zip" utility. To set
+ //-- the offset accurately we need the _local_ extralen.
+ offset += ZIPLOCAL_SIZE +
+ xtoint(Local->filename_len) +
+ xtoint(Local->extrafield_len);
+
+ return offset;
+MOZ_WIN_MEM_TRY_CATCH(return 0)
+}
+
+//---------------------------------------------
+// nsZipArchive::GetData
+//---------------------------------------------
+const uint8_t* nsZipArchive::GetData(nsZipItem* aItem)
+{
+ MOZ_ASSERT(aItem);
+MOZ_WIN_MEM_TRY_BEGIN
+ uint32_t offset = GetDataOffset(aItem);
+
+ // -- check if there is enough source data in the file
+ if (!offset ||
+ mFd->mLen < aItem->Size() ||
+ offset > mFd->mLen - aItem->Size() ||
+ (aItem->Compression() == STORED && aItem->Size() != aItem->RealSize())) {
+ return nullptr;
+ }
+
+ return mFd->mFileData + offset;
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+}
+
+// nsZipArchive::GetComment
+bool nsZipArchive::GetComment(nsACString &aComment)
+{
+MOZ_WIN_MEM_TRY_BEGIN
+ aComment.Assign(mCommentPtr, mCommentLen);
+MOZ_WIN_MEM_TRY_CATCH(return false)
+ return true;
+}
+
+//---------------------------------------------
+// nsZipArchive::SizeOfMapping
+//---------------------------------------------
+int64_t nsZipArchive::SizeOfMapping()
+{
+ return mFd ? mFd->SizeOfMapping() : 0;
+}
+
+//------------------------------------------
+// nsZipArchive constructor and destructor
+//------------------------------------------
+
+nsZipArchive::nsZipArchive()
+ : mRefCnt(0)
+ , mCommentPtr(nullptr)
+ , mCommentLen(0)
+ , mBuiltSynthetics(false)
+{
+ zipLog.AddRef();
+
+ MOZ_COUNT_CTOR(nsZipArchive);
+
+ // initialize the table to nullptr
+ memset(mFiles, 0, sizeof(mFiles));
+}
+
+NS_IMPL_ADDREF(nsZipArchive)
+NS_IMPL_RELEASE(nsZipArchive)
+
+nsZipArchive::~nsZipArchive()
+{
+ CloseArchive();
+
+ MOZ_COUNT_DTOR(nsZipArchive);
+
+ zipLog.Release();
+}
+
+
+//------------------------------------------
+// nsZipFind constructor and destructor
+//------------------------------------------
+
+nsZipFind::nsZipFind(nsZipArchive* aZip, char* aPattern, bool aRegExp)
+ : mArchive(aZip)
+ , mPattern(aPattern)
+ , mItem(nullptr)
+ , mSlot(0)
+ , mRegExp(aRegExp)
+{
+ MOZ_COUNT_CTOR(nsZipFind);
+}
+
+nsZipFind::~nsZipFind()
+{
+ PL_strfree(mPattern);
+
+ MOZ_COUNT_DTOR(nsZipFind);
+}
+
+//------------------------------------------
+// helper functions
+//------------------------------------------
+
+/*
+ * HashName
+ *
+ * returns a hash key for the entry name
+ */
+static uint32_t HashName(const char* aName, uint16_t len)
+{
+ MOZ_ASSERT(aName != 0);
+
+ const uint8_t* p = (const uint8_t*)aName;
+ const uint8_t* endp = p + len;
+ uint32_t val = 0;
+ while (p != endp) {
+ val = val*37 + *p++;
+ }
+
+ return (val % ZIP_TABSIZE);
+}
+
+/*
+ * x t o i n t
+ *
+ * Converts a two byte ugly endianed integer
+ * to our platform's integer.
+ */
+static uint16_t xtoint (const uint8_t *ii)
+{
+ return (uint16_t) ((ii [0]) | (ii [1] << 8));
+}
+
+/*
+ * x t o l o n g
+ *
+ * Converts a four byte ugly endianed integer
+ * to our platform's integer.
+ */
+static uint32_t xtolong (const uint8_t *ll)
+{
+ return (uint32_t)( (ll [0] << 0) |
+ (ll [1] << 8) |
+ (ll [2] << 16) |
+ (ll [3] << 24) );
+}
+
+/*
+ * GetModTime
+ *
+ * returns last modification time in microseconds
+ */
+static PRTime GetModTime(uint16_t aDate, uint16_t aTime)
+{
+ // Note that on DST shift we can't handle correctly the hour that is valid
+ // in both DST zones
+ PRExplodedTime time;
+
+ time.tm_usec = 0;
+
+ time.tm_hour = (aTime >> 11) & 0x1F;
+ time.tm_min = (aTime >> 5) & 0x3F;
+ time.tm_sec = (aTime & 0x1F) * 2;
+
+ time.tm_year = (aDate >> 9) + 1980;
+ time.tm_month = ((aDate >> 5) & 0x0F) - 1;
+ time.tm_mday = aDate & 0x1F;
+
+ time.tm_params.tp_gmt_offset = 0;
+ time.tm_params.tp_dst_offset = 0;
+
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset;
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset;
+
+ return PR_ImplodeTime(&time);
+}
+
+nsZipItem::nsZipItem()
+ : next(nullptr)
+ , central(nullptr)
+ , nameLength(0)
+ , isSynthetic(false)
+{}
+
+uint32_t nsZipItem::LocalOffset()
+{
+ return xtolong(central->localhdr_offset);
+}
+
+uint32_t nsZipItem::Size()
+{
+ return isSynthetic ? 0 : xtolong(central->size);
+}
+
+uint32_t nsZipItem::RealSize()
+{
+ return isSynthetic ? 0 : xtolong(central->orglen);
+}
+
+uint32_t nsZipItem::CRC32()
+{
+ return isSynthetic ? 0 : xtolong(central->crc32);
+}
+
+uint16_t nsZipItem::Date()
+{
+ return isSynthetic ? kSyntheticDate : xtoint(central->date);
+}
+
+uint16_t nsZipItem::Time()
+{
+ return isSynthetic ? kSyntheticTime : xtoint(central->time);
+}
+
+uint16_t nsZipItem::Compression()
+{
+ return isSynthetic ? STORED : xtoint(central->method);
+}
+
+bool nsZipItem::IsDirectory()
+{
+ return isSynthetic || ((nameLength > 0) && ('/' == Name()[nameLength - 1]));
+}
+
+uint16_t nsZipItem::Mode()
+{
+ if (isSynthetic) return 0755;
+ return ((uint16_t)(central->external_attributes[2]) | 0x100);
+}
+
+const uint8_t * nsZipItem::GetExtraField(uint16_t aTag, uint16_t *aBlockSize)
+{
+ if (isSynthetic) return nullptr;
+MOZ_WIN_MEM_TRY_BEGIN
+ const unsigned char *buf = ((const unsigned char*)central) + ZIPCENTRAL_SIZE +
+ nameLength;
+ uint32_t buflen = (uint32_t)xtoint(central->extrafield_len);
+ uint32_t pos = 0;
+ uint16_t tag, blocksize;
+
+ while (buf && (pos + 4) <= buflen) {
+ tag = xtoint(buf + pos);
+ blocksize = xtoint(buf + pos + 2);
+
+ if (aTag == tag && (pos + 4 + blocksize) <= buflen) {
+ *aBlockSize = blocksize;
+ return buf + pos;
+ }
+
+ pos += blocksize + 4;
+ }
+
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+ return nullptr;
+}
+
+
+PRTime nsZipItem::LastModTime()
+{
+ if (isSynthetic) return GetModTime(kSyntheticDate, kSyntheticTime);
+
+ // Try to read timestamp from extra field
+ uint16_t blocksize;
+ const uint8_t *tsField = GetExtraField(EXTENDED_TIMESTAMP_FIELD, &blocksize);
+ if (tsField && blocksize >= 5 && tsField[4] & EXTENDED_TIMESTAMP_MODTIME) {
+ return (PRTime)(xtolong(tsField + 5)) * PR_USEC_PER_SEC;
+ }
+
+ return GetModTime(Date(), Time());
+}
+
+#ifdef XP_UNIX
+bool nsZipItem::IsSymlink()
+{
+ if (isSynthetic) return false;
+ return (xtoint(central->external_attributes+2) & S_IFMT) == S_IFLNK;
+}
+#endif
+
+nsZipCursor::nsZipCursor(nsZipItem *item, nsZipArchive *aZip, uint8_t* aBuf,
+ uint32_t aBufSize, bool doCRC)
+ : mItem(item)
+ , mBuf(aBuf)
+ , mBufSize(aBufSize)
+ , mCRC(0)
+ , mDoCRC(doCRC)
+{
+ if (mItem->Compression() == DEFLATED) {
+#ifdef DEBUG
+ nsresult status =
+#endif
+ gZlibInit(&mZs);
+ NS_ASSERTION(status == NS_OK, "Zlib failed to initialize");
+ NS_ASSERTION(aBuf, "Must pass in a buffer for DEFLATED nsZipItem");
+ }
+
+ mZs.avail_in = item->Size();
+ mZs.next_in = (Bytef*)aZip->GetData(item);
+
+ if (doCRC)
+ mCRC = crc32(0L, Z_NULL, 0);
+}
+
+nsZipCursor::~nsZipCursor()
+{
+ if (mItem->Compression() == DEFLATED) {
+ inflateEnd(&mZs);
+ }
+}
+
+uint8_t* nsZipCursor::ReadOrCopy(uint32_t *aBytesRead, bool aCopy) {
+ int zerr;
+ uint8_t *buf = nullptr;
+ bool verifyCRC = true;
+
+ if (!mZs.next_in)
+ return nullptr;
+MOZ_WIN_MEM_TRY_BEGIN
+ switch (mItem->Compression()) {
+ case STORED:
+ if (!aCopy) {
+ *aBytesRead = mZs.avail_in;
+ buf = mZs.next_in;
+ mZs.next_in += mZs.avail_in;
+ mZs.avail_in = 0;
+ } else {
+ *aBytesRead = mZs.avail_in > mBufSize ? mBufSize : mZs.avail_in;
+ memcpy(mBuf, mZs.next_in, *aBytesRead);
+ mZs.avail_in -= *aBytesRead;
+ mZs.next_in += *aBytesRead;
+ }
+ break;
+ case DEFLATED:
+ buf = mBuf;
+ mZs.next_out = buf;
+ mZs.avail_out = mBufSize;
+
+ zerr = inflate(&mZs, Z_PARTIAL_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END)
+ return nullptr;
+
+ *aBytesRead = mZs.next_out - buf;
+ verifyCRC = (zerr == Z_STREAM_END);
+ break;
+ default:
+ return nullptr;
+ }
+
+ if (mDoCRC) {
+ mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead);
+ if (verifyCRC && mCRC != mItem->CRC32())
+ return nullptr;
+ }
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+ return buf;
+}
+
+nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive *aZip,
+ const char * aEntryName, bool doCRC)
+ : mReturnBuf(nullptr)
+ , mReadlen(0)
+{
+ // make sure the ziparchive hangs around
+ mZipHandle = aZip->GetFD();
+
+ nsZipItem* item = aZip->GetItem(aEntryName);
+ if (!item)
+ return;
+
+ uint32_t size = 0;
+ if (item->Compression() == DEFLATED) {
+ size = item->RealSize();
+ mAutoBuf = MakeUniqueFallible<uint8_t[]>(size);
+ if (!mAutoBuf) {
+ return;
+ }
+ }
+
+ nsZipCursor cursor(item, aZip, mAutoBuf.get(), size, doCRC);
+ mReturnBuf = cursor.Read(&mReadlen);
+ if (!mReturnBuf) {
+ return;
+ }
+
+ if (mReadlen != item->RealSize()) {
+ NS_ASSERTION(mReadlen == item->RealSize(), "nsZipCursor underflow");
+ mReturnBuf = nullptr;
+ return;
+ }
+}
+
+/* static */ const char*
+nsZipArchive::sFileCorruptedReason = nullptr;
diff --git a/modules/libjar/nsZipArchive.h b/modules/libjar/nsZipArchive.h
new file mode 100644
index 0000000000..2de6790327
--- /dev/null
+++ b/modules/libjar/nsZipArchive.h
@@ -0,0 +1,436 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsZipArchive_h_
+#define nsZipArchive_h_
+
+#include "mozilla/Attributes.h"
+
+#define ZIP_TABSIZE 256
+#define ZIP_BUFLEN (4*1024) /* Used as output buffer when deflating items to a file */
+
+#include "plarena.h"
+#include "zlib.h"
+#include "zipstruct.h"
+#include "nsAutoPtr.h"
+#include "nsIFile.h"
+#include "nsISupportsImpl.h" // For mozilla::ThreadSafeAutoRefCnt
+#include "mozilla/FileUtils.h"
+#include "mozilla/FileLocation.h"
+#include "mozilla/UniquePtr.h"
+
+#ifdef HAVE_SEH_EXCEPTIONS
+#define MOZ_WIN_MEM_TRY_BEGIN __try {
+#define MOZ_WIN_MEM_TRY_CATCH(cmd) } \
+ __except(GetExceptionCode()==EXCEPTION_IN_PAGE_ERROR ? \
+ EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) \
+ { \
+ NS_WARNING("unexpected EXCEPTION_IN_PAGE_ERROR"); \
+ cmd; \
+ }
+#else
+#define MOZ_WIN_MEM_TRY_BEGIN {
+#define MOZ_WIN_MEM_TRY_CATCH(cmd) }
+#endif
+
+class nsZipFind;
+struct PRFileDesc;
+
+/**
+ * This file defines some of the basic structures used by libjar to
+ * read Zip files. It makes use of zlib in order to do the decompression.
+ *
+ * A few notes on the classes/structs:
+ * nsZipArchive represents a single Zip file, and maintains an index
+ * of all the items in the file.
+ * nsZipItem represents a single item (file) in the Zip archive.
+ * nsZipFind represents the metadata involved in doing a search,
+ * and current state of the iteration of found objects.
+ * 'MT''safe' reading from the zipfile is performed through JARInputStream,
+ * which maintains its own file descriptor, allowing for multiple reads
+ * concurrently from the same zip file.
+ */
+
+/**
+ * nsZipItem -- a helper struct for nsZipArchive
+ *
+ * each nsZipItem represents one file in the archive and all the
+ * information needed to manipulate it.
+ */
+class nsZipItem final
+{
+public:
+ nsZipItem();
+
+ const char* Name() { return ((const char*)central) + ZIPCENTRAL_SIZE; }
+
+ uint32_t LocalOffset();
+ uint32_t Size();
+ uint32_t RealSize();
+ uint32_t CRC32();
+ uint16_t Date();
+ uint16_t Time();
+ uint16_t Compression();
+ bool IsDirectory();
+ uint16_t Mode();
+ const uint8_t* GetExtraField(uint16_t aTag, uint16_t *aBlockSize);
+ PRTime LastModTime();
+
+#ifdef XP_UNIX
+ bool IsSymlink();
+#endif
+
+ nsZipItem* next;
+ const ZipCentral* central;
+ uint16_t nameLength;
+ bool isSynthetic;
+};
+
+class nsZipHandle;
+
+/**
+ * nsZipArchive -- a class for reading the PKZIP file format.
+ *
+ */
+class nsZipArchive final
+{
+ friend class nsZipFind;
+
+ /** destructing the object closes the archive */
+ ~nsZipArchive();
+
+public:
+ static const char* sFileCorruptedReason;
+
+ /** constructing does not open the archive. See OpenArchive() */
+ nsZipArchive();
+
+ /**
+ * OpenArchive
+ *
+ * It's an error to call this more than once on the same nsZipArchive
+ * object. If we were allowed to use exceptions this would have been
+ * part of the constructor
+ *
+ * @param aZipHandle The nsZipHandle used to access the zip
+ * @param aFd Optional PRFileDesc for Windows readahead optimization
+ * @return status code
+ */
+ nsresult OpenArchive(nsZipHandle *aZipHandle, PRFileDesc *aFd = nullptr);
+
+ /**
+ * OpenArchive
+ *
+ * Convenience function that generates nsZipHandle
+ *
+ * @param aFile The file used to access the zip
+ * @return status code
+ */
+ nsresult OpenArchive(nsIFile *aFile);
+
+ /**
+ * Test the integrity of items in this archive by running
+ * a CRC check after extracting each item into a memory
+ * buffer. If an entry name is supplied only the
+ * specified item is tested. Else, if null is supplied
+ * then all the items in the archive are tested.
+ *
+ * @return status code
+ */
+ nsresult Test(const char *aEntryName);
+
+ /**
+ * Closes an open archive.
+ */
+ nsresult CloseArchive();
+
+ /**
+ * GetItem
+ * @param aEntryName Name of file in the archive
+ * @return pointer to nsZipItem
+ */
+ nsZipItem* GetItem(const char * aEntryName);
+
+ /**
+ * ExtractFile
+ *
+ * @param zipEntry Name of file in archive to extract
+ * @param outFD Filedescriptor to write contents to
+ * @param outname Name of file to write to
+ * @return status code
+ */
+ nsresult ExtractFile(nsZipItem * zipEntry, const char *outname, PRFileDesc * outFD);
+
+ /**
+ * FindInit
+ *
+ * Initializes a search for files in the archive. FindNext() returns
+ * the actual matches. The nsZipFind must be deleted when you're done
+ *
+ * @param aPattern a string or RegExp pattern to search for
+ * (may be nullptr to find all files in archive)
+ * @param aFind a pointer to a pointer to a structure used
+ * in FindNext. In the case of an error this
+ * will be set to nullptr.
+ * @return status code
+ */
+ nsresult FindInit(const char * aPattern, nsZipFind** aFind);
+
+ /*
+ * Gets an undependent handle to the mapped file.
+ */
+ nsZipHandle* GetFD();
+
+ /**
+ * Gets the data offset.
+ * @param aItem Pointer to nsZipItem
+ * returns 0 on failure.
+ */
+ uint32_t GetDataOffset(nsZipItem* aItem);
+
+ /**
+ * Get pointer to the data of the item.
+ * @param aItem Pointer to nsZipItem
+ * reutrns null when zip file is corrupt.
+ */
+ const uint8_t* GetData(nsZipItem* aItem);
+
+ bool GetComment(nsACString &aComment);
+
+ /**
+ * Gets the amount of memory taken up by the archive's mapping.
+ * @return the size
+ */
+ int64_t SizeOfMapping();
+
+ /*
+ * Refcounting
+ */
+ NS_METHOD_(MozExternalRefCountType) AddRef(void);
+ NS_METHOD_(MozExternalRefCountType) Release(void);
+
+private:
+ //--- private members ---
+ mozilla::ThreadSafeAutoRefCnt mRefCnt; /* ref count */
+ NS_DECL_OWNINGTHREAD
+
+ nsZipItem* mFiles[ZIP_TABSIZE];
+ PLArenaPool mArena;
+
+ const char* mCommentPtr;
+ uint16_t mCommentLen;
+
+ // Whether we synthesized the directory entries
+ bool mBuiltSynthetics;
+
+ // file handle
+ RefPtr<nsZipHandle> mFd;
+
+ // file URI, for logging
+ nsCString mURI;
+
+private:
+ //--- private methods ---
+ nsZipItem* CreateZipItem();
+ nsresult BuildFileList(PRFileDesc *aFd = nullptr);
+ nsresult BuildSynthetics();
+
+ nsZipArchive& operator=(const nsZipArchive& rhs) = delete;
+ nsZipArchive(const nsZipArchive& rhs) = delete;
+};
+
+/**
+ * nsZipFind
+ *
+ * a helper class for nsZipArchive, representing a search
+ */
+class nsZipFind final
+{
+public:
+ nsZipFind(nsZipArchive* aZip, char* aPattern, bool regExp);
+ ~nsZipFind();
+
+ nsresult FindNext(const char** aResult, uint16_t* aNameLen);
+
+private:
+ RefPtr<nsZipArchive> mArchive;
+ char* mPattern;
+ nsZipItem* mItem;
+ uint16_t mSlot;
+ bool mRegExp;
+
+ nsZipFind& operator=(const nsZipFind& rhs) = delete;
+ nsZipFind(const nsZipFind& rhs) = delete;
+};
+
+/**
+ * nsZipCursor -- a low-level class for reading the individual items in a zip.
+ */
+class nsZipCursor final
+{
+public:
+ /**
+ * Initializes the cursor
+ *
+ * @param aItem Item of interest
+ * @param aZip Archive
+ * @param aBuf Buffer used for decompression.
+ * This determines the maximum Read() size in the compressed case.
+ * @param aBufSize Buffer size
+ * @param doCRC When set to true Read() will check crc
+ */
+ nsZipCursor(nsZipItem *aItem, nsZipArchive *aZip, uint8_t* aBuf = nullptr, uint32_t aBufSize = 0, bool doCRC = false);
+
+ ~nsZipCursor();
+
+ /**
+ * Performs reads. In the compressed case it uses aBuf(passed in constructor), for stored files
+ * it returns a zero-copy buffer.
+ *
+ * @param aBytesRead Outparam for number of bytes read.
+ * @return data read or nullptr if item is corrupted.
+ */
+ uint8_t* Read(uint32_t *aBytesRead) {
+ return ReadOrCopy(aBytesRead, false);
+ }
+
+ /**
+ * Performs a copy. It always uses aBuf(passed in constructor).
+ *
+ * @param aBytesRead Outparam for number of bytes read.
+ * @return data read or nullptr if item is corrupted.
+ */
+ uint8_t* Copy(uint32_t *aBytesRead) {
+ return ReadOrCopy(aBytesRead, true);
+ }
+
+private:
+ /* Actual implementation for both Read and Copy above */
+ uint8_t* ReadOrCopy(uint32_t *aBytesRead, bool aCopy);
+
+ nsZipItem *mItem;
+ uint8_t *mBuf;
+ uint32_t mBufSize;
+ z_stream mZs;
+ uint32_t mCRC;
+ bool mDoCRC;
+};
+
+/**
+ * nsZipItemPtr - a RAII convenience class for reading the individual items in a zip.
+ * It reads whole files and does zero-copy IO for stored files. A buffer is allocated
+ * for decompression.
+ * Do not use when the file may be very large.
+ */
+class nsZipItemPtr_base
+{
+public:
+ /**
+ * Initializes the reader
+ *
+ * @param aZip Archive
+ * @param aEntryName Archive membername
+ * @param doCRC When set to true Read() will check crc
+ */
+ nsZipItemPtr_base(nsZipArchive *aZip, const char *aEntryName, bool doCRC);
+
+ uint32_t Length() const {
+ return mReadlen;
+ }
+
+protected:
+ RefPtr<nsZipHandle> mZipHandle;
+ mozilla::UniquePtr<uint8_t[]> mAutoBuf;
+ uint8_t *mReturnBuf;
+ uint32_t mReadlen;
+};
+
+template <class T>
+class nsZipItemPtr final : public nsZipItemPtr_base
+{
+ static_assert(sizeof(T) == sizeof(char),
+ "This class cannot be used with larger T without re-examining"
+ " a number of assumptions.");
+
+public:
+ nsZipItemPtr(nsZipArchive *aZip, const char *aEntryName, bool doCRC = false) : nsZipItemPtr_base(aZip, aEntryName, doCRC) { }
+ /**
+ * @return buffer containing the whole zip member or nullptr on error.
+ * The returned buffer is owned by nsZipItemReader.
+ */
+ const T* Buffer() const {
+ return (const T*)mReturnBuf;
+ }
+
+ operator const T*() const {
+ return Buffer();
+ }
+
+ /**
+ * Relinquish ownership of zip member if compressed.
+ * Copy member into a new buffer if uncompressed.
+ * @return a buffer with whole zip member. It is caller's responsibility to free() it.
+ */
+ mozilla::UniquePtr<T[]> Forget() {
+ if (!mReturnBuf)
+ return nullptr;
+ // In uncompressed mmap case, give up buffer
+ if (mAutoBuf.get() == mReturnBuf) {
+ mReturnBuf = nullptr;
+ return mozilla::UniquePtr<T[]>(reinterpret_cast<T*>(mAutoBuf.release()));
+ }
+ auto ret = mozilla::MakeUnique<T[]>(Length());
+ memcpy(ret.get(), mReturnBuf, Length());
+ mReturnBuf = nullptr;
+ return ret;
+ }
+};
+
+class nsZipHandle final
+{
+friend class nsZipArchive;
+friend class mozilla::FileLocation;
+public:
+ static nsresult Init(nsIFile *file, nsZipHandle **ret,
+ PRFileDesc **aFd = nullptr);
+ static nsresult Init(nsZipArchive *zip, const char *entry,
+ nsZipHandle **ret);
+ static nsresult Init(const uint8_t* aData, uint32_t aLen,
+ nsZipHandle **aRet);
+
+ NS_METHOD_(MozExternalRefCountType) AddRef(void);
+ NS_METHOD_(MozExternalRefCountType) Release(void);
+
+ int64_t SizeOfMapping();
+
+ nsresult GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc);
+
+protected:
+ const uint8_t * mFileData; /* pointer to zip data */
+ uint32_t mLen; /* length of zip data */
+ mozilla::FileLocation mFile; /* source file if any, for logging */
+
+private:
+ nsZipHandle();
+ ~nsZipHandle();
+
+ nsresult findDataStart();
+
+ PRFileMap * mMap; /* nspr datastructure for mmap */
+ mozilla::AutoFDClose mNSPRFileDesc;
+ nsAutoPtr<nsZipItemPtr<uint8_t> > mBuf;
+ mozilla::ThreadSafeAutoRefCnt mRefCnt; /* ref count */
+ NS_DECL_OWNINGTHREAD
+
+ const uint8_t * mFileStart; /* pointer to mmaped file */
+ uint32_t mTotalLen; /* total length of the mmaped file */
+
+ /* Magic number for CRX type expressed in Big Endian since it is a literal */
+ static const uint32_t kCRXMagic = 0x34327243;
+};
+
+nsresult gZlibInit(z_stream *zs);
+
+#endif /* nsZipArchive_h_ */
diff --git a/modules/libjar/test/chrome/chrome.ini b/modules/libjar/test/chrome/chrome.ini
new file mode 100644
index 0000000000..b130cbc337
--- /dev/null
+++ b/modules/libjar/test/chrome/chrome.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ signed-added.zip
+ signed-badca.zip
+ signed-tampered.zip
+ signed.zip
+ unsigned.zip
+
+[test_bug386153.html]
diff --git a/modules/libjar/test/chrome/signed-added.zip b/modules/libjar/test/chrome/signed-added.zip
new file mode 100644
index 0000000000..c90bc250a7
--- /dev/null
+++ b/modules/libjar/test/chrome/signed-added.zip
Binary files differ
diff --git a/modules/libjar/test/chrome/signed-badca.zip b/modules/libjar/test/chrome/signed-badca.zip
new file mode 100644
index 0000000000..f493cbd1a7
--- /dev/null
+++ b/modules/libjar/test/chrome/signed-badca.zip
Binary files differ
diff --git a/modules/libjar/test/chrome/signed-tampered.zip b/modules/libjar/test/chrome/signed-tampered.zip
new file mode 100644
index 0000000000..d3b9d16cc6
--- /dev/null
+++ b/modules/libjar/test/chrome/signed-tampered.zip
Binary files differ
diff --git a/modules/libjar/test/chrome/signed.zip b/modules/libjar/test/chrome/signed.zip
new file mode 100644
index 0000000000..94e1147837
--- /dev/null
+++ b/modules/libjar/test/chrome/signed.zip
Binary files differ
diff --git a/modules/libjar/test/chrome/test_bug386153.html b/modules/libjar/test/chrome/test_bug386153.html
new file mode 100644
index 0000000000..3fe541a1a8
--- /dev/null
+++ b/modules/libjar/test/chrome/test_bug386153.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386153
+-->
+<head>
+ <title>Test for Bug 386153</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=386153">Mozilla Bug 386153</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 386153 **/
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// Opens a zip file from the test directory.
+function openZip(path) {
+
+ var location = window.location.href;
+ location = getRootDirectory(location);
+ var jar = getJar(location);
+ if (jar != null) {
+ var resolved = extractJarToTmp(jar);
+ } else {
+ var resolvedURI = getResolvedURI(window.location.href);
+ var resolved = getChromeDir(resolvedURI);
+ }
+ resolved.append(path);
+
+ var zip = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zip.open(resolved);
+ return zip;
+}
+
+// Gets the pretty name from the signing cert or null if the zip is unsigned.
+function getSigner(zip) {
+ var signingCert = zip.getSigningCert(null);
+ if (signingCert) {
+ return signingCert.organization;
+ }
+ return null;
+}
+
+function verifySigning(zip) {
+ var signingCert = zip.getSigningCert(null);
+ var count = 0;
+ var entries = zip.findEntries(null);
+ while (entries.hasMore()) {
+ var entry = entries.getNext();
+ // Nothing in META-INF is in the manifest.
+ if (entry.substr(0, 9) == "META-INF/")
+ continue;
+ // Directory entries aren't in the manifest.
+ if (entry.substr(-1) == "/")
+ continue;
+ count++;
+ var entryCert = zip.getSigningCert(entry);
+ if (!entryCert || !signingCert.equals(entryCert)) {
+ return false;
+ }
+ }
+ return zip.manifestEntriesCount == count;
+}
+
+var zip = openZip("unsigned.zip");
+is(getSigner(zip), null, "Should not be signed");
+
+zip = openZip("signed.zip");
+is(getSigner(zip), "Mozilla Testing", "Should be signed by the right cert");
+ok(verifySigning(zip), "Should be correctly signed");
+
+zip = openZip("signed-added.zip");
+is(getSigner(zip), "Mozilla Testing", "Should be signed by the right cert");
+ok(!verifySigning(zip), "Should be incorrectly signed");
+
+zip = openZip("signed-tampered.zip");
+is(getSigner(zip), "Mozilla Testing", "Should be signed by the right cert");
+ok(!verifySigning(zip), "Should be incorrectly signed");
+
+zip = openZip("signed-badca.zip");
+is(getSigner(zip), null, "Should not appear to be signed");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/modules/libjar/test/chrome/unsigned.zip b/modules/libjar/test/chrome/unsigned.zip
new file mode 100644
index 0000000000..f1e26b06a6
--- /dev/null
+++ b/modules/libjar/test/chrome/unsigned.zip
Binary files differ
diff --git a/modules/libjar/test/mochitest/bug403331.zip b/modules/libjar/test/mochitest/bug403331.zip
new file mode 100644
index 0000000000..48ba268ddf
--- /dev/null
+++ b/modules/libjar/test/mochitest/bug403331.zip
Binary files differ
diff --git a/modules/libjar/test/mochitest/bug403331.zip^headers^ b/modules/libjar/test/mochitest/bug403331.zip^headers^
new file mode 100644
index 0000000000..28b8aa0a57
--- /dev/null
+++ b/modules/libjar/test/mochitest/bug403331.zip^headers^
@@ -0,0 +1 @@
+Content-Type: application/java-archive
diff --git a/modules/libjar/test/mochitest/mochitest.ini b/modules/libjar/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..0ae466ccab
--- /dev/null
+++ b/modules/libjar/test/mochitest/mochitest.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ bug403331.zip
+ bug403331.zip^headers^
+ openredirect.sjs
+ !/dom/base/test/file_bug945152.jar
+
+[test_bug403331.html]
+[test_bug1034143_mapped.html]
+run-if = os == 'linux'
+[test_bug1173171.html] \ No newline at end of file
diff --git a/modules/libjar/test/mochitest/openredirect.sjs b/modules/libjar/test/mochitest/openredirect.sjs
new file mode 100644
index 0000000000..b6249cadff
--- /dev/null
+++ b/modules/libjar/test/mochitest/openredirect.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response)
+{
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", request.queryString, false);
+}
diff --git a/modules/libjar/test/mochitest/test_bug1034143_mapped.html b/modules/libjar/test/mochitest/test_bug1034143_mapped.html
new file mode 100644
index 0000000000..d65ae31f28
--- /dev/null
+++ b/modules/libjar/test/mochitest/test_bug1034143_mapped.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1034143
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 945152</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034143">Mozilla Bug 1034143</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+// Ensure that XMLHttpRequest's memory-mapping code can handle a case
+// where the nsIJARChannel's jarFile property is null, but which is
+// otherwise eligible for bug 945152's memory-mapping optimization.
+
+function runTest() {
+ const jarURL = "jar:http://example.org/tests/dom/base/test/file_bug945152.jar!/data_1.txt";
+ let xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true });
+ xhr.open("GET", jarURL);
+ xhr.onerror = function onerror(e) {
+ ok(false, "JAR XHR failed: " + e.status);
+ SimpleTest.finish();
+ };
+ xhr.onload = function onload(e) {
+ ok(xhr.status == 200, "Status is 200");
+ let ct = xhr.getResponseHeader("Content-Type");
+ ok(ct.indexOf("mem-mapped") == -1, "Data is not memory-mapped");
+ SimpleTest.finish();
+ };
+ xhr.responseType = 'arraybuffer';
+ xhr.send();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.mapped_arraybuffer.enabled", true],
+ ["network.jar.block-remote-files", false]]}, function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest);
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/modules/libjar/test/mochitest/test_bug1173171.html b/modules/libjar/test/mochitest/test_bug1173171.html
new file mode 100644
index 0000000000..9fed630b31
--- /dev/null
+++ b/modules/libjar/test/mochitest/test_bug1173171.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1173171
+-->
+<head>
+ <title>Test for Bug 1173171</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<iframe id="testFrame"></iframe>
+
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+/** Test for Bug 1173171 **/
+
+// __setPref(key, value)__.
+// Set a pref value asynchronously, returning a prmoise that resolves
+// when it succeeds.
+let pushPref = function (key, value) {
+ return new Promise(function(resolve, reject) {
+ SpecialPowers.pushPrefEnv({"set": [[key, value]]}, resolve);
+ });
+};
+
+// __xhr(method, url, responseType__.
+// A simple async XMLHttpRequest call.
+// Returns a promise with the response.
+let xhr = function (method, url, responseType) {
+ return new Promise(function (resolve, reject) {
+ let xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ xhr.onload = function () {
+ resolve(xhr.response);
+ };
+ xhr.onerror = function() {
+ resolve(null);
+ };
+ xhr.responseType = responseType;
+ xhr.send();
+ });
+};
+
+let jarURL = "jar:http://mochi.test:8888/tests/modules/libjar/test/mochitest/bug403331.zip!/test.html";
+
+// Test behavior when blocking is deactivated and activated.
+add_task(function* () {
+ for (let shouldBlock of [false, true]) {
+ yield pushPref("network.jar.block-remote-files", shouldBlock);
+ let response = yield xhr("GET", jarURL, "document");
+ ok(shouldBlock === (response === null),
+ "Remote jars should be blocked if and only if the 'network.jar.block-remote-files' pref is active.");
+ }
+});
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/modules/libjar/test/mochitest/test_bug403331.html b/modules/libjar/test/mochitest/test_bug403331.html
new file mode 100644
index 0000000000..b292c4c9ba
--- /dev/null
+++ b/modules/libjar/test/mochitest/test_bug403331.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403331
+-->
+<head>
+ <title>Test for Bug 403331</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<iframe id="testFrame"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 403331 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var testFrame = document.getElementById('testFrame');
+
+ // Try a redirected load from another domain to this one.
+
+ testFrame.onload = function() {
+ // If properly redirected, we'll be able to access elements in the loaded
+ // document.
+ var item = testFrame.contentDocument.getElementById('testitem');
+ is(item.textContent, "testcontents", "Should be able to access the child document");
+
+ SimpleTest.finish();
+ }
+
+ testFrame.src = "jar:http://example.org:80/tests/modules/libjar/test/mochitest/openredirect.sjs?http://mochi.test:8888/tests/modules/libjar/test/mochitest/bug403331.zip!/test.html";
+}
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, runTest);
+});
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/modules/libjar/test/unit/data/empty b/modules/libjar/test/unit/data/empty
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/libjar/test/unit/data/empty
diff --git a/modules/libjar/test/unit/data/test_bug333423.zip b/modules/libjar/test/unit/data/test_bug333423.zip
new file mode 100644
index 0000000000..42662a4085
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug333423.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_bug336691.zip b/modules/libjar/test/unit/data/test_bug336691.zip
new file mode 100644
index 0000000000..16bd2d6f2c
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug336691.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_bug370103.jar b/modules/libjar/test/unit/data/test_bug370103.jar
new file mode 100644
index 0000000000..7723568bad
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug370103.jar
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_bug379841.zip b/modules/libjar/test/unit/data/test_bug379841.zip
new file mode 100644
index 0000000000..f3c5205197
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug379841.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_bug589292.zip b/modules/libjar/test/unit/data/test_bug589292.zip
new file mode 100644
index 0000000000..b7b9e65b47
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug589292.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_bug597702.zip b/modules/libjar/test/unit/data/test_bug597702.zip
new file mode 100644
index 0000000000..55ce8fff42
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug597702.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_bug637286.zip b/modules/libjar/test/unit/data/test_bug637286.zip
new file mode 100644
index 0000000000..5dc8e747ea
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug637286.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_bug658093.zip b/modules/libjar/test/unit/data/test_bug658093.zip
new file mode 100644
index 0000000000..d32180101a
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_bug658093.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_corrupt.zip b/modules/libjar/test/unit/data/test_corrupt.zip
new file mode 100644
index 0000000000..d7f5f42f93
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_corrupt.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_corrupt2.zip b/modules/libjar/test/unit/data/test_corrupt2.zip
new file mode 100644
index 0000000000..0cfbf08886
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_corrupt2.zip
@@ -0,0 +1 @@
+2
diff --git a/modules/libjar/test/unit/data/test_corrupt3.zip b/modules/libjar/test/unit/data/test_corrupt3.zip
new file mode 100644
index 0000000000..86f8d76c9e
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_corrupt3.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_crx_dummy.crx b/modules/libjar/test/unit/data/test_crx_dummy.crx
new file mode 100644
index 0000000000..516e4ff807
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_crx_dummy.crx
Binary files differ
diff --git a/modules/libjar/test/unit/data/test_umlaute.zip b/modules/libjar/test/unit/data/test_umlaute.zip
new file mode 100644
index 0000000000..d147138e18
--- /dev/null
+++ b/modules/libjar/test/unit/data/test_umlaute.zip
Binary files differ
diff --git a/modules/libjar/test/unit/data/uncompressed.zip b/modules/libjar/test/unit/data/uncompressed.zip
new file mode 100644
index 0000000000..192bf15616
--- /dev/null
+++ b/modules/libjar/test/unit/data/uncompressed.zip
Binary files differ
diff --git a/modules/libjar/test/unit/test_bug278262.js b/modules/libjar/test/unit/test_bug278262.js
new file mode 100644
index 0000000000..d63276b896
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug278262.js
@@ -0,0 +1,39 @@
+// Regression test for bug 278262 - JAR URIs should resolve relative URIs in the base section.
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+const path = "data/test_bug333423.zip";
+
+function test_relative_sub() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var spec = "jar:" + ios.newFileURI(do_get_file(path)).spec + "!/";
+ var base = ios.newURI(spec, null, null);
+ var uri = ios.newURI("../modules/libjar", null, base);
+
+ // This is the URI we expect to see.
+ var expected = "jar:" + ios.newFileURI(do_get_file(path)).spec +
+ "!/modules/libjar";
+
+ do_check_eq(uri.spec, expected);
+}
+
+function test_relative_base() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var base = ios.newFileURI(do_get_file("data/empty"));
+ var uri = ios.newURI("jar:../" + path + "!/", null, base);
+
+ // This is the URI we expect to see.
+ var expected = "jar:" + ios.newFileURI(do_get_file(path)).spec +
+ "!/";
+
+ do_check_eq(uri.spec, expected);
+}
+
+function run_test() {
+ test_relative_sub();
+ test_relative_base();
+}
diff --git a/modules/libjar/test/unit/test_bug333423.js b/modules/libjar/test/unit/test_bug333423.js
new file mode 100644
index 0000000000..cd965b93e0
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug333423.js
@@ -0,0 +1,22 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Regression test for bug 333423 - crash enumerating entries of a
+// closed nsIZipReader
+function run_test() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ // the build script have created the zip we can test on in the current dir.
+ var file = do_get_file("data/test_bug333423.zip");
+
+ var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipreader.open(file);
+ zipreader.close();
+ var entries = zipreader.findEntries('*.*');
+ do_check_true(!entries.hasMore()); // this shouldn't crash
+}
diff --git a/modules/libjar/test/unit/test_bug336691.js b/modules/libjar/test/unit/test_bug336691.js
new file mode 100644
index 0000000000..e10dd3c9cb
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug336691.js
@@ -0,0 +1,12 @@
+// Regression test for bug 336691 - nsZipArchive::Test shouldn't try to ExtractFile on directories.
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ var file = do_get_file("data/test_bug336691.zip");
+ var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipReader.open(file);
+ zipReader.test(null); // We shouldn't crash here.
+ zipReader.close();
+}
diff --git a/modules/libjar/test/unit/test_bug370103.js b/modules/libjar/test/unit/test_bug370103.js
new file mode 100644
index 0000000000..210cf7a153
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug370103.js
@@ -0,0 +1,28 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// Regression test for bug 370103 - crash when passing a null listener to
+// nsIChannel.asyncOpen
+function run_test() {
+ // Compose the jar: url
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var file = do_get_file("data/test_bug370103.jar");
+ var url = ioService.newFileURI(file).spec;
+ url = "jar:" + url + "!/test_bug370103";
+
+ // Try opening channel with null listener
+ var channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+
+ var exception = false;
+ try {
+ channel.asyncOpen2(null);
+ }
+ catch(e) {
+ exception = true;
+ }
+
+ do_check_true(exception); // should throw exception instead of crashing
+}
diff --git a/modules/libjar/test/unit/test_bug379841.js b/modules/libjar/test/unit/test_bug379841.js
new file mode 100644
index 0000000000..a6840a5775
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug379841.js
@@ -0,0 +1,23 @@
+// Regression test for bug 379841 - nsIZipReader's last modified time ignores seconds
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+const path = "data/test_bug379841.zip";
+// Retrieved time should be within 2 seconds of original file's time.
+const MAX_TIME_DIFF = 2000000;
+
+var ENTRY_NAME = "test";
+// Actual time of file was 07 May 2007 13:35:49 UTC
+var ENTRY_TIME = new Date(Date.UTC(2007, 4, 7, 13, 35, 49, 0));
+
+function run_test() {
+ var file = do_get_file(path);
+ var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipReader.open(file);
+ var entry = zipReader.getEntry(ENTRY_NAME);
+ var diff = Math.abs(entry.lastModifiedTime - ENTRY_TIME.getTime()*1000);
+ zipReader.close();
+ if (diff >= MAX_TIME_DIFF)
+ do_throw(diff);
+}
diff --git a/modules/libjar/test/unit/test_bug407303.js b/modules/libjar/test/unit/test_bug407303.js
new file mode 100644
index 0000000000..313d88c641
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug407303.js
@@ -0,0 +1,42 @@
+// Regression test for bug 407303 - A failed channel should not be checked
+// for an unsafe content type.
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// XXX: NS_ERROR_UNKNOWN_HOST is not in Components.results
+const NS_ERROR_UNKNOWN_HOST = 0x804B001E;
+
+var listener = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIRequestObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStartRequest: function(request, context) {
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_throw("shouldn't get data!");
+ },
+
+ onStopRequest: function(request, context, status) {
+ do_check_eq(status, NS_ERROR_UNKNOWN_HOST);
+ do_test_finished();
+ }
+};
+
+function run_test() {
+ Services.prefs.setBoolPref("network.jar.block-remote-files", false);
+ var channel = NetUtil.newChannel({
+ uri: "jar:http://test.invalid/test.jar!/index.html",
+ loadUsingSystemPrincipal: true}
+ );
+ channel.asyncOpen2(listener);
+ do_test_pending();
+}
diff --git a/modules/libjar/test/unit/test_bug453254.js b/modules/libjar/test/unit/test_bug453254.js
new file mode 100644
index 0000000000..bc9d805b43
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug453254.js
@@ -0,0 +1,12 @@
+function run_test() {
+ const zipCache = Components.classes["@mozilla.org/libjar/zip-reader-cache;1"]
+ .createInstance(Components.interfaces.nsIZipReaderCache);
+ zipCache.init(1024);
+ try {
+ zipCache.getZip(null);
+ do_throw("Shouldn't get here!");
+ } catch (e if ((e instanceof Components.interfaces.nsIException) &&
+ (e.result == Components.results.NS_ERROR_INVALID_POINTER))) {
+ // do nothing, this test passes
+ }
+}
diff --git a/modules/libjar/test/unit/test_bug458158.js b/modules/libjar/test/unit/test_bug458158.js
new file mode 100644
index 0000000000..5827123372
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug458158.js
@@ -0,0 +1,11 @@
+function run_test() {
+ var zReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
+ .createInstance(Components.interfaces.nsIZipReader);
+ try {
+ zReader.open(null);
+ do_throw("Shouldn't get here!");
+ } catch (e if (e instanceof Components.interfaces.nsIException &&
+ e.result == Components.results.NS_ERROR_NULL_POINTER)) {
+ // do nothing, this test passes
+ }
+}
diff --git a/modules/libjar/test/unit/test_bug589292.js b/modules/libjar/test/unit/test_bug589292.js
new file mode 100644
index 0000000000..1fa103e3c8
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug589292.js
@@ -0,0 +1,25 @@
+// Make sure we behave appropriately when asking for content-disposition
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const path = "data/test_bug589292.zip";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var spec = "jar:" + ios.newFileURI(do_get_file(path)).spec + "!/foo.txt";
+ var channel = NetUtil.newChannel({uri: spec, loadUsingSystemPrincipal: true});
+ instr = channel.open2();
+ var val;
+ try {
+ val = channel.contentDisposition;
+ do_check_true(false, "The channel has content disposition?!");
+ } catch (e) {
+ // This is what we want to happen - there's no underlying channel, so no
+ // content-disposition header is available
+ do_check_true(true, "How are you reading this?!");
+ }
+}
diff --git a/modules/libjar/test/unit/test_bug597702.js b/modules/libjar/test/unit/test_bug597702.js
new file mode 100644
index 0000000000..3f32a89234
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug597702.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// Check that reading non existant inner jars results in the right error
+
+function run_test() {
+ var file = do_get_file("data/test_bug597702.zip");
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var outerJarBase = "jar:" + ios.newFileURI(file).spec + "!/";
+ var goodSpec = "jar:" + outerJarBase + "inner.jar!/hello";
+ var badSpec = "jar:" + outerJarBase + "jar_that_isnt_in_the.jar!/hello";
+ var goodChannel = NetUtil.newChannel({uri: goodSpec, loadUsingSystemPrincipal: true});
+ var badChannel = NetUtil.newChannel({uri: badSpec, loadUsingSystemPrincipal: true});
+
+ try {
+ instr = goodChannel.open2();
+ } catch (e) {
+ do_throw("Failed to open file in inner jar");
+ }
+
+ try {
+ instr = badChannel.open2();
+ do_throw("Failed to report that file doesn't exist");
+ } catch (e) {
+ do_check_true(e.name == "NS_ERROR_FILE_NOT_FOUND");
+ }
+}
+
diff --git a/modules/libjar/test/unit/test_bug637286.js b/modules/libjar/test/unit/test_bug637286.js
new file mode 100644
index 0000000000..d7609f78f1
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug637286.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// Check that the zip cache can expire entries from nested jars
+var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+function open_inner_zip(base, idx) {
+ var spec = "jar:" + base + "inner" + idx + ".zip!/foo";
+ var channel = NetUtil.newChannel({uri: spec, loadUsingSystemPrincipal: true});
+ var stream = channel.open2();
+}
+
+function run_test() {
+ var file = do_get_file("data/test_bug637286.zip");
+ var outerJarBase = "jar:" + ios.newFileURI(file).spec + "!/";
+
+ for (var i = 0; i < 40; i++) {
+ open_inner_zip(outerJarBase, i);
+ gc();
+ }
+}
+
diff --git a/modules/libjar/test/unit/test_bug658093.js b/modules/libjar/test/unit/test_bug658093.js
new file mode 100644
index 0000000000..52cf13b1d8
--- /dev/null
+++ b/modules/libjar/test/unit/test_bug658093.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// Check that we don't crash on reading a directory entry signature
+var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+function run_test() {
+ var file = do_get_file("data/test_bug658093.zip");
+ var spec = "jar:" + ios.newFileURI(file).spec + "!/0000";
+ var channel = NetUtil.newChannel({uri: spec, loadUsingSystemPrincipal: true});
+ var failed = false;
+ try {
+ var stream = channel.open2();
+ } catch (e) {
+ failed = true;
+ }
+ do_check_true(failed);
+}
diff --git a/modules/libjar/test/unit/test_corrupt_1211262.js b/modules/libjar/test/unit/test_corrupt_1211262.js
new file mode 100644
index 0000000000..fe289959fc
--- /dev/null
+++ b/modules/libjar/test/unit/test_corrupt_1211262.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Regression test ensuring that that a STORED entry with differing compressed
+// and uncompressed sizes is considered to be corrupt.
+function run_test() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var file = do_get_file("data/test_corrupt3.zip");
+
+ var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipreader.open(file);
+
+ var entries = zipreader.findEntries('*');
+ var failed = false;
+
+ while (entries.hasMore()) {
+ let entryPath = entries.getNext();
+ let entry = zipreader.getEntry(entryPath);
+ if (!entry.isDirectory) {
+ try {
+ let inputStream = zipreader.getInputStream(entryPath);
+ } catch (e) {
+ failed = true;
+ }
+ }
+ }
+
+ do_check_true(failed);
+}
+
diff --git a/modules/libjar/test/unit/test_corrupt_536911.js b/modules/libjar/test/unit/test_corrupt_536911.js
new file mode 100644
index 0000000000..f0748dbf27
--- /dev/null
+++ b/modules/libjar/test/unit/test_corrupt_536911.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function wrapInputStream(input)
+{
+ var nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+ var factory = Components.classes["@mozilla.org/scriptableinputstream;1"];
+ var wrapper = factory.createInstance(nsIScriptableInputStream);
+ wrapper.init(input);
+ return wrapper;
+}
+
+// Check that files can be read from after closing zipreader
+function run_test() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ // the build script have created the zip we can test on in the current dir.
+ var file = do_get_file("data/test_corrupt.zip");
+
+ var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipreader.open(file);
+ // var entries = zipreader.findEntries(null);
+ // the signature for file is corrupt, should not segfault
+ var failed = false;
+ try {
+ var stream = wrapInputStream(zipreader.getInputStream("file"));
+ stream.read(1024);
+ } catch (ex) {
+ failed = true;
+ }
+ do_check_true(failed);
+}
+
diff --git a/modules/libjar/test/unit/test_corrupt_541828.js b/modules/libjar/test/unit/test_corrupt_541828.js
new file mode 100644
index 0000000000..022eb96eb7
--- /dev/null
+++ b/modules/libjar/test/unit/test_corrupt_541828.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Check that files can be read from after closing zipreader
+function run_test() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ // the build script have created the zip we can test on in the current dir.
+ var file = do_get_file("data/test_corrupt2.zip");
+
+ var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ var failed = false;
+ try {
+ zipreader.open(file);
+ // corrupt files should trigger an exception
+ } catch (ex) {
+ failed = true;
+ }
+ do_check_true(failed);
+}
+
diff --git a/modules/libjar/test/unit/test_crx.js b/modules/libjar/test/unit/test_crx.js
new file mode 100644
index 0000000000..e06f36323a
--- /dev/null
+++ b/modules/libjar/test/unit/test_crx.js
@@ -0,0 +1,43 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function wrapInputStream(input)
+{
+ let nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+ let factory = Components.classes["@mozilla.org/scriptableinputstream;1"];
+ let wrapper = factory.createInstance(nsIScriptableInputStream);
+ wrapper.init(input);
+ return wrapper;
+}
+
+// Make sure that we can read from CRX files as if they were ZIP files.
+function run_test() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ // Note: test_crx_dummy.crx is a dummy crx file created for this test. The
+ // public key and signature fields in the header are both empty.
+ let file = do_get_file("data/test_crx_dummy.crx");
+
+ let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipreader.open(file);
+ // do crc stuff
+ function check_archive_crc() {
+ zipreader.test(null);
+ return true;
+ }
+ Assert.ok(check_archive_crc())
+ let entries = zipreader.findEntries(null);
+ let stream = wrapInputStream(zipreader.getInputStream("modules/libjar/test/Makefile.in"))
+ let dirstream= wrapInputStream(zipreader.getInputStream("modules/libjar/test/"))
+ zipreader.close();
+ zipreader = null;
+ Components.utils.forceGC();
+ Assert.ok(stream.read(1024).length > 0);
+ Assert.ok(dirstream.read(100).length > 0);
+}
+
diff --git a/modules/libjar/test/unit/test_dirjar_bug525755.js b/modules/libjar/test/unit/test_dirjar_bug525755.js
new file mode 100644
index 0000000000..00f099a4e1
--- /dev/null
+++ b/modules/libjar/test/unit/test_dirjar_bug525755.js
@@ -0,0 +1,25 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Check that we refuse to open weird files
+function run_test() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ // open a bogus file
+ var file = do_get_file("/");
+
+ var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ var failed = false;
+ try {
+ zipreader.open(file);
+ } catch (e) {
+ failed = true;
+ }
+ do_check_true(failed);
+ zipreader = null;
+}
+
diff --git a/modules/libjar/test/unit/test_jarchannel.js b/modules/libjar/test/unit/test_jarchannel.js
new file mode 100644
index 0000000000..46eda86914
--- /dev/null
+++ b/modules/libjar/test/unit/test_jarchannel.js
@@ -0,0 +1,216 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Tests some basic jar channel functionality
+ */
+
+
+const {classes: Cc,
+ interfaces: Ci,
+ results: Cr,
+ utils: Cu,
+ Constructor: ctor
+ } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+const dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+const obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+
+const nsIBinaryInputStream = ctor("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+ );
+
+const fileBase = "test_bug637286.zip";
+const file = do_get_file("data/" + fileBase);
+const jarBase = "jar:" + ios.newFileURI(file).spec + "!";
+const tmpDir = dirSvc.get("TmpD", Ci.nsIFile);
+
+function Listener(callback) {
+ this._callback = callback;
+}
+Listener.prototype = {
+ gotStartRequest: false,
+ available: -1,
+ gotStopRequest: false,
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIRequestObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ onDataAvailable: function(request, ctx, stream, offset, count) {
+ try {
+ this.available = stream.available();
+ do_check_eq(this.available, count);
+ // Need to consume stream to avoid assertion
+ new nsIBinaryInputStream(stream).readBytes(count);
+ }
+ catch (ex) {
+ do_throw(ex);
+ }
+ },
+ onStartRequest: function(request, ctx) {
+ this.gotStartRequest = true;
+ },
+ onStopRequest: function(request, ctx, status) {
+ this.gotStopRequest = true;
+ do_check_eq(status, 0);
+ if (this._callback) {
+ this._callback.call(null, this);
+ }
+ }
+};
+
+/**
+ * Basic reading test for asynchronously opened jar channel
+ */
+function testAsync() {
+ var uri = jarBase + "/inner40.zip";
+ var chan = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+ do_check_true(chan.contentLength < 0);
+ chan.asyncOpen2(new Listener(function(l) {
+ do_check_true(chan.contentLength > 0);
+ do_check_true(l.gotStartRequest);
+ do_check_true(l.gotStopRequest);
+ do_check_eq(l.available, chan.contentLength);
+
+ run_next_test();
+ }));
+}
+
+add_test(testAsync);
+// Run same test again so we test the codepath for a zipcache hit
+add_test(testAsync);
+
+/**
+ * Basic test for nsIZipReader.
+ */
+function testZipEntry() {
+ var uri = jarBase + "/inner40.zip";
+ var chan = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIJARChannel);
+ var entry = chan.zipEntry;
+ do_check_true(entry.CRC32 == 0x8b635486);
+ do_check_true(entry.realSize == 184);
+ run_next_test();
+}
+
+add_test(testZipEntry);
+
+
+/**
+ * Basic reading test for synchronously opened jar channels
+ */
+add_test(function testSync() {
+ var uri = jarBase + "/inner40.zip";
+ var chan = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+ var stream = chan.open2();
+ do_check_true(chan.contentLength > 0);
+ do_check_eq(stream.available(), chan.contentLength);
+ stream.close();
+ stream.close(); // should still not throw
+
+ run_next_test();
+});
+
+
+/**
+ * Basic reading test for synchronously opened, nested jar channels
+ */
+add_test(function testSyncNested() {
+ var uri = "jar:" + jarBase + "/inner40.zip!/foo";
+ var chan = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+ var stream = chan.open2();
+ do_check_true(chan.contentLength > 0);
+ do_check_eq(stream.available(), chan.contentLength);
+ stream.close();
+ stream.close(); // should still not throw
+
+ run_next_test();
+});
+
+/**
+ * Basic reading test for asynchronously opened, nested jar channels
+ */
+add_test(function testAsyncNested(next) {
+ var uri = "jar:" + jarBase + "/inner40.zip!/foo";
+ var chan = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+ chan.asyncOpen2(new Listener(function(l) {
+ do_check_true(chan.contentLength > 0);
+ do_check_true(l.gotStartRequest);
+ do_check_true(l.gotStopRequest);
+ do_check_eq(l.available, chan.contentLength);
+
+ run_next_test();
+ }));
+});
+
+/**
+ * Verify that file locks are released when closing a synchronously
+ * opened jar channel stream
+ */
+add_test(function testSyncCloseUnlocks() {
+ var copy = tmpDir.clone();
+ copy.append(fileBase);
+ file.copyTo(copy.parent, copy.leafName);
+ var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
+ var chan = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+ var stream = chan.open2();
+ do_check_true(chan.contentLength > 0);
+ stream.close();
+
+ // Drop any jar caches
+ obs.notifyObservers(null, "chrome-flush-caches", null);
+
+ try {
+ copy.remove(false);
+ }
+ catch (ex) {
+ do_throw(ex);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify that file locks are released when closing an asynchronously
+ * opened jar channel stream
+ */
+add_test(function testAsyncCloseUnlocks() {
+ var copy = tmpDir.clone();
+ copy.append(fileBase);
+ file.copyTo(copy.parent, copy.leafName);
+
+ var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
+ var chan = NetUtil.newChannel({uri: uri, loadUsingSystemPrincipal: true});
+
+ chan.asyncOpen2(new Listener(function (l) {
+ do_check_true(chan.contentLength > 0);
+
+ // Drop any jar caches
+ obs.notifyObservers(null, "chrome-flush-caches", null);
+
+ try {
+ copy.remove(false);
+ }
+ catch (ex) {
+ do_throw(ex);
+ }
+
+ run_next_test();
+ }));
+});
+
+
+function run_test() {
+ return run_next_test();
+}
diff --git a/modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js b/modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js
new file mode 100644
index 0000000000..741b84b03f
--- /dev/null
+++ b/modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js
@@ -0,0 +1,42 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function wrapInputStream(input)
+{
+ var nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+ var factory = Components.classes["@mozilla.org/scriptableinputstream;1"];
+ var wrapper = factory.createInstance(nsIScriptableInputStream);
+ wrapper.init(input);
+ return wrapper;
+}
+
+// Check that files can be read from after closing zipreader
+function run_test() {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ // the build script have created the zip we can test on in the current dir.
+ var file = do_get_file("data/test_bug333423.zip");
+
+ var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipreader.open(file);
+ // do crc stuff
+ function check_archive_crc() {
+ zipreader.test(null);
+ return true;
+ }
+ do_check_true(check_archive_crc())
+ var entries = zipreader.findEntries(null);
+ var stream = wrapInputStream(zipreader.getInputStream("modules/libjar/test/Makefile.in"))
+ var dirstream= wrapInputStream(zipreader.getInputStream("modules/libjar/test/"))
+ zipreader.close();
+ zipreader = null;
+ Components.utils.forceGC();
+ do_check_true(stream.read(1024).length > 0);
+ do_check_true(dirstream.read(100).length > 0);
+}
+
diff --git a/modules/libjar/test/unit/test_not_found.js b/modules/libjar/test/unit/test_not_found.js
new file mode 100644
index 0000000000..f7944cd47f
--- /dev/null
+++ b/modules/libjar/test/unit/test_not_found.js
@@ -0,0 +1,24 @@
+// Should report file not found on non-existent files
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+const path = "data/test_bug333423.zip";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var spec = "jar:" + ios.newFileURI(do_get_file(path)).spec + "!/";
+ var channel = NetUtil.newChannel({
+ uri: spec + "file_that_isnt_in.archive",
+ loadUsingSystemPrincipal: true
+ });
+ try {
+ instr = channel.open2();
+ do_throw("Failed to report that file doesn't exist")
+ } catch (e) {
+ do_check_true(e.name == "NS_ERROR_FILE_NOT_FOUND")
+ }
+}
diff --git a/modules/libjar/test/unit/test_umlaute.js b/modules/libjar/test/unit/test_umlaute.js
new file mode 100644
index 0000000000..bfaa0eabc4
--- /dev/null
+++ b/modules/libjar/test/unit/test_umlaute.js
@@ -0,0 +1,40 @@
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ var dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+ var tmpDir = dirService.get("TmpD", Ci.nsIFile);
+
+ var zipfile = do_get_file("data/test_umlaute.zip");
+
+ var testFile = tmpDir.clone();
+ testFile.append("test_\u00FC.txt");
+ if (testFile.exists()) {
+ testFile.remove(false);
+ }
+
+ var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader);
+ zipreader.open(zipfile);
+
+ var entries = zipreader.findEntries(null);
+ do_check_true(entries.hasMore());
+
+ var entryName = entries.getNext();
+ do_check_eq(entryName, "test_\u00FC.txt");
+
+ do_check_true(zipreader.hasEntry(entryName));
+
+ var target = tmpDir.clone();
+ target.append(entryName);
+ target.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0o640); // 0640
+
+ zipreader.extract(entryName, target);
+
+ var entry = zipreader.getEntry(entryName);
+ do_check_true(entry != null);
+
+ zipreader.test(entryName);
+
+ zipreader.close();
+}
diff --git a/modules/libjar/test/unit/test_uncompressed.js b/modules/libjar/test/unit/test_uncompressed.js
new file mode 100644
index 0000000000..80fbbcee59
--- /dev/null
+++ b/modules/libjar/test/unit/test_uncompressed.js
@@ -0,0 +1,12 @@
+// Make sure uncompressed files pass crc
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function run_test() {
+ var file = do_get_file("data/uncompressed.zip");
+ var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipReader.open(file);
+ zipReader.test("hello");
+ zipReader.close();
+}
diff --git a/modules/libjar/test/unit/xpcshell.ini b/modules/libjar/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..ee22f4c24a
--- /dev/null
+++ b/modules/libjar/test/unit/xpcshell.ini
@@ -0,0 +1,44 @@
+[DEFAULT]
+head =
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ data/empty
+ data/test_bug333423.zip
+ data/test_bug336691.zip
+ data/test_bug370103.jar
+ data/test_bug379841.zip
+ data/test_bug589292.zip
+ data/test_bug597702.zip
+ data/test_bug637286.zip
+ data/test_bug658093.zip
+ data/test_corrupt.zip
+ data/test_corrupt2.zip
+ data/test_corrupt3.zip
+ data/test_crx_dummy.crx
+ data/test_umlaute.zip
+ data/uncompressed.zip
+
+[test_jarchannel.js]
+skip-if = os == "mac"
+[test_bug278262.js]
+[test_bug333423.js]
+[test_bug336691.js]
+[test_bug370103.js]
+[test_bug379841.js]
+[test_bug407303.js]
+[test_bug453254.js]
+[test_bug458158.js]
+[test_bug589292.js]
+[test_bug597702.js]
+[test_bug637286.js]
+[test_bug658093.js]
+[test_corrupt_536911.js]
+[test_corrupt_541828.js]
+[test_corrupt_1211262.js]
+[test_crx.js]
+[test_dirjar_bug525755.js]
+[test_jarinput_stream_zipreader_reference.js]
+[test_not_found.js]
+[test_uncompressed.js]
+[test_umlaute.js]
diff --git a/modules/libjar/zipstruct.h b/modules/libjar/zipstruct.h
new file mode 100644
index 0000000000..f06afe14ea
--- /dev/null
+++ b/modules/libjar/zipstruct.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _zipstruct_h
+#define _zipstruct_h
+
+
+/*
+ * Certain constants and structures for
+ * the Phil Katz ZIP archive format.
+ *
+ */
+
+typedef struct ZipLocal_
+ {
+ unsigned char signature [4];
+ unsigned char word [2];
+ unsigned char bitflag [2];
+ unsigned char method [2];
+ unsigned char time [2];
+ unsigned char date [2];
+ unsigned char crc32 [4];
+ unsigned char size [4];
+ unsigned char orglen [4];
+ unsigned char filename_len [2];
+ unsigned char extrafield_len [2];
+} ZipLocal;
+
+/*
+ * 'sizeof(struct XXX)' includes padding on ARM (see bug 87965)
+ * As the internals of a jar/zip file must not depend on the target
+ * architecture (i386, ppc, ARM, ...), use a fixed value instead.
+ */
+#define ZIPLOCAL_SIZE (4+2+2+2+2+2+4+4+4+2+2)
+
+typedef struct ZipCentral_
+ {
+ unsigned char signature [4];
+ unsigned char version_made_by [2];
+ unsigned char version [2];
+ unsigned char bitflag [2];
+ unsigned char method [2];
+ unsigned char time [2];
+ unsigned char date [2];
+ unsigned char crc32 [4];
+ unsigned char size [4];
+ unsigned char orglen [4];
+ unsigned char filename_len [2];
+ unsigned char extrafield_len [2];
+ unsigned char commentfield_len [2];
+ unsigned char diskstart_number [2];
+ unsigned char internal_attributes [2];
+ unsigned char external_attributes [4];
+ unsigned char localhdr_offset [4];
+} ZipCentral;
+
+/*
+ * 'sizeof(struct XXX)' includes padding on ARM (see bug 87965)
+ * As the internals of a jar/zip file must not depend on the target
+ * architecture (i386, ppc, ARM, ...), use a fixed value instead.
+ */
+#define ZIPCENTRAL_SIZE (4+2+2+2+2+2+2+4+4+4+2+2+2+2+2+4+4)
+
+typedef struct ZipEnd_
+ {
+ unsigned char signature [4];
+ unsigned char disk_nr [2];
+ unsigned char start_central_dir [2];
+ unsigned char total_entries_disk [2];
+ unsigned char total_entries_archive [2];
+ unsigned char central_dir_size [4];
+ unsigned char offset_central_dir [4];
+ unsigned char commentfield_len [2];
+} ZipEnd;
+
+/*
+ * 'sizeof(struct XXX)' includes padding on ARM (see bug 87965)
+ * As the internals of a jar/zip file must not depend on the target
+ * architecture (i386, ppc, ARM, ...), use a fixed value instead.
+ */
+#define ZIPEND_SIZE (4+2+2+2+2+4+4+2)
+
+/* signatures */
+#define LOCALSIG 0x04034B50l
+#define CENTRALSIG 0x02014B50l
+#define ENDSIG 0x06054B50l
+
+/* extra fields */
+#define EXTENDED_TIMESTAMP_FIELD 0x5455
+#define EXTENDED_TIMESTAMP_MODTIME 0x01
+
+/* compression methods */
+#define STORED 0
+#define SHRUNK 1
+#define REDUCED1 2
+#define REDUCED2 3
+#define REDUCED3 4
+#define REDUCED4 5
+#define IMPLODED 6
+#define TOKENIZED 7
+#define DEFLATED 8
+#define UNSUPPORTED 0xFF
+
+
+#endif /* _zipstruct_h */
diff --git a/modules/libjar/zipwriter/StreamFunctions.cpp b/modules/libjar/zipwriter/StreamFunctions.cpp
new file mode 100644
index 0000000000..9a38f2dcae
--- /dev/null
+++ b/modules/libjar/zipwriter/StreamFunctions.cpp
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "nscore.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+
+/*
+ * Fully reads the required amount of data. Keeps reading until all the
+ * data is retrieved or an error is hit.
+ */
+nsresult ZW_ReadData(nsIInputStream *aStream, char *aBuffer, uint32_t aCount)
+{
+ while (aCount > 0) {
+ uint32_t read;
+ nsresult rv = aStream->Read(aBuffer, aCount, &read);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aCount -= read;
+ aBuffer += read;
+ // If we hit EOF before reading the data we need then throw.
+ if (read == 0 && aCount > 0)
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Fully writes the required amount of data. Keeps writing until all the
+ * data is written or an error is hit.
+ */
+nsresult ZW_WriteData(nsIOutputStream *aStream, const char *aBuffer,
+ uint32_t aCount)
+{
+ while (aCount > 0) {
+ uint32_t written;
+ nsresult rv = aStream->Write(aBuffer, aCount, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (written <= 0)
+ return NS_ERROR_FAILURE;
+ aCount -= written;
+ aBuffer += written;
+ }
+
+ return NS_OK;
+}
diff --git a/modules/libjar/zipwriter/StreamFunctions.h b/modules/libjar/zipwriter/StreamFunctions.h
new file mode 100644
index 0000000000..012fce986d
--- /dev/null
+++ b/modules/libjar/zipwriter/StreamFunctions.h
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef _nsStreamFunctions_h_
+#define _nsStreamFunctions_h_
+
+#include "nscore.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+
+/*
+ * ZIP file data is stored little-endian. These are helper functions to read and
+ * write little endian data to/from a char buffer.
+ * The off argument, where present, is incremented according to the number of
+ * bytes consumed from the buffer.
+ */
+inline void WRITE8(uint8_t* buf, uint32_t* off, uint8_t val)
+{
+ buf[(*off)++] = val;
+}
+
+inline void WRITE16(uint8_t* buf, uint32_t* off, uint16_t val)
+{
+ WRITE8(buf, off, val & 0xff);
+ WRITE8(buf, off, (val >> 8) & 0xff);
+}
+
+inline void WRITE32(uint8_t* buf, uint32_t* off, uint32_t val)
+{
+ WRITE16(buf, off, val & 0xffff);
+ WRITE16(buf, off, (val >> 16) & 0xffff);
+}
+
+inline uint8_t READ8(const uint8_t* buf, uint32_t* off)
+{
+ return buf[(*off)++];
+}
+
+inline uint16_t READ16(const uint8_t* buf, uint32_t* off)
+{
+ uint16_t val = READ8(buf, off);
+ val |= READ8(buf, off) << 8;
+ return val;
+}
+
+inline uint32_t READ32(const uint8_t* buf, uint32_t* off)
+{
+ uint32_t val = READ16(buf, off);
+ val |= READ16(buf, off) << 16;
+ return val;
+}
+
+inline uint32_t PEEK32(const uint8_t* buf)
+{
+ return (uint32_t)( (buf [0] ) |
+ (buf [1] << 8) |
+ (buf [2] << 16) |
+ (buf [3] << 24) );
+}
+
+nsresult ZW_ReadData(nsIInputStream *aStream, char *aBuffer, uint32_t aCount);
+
+nsresult ZW_WriteData(nsIOutputStream *aStream, const char *aBuffer,
+ uint32_t aCount);
+
+#endif
diff --git a/modules/libjar/zipwriter/ZipWriterModule.cpp b/modules/libjar/zipwriter/ZipWriterModule.cpp
new file mode 100644
index 0000000000..2891da58d6
--- /dev/null
+++ b/modules/libjar/zipwriter/ZipWriterModule.cpp
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsDeflateConverter.h"
+#include "nsZipWriter.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeflateConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsZipWriter)
+
+NS_DEFINE_NAMED_CID(DEFLATECONVERTER_CID);
+NS_DEFINE_NAMED_CID(ZIPWRITER_CID);
+
+static const mozilla::Module::CIDEntry kZipWriterCIDs[] = {
+ { &kDEFLATECONVERTER_CID, false, nullptr, nsDeflateConverterConstructor },
+ { &kZIPWRITER_CID, false, nullptr, nsZipWriterConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kZipWriterContracts[] = {
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=deflate", &kDEFLATECONVERTER_CID },
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=gzip", &kDEFLATECONVERTER_CID },
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=x-gzip", &kDEFLATECONVERTER_CID },
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=rawdeflate", &kDEFLATECONVERTER_CID },
+ { ZIPWRITER_CONTRACTID, &kZIPWRITER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kZipWriterModule = {
+ mozilla::Module::kVersion,
+ kZipWriterCIDs,
+ kZipWriterContracts
+};
+
+NSMODULE_DEFN(ZipWriterModule) = &kZipWriterModule;
diff --git a/modules/libjar/zipwriter/moz.build b/modules/libjar/zipwriter/moz.build
new file mode 100644
index 0000000000..e874867c6d
--- /dev/null
+++ b/modules/libjar/zipwriter/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
+XPIDL_SOURCES += [
+ 'nsIZipWriter.idl',
+]
+
+XPIDL_MODULE = 'zipwriter'
+
+UNIFIED_SOURCES += [
+ 'nsDeflateConverter.cpp',
+ 'nsZipDataStream.cpp',
+ 'nsZipHeader.cpp',
+ 'nsZipWriter.cpp',
+ 'StreamFunctions.cpp',
+ 'ZipWriterModule.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/modules/libjar/zipwriter/nsDeflateConverter.cpp b/modules/libjar/zipwriter/nsDeflateConverter.cpp
new file mode 100644
index 0000000000..d51cb2cebe
--- /dev/null
+++ b/modules/libjar/zipwriter/nsDeflateConverter.cpp
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "StreamFunctions.h"
+#include "nsDeflateConverter.h"
+#include "nsStringStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "plstr.h"
+#include "mozilla/UniquePtr.h"
+
+#define ZLIB_TYPE "deflate"
+#define GZIP_TYPE "gzip"
+#define X_GZIP_TYPE "x-gzip"
+
+using namespace mozilla;
+
+/**
+ * nsDeflateConverter is a stream converter applies the deflate compression
+ * method to the data.
+ */
+NS_IMPL_ISUPPORTS(nsDeflateConverter, nsIStreamConverter,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+nsresult nsDeflateConverter::Init()
+{
+ int zerr;
+
+ mOffset = 0;
+
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+
+ int32_t window = MAX_WBITS;
+ switch (mWrapMode) {
+ case WRAP_NONE:
+ window = -window;
+ break;
+ case WRAP_GZIP:
+ window += 16;
+ break;
+ default:
+ break;
+ }
+
+ zerr = deflateInit2(&mZstream, mLevel, Z_DEFLATED, window, 8,
+ Z_DEFAULT_STRATEGY);
+ if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY;
+
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = sizeof(mWriteBuffer);
+
+ // mark the input buffer as empty.
+ mZstream.avail_in = 0;
+ mZstream.next_in = Z_NULL;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDeflateConverter::Convert(nsIInputStream *aFromStream,
+ const char *aFromType,
+ const char *aToType,
+ nsISupports *aCtxt,
+ nsIInputStream **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsDeflateConverter::AsyncConvertData(const char *aFromType,
+ const char *aToType,
+ nsIStreamListener *aListener,
+ nsISupports *aCtxt)
+{
+ if (mListener)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ if (!PL_strncasecmp(aToType, ZLIB_TYPE, sizeof(ZLIB_TYPE)-1))
+ mWrapMode = WRAP_ZLIB;
+ else if (!PL_strcasecmp(aToType, GZIP_TYPE) ||
+ !PL_strcasecmp(aToType, X_GZIP_TYPE))
+ mWrapMode = WRAP_GZIP;
+ else
+ mWrapMode = WRAP_NONE;
+
+ nsresult rv = Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mListener = aListener;
+ mContext = aCtxt;
+ return rv;
+}
+
+NS_IMETHODIMP nsDeflateConverter::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mListener)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ auto buffer = MakeUnique<char[]>(aCount);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = ZW_ReadData(aInputStream, buffer.get(), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure we aren't reading too much
+ mZstream.avail_in = aCount;
+ mZstream.next_in = (unsigned char*)buffer.get();
+
+ int zerr = Z_OK;
+ // deflate loop
+ while (mZstream.avail_in > 0 && zerr == Z_OK) {
+ zerr = deflate(&mZstream, Z_NO_FLUSH);
+
+ while (mZstream.avail_out == 0) {
+ // buffer is full, push the data out to the listener
+ rv = PushAvailableData(aRequest, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ zerr = deflate(&mZstream, Z_NO_FLUSH);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDeflateConverter::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ if (!mListener)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mListener->OnStartRequest(aRequest, mContext);
+}
+
+NS_IMETHODIMP nsDeflateConverter::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (!mListener)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ int zerr;
+ do {
+ zerr = deflate(&mZstream, Z_FINISH);
+ rv = PushAvailableData(aRequest, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (zerr == Z_OK);
+
+ deflateEnd(&mZstream);
+
+ return mListener->OnStopRequest(aRequest, mContext, aStatusCode);
+}
+
+nsresult nsDeflateConverter::PushAvailableData(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ uint32_t bytesToWrite = sizeof(mWriteBuffer) - mZstream.avail_out;
+ // We don't need to do anything if there isn't any data
+ if (bytesToWrite == 0)
+ return NS_OK;
+
+ MOZ_ASSERT(bytesToWrite <= INT32_MAX);
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ (char*)mWriteBuffer, bytesToWrite);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mListener->OnDataAvailable(aRequest, mContext, stream, mOffset,
+ bytesToWrite);
+
+ // now set the state for 'deflate'
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = sizeof(mWriteBuffer);
+
+ mOffset += bytesToWrite;
+ return rv;
+}
diff --git a/modules/libjar/zipwriter/nsDeflateConverter.h b/modules/libjar/zipwriter/nsDeflateConverter.h
new file mode 100644
index 0000000000..9678af87d6
--- /dev/null
+++ b/modules/libjar/zipwriter/nsDeflateConverter.h
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef _nsDeflateConverter_h_
+#define _nsDeflateConverter_h_
+
+#include "nsIStreamConverter.h"
+#include "nsCOMPtr.h"
+#include "nsIPipe.h"
+#include "zlib.h"
+#include "mozilla/Attributes.h"
+
+#define DEFLATECONVERTER_CID { 0x461cd5dd, 0x73c6, 0x47a4, \
+ { 0x8c, 0xc3, 0x60, 0x3b, 0x37, 0xd8, 0x4a, 0x61 } }
+
+#define ZIP_BUFLEN (4 * 1024 - 1)
+
+class nsDeflateConverter final : public nsIStreamConverter
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISTREAMCONVERTER
+
+ nsDeflateConverter()
+ {
+ // 6 is Z_DEFAULT_COMPRESSION but we need the actual value
+ mLevel = 6;
+ }
+
+ explicit nsDeflateConverter(int32_t level)
+ {
+ mLevel = level;
+ }
+
+private:
+
+ ~nsDeflateConverter()
+ {
+ }
+
+ enum WrapMode {
+ WRAP_ZLIB,
+ WRAP_GZIP,
+ WRAP_NONE
+ };
+
+ WrapMode mWrapMode;
+ uint64_t mOffset;
+ int32_t mLevel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mContext;
+ z_stream mZstream;
+ unsigned char mWriteBuffer[ZIP_BUFLEN];
+
+ nsresult Init();
+ nsresult PushAvailableData(nsIRequest *aRequest, nsISupports *aContext);
+};
+
+#endif
diff --git a/modules/libjar/zipwriter/nsIZipWriter.idl b/modules/libjar/zipwriter/nsIZipWriter.idl
new file mode 100644
index 0000000000..bcea5aaf77
--- /dev/null
+++ b/modules/libjar/zipwriter/nsIZipWriter.idl
@@ -0,0 +1,220 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "nsISupports.idl"
+interface nsIChannel;
+interface nsIInputStream;
+interface nsIRequestObserver;
+interface nsIFile;
+interface nsIZipEntry;
+
+/**
+ * nsIZipWriter
+ *
+ * An interface for a zip archiver that can be used from script.
+ *
+ * The interface supports both a synchronous method of archiving data and a
+ * queueing system to allow operations to be prepared then run in sequence
+ * with notification after completion.
+ *
+ * Operations added to the queue do not get performed until performQueue is
+ * called at which point they will be performed in the order that they were
+ * added to the queue.
+ *
+ * Operations performed on the queue will throw any errors out to the
+ * observer.
+ *
+ * An attempt to perform a synchronous operation while the background queue
+ * is in progress will throw NS_ERROR_IN_PROGRESS.
+ *
+ * Entry names should use /'s as path separators and should not start with
+ * a /.
+ *
+ * It is not generally necessary to add directory entries in order to add file
+ * entries within them, however it is possible that some zip programs may
+ * experience problems what that.
+ */
+[scriptable, uuid(3ca10750-797e-4a22-bcfe-66170b5e96dd)]
+interface nsIZipWriter : nsISupports
+{
+ /**
+ * Some predefined compression levels
+ */
+ const uint32_t COMPRESSION_NONE = 0;
+ const uint32_t COMPRESSION_FASTEST = 1;
+ const uint32_t COMPRESSION_DEFAULT = 6;
+ const uint32_t COMPRESSION_BEST = 9;
+
+ /**
+ * Gets or sets the comment associated with the open zip file.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ */
+ attribute ACString comment;
+
+ /**
+ * Indicates that operations on the background queue are being performed.
+ */
+ readonly attribute boolean inQueue;
+
+ /**
+ * The file that the zipwriter is writing to.
+ */
+ readonly attribute nsIFile file;
+
+ /**
+ * Opens a zip file.
+ *
+ * @param aFile the zip file to open
+ * @param aIoFlags the open flags for the zip file from prio.h
+ *
+ * @throws NS_ERROR_ALREADY_INITIALIZED if a zip file is already open
+ * @throws NS_ERROR_INVALID_ARG if aFile is null
+ * @throws NS_ERROR_FILE_NOT_FOUND if aFile does not exist and flags did
+ * not allow for creation
+ * @throws NS_ERROR_FILE_CORRUPTED if the file does not contain zip markers
+ * @throws <other-error> on failure to open zip file (most likely corrupt
+ * or unsupported form)
+ */
+ void open(in nsIFile aFile, in int32_t aIoFlags);
+
+ /**
+ * Returns a nsIZipEntry describing a specified zip entry or null if there
+ * is no such entry in the zip file
+ *
+ * @param aZipEntry the path of the entry
+ */
+ nsIZipEntry getEntry(in AUTF8String aZipEntry);
+
+ /**
+ * Checks whether the zipfile contains an entry specified by zipEntry.
+ *
+ * @param aZipEntry the path of the entry
+ */
+ boolean hasEntry(in AUTF8String aZipEntry);
+
+ /**
+ * Adds a new directory entry to the zip file. If aZipEntry does not end with
+ * "/" then it will be added.
+ *
+ * @param aZipEntry the path of the directory entry
+ * @param aModTime the modification time of the entry in microseconds
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the
+ * file
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ */
+ void addEntryDirectory(in AUTF8String aZipEntry, in PRTime aModTime,
+ in boolean aQueue);
+
+ /**
+ * Adds a new file or directory to the zip file. If the specified file is
+ * a directory then this will be equivalent to a call to
+ * addEntryDirectory(aZipEntry, aFile.lastModifiedTime, aQueue)
+ *
+ * @param aZipEntry the path of the file entry
+ * @param aCompression the compression level, 0 is no compression, 9 is best
+ * @param aFile the file to get the data and modification time from
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ * @throws NS_ERROR_FILE_NOT_FOUND if file does not exist
+ */
+ void addEntryFile(in AUTF8String aZipEntry,
+ in int32_t aCompression, in nsIFile aFile,
+ in boolean aQueue);
+
+ /**
+ * Adds data from a channel to the zip file. If the operation is performed
+ * on the queue then the channel will be opened asynchronously, otherwise
+ * the channel must support being opened synchronously.
+ *
+ * @param aZipEntry the path of the file entry
+ * @param aModTime the modification time of the entry in microseconds
+ * @param aCompression the compression level, 0 is no compression, 9 is best
+ * @param aChannel the channel to get the data from
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ */
+ void addEntryChannel(in AUTF8String aZipEntry, in PRTime aModTime,
+ in int32_t aCompression, in nsIChannel aChannel,
+ in boolean aQueue);
+
+ /**
+ * Adds data from an input stream to the zip file.
+ *
+ * @param aZipEntry the path of the file entry
+ * @param aModTime the modification time of the entry in microseconds
+ * @param aCompression the compression level, 0 is no compression, 9 is best
+ * @param aStream the input stream to get the data from
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ */
+ void addEntryStream(in AUTF8String aZipEntry, in PRTime aModTime,
+ in int32_t aCompression, in nsIInputStream aStream,
+ in boolean aQueue);
+
+ /**
+ * Removes an existing entry from the zip file.
+ *
+ * @param aZipEntry the path of the entry to be removed
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ * @throws NS_ERROR_FILE_NOT_FOUND if no entry with the given path exists
+ * @throws <other-error> on failure to update the zip file
+ */
+ void removeEntry(in AUTF8String aZipEntry, in boolean aQueue);
+
+ /**
+ * Processes all queued items until complete or some error occurs. The
+ * observer will be notified when the first operation starts and when the
+ * last operation completes. Any failures will be passed to the observer.
+ * The zip writer will be busy until the queue is complete or some error
+ * halted processing of the queue early. In the event of an early failure,
+ * remaining items will stay in the queue and calling processQueue will
+ * continue.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_IN_PROGRESS if the queue is already in progress
+ */
+ void processQueue(in nsIRequestObserver aObserver, in nsISupports aContext);
+
+ /**
+ * Closes the zip file.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ * @throws <other-error> on failure to complete the zip file
+ */
+ void close();
+
+ /**
+ * Make all stored(uncompressed) files align to given alignment size.
+ *
+ * @param aAlignSize is the alignment size, valid values from 2 to 32768, and
+ must be power of 2.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aAlignSize is invalid
+ * @throws <other-error> on failure to update the zip file
+ */
+ void alignStoredFiles(in uint16_t aAlignSize);
+};
diff --git a/modules/libjar/zipwriter/nsZipDataStream.cpp b/modules/libjar/zipwriter/nsZipDataStream.cpp
new file mode 100644
index 0000000000..4a7b785b71
--- /dev/null
+++ b/modules/libjar/zipwriter/nsZipDataStream.cpp
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "StreamFunctions.h"
+#include "nsZipDataStream.h"
+#include "nsStringStream.h"
+#include "nsISeekableStream.h"
+#include "nsDeflateConverter.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+
+#define ZIP_METHOD_STORE 0
+#define ZIP_METHOD_DEFLATE 8
+
+using namespace mozilla;
+
+/**
+ * nsZipDataStream handles the writing an entry's into the zip file.
+ * It is set up to wither write the data as is, or in the event that compression
+ * has been requested to pass it through a stream converter.
+ * Currently only the deflate compression method is supported.
+ * The CRC checksum for the entry's data is also generated here.
+ */
+NS_IMPL_ISUPPORTS(nsZipDataStream, nsIStreamListener,
+ nsIRequestObserver)
+
+nsresult nsZipDataStream::Init(nsZipWriter *aWriter,
+ nsIOutputStream *aStream,
+ nsZipHeader *aHeader,
+ int32_t aCompression)
+{
+ mWriter = aWriter;
+ mHeader = aHeader;
+ mStream = aStream;
+ mHeader->mCRC = crc32(0L, Z_NULL, 0);
+
+ nsresult rv = NS_NewSimpleStreamListener(getter_AddRefs(mOutput), aStream,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aCompression > 0) {
+ mHeader->mMethod = ZIP_METHOD_DEFLATE;
+ nsCOMPtr<nsIStreamConverter> converter =
+ new nsDeflateConverter(aCompression);
+ NS_ENSURE_TRUE(converter, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = converter->AsyncConvertData("uncompressed", "rawdeflate", mOutput,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOutput = do_QueryInterface(converter, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ mHeader->mMethod = ZIP_METHOD_STORE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipDataStream::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ auto buffer = MakeUnique<char[]>(aCount);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = ZW_ReadData(aInputStream, buffer.get(), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ProcessData(aRequest, aContext, buffer.get(), aOffset, aCount);
+}
+
+NS_IMETHODIMP nsZipDataStream::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mOutput->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP nsZipDataStream::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mOutput->OnStopRequest(aRequest, aContext, aStatusCode);
+ mOutput = nullptr;
+ if (NS_FAILED(rv)) {
+ mWriter->EntryCompleteCallback(mHeader, rv);
+ }
+ else {
+ rv = CompleteEntry();
+ rv = mWriter->EntryCompleteCallback(mHeader, rv);
+ }
+
+ mStream = nullptr;
+ mWriter = nullptr;
+ mHeader = nullptr;
+
+ return rv;
+}
+
+inline nsresult nsZipDataStream::CompleteEntry()
+{
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t pos;
+ rv = seekable->Tell(&pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHeader->mCSize = pos - mHeader->mOffset - mHeader->GetFileHeaderLength();
+ mHeader->mWriteOnClose = true;
+ return NS_OK;
+}
+
+nsresult nsZipDataStream::ProcessData(nsIRequest *aRequest,
+ nsISupports *aContext, char *aBuffer,
+ uint64_t aOffset, uint32_t aCount)
+{
+ mHeader->mCRC = crc32(mHeader->mCRC,
+ reinterpret_cast<const unsigned char*>(aBuffer),
+ aCount);
+
+ MOZ_ASSERT(aCount <= INT32_MAX);
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ aBuffer, aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOutput->OnDataAvailable(aRequest, aContext, stream, aOffset, aCount);
+ mHeader->mUSize += aCount;
+
+ return rv;
+}
+
+nsresult nsZipDataStream::ReadStream(nsIInputStream *aStream)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = OnStartRequest(nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto buffer = MakeUnique<char[]>(4096);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t read = 0;
+ uint32_t offset = 0;
+ do
+ {
+ rv = aStream->Read(buffer.get(), 4096, &read);
+ if (NS_FAILED(rv)) {
+ OnStopRequest(nullptr, nullptr, rv);
+ return rv;
+ }
+
+ if (read > 0) {
+ rv = ProcessData(nullptr, nullptr, buffer.get(), offset, read);
+ if (NS_FAILED(rv)) {
+ OnStopRequest(nullptr, nullptr, rv);
+ return rv;
+ }
+ offset += read;
+ }
+ } while (read > 0);
+
+ return OnStopRequest(nullptr, nullptr, NS_OK);
+}
diff --git a/modules/libjar/zipwriter/nsZipDataStream.h b/modules/libjar/zipwriter/nsZipDataStream.h
new file mode 100644
index 0000000000..ffa455274c
--- /dev/null
+++ b/modules/libjar/zipwriter/nsZipDataStream.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef _nsZipDataStream_h_
+#define _nsZipDataStream_h_
+
+#include "nsZipWriter.h"
+#include "nsIOutputStream.h"
+#include "nsIStreamListener.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsZipDataStream final : public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsZipDataStream()
+ {
+ }
+
+ nsresult Init(nsZipWriter *aWriter, nsIOutputStream *aStream,
+ nsZipHeader *aHeader, int32_t aCompression);
+
+ nsresult ReadStream(nsIInputStream *aStream);
+
+private:
+
+ ~nsZipDataStream() {}
+
+ nsCOMPtr<nsIStreamListener> mOutput;
+ nsCOMPtr<nsIOutputStream> mStream;
+ RefPtr<nsZipWriter> mWriter;
+ RefPtr<nsZipHeader> mHeader;
+
+ nsresult CompleteEntry();
+ nsresult ProcessData(nsIRequest *aRequest, nsISupports *aContext,
+ char *aBuffer, uint64_t aOffset, uint32_t aCount);
+};
+
+#endif
diff --git a/modules/libjar/zipwriter/nsZipHeader.cpp b/modules/libjar/zipwriter/nsZipHeader.cpp
new file mode 100644
index 0000000000..af2ee03357
--- /dev/null
+++ b/modules/libjar/zipwriter/nsZipHeader.cpp
@@ -0,0 +1,389 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "StreamFunctions.h"
+#include "nsZipHeader.h"
+#include "nsMemory.h"
+#include "prtime.h"
+
+#define ZIP_FILE_HEADER_SIGNATURE 0x04034b50
+#define ZIP_FILE_HEADER_SIZE 30
+#define ZIP_CDS_HEADER_SIGNATURE 0x02014b50
+#define ZIP_CDS_HEADER_SIZE 46
+
+#define FLAGS_IS_UTF8 0x800
+
+#define ZIP_EXTENDED_TIMESTAMP_FIELD 0x5455
+#define ZIP_EXTENDED_TIMESTAMP_MODTIME 0x01
+
+using namespace mozilla;
+
+/**
+ * nsZipHeader represents an entry from a zip file.
+ */
+NS_IMPL_ISUPPORTS(nsZipHeader, nsIZipEntry)
+
+NS_IMETHODIMP nsZipHeader::GetCompression(uint16_t *aCompression)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aCompression = mMethod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetSize(uint32_t *aSize)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aSize = mCSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetRealSize(uint32_t *aRealSize)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aRealSize = mUSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetCRC32(uint32_t *aCRC32)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aCRC32 = mCRC;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetIsDirectory(bool *aIsDirectory)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ if (mName.Last() == '/')
+ *aIsDirectory = true;
+ else
+ *aIsDirectory = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetLastModifiedTime(PRTime *aLastModifiedTime)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ // Try to read timestamp from extra field
+ uint16_t blocksize;
+ const uint8_t *tsField = GetExtraField(ZIP_EXTENDED_TIMESTAMP_FIELD, false, &blocksize);
+ if (tsField && blocksize >= 5) {
+ uint32_t pos = 4;
+ uint8_t flags;
+ flags = READ8(tsField, &pos);
+ if (flags & ZIP_EXTENDED_TIMESTAMP_MODTIME) {
+ *aLastModifiedTime = (PRTime)(READ32(tsField, &pos))
+ * PR_USEC_PER_SEC;
+ return NS_OK;
+ }
+ }
+
+ // Use DOS date/time fields
+ // Note that on DST shift we can't handle correctly the hour that is valid
+ // in both DST zones
+ PRExplodedTime time;
+
+ time.tm_usec = 0;
+
+ time.tm_hour = (mTime >> 11) & 0x1F;
+ time.tm_min = (mTime >> 5) & 0x3F;
+ time.tm_sec = (mTime & 0x1F) * 2;
+
+ time.tm_year = (mDate >> 9) + 1980;
+ time.tm_month = ((mDate >> 5) & 0x0F) - 1;
+ time.tm_mday = mDate & 0x1F;
+
+ time.tm_params.tp_gmt_offset = 0;
+ time.tm_params.tp_dst_offset = 0;
+
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset;
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset;
+
+ *aLastModifiedTime = PR_ImplodeTime(&time);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetIsSynthetic(bool *aIsSynthetic)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aIsSynthetic = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetPermissions(uint32_t *aPermissions)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ // Always give user read access at least, this matches nsIZipReader's behaviour
+ *aPermissions = ((mEAttr >> 16) & 0xfff) | 0x100;
+ return NS_OK;
+}
+
+void nsZipHeader::Init(const nsACString & aPath, PRTime aDate, uint32_t aAttr,
+ uint32_t aOffset)
+{
+ NS_ASSERTION(!mInited, "Already initalised");
+
+ PRExplodedTime time;
+ PR_ExplodeTime(aDate, PR_LocalTimeParameters, &time);
+
+ mTime = time.tm_sec / 2 + (time.tm_min << 5) + (time.tm_hour << 11);
+ mDate = time.tm_mday + ((time.tm_month + 1) << 5) +
+ ((time.tm_year - 1980) << 9);
+
+ // Store modification timestamp as extra field
+ // First fill CDS extra field
+ mFieldLength = 9;
+ mExtraField = MakeUnique<uint8_t[]>(mFieldLength);
+ if (!mExtraField) {
+ mFieldLength = 0;
+ } else {
+ uint32_t pos = 0;
+ WRITE16(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_FIELD);
+ WRITE16(mExtraField.get(), &pos, 5);
+ WRITE8(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_MODTIME);
+ WRITE32(mExtraField.get(), &pos, aDate / PR_USEC_PER_SEC);
+
+ // Fill local extra field
+ mLocalExtraField = MakeUnique<uint8_t[]>(mFieldLength);
+ if (mLocalExtraField) {
+ mLocalFieldLength = mFieldLength;
+ memcpy(mLocalExtraField.get(), mExtraField.get(), mLocalFieldLength);
+ }
+ }
+
+ mEAttr = aAttr;
+ mOffset = aOffset;
+ mName = aPath;
+ mComment = NS_LITERAL_CSTRING("");
+ // Claim a UTF-8 path in case it needs it.
+ mFlags |= FLAGS_IS_UTF8;
+ mInited = true;
+}
+
+uint32_t nsZipHeader::GetFileHeaderLength()
+{
+ return ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength;
+}
+
+nsresult nsZipHeader::WriteFileHeader(nsIOutputStream *aStream)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ uint8_t buf[ZIP_FILE_HEADER_SIZE];
+ uint32_t pos = 0;
+ WRITE32(buf, &pos, ZIP_FILE_HEADER_SIGNATURE);
+ WRITE16(buf, &pos, mVersionNeeded);
+ WRITE16(buf, &pos, mFlags);
+ WRITE16(buf, &pos, mMethod);
+ WRITE16(buf, &pos, mTime);
+ WRITE16(buf, &pos, mDate);
+ WRITE32(buf, &pos, mCRC);
+ WRITE32(buf, &pos, mCSize);
+ WRITE32(buf, &pos, mUSize);
+ WRITE16(buf, &pos, mName.Length());
+ WRITE16(buf, &pos, mLocalFieldLength);
+
+ nsresult rv = ZW_WriteData(aStream, (const char *)buf, pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ZW_WriteData(aStream, mName.get(), mName.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mLocalFieldLength)
+ {
+ rv = ZW_WriteData(aStream, (const char *)mLocalExtraField.get(), mLocalFieldLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsZipHeader::GetCDSHeaderLength()
+{
+ return ZIP_CDS_HEADER_SIZE + mName.Length() + mComment.Length() +
+ mFieldLength;
+}
+
+nsresult nsZipHeader::WriteCDSHeader(nsIOutputStream *aStream)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ uint8_t buf[ZIP_CDS_HEADER_SIZE];
+ uint32_t pos = 0;
+ WRITE32(buf, &pos, ZIP_CDS_HEADER_SIGNATURE);
+ WRITE16(buf, &pos, mVersionMade);
+ WRITE16(buf, &pos, mVersionNeeded);
+ WRITE16(buf, &pos, mFlags);
+ WRITE16(buf, &pos, mMethod);
+ WRITE16(buf, &pos, mTime);
+ WRITE16(buf, &pos, mDate);
+ WRITE32(buf, &pos, mCRC);
+ WRITE32(buf, &pos, mCSize);
+ WRITE32(buf, &pos, mUSize);
+ WRITE16(buf, &pos, mName.Length());
+ WRITE16(buf, &pos, mFieldLength);
+ WRITE16(buf, &pos, mComment.Length());
+ WRITE16(buf, &pos, mDisk);
+ WRITE16(buf, &pos, mIAttr);
+ WRITE32(buf, &pos, mEAttr);
+ WRITE32(buf, &pos, mOffset);
+
+ nsresult rv = ZW_WriteData(aStream, (const char *)buf, pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ZW_WriteData(aStream, mName.get(), mName.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mExtraField) {
+ rv = ZW_WriteData(aStream, (const char *)mExtraField.get(), mFieldLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return ZW_WriteData(aStream, mComment.get(), mComment.Length());
+}
+
+nsresult nsZipHeader::ReadCDSHeader(nsIInputStream *stream)
+{
+ NS_ASSERTION(!mInited, "Already initalised");
+
+ uint8_t buf[ZIP_CDS_HEADER_SIZE];
+
+ nsresult rv = ZW_ReadData(stream, (char *)buf, ZIP_CDS_HEADER_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t pos = 0;
+ uint32_t signature = READ32(buf, &pos);
+ if (signature != ZIP_CDS_HEADER_SIGNATURE)
+ return NS_ERROR_FILE_CORRUPTED;
+
+ mVersionMade = READ16(buf, &pos);
+ mVersionNeeded = READ16(buf, &pos);
+ mFlags = READ16(buf, &pos);
+ mMethod = READ16(buf, &pos);
+ mTime = READ16(buf, &pos);
+ mDate = READ16(buf, &pos);
+ mCRC = READ32(buf, &pos);
+ mCSize = READ32(buf, &pos);
+ mUSize = READ32(buf, &pos);
+ uint16_t namelength = READ16(buf, &pos);
+ mFieldLength = READ16(buf, &pos);
+ uint16_t commentlength = READ16(buf, &pos);
+ mDisk = READ16(buf, &pos);
+ mIAttr = READ16(buf, &pos);
+ mEAttr = READ32(buf, &pos);
+ mOffset = READ32(buf, &pos);
+
+ if (namelength > 0) {
+ auto field = MakeUnique<char[]>(namelength);
+ NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
+ rv = ZW_ReadData(stream, field.get(), namelength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mName.Assign(field.get(), namelength);
+ }
+ else
+ mName = NS_LITERAL_CSTRING("");
+
+ if (mFieldLength > 0) {
+ mExtraField = MakeUnique<uint8_t[]>(mFieldLength);
+ NS_ENSURE_TRUE(mExtraField, NS_ERROR_OUT_OF_MEMORY);
+ rv = ZW_ReadData(stream, (char *)mExtraField.get(), mFieldLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (commentlength > 0) {
+ auto field = MakeUnique<char[]>(commentlength);
+ NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
+ rv = ZW_ReadData(stream, field.get(), commentlength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mComment.Assign(field.get(), commentlength);
+ }
+ else
+ mComment = NS_LITERAL_CSTRING("");
+
+ mInited = true;
+ return NS_OK;
+}
+
+const uint8_t * nsZipHeader::GetExtraField(uint16_t aTag, bool aLocal, uint16_t *aBlockSize)
+{
+ const uint8_t *buf = aLocal ? mLocalExtraField.get() : mExtraField.get();
+ uint32_t buflen = aLocal ? mLocalFieldLength : mFieldLength;
+ uint32_t pos = 0;
+ uint16_t tag, blocksize;
+
+ while (buf && (pos + 4) <= buflen) {
+ tag = READ16(buf, &pos);
+ blocksize = READ16(buf, &pos);
+
+ if (aTag == tag && (pos + blocksize) <= buflen) {
+ *aBlockSize = blocksize;
+ return buf + pos - 4;
+ }
+
+ pos += blocksize;
+ }
+
+ return nullptr;
+}
+
+/*
+ * Pad extra field to align data starting position to specified size.
+ */
+nsresult nsZipHeader::PadExtraField(uint32_t aOffset, uint16_t aAlignSize)
+{
+ uint32_t pad_size;
+ uint32_t pa_offset;
+ uint32_t pa_end;
+
+ // Check for range and power of 2.
+ if (aAlignSize < 2 || aAlignSize > 32768 ||
+ (aAlignSize & (aAlignSize - 1)) != 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Point to current starting data position.
+ aOffset += ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength;
+
+ // Calculate aligned offset.
+ pa_offset = aOffset & ~(aAlignSize - 1);
+ pa_end = pa_offset + aAlignSize;
+ pad_size = pa_end - aOffset;
+ if (pad_size == 0) {
+ return NS_OK;
+ }
+
+ // Leave enough room(at least 4 bytes) for valid values in extra field.
+ while (pad_size < 4) {
+ pad_size += aAlignSize;
+ }
+ // Extra field length is 2 bytes.
+ if (mLocalFieldLength + pad_size > 65535) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<uint8_t[]> field = Move(mLocalExtraField);
+ uint32_t pos = mLocalFieldLength;
+
+ mLocalExtraField = MakeUnique<uint8_t[]>(mLocalFieldLength + pad_size);
+ memcpy(mLocalExtraField.get(), field.get(), mLocalFieldLength);
+ // Use 0xFFFF as tag ID to avoid conflict with other IDs.
+ // For more information, please read "Extensible data fields" section in:
+ // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+ WRITE16(mLocalExtraField.get(), &pos, 0xFFFF);
+ WRITE16(mLocalExtraField.get(), &pos, pad_size - 4);
+ memset(mLocalExtraField.get() + pos, 0, pad_size - 4);
+ mLocalFieldLength += pad_size;
+
+ return NS_OK;
+}
diff --git a/modules/libjar/zipwriter/nsZipHeader.h b/modules/libjar/zipwriter/nsZipHeader.h
new file mode 100644
index 0000000000..f09aa2090a
--- /dev/null
+++ b/modules/libjar/zipwriter/nsZipHeader.h
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef _nsZipHeader_h_
+#define _nsZipHeader_h_
+
+#include "nsString.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsIZipReader.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+// High word is S_IFREG, low word is DOS file attribute
+#define ZIP_ATTRS_FILE 0x80000000
+// High word is S_IFDIR, low word is DOS dir attribute
+#define ZIP_ATTRS_DIRECTORY 0x40000010
+#define PERMISSIONS_FILE 0644
+#define PERMISSIONS_DIR 0755
+
+// Combine file type attributes with unix style permissions
+#define ZIP_ATTRS(p, a) ((p & 0xfff) << 16) | a
+
+class nsZipHeader final : public nsIZipEntry
+{
+ ~nsZipHeader()
+ {
+ mExtraField = nullptr;
+ mLocalExtraField = nullptr;
+ }
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIZIPENTRY
+
+ nsZipHeader() :
+ mCRC(0),
+ mCSize(0),
+ mUSize(0),
+ mEAttr(0),
+ mOffset(0),
+ mFieldLength(0),
+ mLocalFieldLength(0),
+ mVersionMade(0x0300 + 23), // Generated on Unix by v2.3 (matches infozip)
+ mVersionNeeded(20), // Requires v2.0 to extract
+ mFlags(0),
+ mMethod(0),
+ mTime(0),
+ mDate(0),
+ mDisk(0),
+ mIAttr(0),
+ mInited(false),
+ mWriteOnClose(false),
+ mExtraField(nullptr),
+ mLocalExtraField(nullptr)
+ {
+ }
+
+ uint32_t mCRC;
+ uint32_t mCSize;
+ uint32_t mUSize;
+ uint32_t mEAttr;
+ uint32_t mOffset;
+ uint32_t mFieldLength;
+ uint32_t mLocalFieldLength;
+ uint16_t mVersionMade;
+ uint16_t mVersionNeeded;
+ uint16_t mFlags;
+ uint16_t mMethod;
+ uint16_t mTime;
+ uint16_t mDate;
+ uint16_t mDisk;
+ uint16_t mIAttr;
+ bool mInited;
+ bool mWriteOnClose;
+ nsCString mName;
+ nsCString mComment;
+ mozilla::UniquePtr<uint8_t[]> mExtraField;
+ mozilla::UniquePtr<uint8_t[]> mLocalExtraField;
+
+ void Init(const nsACString & aPath, PRTime aDate, uint32_t aAttr,
+ uint32_t aOffset);
+ uint32_t GetFileHeaderLength();
+ nsresult WriteFileHeader(nsIOutputStream *aStream);
+ uint32_t GetCDSHeaderLength();
+ nsresult WriteCDSHeader(nsIOutputStream *aStream);
+ nsresult ReadCDSHeader(nsIInputStream *aStream);
+ const uint8_t * GetExtraField(uint16_t aTag, bool aLocal, uint16_t *aBlockSize);
+ nsresult PadExtraField(uint32_t aOffset, uint16_t aAlignSize);
+};
+
+#endif
diff --git a/modules/libjar/zipwriter/nsZipWriter.cpp b/modules/libjar/zipwriter/nsZipWriter.cpp
new file mode 100644
index 0000000000..25231fac0c
--- /dev/null
+++ b/modules/libjar/zipwriter/nsZipWriter.cpp
@@ -0,0 +1,1133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "nsZipWriter.h"
+
+#include <algorithm>
+
+#include "StreamFunctions.h"
+#include "nsZipDataStream.h"
+#include "nsISeekableStream.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStreamPump.h"
+#include "nsILoadInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsError.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsIFile.h"
+#include "prio.h"
+
+#define ZIP_EOCDR_HEADER_SIZE 22
+#define ZIP_EOCDR_HEADER_SIGNATURE 0x06054b50
+
+using namespace mozilla;
+
+/**
+ * nsZipWriter is used to create and add to zip files.
+ * It is based on the spec available at
+ * http://www.pkware.com/documents/casestudies/APPNOTE.TXT.
+ *
+ * The basic structure of a zip file created is slightly simpler than that
+ * illustrated in the spec because certain features of the zip format are
+ * unsupported:
+ *
+ * [local file header 1]
+ * [file data 1]
+ * .
+ * .
+ * .
+ * [local file header n]
+ * [file data n]
+ * [central directory]
+ * [end of central directory record]
+ */
+NS_IMPL_ISUPPORTS(nsZipWriter, nsIZipWriter,
+ nsIRequestObserver)
+
+nsZipWriter::nsZipWriter()
+ : mCDSOffset(0)
+ , mCDSDirty(false)
+ , mInQueue(false)
+{}
+
+nsZipWriter::~nsZipWriter()
+{
+ if (mStream && !mInQueue)
+ Close();
+}
+
+NS_IMETHODIMP nsZipWriter::GetComment(nsACString & aComment)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aComment = mComment;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::SetComment(const nsACString & aComment)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ mComment = aComment;
+ mCDSDirty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::GetInQueue(bool *aInQueue)
+{
+ *aInQueue = mInQueue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::GetFile(nsIFile **aFile)
+{
+ if (!mFile)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aFile = file);
+ return NS_OK;
+}
+
+/*
+ * Reads file entries out of an existing zip file.
+ */
+nsresult nsZipWriter::ReadFile(nsIFile *aFile)
+{
+ int64_t size;
+ nsresult rv = aFile->GetFileSize(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the file is too short, it cannot be a valid archive, thus we fail
+ // without even attempting to open it
+ NS_ENSURE_TRUE(size > ZIP_EOCDR_HEADER_SIZE, NS_ERROR_FILE_CORRUPTED);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint8_t buf[1024];
+ int64_t seek = size - 1024;
+ uint32_t length = 1024;
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(inputStream);
+
+ while (true) {
+ if (seek < 0) {
+ length += (int32_t)seek;
+ seek = 0;
+ }
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, seek);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ rv = ZW_ReadData(inputStream, (char *)buf, length);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ /*
+ * We have to backtrack from the end of the file until we find the
+ * CDS signature
+ */
+ // We know it's at least this far from the end
+ for (uint32_t pos = length - ZIP_EOCDR_HEADER_SIZE;
+ (int32_t)pos >= 0; pos--) {
+ uint32_t sig = PEEK32(buf + pos);
+ if (sig == ZIP_EOCDR_HEADER_SIGNATURE) {
+ // Skip down to entry count
+ pos += 10;
+ uint32_t entries = READ16(buf, &pos);
+ // Skip past CDS size
+ pos += 4;
+ mCDSOffset = READ32(buf, &pos);
+ uint32_t commentlen = READ16(buf, &pos);
+
+ if (commentlen == 0)
+ mComment.Truncate();
+ else if (pos + commentlen <= length)
+ mComment.Assign((const char *)buf + pos, commentlen);
+ else {
+ if ((seek + pos + commentlen) > size) {
+ inputStream->Close();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ auto field = MakeUnique<char[]>(commentlen);
+ NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ seek + pos);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ rv = ZW_ReadData(inputStream, field.get(), length);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ mComment.Assign(field.get(), commentlen);
+ }
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mCDSOffset);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ for (uint32_t entry = 0; entry < entries; entry++) {
+ nsZipHeader* header = new nsZipHeader();
+ if (!header) {
+ inputStream->Close();
+ mEntryHash.Clear();
+ mHeaders.Clear();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = header->ReadCDSHeader(inputStream);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ mEntryHash.Clear();
+ mHeaders.Clear();
+ return rv;
+ }
+ mEntryHash.Put(header->mName, mHeaders.Count());
+ if (!mHeaders.AppendObject(header))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return inputStream->Close();
+ }
+ }
+
+ if (seek == 0) {
+ // We've reached the start with no signature found. Corrupt.
+ inputStream->Close();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Overlap by the size of the end of cdr
+ seek -= (1024 - ZIP_EOCDR_HEADER_SIZE);
+ }
+ // Will never reach here in reality
+ NS_NOTREACHED("Loop should never complete");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsZipWriter::Open(nsIFile *aFile, int32_t aIoFlags)
+{
+ if (mStream)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ // Need to be able to write to the file
+ if (aIoFlags & PR_RDONLY)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = aFile->Clone(getter_AddRefs(mFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = mFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists && !(aIoFlags & PR_CREATE_FILE))
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ if (exists && !(aIoFlags & (PR_TRUNCATE | PR_WRONLY))) {
+ rv = ReadFile(mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCDSDirty = false;
+ }
+ else {
+ mCDSOffset = 0;
+ mCDSDirty = true;
+ mComment.Truncate();
+ }
+
+ // Silently drop PR_APPEND
+ aIoFlags &= 0xef;
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), mFile, aIoFlags);
+ if (NS_FAILED(rv)) {
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ return rv;
+ }
+
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(mStream), stream, 64 * 1024);
+ if (NS_FAILED(rv)) {
+ stream->Close();
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ return rv;
+ }
+
+ if (mCDSOffset > 0) {
+ rv = SeekCDS();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::GetEntry(const nsACString & aZipEntry,
+ nsIZipEntry **_retval)
+{
+ int32_t pos;
+ if (mEntryHash.Get(aZipEntry, &pos))
+ NS_ADDREF(*_retval = mHeaders[pos]);
+ else
+ *_retval = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::HasEntry(const nsACString & aZipEntry,
+ bool *_retval)
+{
+ *_retval = mEntryHash.Get(aZipEntry, nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryDirectory(const nsACString & aZipEntry,
+ PRTime aModTime, bool aQueue)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mModTime = aModTime;
+ item.mPermissions = PERMISSIONS_DIR;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+ return InternalAddEntryDirectory(aZipEntry, aModTime, PERMISSIONS_DIR);
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryFile(const nsACString & aZipEntry,
+ int32_t aCompression, nsIFile *aFile,
+ bool aQueue)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mCompression = aCompression;
+ rv = aFile->Clone(getter_AddRefs(item.mFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ bool exists;
+ rv = aFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isdir;
+ rv = aFile->IsDirectory(&isdir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRTime modtime;
+ rv = aFile->GetLastModifiedTime(&modtime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ modtime *= PR_USEC_PER_MSEC;
+
+ uint32_t permissions;
+ rv = aFile->GetPermissions(&permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isdir)
+ return InternalAddEntryDirectory(aZipEntry, modtime, permissions);
+
+ if (mEntryHash.Get(aZipEntry, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddEntryStream(aZipEntry, modtime, aCompression, inputStream,
+ false, permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return inputStream->Close();
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryChannel(const nsACString & aZipEntry,
+ PRTime aModTime,
+ int32_t aCompression,
+ nsIChannel *aChannel,
+ bool aQueue)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mModTime = aModTime;
+ item.mCompression = aCompression;
+ item.mPermissions = PERMISSIONS_FILE;
+ item.mChannel = aChannel;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+ if (mEntryHash.Get(aZipEntry, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_MaybeOpenChannelUsingOpen2(aChannel,
+ getter_AddRefs(inputStream));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddEntryStream(aZipEntry, aModTime, aCompression, inputStream,
+ false, PERMISSIONS_FILE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return inputStream->Close();
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
+ PRTime aModTime,
+ int32_t aCompression,
+ nsIInputStream *aStream,
+ bool aQueue)
+{
+ return AddEntryStream(aZipEntry, aModTime, aCompression, aStream, aQueue,
+ PERMISSIONS_FILE);
+}
+
+nsresult nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
+ PRTime aModTime,
+ int32_t aCompression,
+ nsIInputStream *aStream,
+ bool aQueue,
+ uint32_t aPermissions)
+{
+ NS_ENSURE_ARG_POINTER(aStream);
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mModTime = aModTime;
+ item.mCompression = aCompression;
+ item.mPermissions = aPermissions;
+ item.mStream = aStream;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+ if (mEntryHash.Get(aZipEntry, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ RefPtr<nsZipHeader> header = new nsZipHeader();
+ NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
+ header->Init(aZipEntry, aModTime, ZIP_ATTRS(aPermissions, ZIP_ATTRS_FILE),
+ mCDSOffset);
+ nsresult rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ SeekCDS();
+ return rv;
+ }
+
+ RefPtr<nsZipDataStream> stream = new nsZipDataStream();
+ if (!stream) {
+ SeekCDS();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = stream->Init(this, mStream, header, aCompression);
+ if (NS_FAILED(rv)) {
+ SeekCDS();
+ return rv;
+ }
+
+ rv = stream->ReadStream(aStream);
+ if (NS_FAILED(rv))
+ SeekCDS();
+ return rv;
+}
+
+NS_IMETHODIMP nsZipWriter::RemoveEntry(const nsACString & aZipEntry,
+ bool aQueue)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_REMOVE;
+ item.mZipEntry = aZipEntry;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ int32_t pos;
+ if (mEntryHash.Get(aZipEntry, &pos)) {
+ // Flush any remaining data before we seek.
+ nsresult rv = mStream->Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (pos < mHeaders.Count() - 1) {
+ // This is not the last entry, pull back the data.
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mHeaders[pos]->mOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekable = do_QueryInterface(inputStream);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mHeaders[pos + 1]->mOffset);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ uint32_t count = mCDSOffset - mHeaders[pos + 1]->mOffset;
+ uint32_t read = 0;
+ char buf[4096];
+ while (count > 0) {
+ read = std::min(count, (uint32_t) sizeof(buf));
+
+ rv = inputStream->Read(buf, read, &read);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ Cleanup();
+ return rv;
+ }
+
+ rv = ZW_WriteData(mStream, buf, read);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ Cleanup();
+ return rv;
+ }
+
+ count -= read;
+ }
+ inputStream->Close();
+
+ // Rewrite header offsets and update hash
+ uint32_t shift = (mHeaders[pos + 1]->mOffset -
+ mHeaders[pos]->mOffset);
+ mCDSOffset -= shift;
+ int32_t pos2 = pos + 1;
+ while (pos2 < mHeaders.Count()) {
+ mEntryHash.Put(mHeaders[pos2]->mName, pos2-1);
+ mHeaders[pos2]->mOffset -= shift;
+ pos2++;
+ }
+ }
+ else {
+ // Remove the last entry is just a case of moving the CDS
+ mCDSOffset = mHeaders[pos]->mOffset;
+ rv = SeekCDS();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mEntryHash.Remove(mHeaders[pos]->mName);
+ mHeaders.RemoveObjectAt(pos);
+ mCDSDirty = true;
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_FILE_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsZipWriter::ProcessQueue(nsIRequestObserver *aObserver,
+ nsISupports *aContext)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ mProcessObserver = aObserver;
+ mProcessContext = aContext;
+ mInQueue = true;
+
+ if (mProcessObserver)
+ mProcessObserver->OnStartRequest(nullptr, mProcessContext);
+
+ BeginProcessingNextItem();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::Close()
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ if (mCDSDirty) {
+ uint32_t size = 0;
+ for (int32_t i = 0; i < mHeaders.Count(); i++) {
+ nsresult rv = mHeaders[i]->WriteCDSHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ size += mHeaders[i]->GetCDSHeaderLength();
+ }
+
+ uint8_t buf[ZIP_EOCDR_HEADER_SIZE];
+ uint32_t pos = 0;
+ WRITE32(buf, &pos, ZIP_EOCDR_HEADER_SIGNATURE);
+ WRITE16(buf, &pos, 0);
+ WRITE16(buf, &pos, 0);
+ WRITE16(buf, &pos, mHeaders.Count());
+ WRITE16(buf, &pos, mHeaders.Count());
+ WRITE32(buf, &pos, size);
+ WRITE32(buf, &pos, mCDSOffset);
+ WRITE16(buf, &pos, mComment.Length());
+
+ nsresult rv = ZW_WriteData(mStream, (const char *)buf, pos);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ rv = ZW_WriteData(mStream, mComment.get(), mComment.Length());
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ rv = seekable->SetEOF();
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ // Go back and rewrite the file headers
+ for (int32_t i = 0; i < mHeaders.Count(); i++) {
+ nsZipHeader *header = mHeaders[i];
+ if (!header->mWriteOnClose)
+ continue;
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, header->mOffset);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ }
+ }
+
+ nsresult rv = mStream->Close();
+ mStream = nullptr;
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ mQueue.Clear();
+
+ return rv;
+}
+
+// Our nsIRequestObserver monitors removal operations performed on the queue
+NS_IMETHODIMP nsZipWriter::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (NS_FAILED(aStatusCode)) {
+ FinishQueue(aStatusCode);
+ Cleanup();
+ }
+
+ nsresult rv = mStream->Flush();
+ if (NS_FAILED(rv)) {
+ FinishQueue(rv);
+ Cleanup();
+ return rv;
+ }
+ rv = SeekCDS();
+ if (NS_FAILED(rv)) {
+ FinishQueue(rv);
+ return rv;
+ }
+
+ BeginProcessingNextItem();
+
+ return NS_OK;
+}
+
+/*
+ * Make all stored(uncompressed) files align to given alignment size.
+ */
+NS_IMETHODIMP nsZipWriter::AlignStoredFiles(uint16_t aAlignSize)
+{
+ nsresult rv;
+
+ // Check for range and power of 2.
+ if (aAlignSize < 2 || aAlignSize > 32768 ||
+ (aAlignSize & (aAlignSize - 1)) != 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (int i = 0; i < mHeaders.Count(); i++) {
+ nsZipHeader *header = mHeaders[i];
+
+ // Check whether this entry is file and compression method is stored.
+ bool isdir;
+ rv = header->GetIsDirectory(&isdir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (isdir || header->mMethod != 0) {
+ continue;
+ }
+ // Pad extra field to align data starting position to specified size.
+ uint32_t old_len = header->mLocalFieldLength;
+ rv = header->PadExtraField(header->mOffset, aAlignSize);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ // No padding means data already aligned.
+ uint32_t shift = header->mLocalFieldLength - old_len;
+ if (shift == 0) {
+ continue;
+ }
+
+ // Flush any remaining data before we start.
+ rv = mStream->Flush();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Open zip file for reading.
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISeekableStream> in_seekable = do_QueryInterface(inputStream);
+ nsCOMPtr<nsISeekableStream> out_seekable = do_QueryInterface(mStream);
+
+ uint32_t data_offset = header->mOffset + header->GetFileHeaderLength() - shift;
+ uint32_t count = mCDSOffset - data_offset;
+ uint32_t read;
+ char buf[4096];
+
+ // Shift data to aligned postion.
+ while (count > 0) {
+ read = std::min(count, (uint32_t) sizeof(buf));
+
+ rv = in_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ data_offset + count - read);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = inputStream->Read(buf, read, &read);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ data_offset + count - read + shift);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = ZW_WriteData(mStream, buf, read);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ count -= read;
+ }
+ inputStream->Close();
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ // Update current header
+ rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ header->mOffset);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ // Update offset of all other headers
+ int pos = i + 1;
+ while (pos < mHeaders.Count()) {
+ mHeaders[pos]->mOffset += shift;
+ pos++;
+ }
+ mCDSOffset += shift;
+ rv = SeekCDS();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCDSDirty = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsZipWriter::InternalAddEntryDirectory(const nsACString & aZipEntry,
+ PRTime aModTime,
+ uint32_t aPermissions)
+{
+ RefPtr<nsZipHeader> header = new nsZipHeader();
+ NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t zipAttributes = ZIP_ATTRS(aPermissions, ZIP_ATTRS_DIRECTORY);
+
+ if (aZipEntry.Last() != '/') {
+ nsCString dirPath;
+ dirPath.Assign(aZipEntry + NS_LITERAL_CSTRING("/"));
+ header->Init(dirPath, aModTime, zipAttributes, mCDSOffset);
+ }
+ else
+ header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset);
+
+ if (mEntryHash.Get(header->mName, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ nsresult rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ mCDSDirty = true;
+ mCDSOffset += header->GetFileHeaderLength();
+ mEntryHash.Put(header->mName, mHeaders.Count());
+
+ if (!mHeaders.AppendObject(header)) {
+ Cleanup();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Recovering from an error while adding a new entry is simply a case of
+ * seeking back to the CDS. If we fail trying to do that though then cleanup
+ * and bail out.
+ */
+nsresult nsZipWriter::SeekCDS()
+{
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mCDSOffset);
+ if (NS_FAILED(rv))
+ Cleanup();
+ return rv;
+}
+
+/*
+ * In a bad error condition this essentially closes down the component as best
+ * it can.
+ */
+void nsZipWriter::Cleanup()
+{
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ if (mStream)
+ mStream->Close();
+ mStream = nullptr;
+ mFile = nullptr;
+}
+
+/*
+ * Called when writing a file to the zip is complete.
+ */
+nsresult nsZipWriter::EntryCompleteCallback(nsZipHeader* aHeader,
+ nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus)) {
+ mEntryHash.Put(aHeader->mName, mHeaders.Count());
+ if (!mHeaders.AppendObject(aHeader)) {
+ mEntryHash.Remove(aHeader->mName);
+ SeekCDS();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mCDSDirty = true;
+ mCDSOffset += aHeader->mCSize + aHeader->GetFileHeaderLength();
+
+ if (mInQueue)
+ BeginProcessingNextItem();
+
+ return NS_OK;
+ }
+
+ nsresult rv = SeekCDS();
+ if (mInQueue)
+ FinishQueue(aStatus);
+ return rv;
+}
+
+inline nsresult nsZipWriter::BeginProcessingAddition(nsZipQueueItem* aItem,
+ bool* complete)
+{
+ if (aItem->mFile) {
+ bool exists;
+ nsresult rv = aItem->mFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isdir;
+ rv = aItem->mFile->IsDirectory(&isdir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aItem->mFile->GetLastModifiedTime(&aItem->mModTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aItem->mModTime *= PR_USEC_PER_MSEC;
+
+ rv = aItem->mFile->GetPermissions(&aItem->mPermissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isdir) {
+ // Set up for fall through to stream reader
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(aItem->mStream),
+ aItem->mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // If a dir then this will fall through to the plain dir addition
+ }
+
+ uint32_t zipAttributes = ZIP_ATTRS(aItem->mPermissions, ZIP_ATTRS_FILE);
+
+ if (aItem->mStream || aItem->mChannel) {
+ RefPtr<nsZipHeader> header = new nsZipHeader();
+ NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
+
+ header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes,
+ mCDSOffset);
+ nsresult rv = header->WriteFileHeader(mStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsZipDataStream> stream = new nsZipDataStream();
+ NS_ENSURE_TRUE(stream, NS_ERROR_OUT_OF_MEMORY);
+ rv = stream->Init(this, mStream, header, aItem->mCompression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aItem->mStream) {
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), aItem->mStream,
+ -1, -1, 0, 0, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pump->AsyncRead(stream, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(aItem->mChannel, stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+
+ // Must be plain directory addition
+ *complete = true;
+ return InternalAddEntryDirectory(aItem->mZipEntry, aItem->mModTime,
+ aItem->mPermissions);
+}
+
+inline nsresult nsZipWriter::BeginProcessingRemoval(int32_t aPos)
+{
+ // Open the zip file for reading
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream, -1, -1, 0,
+ 0, true);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), mStream, this);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mHeaders[aPos]->mOffset);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ uint32_t shift = (mHeaders[aPos + 1]->mOffset -
+ mHeaders[aPos]->mOffset);
+ mCDSOffset -= shift;
+ int32_t pos2 = aPos + 1;
+ while (pos2 < mHeaders.Count()) {
+ mEntryHash.Put(mHeaders[pos2]->mName, pos2 - 1);
+ mHeaders[pos2]->mOffset -= shift;
+ pos2++;
+ }
+
+ mEntryHash.Remove(mHeaders[aPos]->mName);
+ mHeaders.RemoveObjectAt(aPos);
+ mCDSDirty = true;
+
+ rv = pump->AsyncRead(listener, nullptr);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ Cleanup();
+ return rv;
+ }
+ return NS_OK;
+}
+
+/*
+ * Starts processing on the next item in the queue.
+ */
+void nsZipWriter::BeginProcessingNextItem()
+{
+ while (!mQueue.IsEmpty()) {
+
+ nsZipQueueItem next = mQueue[0];
+ mQueue.RemoveElementAt(0);
+
+ if (next.mOperation == OPERATION_REMOVE) {
+ int32_t pos = -1;
+ if (mEntryHash.Get(next.mZipEntry, &pos)) {
+ if (pos < mHeaders.Count() - 1) {
+ nsresult rv = BeginProcessingRemoval(pos);
+ if (NS_FAILED(rv)) FinishQueue(rv);
+ return;
+ }
+
+ mCDSOffset = mHeaders[pos]->mOffset;
+ nsresult rv = SeekCDS();
+ if (NS_FAILED(rv)) {
+ FinishQueue(rv);
+ return;
+ }
+ mEntryHash.Remove(mHeaders[pos]->mName);
+ mHeaders.RemoveObjectAt(pos);
+ }
+ else {
+ FinishQueue(NS_ERROR_FILE_NOT_FOUND);
+ return;
+ }
+ }
+ else if (next.mOperation == OPERATION_ADD) {
+ if (mEntryHash.Get(next.mZipEntry, nullptr)) {
+ FinishQueue(NS_ERROR_FILE_ALREADY_EXISTS);
+ return;
+ }
+
+ bool complete = false;
+ nsresult rv = BeginProcessingAddition(&next, &complete);
+ if (NS_FAILED(rv)) {
+ SeekCDS();
+ FinishQueue(rv);
+ return;
+ }
+ if (!complete)
+ return;
+ }
+ }
+
+ FinishQueue(NS_OK);
+}
+
+/*
+ * Ends processing with the given status.
+ */
+void nsZipWriter::FinishQueue(nsresult aStatus)
+{
+ nsCOMPtr<nsIRequestObserver> observer = mProcessObserver;
+ nsCOMPtr<nsISupports> context = mProcessContext;
+ // Clean up everything first in case the observer decides to queue more
+ // things
+ mProcessObserver = nullptr;
+ mProcessContext = nullptr;
+ mInQueue = false;
+
+ if (observer)
+ observer->OnStopRequest(nullptr, context, aStatus);
+}
diff --git a/modules/libjar/zipwriter/nsZipWriter.h b/modules/libjar/zipwriter/nsZipWriter.h
new file mode 100644
index 0000000000..f7fa1b1633
--- /dev/null
+++ b/modules/libjar/zipwriter/nsZipWriter.h
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef _nsZipWriter_h_
+#define _nsZipWriter_h_
+
+#include "nsIZipWriter.h"
+#include "nsIFileStreams.h"
+#include "nsIBufferedStreams.h"
+#include "nsIRequestObserver.h"
+#include "nsZipHeader.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsDataHashtable.h"
+#include "mozilla/Attributes.h"
+
+#define ZIPWRITER_CONTRACTID "@mozilla.org/zipwriter;1"
+#define ZIPWRITER_CID { 0x430d416c, 0xa722, 0x4ad1, \
+ { 0xbe, 0x98, 0xd9, 0xa4, 0x45, 0xf8, 0x5e, 0x3f } }
+
+#define OPERATION_ADD 0
+#define OPERATION_REMOVE 1
+struct nsZipQueueItem
+{
+public:
+ uint32_t mOperation;
+ nsCString mZipEntry;
+ nsCOMPtr<nsIFile> mFile;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIInputStream> mStream;
+ PRTime mModTime;
+ int32_t mCompression;
+ uint32_t mPermissions;
+};
+
+class nsZipWriter final : public nsIZipWriter,
+ public nsIRequestObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIZIPWRITER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ nsZipWriter();
+ nsresult EntryCompleteCallback(nsZipHeader *aHeader, nsresult aStatus);
+
+private:
+ ~nsZipWriter();
+
+ uint32_t mCDSOffset;
+ bool mCDSDirty;
+ bool mInQueue;
+
+ nsCOMPtr<nsIFile> mFile;
+ nsCOMPtr<nsIRequestObserver> mProcessObserver;
+ nsCOMPtr<nsISupports> mProcessContext;
+ nsCOMPtr<nsIOutputStream> mStream;
+ nsCOMArray<nsZipHeader> mHeaders;
+ nsTArray<nsZipQueueItem> mQueue;
+ nsDataHashtable<nsCStringHashKey, int32_t> mEntryHash;
+ nsCString mComment;
+
+ nsresult SeekCDS();
+ void Cleanup();
+ nsresult ReadFile(nsIFile *aFile);
+ nsresult InternalAddEntryDirectory(const nsACString & aZipEntry,
+ PRTime aModTime, uint32_t aPermissions);
+ nsresult BeginProcessingAddition(nsZipQueueItem* aItem, bool* complete);
+ nsresult BeginProcessingRemoval(int32_t aPos);
+ nsresult AddEntryStream(const nsACString & aZipEntry, PRTime aModTime,
+ int32_t aCompression, nsIInputStream *aStream,
+ bool aQueue, uint32_t aPermissions);
+ void BeginProcessingNextItem();
+ void FinishQueue(nsresult aStatus);
+};
+
+#endif
diff --git a/modules/libjar/zipwriter/test/unit/data/emptyfile.txt b/modules/libjar/zipwriter/test/unit/data/emptyfile.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/emptyfile.txt
diff --git a/modules/libjar/zipwriter/test/unit/data/smallfile.txt b/modules/libjar/zipwriter/test/unit/data/smallfile.txt
new file mode 100644
index 0000000000..e068fcf81d
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/smallfile.txt
@@ -0,0 +1 @@
+Small (16 bytes) \ No newline at end of file
diff --git a/modules/libjar/zipwriter/test/unit/data/test.png b/modules/libjar/zipwriter/test/unit/data/test.png
new file mode 100644
index 0000000000..c648f7299d
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test.png
Binary files differ
diff --git a/modules/libjar/zipwriter/test/unit/data/test.txt b/modules/libjar/zipwriter/test/unit/data/test.txt
new file mode 100644
index 0000000000..1040981a30
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test.txt
@@ -0,0 +1,5 @@
+This is a test text file for the zipwriter component.
+It will be made available in the unit test directory.
+It will also be compressed into a testcase zip file
+made by a 3rd party zip tool to test the opening of
+existing zip files.
diff --git a/modules/libjar/zipwriter/test/unit/data/test.zip b/modules/libjar/zipwriter/test/unit/data/test.zip
new file mode 100644
index 0000000000..96581fe8b5
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test.zip
Binary files differ
diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug399727.html b/modules/libjar/zipwriter/test/unit/data/test_bug399727.html
new file mode 100644
index 0000000000..a6556274f1
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test_bug399727.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="Content-Language" content="en-gb"><meta http-equiv="Content-Type" content="text/html; charset=windows-1252"><meta name="generator" content="Fog Creek CityDesk 2.0.19"><meta name="citydesk" content="BB40F561/251"><title>TK's Sleater-Kinney pages</title><style type="text/css">
+<!--
+body {
+ font-family: verdana, arial;
+ font-size: 10pt;
+ color: black;
+ background-color: #D0E8E8;
+}
+td {
+ font-family: verdana, arial;
+ font-size: 10pt;
+}
+hr {
+ color: #808080;
+ height: 1px;
+}
+caption {
+ font-family: verdana, arial;
+ font-size: 8pt;
+ color: #808080;
+ background-color: white;
+}
+.bigText {
+ font-family: verdana, arial;
+ font-size: 18pt;
+ font-weight: bold;
+ color: black;
+ padding-bottom: 10px;
+}
+.smallText {
+ font-family: verdana, arial;
+ font-size: 7pt;
+ color: #808080;
+}
+.tabText {
+ font-family: Courier, "Courier New", monospace;
+ font-size: 7pt;
+ color: #808080;
+}
+.emphasis
+{
+ font-weight: bold;
+ color: #000000;
+}
+.articleHeader {
+ padding-bottom: 4px;
+ border-bottom: #808080 dotted 1px;
+}
+.headerCell {
+ background-color: #689090;
+ color: white;
+ padding: 4px;
+}
+.navbarText {
+ color: white;
+ font-weight: bold;
+}
+.articleTable {
+ border: #808080 solid 1px;
+ background-color: white;
+ padding: 15px;
+}
+
+
+
+-->
+</style></head><body>
+
+
+
+
+
+
+
+
+
+
+<div align="center">
+ <center>
+ <table border="0" cellpadding="0" cellspacing="0" width="700">
+ <tbody><tr>
+ <td><p class="bigText">TK's Sleater-Kinney pages</p>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <table class="articleTable" border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+ <td class="headerCell" align="right"><p class="navbarText">
+ : <a class="navbarText" href="http://tk-jk.net/S-K/index.html"> <span class="1home">Home</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/Archive.html"> <span class="2archive">Archive</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/Tabs.html"> <span class="3tabs">Tabs</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/Drumtabs.html"> <span class="4drumtabs">Drum tabs</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/SetLists.html"> <span class="5setlists">Set Lists</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/About.html"> <span class="6about">About</span></a> :
+</p>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <div align="left">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+ <td width="100%">
+<p class="bigText"><strong>All Hands on the Bad One</strong>
+</p>
+<p></p><p><strong>All Hands on the Bad One, <em>"All Hands on the Bad One"</em></strong></p>
+<p dir="ltr">This, followed by <a href="http://tk-jk.net/S-K/Tabs/YouthDecay.html">Youth Decay</a>&nbsp;- wow - most bands would be happy just to have just one of these songs.&nbsp; S-K has a boatload.</p>
+<p dir="ltr">Tabbing this was very frustrating.&nbsp;Riff 3 is a
+masterpiece of guitar and vocal harmony, rhythm and guitar
+interplay.&nbsp; It has a bass intro and stacks guitars and vocals on
+top. This is the definition of Sleater-Kinney magic. You can't&nbsp;get
+it all done on two guitars.&nbsp; You can get close.&nbsp;&nbsp; I
+haven't done it justice.</p>
+<p dir="ltr">As usual the guitar 2's rhythms are vital to the S-K sound.&nbsp; It's still good if you don't get it just right.</p>
+<p dir="ltr">I sure hope I've done this well enough for you to figure out.</p>
+<ul dir="ltr">
+<li>
+<div style="margin-right: 0px;">Quarter note =&nbsp;153 or so.</div>
+</li><li>
+<div style="margin-right: 0px;">Standard tuning. But tuning down 3 half steps makes Riff 3 easier and more authentic.</div>
+</li><li>
+<div style="margin-right: 0px;">Tabbed&nbsp;where it felt good&nbsp;on the fretboard but there are alternatives.</div>
+</li><li>
+<div style="margin-right: 0px;">Tuning down is required for authenticity but it sounds great in standard tuning</div></li></ul><strong>
+</strong><p dir="ltr"><strong><br>1:</strong></p>
+<p></p><span><img alt="All Hands on the Bad One Riff 1" src="AllHandsontheBadOne_files/AHOTBO1.jpg" cd:pos="2" align="left" border="0" height="500" hspace="4" width="580"><br clear="all"></span>
+<p><strong></strong>&nbsp;</p>
+<p><strong>2:</strong></p><span><img alt="All Hands on the Bad One Riff 2" src="AllHandsontheBadOne_files/AHOTBO2.jpg" cd:pos="2" align="left" border="0" height="257" hspace="4" width="580"><br clear="all"></span>
+<p><strong></strong>&nbsp;</p>
+<p><strong>Riff 3 (3a and 3b) is a little masterpeice:</strong></p><span><img alt="All Hands on the Bad One Riff 3a" src="AllHandsontheBadOne_files/AHOTBO3a.jpg" cd:pos="2" align="left" border="0" height="446" hspace="4" width="580"><br clear="all"></span>
+<p><span><img alt="All Hands on the Bad One Riff 3b" src="AllHandsontheBadOne_files/AHOTBO3b.jpg" cd:pos="2" align="left" border="0" height="179" hspace="4" width="580"><br clear="all"></span></p>
+<p>&nbsp;</p>
+<p>The Outro</p>
+<p><span><img alt="All Hands on the Bad One Riff 4" src="AllHandsontheBadOne_files/AHOTBO4.jpg" cd:pos="2" align="left" border="0" height="216" hspace="4" width="580"><br clear="all"></span></p><p></p>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <hr>
+ <center>
+ <div class="smallText">
+ The views expressed within this site pretty much represent those of the author.<br>
+ Copyright (c) 2002 <a href="http://tk-jk.net/">Terry Kearns</a>. All rights reserved.
+ </div>
+ </center>
+ </td>
+ </tr>
+ </tbody></table>
+ </td>
+ </tr>
+ </tbody></table>
+ </center>
+</div>
+</body></html> \ No newline at end of file
diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug399727.zlib b/modules/libjar/zipwriter/test/unit/data/test_bug399727.zlib
new file mode 100644
index 0000000000..080b63c520
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test_bug399727.zlib
Binary files differ
diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug446708/thumbs/st14-1.tiff b/modules/libjar/zipwriter/test/unit/data/test_bug446708/thumbs/st14-1.tiff
new file mode 100644
index 0000000000..6b04657580
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test_bug446708/thumbs/st14-1.tiff
Binary files differ
diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug717061.gz b/modules/libjar/zipwriter/test/unit/data/test_bug717061.gz
new file mode 100644
index 0000000000..f990c6e519
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test_bug717061.gz
Binary files differ
diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug717061.html b/modules/libjar/zipwriter/test/unit/data/test_bug717061.html
new file mode 100644
index 0000000000..80ce0319b4
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/data/test_bug717061.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="Content-Language" content="en-us"><title>Lorem Ipsum</title><style type="text/css">
+body {
+ font-family: verdana, arial;
+ font-size: 10pt;
+ color: black;
+ background-color: #D0E8E8;
+}
+</style></head><body>
+<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
+
+<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
+
+<p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.</p>
+
+</body></html>
diff --git a/modules/libjar/zipwriter/test/unit/head_zipwriter.js b/modules/libjar/zipwriter/test/unit/head_zipwriter.js
new file mode 100644
index 0000000000..bc77455a5f
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/head_zipwriter.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+const NS_ERROR_IN_PROGRESS = 2152398863;
+
+const PR_RDONLY = 0x01
+const PR_WRONLY = 0x02
+const PR_RDWR = 0x04
+const PR_CREATE_FILE = 0x08
+const PR_APPEND = 0x10
+const PR_TRUNCATE = 0x20
+const PR_SYNC = 0x40
+const PR_EXCL = 0x80
+
+const ZIP_EOCDR_HEADER_SIZE = 22;
+const ZIP_FILE_HEADER_SIZE = 30;
+const ZIP_CDS_HEADER_SIZE = 46;
+const ZIP_METHOD_STORE = 0
+const ZIP_METHOD_DEFLATE = 8
+const ZIP_EXTENDED_TIMESTAMP_SIZE = 9;
+
+const PR_USEC_PER_MSEC = 1000;
+const PR_USEC_PER_SEC = 1000000;
+const PR_MSEC_PER_SEC = 1000;
+
+const DATA_DIR = "data/";
+
+var ioSvc = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+var ZipWriter = Components.Constructor("@mozilla.org/zipwriter;1",
+ "nsIZipWriter");
+var ZipReader = Components.Constructor("@mozilla.org/libjar/zip-reader;1",
+ "nsIZipReader", "open");
+
+var tmpDir = do_get_profile();
+var tmpFile = tmpDir.clone();
+tmpFile.append("zipwriter-test.zip");
+if (tmpFile.exists())
+ tmpFile.remove(true);
+
+var zipW = new ZipWriter();
diff --git a/modules/libjar/zipwriter/test/unit/tail_zipwriter.js b/modules/libjar/zipwriter/test/unit/tail_zipwriter.js
new file mode 100644
index 0000000000..033cc23c29
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/tail_zipwriter.js
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+try {
+ zipW.close();
+}
+catch (e) {
+ // Just ignore a failure here and attempt to delete the file anyway.
+}
+
+if (tmpFile.exists())
+ tmpFile.remove(true);
diff --git a/modules/libjar/zipwriter/test/unit/test_alignment.js b/modules/libjar/zipwriter/test/unit/test_alignment.js
new file mode 100644
index 0000000000..da497253c8
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_alignment.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DATA = "ZIP WRITER TEST DATA";
+const FILENAME = "test_data.txt";
+const CRC = 0xe6164331;
+const time = 1199145600000; // Jan 1st 2008
+
+var TESTS = [
+ {
+ name: "test.txt",
+ compression: Ci.nsIZipWriter.COMPRESSION_DEFAULT
+ },
+ {
+ name: "test.png",
+ compression: Ci.nsIZipWriter.COMPRESSION_NONE
+ }
+];
+
+function swap16(n)
+{
+ return (((n >> 8) & 0xFF) << 0) |
+ (((n >> 0) & 0xFF) << 8);
+}
+
+function swap32(n)
+{
+ return (((n >> 24) & 0xFF) << 0) |
+ (((n >> 16) & 0xFF) << 8) |
+ (((n >> 8) & 0xFF) << 16) |
+ (((n >> 0) & 0xFF) << 24);
+}
+
+function move_to_data(bis, offset)
+{
+ bis.readBytes(18); // Move to compressed size
+ var size = swap32(bis.read32());
+ bis.readBytes(4);
+ var file_len = swap16(bis.read16());
+ var extra_len = swap16(bis.read16());
+ var file = bis.readBytes(file_len);
+ bis.readBytes(extra_len);
+ offset += ZIP_FILE_HEADER_SIZE + file_len + extra_len;
+
+ return {offset: offset, size: size};
+}
+
+function test_alignment(align_size)
+{
+ // Create zip for testing.
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ for (var i = 0; i < TESTS.length; i++) {
+ var source = do_get_file(DATA_DIR + TESTS[i].name);
+ zipW.addEntryFile(TESTS[i].name, TESTS[i].compression, source, false);
+ }
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(DATA, DATA.length);
+ zipW.addEntryStream(FILENAME, time * PR_USEC_PER_MSEC,
+ Ci.nsIZipWriter.COMPRESSION_NONE, stream, false);
+ zipW.alignStoredFiles(align_size);
+ zipW.close();
+
+ // Check data can be decompressed.
+ var zipR = new ZipReader(tmpFile);
+ var stream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ stream.init(zipR.getInputStream(FILENAME));
+ var result = stream.read(DATA.length);
+ do_check_eq(result, DATA);
+ stream.close();
+ zipR.close();
+
+ // Check data is correct and aligned.
+ var fis = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fis.init(tmpFile, -1, -1, null);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fis);
+ var offset = 0;
+
+ var ret = move_to_data(bis, offset); // "test.txt"
+ offset = ret.offset;
+ bis.readBytes(ret.size);
+ offset += ret.size;
+
+ ret = move_to_data(bis, offset); // "test.png"
+ offset = ret.offset;
+ do_check_eq(offset % align_size, 0);
+ bis.readBytes(ret.size);
+ offset += ret.size;
+
+ ret = move_to_data(bis, offset); // "test_data.txt"
+ offset = ret.offset;
+ var result = bis.readBytes(DATA.length);
+ do_check_eq(result, DATA);
+ do_check_eq(offset % align_size, 0);
+
+ fis.close();
+ bis.close();
+}
+
+function run_test()
+{
+ test_alignment(2);
+ test_alignment(4);
+ test_alignment(16);
+ test_alignment(4096);
+ test_alignment(32768);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_asyncadd.js b/modules/libjar/zipwriter/test/unit/test_asyncadd.js
new file mode 100644
index 0000000000..a6659f61ad
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_asyncadd.js
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// Values taken from using zipinfo to list the test.zip contents
+var TESTS = [
+ {
+ name: "test.txt",
+ size: 232,
+ crc: 0x0373ac26
+ },
+ {
+ name: "test.png",
+ size: 3402,
+ crc: 0x504a5c30
+ }
+];
+
+var size = 0;
+
+var observer = {
+ onStartRequest: function(request, context)
+ {
+ },
+
+ onStopRequest: function(request, context, status)
+ {
+ do_check_eq(status, Components.results.NS_OK);
+
+ zipW.close();
+ size += ZIP_EOCDR_HEADER_SIZE;
+
+ do_check_eq(size, tmpFile.fileSize);
+
+ // Test the stored data with the zipreader
+ var zipR = new ZipReader(tmpFile);
+
+ for (var i = 0; i < TESTS.length; i++) {
+ var source = do_get_file(DATA_DIR + TESTS[i].name);
+ for (let method in methods) {
+ var entryName = method + "/" + TESTS[i].name;
+ do_check_true(zipR.hasEntry(entryName));
+
+ var entry = zipR.getEntry(entryName);
+ do_check_eq(entry.realSize, TESTS[i].size);
+ do_check_eq(entry.size, TESTS[i].size);
+ do_check_eq(entry.CRC32, TESTS[i].crc);
+ do_check_eq(Math.floor(entry.lastModifiedTime / PR_USEC_PER_SEC),
+ Math.floor(source.lastModifiedTime / PR_MSEC_PER_SEC));
+
+ zipR.test(entryName);
+ }
+ }
+
+ zipR.close();
+ do_test_finished();
+ }
+};
+
+var methods = {
+ file: function method_file(entry, source)
+ {
+ zipW.addEntryFile(entry, Ci.nsIZipWriter.COMPRESSION_NONE, source,
+ true);
+ },
+ channel: function method_channel(entry, source)
+ {
+ zipW.addEntryChannel(entry, source.lastModifiedTime * PR_MSEC_PER_SEC,
+ Ci.nsIZipWriter.COMPRESSION_NONE,
+ NetUtil.newChannel({
+ uri: ioSvc.newFileURI(source),
+ loadUsingSystemPrincipal: true
+ }), true);
+ },
+ stream: function method_stream(entry, source)
+ {
+ zipW.addEntryStream(entry, source.lastModifiedTime * PR_MSEC_PER_SEC,
+ Ci.nsIZipWriter.COMPRESSION_NONE,
+ NetUtil.newChannel({
+ uri: ioSvc.newFileURI(source),
+ loadUsingSystemPrincipal: true
+ }).open2(), true);
+ }
+}
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ for (var i = 0; i < TESTS.length; i++) {
+ var source = do_get_file(DATA_DIR+TESTS[i].name);
+ for (let method in methods) {
+ var entry = method + "/" + TESTS[i].name;
+ methods[method](entry, source);
+ size += ZIP_FILE_HEADER_SIZE + ZIP_CDS_HEADER_SIZE +
+ (ZIP_EXTENDED_TIMESTAMP_SIZE * 2) +
+ (entry.length*2) + TESTS[i].size;
+ }
+ }
+ do_test_pending();
+ zipW.processQueue(observer, null);
+ do_check_true(zipW.inQueue);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_asyncbadadd.js b/modules/libjar/zipwriter/test/unit/test_asyncbadadd.js
new file mode 100644
index 0000000000..2f8ebe6449
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_asyncbadadd.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const FILENAME = "missing.txt";
+
+var observer = {
+ onStartRequest: function(request, context)
+ {
+ },
+
+ onStopRequest: function(request, context, status)
+ {
+ do_check_eq(status, Components.results.NS_ERROR_FILE_NOT_FOUND);
+ zipW.close();
+ do_check_eq(ZIP_EOCDR_HEADER_SIZE, tmpFile.fileSize);
+ do_test_finished();
+ }
+};
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ var source = tmpDir.clone();
+ source.append(FILENAME);
+ zipW.addEntryFile(FILENAME, Ci.nsIZipWriter.COMPRESSION_NONE, source, true);
+
+ do_test_pending();
+ zipW.processQueue(observer, null);
+
+ // With nothing to actually do the queue would have completed immediately
+ do_check_false(zipW.inQueue);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_asyncbadremove.js b/modules/libjar/zipwriter/test/unit/test_asyncbadremove.js
new file mode 100644
index 0000000000..609d4345df
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_asyncbadremove.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const FILENAME = "missing.txt";
+
+var observer = {
+ onStartRequest: function(request, context)
+ {
+ },
+
+ onStopRequest: function(request, context, status)
+ {
+ do_check_eq(status, Components.results.NS_ERROR_FILE_NOT_FOUND);
+ zipW.close();
+ do_check_eq(ZIP_EOCDR_HEADER_SIZE, tmpFile.fileSize);
+ do_test_finished();
+ }
+};
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ zipW.removeEntry(FILENAME, true);
+ do_test_pending();
+ zipW.processQueue(observer, null);
+
+ // With nothing to actually do the queue would have completed immediately
+ do_check_false(zipW.inQueue);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_asyncremove.js b/modules/libjar/zipwriter/test/unit/test_asyncremove.js
new file mode 100644
index 0000000000..c44ff3354d
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_asyncremove.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+var TESTS = [
+ "test.txt",
+ "test.png"
+];
+
+var observer = {
+ onStartRequest: function(request, context)
+ {
+ },
+
+ onStopRequest: function(request, context, status)
+ {
+ do_check_eq(status, Components.results.NS_OK);
+
+ zipW.close();
+
+ // Empty zip file should just be the end of central directory marker
+ var newTmpFile = tmpFile.clone();
+ do_check_eq(newTmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE);
+ do_test_finished();
+ }
+};
+
+function run_test()
+{
+ // Copy our test zip to the tmp dir so we can modify it
+ var testzip = do_get_file(DATA_DIR + "test.zip");
+ testzip.copyTo(tmpDir, tmpFile.leafName);
+
+ do_check_true(tmpFile.exists());
+
+ zipW.open(tmpFile, PR_RDWR);
+
+ for (var i = 0; i < TESTS.length; i++) {
+ do_check_true(zipW.hasEntry(TESTS[i]));
+ zipW.removeEntry(TESTS[i], true);
+ }
+
+ do_test_pending();
+ zipW.processQueue(observer, null);
+ do_check_true(zipW.inQueue);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_bug399727.js b/modules/libjar/zipwriter/test/unit/test_bug399727.js
new file mode 100644
index 0000000000..065cacae3b
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug399727.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function BinaryComparer(file, callback) {
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+ this.length = file.fileSize;
+ this.fileStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ this.fileStream.setInputStream(fstream);
+ this.offset = 0;
+ this.callback = callback;
+}
+
+BinaryComparer.prototype = {
+ fileStream: null,
+ offset: null,
+ length: null,
+ callback: null,
+
+ onStartRequest: function(aRequest, aContext) {
+ },
+
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ this.fileStream.close();
+ do_check_eq(aStatusCode, Components.results.NS_OK);
+ do_check_eq(this.offset, this.length);
+ this.callback();
+ },
+
+ onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+ var stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(aInputStream);
+ var source, actual;
+ for (var i = 0; i < aCount; i++) {
+ try {
+ source = this.fileStream.read8();
+ }
+ catch (e) {
+ do_throw("Unable to read from file at offset " + this.offset + " " + e);
+ }
+ try {
+ actual = stream.read8();
+ }
+ catch (e) {
+ do_throw("Unable to read from converted stream at offset " + this.offset + " " + e);
+ }
+ if (source != actual)
+ do_throw("Invalid value " + actual + " at offset " + this.offset + ", should have been " + source);
+ this.offset++;
+ }
+ }
+}
+
+function comparer_callback()
+{
+ do_test_finished();
+}
+
+function run_test()
+{
+ var source = do_get_file(DATA_DIR + "test_bug399727.html");
+ var comparer = new BinaryComparer(do_get_file(DATA_DIR + "test_bug399727.zlib"),
+ comparer_callback);
+
+ // Prepare the stream converter
+ var scs = Cc["@mozilla.org/streamConverters;1"].
+ getService(Ci.nsIStreamConverterService);
+ var converter = scs.asyncConvertData("uncompressed", "deflate", comparer, null);
+
+ // Open the expected output file
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(source, -1, 0, 0);
+
+ // Set up a pump to push data from the file to the stream converter
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"].
+ createInstance(Ci.nsIInputStreamPump);
+ pump.init(fstream, -1, -1, 0, 0, true);
+ pump.asyncRead(converter, null);
+ do_test_pending();
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_bug419769_1.js b/modules/libjar/zipwriter/test/unit/test_bug419769_1.js
new file mode 100644
index 0000000000..cd81a31c31
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug419769_1.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DATA = "";
+const FILENAME = "test.txt";
+const CRC = 0x00000000;
+// XXX Must use a constant time here away from DST changes. See bug 402434.
+const time = 1199145600000; // Jan 1st 2008
+
+function testpass(source)
+{
+ // Should exist.
+ do_check_true(source.hasEntry(FILENAME));
+
+ var entry = source.getEntry(FILENAME);
+ do_check_neq(entry, null);
+
+ do_check_false(entry.isDirectory);
+
+ // Should be stored
+ do_check_eq(entry.compression, ZIP_METHOD_DEFLATE);
+
+ do_check_eq(entry.lastModifiedTime / PR_USEC_PER_MSEC, time);
+
+ // File size should match our data size.
+ do_check_eq(entry.realSize, DATA.length);
+
+ // Check that the CRC is accurate
+ do_check_eq(entry.CRC32, CRC);
+}
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ // Shouldn't be there to start with.
+ do_check_false(zipW.hasEntry(FILENAME));
+
+ do_check_false(zipW.inQueue);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(DATA, DATA.length);
+ zipW.addEntryStream(FILENAME, time * PR_USEC_PER_MSEC,
+ Ci.nsIZipWriter.COMPRESSION_BEST, stream, false);
+
+ // Check that zip state is right at this stage.
+ testpass(zipW);
+ zipW.close();
+
+ // Check to see if we get the same results loading afresh.
+ zipW.open(tmpFile, PR_RDWR);
+ testpass(zipW);
+ zipW.close();
+
+ // Test the stored data with the zipreader
+ var zipR = new ZipReader(tmpFile);
+ testpass(zipR);
+ zipR.test(FILENAME);
+ var stream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ stream.init(zipR.getInputStream(FILENAME));
+ var result = stream.read(DATA.length);
+ stream.close();
+ zipR.close();
+
+ do_check_eq(result, DATA);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_bug419769_2.js b/modules/libjar/zipwriter/test/unit/test_bug419769_2.js
new file mode 100644
index 0000000000..f46ab395e2
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug419769_2.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DATA = "";
+const FILENAME = "test.txt";
+const CRC = 0x00000000;
+const time = Date.now();
+
+function testpass(source)
+{
+ // Should exist.
+ do_check_true(source.hasEntry(FILENAME));
+
+ var entry = source.getEntry(FILENAME);
+ do_check_neq(entry, null);
+
+ do_check_false(entry.isDirectory);
+
+ // Should be stored
+ do_check_eq(entry.compression, ZIP_METHOD_DEFLATE);
+
+ // File size should match our data size.
+ do_check_eq(entry.realSize, DATA.length);
+
+ // Check that the CRC is accurate
+ do_check_eq(entry.CRC32, CRC);
+}
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ // Shouldn't be there to start with.
+ do_check_false(zipW.hasEntry(FILENAME));
+
+ do_check_false(zipW.inQueue);
+
+ var file = do_get_file(DATA_DIR + "emptyfile.txt");
+ zipW.addEntryFile(FILENAME, Ci.nsIZipWriter.COMPRESSION_BEST, file, false);
+
+ // Check that zip state is right at this stage.
+ testpass(zipW);
+ zipW.close();
+
+ // Check to see if we get the same results loading afresh.
+ zipW.open(tmpFile, PR_RDWR);
+ testpass(zipW);
+ zipW.close();
+
+ // Test the stored data with the zipreader
+ var zipR = new ZipReader(tmpFile);
+ testpass(zipR);
+ zipR.test(FILENAME);
+ var stream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ stream.init(zipR.getInputStream(FILENAME));
+ var result = stream.read(DATA.length);
+ stream.close();
+ zipR.close();
+
+ do_check_eq(result, DATA);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_bug425768.js b/modules/libjar/zipwriter/test/unit/test_bug425768.js
new file mode 100644
index 0000000000..f597673423
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug425768.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DIRNAME = "test/";
+const time = Date.now();
+
+function run_test()
+{
+ // Copy in the test file.
+ var source = do_get_file("data/test.zip");
+ source.copyTo(tmpFile.parent, tmpFile.leafName);
+
+ // Open it and add something so the CDS is rewritten.
+ zipW.open(tmpFile, PR_RDWR | PR_APPEND);
+ zipW.addEntryDirectory(DIRNAME, time * PR_USEC_PER_MSEC, false);
+ do_check_true(zipW.hasEntry(DIRNAME));
+ zipW.close();
+
+ var zipR = new ZipReader(tmpFile);
+ do_check_true(zipR.hasEntry(DIRNAME));
+ zipR.close();
+
+ // Adding the directory would have added a fixed amount to the file size.
+ // Any difference suggests the CDS was written out incorrectly.
+ var extra = ZIP_FILE_HEADER_SIZE + ZIP_CDS_HEADER_SIZE +
+ (DIRNAME.length * 2) + (ZIP_EXTENDED_TIMESTAMP_SIZE * 2);
+
+ do_check_eq(source.fileSize + extra, tmpFile.fileSize);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_bug433248.js b/modules/libjar/zipwriter/test/unit/test_bug433248.js
new file mode 100644
index 0000000000..15f2e4970c
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug433248.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test()
+{
+ var test;
+ // zipW is an uninitialised zipwriter at this point.
+ try {
+ test = zipW.file;
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ try {
+ test = zipW.comment;
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ try {
+ zipW.comment = "test";
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ try {
+ zipW.addEntryDirectory("test", 0, false);
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ try {
+ zipW.addEntryFile("test", Ci.nsIZipWriter.COMPRESSION_DEFAULT, tmpDir, false);
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ try {
+ zipW.removeEntry("test", false);
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ try {
+ zipW.processQueue(null, null);
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ try {
+ zipW.close();
+ do_throw("Should have thrown uninitialized error.");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_NOT_INITIALIZED);
+ }
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_bug446708.js b/modules/libjar/zipwriter/test/unit/test_bug446708.js
new file mode 100644
index 0000000000..2b4e7a0ce9
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug446708.js
@@ -0,0 +1,37 @@
+function run_test() {
+ var testBundle = do_get_file("data/test_bug446708");
+
+ RecursivelyZipDirectory(testBundle);
+}
+
+// Add |file| to the zip. |path| is the current path for the file.
+function AddToZip(zipWriter, path, file)
+{
+ var currentPath = path + file.leafName;
+
+ if (file.isDirectory()) {
+ currentPath += "/";
+ }
+
+ // THIS IS WHERE THE ERROR OCCURS, FOR THE FILE "st14-1.tiff" IN "test_bug446708"
+ zipWriter.addEntryFile(currentPath, Ci.nsIZipWriter.COMPRESSION_DEFAULT, file, false);
+
+ // if it's a dir, continue adding its contents recursively...
+ if (file.isDirectory()) {
+ var entries = file.QueryInterface(Components.interfaces.nsIFile).directoryEntries;
+ while (entries.hasMoreElements()) {
+ var entry = entries.getNext().QueryInterface(Components.interfaces.nsIFile);
+ AddToZip(zipWriter, currentPath, entry);
+ }
+ }
+
+ // ...otherwise, we're done
+}
+
+function RecursivelyZipDirectory(bundle)
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ AddToZip(zipW, "", bundle);
+ zipW.close();
+}
+
diff --git a/modules/libjar/zipwriter/test/unit/test_bug467740.js b/modules/libjar/zipwriter/test/unit/test_bug467740.js
new file mode 100644
index 0000000000..cdb2b6042c
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug467740.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test()
+{
+ // In this test we try to open some files that aren't archives:
+ // - An empty file, that is certainly not an archive.
+ // - A file that couldn't be mistaken for archive, since it is too small.
+ // - A file that could be mistaken for archive, if we checked only the file
+ // size, but is invalid since it contains no ZIP signature.
+ var invalidArchives = ["emptyfile.txt", "smallfile.txt", "test.png"];
+
+ invalidArchives.forEach(function(invalidArchive) {
+ // Get a reference to the invalid file
+ var invalidFile = do_get_file(DATA_DIR + invalidArchive);
+
+ // Opening the invalid file should fail (but not crash)
+ try {
+ zipW.open(invalidFile, PR_RDWR);
+ do_throw("Should have thrown NS_ERROR_FILE_CORRUPTED on " +
+ invalidArchive + " !");
+ } catch (e if (e instanceof Ci.nsIException &&
+ e.result == Components.results.NS_ERROR_FILE_CORRUPTED)) {
+ // do nothing
+ }
+ });
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_bug717061.js b/modules/libjar/zipwriter/test/unit/test_bug717061.js
new file mode 100644
index 0000000000..35163a4646
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_bug717061.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0
+ */
+
+function BinaryComparer(file, callback) {
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+ this.length = file.fileSize;
+ this.fileStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ this.fileStream.setInputStream(fstream);
+ this.offset = 0;
+ this.callback = callback;
+}
+
+BinaryComparer.prototype = {
+ fileStream: null,
+ offset: null,
+ length: null,
+ callback: null,
+
+ onStartRequest: function(aRequest, aContext) {
+ },
+
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ this.fileStream.close();
+ do_check_eq(aStatusCode, Components.results.NS_OK);
+ do_check_eq(this.offset, this.length);
+ this.callback();
+ },
+
+ onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+ var stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(aInputStream);
+ var source, actual;
+ for (var i = 0; i < aCount; i++) {
+ try {
+ source = this.fileStream.read8();
+ }
+ catch (e) {
+ do_throw("Unable to read from file at offset " + this.offset + " " + e);
+ }
+ try {
+ actual = stream.read8();
+ }
+ catch (e) {
+ do_throw("Unable to read from converted stream at offset " + this.offset + " " + e);
+ }
+ // The byte at offset 9 is the OS byte (see RFC 1952, section 2.3), which
+ // can legitimately differ when the source is compressed on different
+ // operating systems. The actual .gz for this test was created on a Unix
+ // system, but we want the test to work correctly everywhere. So ignore
+ // the byte at offset 9.
+ if (this.offset == 9)
+ ;
+ else if (source != actual)
+ do_throw("Invalid value " + actual + " at offset " + this.offset + ", should have been " + source);
+ this.offset++;
+ }
+ }
+}
+
+function comparer_callback()
+{
+ do_test_finished();
+}
+
+function run_test()
+{
+ var source = do_get_file(DATA_DIR + "test_bug717061.html");
+ var comparer = new BinaryComparer(do_get_file(DATA_DIR + "test_bug717061.gz"),
+ comparer_callback);
+
+ // Prepare the stream converter
+ var scs = Cc["@mozilla.org/streamConverters;1"].
+ getService(Ci.nsIStreamConverterService);
+ var converter = scs.asyncConvertData("uncompressed", "gzip", comparer, null);
+
+ // Open the expected output file
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(source, -1, 0, 0);
+
+ // Set up a pump to push data from the file to the stream converter
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"].
+ createInstance(Ci.nsIInputStreamPump);
+ pump.init(fstream, -1, -1, 0, 0, true);
+ pump.asyncRead(converter, null);
+ do_test_pending();
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_createempty.js b/modules/libjar/zipwriter/test/unit/test_createempty.js
new file mode 100644
index 0000000000..4de1992e42
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_createempty.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ zipW.close();
+
+ // Should have created a zip file
+ do_check_true(tmpFile.exists());
+
+ // Empty zip file should just be the end of central directory marker
+ do_check_eq(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_deflatedata.js b/modules/libjar/zipwriter/test/unit/test_deflatedata.js
new file mode 100644
index 0000000000..6c0f39bc2b
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_deflatedata.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DATA = "ZIP WRITER TEST DATA";
+const FILENAME = "test.txt";
+const CRC = 0xe6164331;
+// XXX Must use a constant time here away from DST changes. See bug 402434.
+const time = 1199145600000; // Jan 1st 2008
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ // Shouldn't be there to start with.
+ do_check_false(zipW.hasEntry(FILENAME));
+
+ do_check_false(zipW.inQueue);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(DATA, DATA.length);
+ zipW.addEntryStream(FILENAME, time * PR_USEC_PER_MSEC,
+ Ci.nsIZipWriter.COMPRESSION_BEST, stream, false);
+
+ var entry = zipW.getEntry(FILENAME);
+
+ do_check_true(entry != null);
+
+ // Check entry seems right.
+ do_check_eq(entry.compression, ZIP_METHOD_DEFLATE);
+ do_check_eq(entry.CRC32, CRC);
+ do_check_eq(entry.realSize, DATA.length);
+ do_check_eq(entry.lastModifiedTime / PR_USEC_PER_MSEC, time);
+
+ zipW.close();
+
+ // Test the stored data with the zipreader
+ var zipR = new ZipReader(tmpFile);
+ do_check_true(zipR.hasEntry(FILENAME));
+
+ zipR.test(FILENAME);
+
+ var stream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ stream.init(zipR.getInputStream(FILENAME));
+ var result = stream.read(DATA.length);
+ stream.close();
+ zipR.close();
+
+ do_check_eq(result, DATA);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_directory.js b/modules/libjar/zipwriter/test/unit/test_directory.js
new file mode 100644
index 0000000000..3196ebed9c
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_directory.js
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DIRNAME1 = "test";
+const DIRNAME1_CORRECT = "test/";
+const DIRNAME2 = "test2/";
+const time = Date.now();
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ zipW.addEntryDirectory(DIRNAME1, time * PR_USEC_PER_MSEC, false);
+ do_check_false(zipW.hasEntry(DIRNAME1));
+ do_check_true(zipW.hasEntry(DIRNAME1_CORRECT));
+ var entry = zipW.getEntry(DIRNAME1_CORRECT);
+ do_check_true(entry.isDirectory);
+
+ zipW.addEntryDirectory(DIRNAME2, time * PR_USEC_PER_MSEC, false);
+ do_check_true(zipW.hasEntry(DIRNAME2));
+ entry = zipW.getEntry(DIRNAME2);
+ do_check_true(entry.isDirectory);
+
+ zipW.close();
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_editexisting.js b/modules/libjar/zipwriter/test/unit/test_editexisting.js
new file mode 100644
index 0000000000..84b20690b9
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_editexisting.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Values taken from using zipinfo to list the test.zip contents
+var TESTS = [
+ {
+ name: "test.txt",
+ size: 232,
+ crc: 0x0373ac26,
+ time: Date.UTC(2007, 4, 1, 20, 44, 55)
+ },
+ {
+ name: "test.png",
+ size: 3402,
+ crc: 0x504a5c30,
+ time: Date.UTC(2007, 4, 1, 20, 49, 39)
+ }
+];
+var BADENTRY = "unknown.txt";
+
+function run_test()
+{
+ // Copy our test zip to the tmp dir so we can modify it
+ var testzip = do_get_file(DATA_DIR + "test.zip");
+ testzip.copyTo(tmpDir, tmpFile.leafName);
+
+ do_check_true(tmpFile.exists());
+
+ zipW.open(tmpFile, PR_RDWR);
+
+ for (var i = 0; i < TESTS.length; i++) {
+ do_check_true(zipW.hasEntry(TESTS[i].name));
+ var entry = zipW.getEntry(TESTS[i].name);
+ do_check_true(entry != null);
+
+ do_check_eq(entry.realSize, TESTS[i].size);
+ do_check_eq(entry.CRC32, TESTS[i].crc);
+ do_check_eq(entry.lastModifiedTime / PR_USEC_PER_MSEC, TESTS[i].time);
+ }
+
+ try {
+ zipW.removeEntry(BADENTRY, false);
+ do_throw("shouldn't be able to remove an entry that doesn't exist");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_FILE_NOT_FOUND);
+ }
+
+ for (var i = 0; i < TESTS.length; i++) {
+ zipW.removeEntry(TESTS[i].name, false);
+ }
+
+ zipW.close();
+
+ // Certain platforms cache the file size so get a fresh file to check.
+ tmpFile = tmpFile.clone();
+
+ // Empty zip file should just be the end of central directory marker
+ do_check_eq(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_storedata.js b/modules/libjar/zipwriter/test/unit/test_storedata.js
new file mode 100644
index 0000000000..d3fbc6ba5b
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_storedata.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DATA = "ZIP WRITER TEST DATA";
+const FILENAME = "test.txt";
+const FILENAME2 = "test2.txt";
+const CRC = 0xe6164331;
+// XXX Must use a constant time here away from DST changes. See bug 402434.
+const time = 1199145600000; // Jan 1st 2008
+
+function testpass(source)
+{
+ // Should exist.
+ do_check_true(source.hasEntry(FILENAME));
+
+ var entry = source.getEntry(FILENAME);
+ do_check_neq(entry, null);
+
+ do_check_false(entry.isDirectory);
+
+ // Should be stored
+ do_check_eq(entry.compression, ZIP_METHOD_STORE);
+
+ do_check_eq(entry.lastModifiedTime / PR_USEC_PER_MSEC, time);
+
+ // File size should match our data size.
+ do_check_eq(entry.realSize, DATA.length);
+ // When stored sizes should match.
+ do_check_eq(entry.size, entry.realSize);
+
+ // Check that the CRC is accurate
+ do_check_eq(entry.CRC32, CRC);
+}
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ // Shouldn't be there to start with.
+ do_check_false(zipW.hasEntry(FILENAME));
+
+ do_check_false(zipW.inQueue);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(DATA, DATA.length);
+ zipW.addEntryStream(FILENAME, time * PR_USEC_PER_MSEC,
+ Ci.nsIZipWriter.COMPRESSION_NONE, stream, false);
+
+ // Check that zip state is right at this stage.
+ testpass(zipW);
+ zipW.close();
+
+ do_check_eq(tmpFile.fileSize,
+ DATA.length + ZIP_FILE_HEADER_SIZE + ZIP_CDS_HEADER_SIZE +
+ (ZIP_EXTENDED_TIMESTAMP_SIZE * 2) +
+ (FILENAME.length * 2) + ZIP_EOCDR_HEADER_SIZE);
+
+ // Check to see if we get the same results loading afresh.
+ zipW.open(tmpFile, PR_RDWR);
+ testpass(zipW);
+ zipW.close();
+
+ // Test the stored data with the zipreader
+ var zipR = new ZipReader(tmpFile);
+ testpass(zipR);
+ zipR.test(FILENAME);
+ var stream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ stream.init(zipR.getInputStream(FILENAME));
+ var result = stream.read(DATA.length);
+ stream.close();
+ zipR.close();
+
+ do_check_eq(result, DATA);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_sync.js b/modules/libjar/zipwriter/test/unit/test_sync.js
new file mode 100644
index 0000000000..b2b6e7f6e3
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_sync.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Values taken from using zipinfo to list the test.zip contents
+var TESTS = [
+ {
+ name: "test.txt",
+ size: 232,
+ crc: 0x0373ac26
+ },
+ {
+ name: "test.png",
+ size: 3402,
+ crc: 0x504a5c30
+ }
+];
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ var size = 0;
+ for (var i = 0; i < TESTS.length; i++) {
+ var source = do_get_file(DATA_DIR + TESTS[i].name);
+ zipW.addEntryFile(TESTS[i].name, Ci.nsIZipWriter.COMPRESSION_NONE, source,
+ false);
+ size += ZIP_FILE_HEADER_SIZE + ZIP_CDS_HEADER_SIZE +
+ (ZIP_EXTENDED_TIMESTAMP_SIZE * 2) +
+ (TESTS[i].name.length*2) + TESTS[i].size;
+ }
+
+ zipW.close();
+ size += ZIP_EOCDR_HEADER_SIZE;
+
+ do_check_eq(size, tmpFile.fileSize);
+
+ // Test the stored data with the zipreader
+ var zipR = new ZipReader(tmpFile);
+
+ for (var i = 0; i < TESTS.length; i++) {
+ var source = do_get_file(DATA_DIR + TESTS[i].name);
+ do_check_true(zipR.hasEntry(TESTS[i].name));
+
+ var entry = zipR.getEntry(TESTS[i].name);
+ do_check_eq(entry.realSize, TESTS[i].size);
+ do_check_eq(entry.size, TESTS[i].size);
+ do_check_eq(entry.CRC32, TESTS[i].crc);
+ do_check_eq(Math.floor(entry.lastModifiedTime / PR_USEC_PER_SEC),
+ Math.floor(source.lastModifiedTime / PR_MSEC_PER_SEC));
+
+ zipR.test(TESTS[i].name);
+ }
+
+ zipR.close();
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_undochange.js b/modules/libjar/zipwriter/test/unit/test_undochange.js
new file mode 100644
index 0000000000..59647f9af8
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_undochange.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Values taken from using zipinfo to list the test.zip contents
+var TESTS = [
+ "test.txt",
+ "test.png"
+];
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ for (var i = 0; i < TESTS.length; i++) {
+ var source = do_get_file(DATA_DIR + TESTS[i]);
+ zipW.addEntryFile(TESTS[i], Ci.nsIZipWriter.COMPRESSION_NONE, source,
+ false);
+ }
+
+ try {
+ var source = do_get_file(DATA_DIR + TESTS[0]);
+ zipW.addEntryFile(TESTS[0], Ci.nsIZipWriter.COMPRESSION_NONE, source,
+ false);
+ do_throw("Should not be able to add the same file twice");
+ }
+ catch (e) {
+ do_check_eq(e.result, Components.results.NS_ERROR_FILE_ALREADY_EXISTS);
+ }
+
+ // Remove all the tests and see if we are left with an empty zip
+ for (var i = 0; i < TESTS.length; i++) {
+ zipW.removeEntry(TESTS[i], false);
+ }
+
+ zipW.close();
+
+ // Empty zip file should just be the end of central directory marker
+ var newTmpFile = tmpFile.clone();
+ do_check_eq(newTmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_zipcomment.js b/modules/libjar/zipwriter/test/unit/test_zipcomment.js
new file mode 100644
index 0000000000..0bc111c6cf
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_zipcomment.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DATA = "ZIP WRITER TEST COMMENT";
+const DATA2 = "ANOTHER ONE";
+
+function run_test()
+{
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ zipW.comment = DATA;
+ zipW.close();
+
+ // Should have created a zip file
+ do_check_true(tmpFile.exists());
+
+ // Empty zip file should just be the end of central directory marker
+ // and comment
+ do_check_eq(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE + DATA.length);
+
+ zipW.open(tmpFile, PR_RDWR);
+ // Should have the set comment
+ do_check_eq(zipW.comment, DATA);
+ zipW.comment = DATA2;
+ zipW.close();
+
+ // Certain platforms cache the file size so get a fresh file to check.
+ tmpFile = tmpFile.clone();
+
+ // Empty zip file should just be the end of central directory marker
+ // and comment. This should now be shorter
+ do_check_eq(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE + DATA2.length);
+}
diff --git a/modules/libjar/zipwriter/test/unit/test_zippermissions.js b/modules/libjar/zipwriter/test/unit/test_zippermissions.js
new file mode 100644
index 0000000000..c3f276dfc7
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/test_zippermissions.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+const DATA = "ZIP WRITER TEST DATA";
+
+var TESTS = [];
+
+function build_tests() {
+ var id = 0;
+
+ // Minimum mode is 0o400
+ for (let u = 4; u <= 7; u++) {
+ for (let g = 0; g <= 7; g++) {
+ for (let o = 0; o <= 7; o++) {
+ TESTS[id] = {
+ name: "test" + u + g + o,
+ permission: (u << 6) + (g << 3) + o
+ };
+ id++;
+ }
+ }
+ }
+}
+
+function run_test() {
+ build_tests();
+
+ var foStream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+
+ var tmp = tmpDir.clone();
+ tmp.append("temp-permissions");
+ tmp.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE, 0o755);
+
+ var file = tmp.clone();
+ file.append("tempfile");
+
+ zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ for (let i = 0; i < TESTS.length; i++) {
+ // Open the file with the permissions to match how the zipreader extracts
+ // This obeys the umask
+ foStream.init(file, 0x02 | 0x08 | 0x20, TESTS[i].permission, 0);
+ foStream.close();
+
+ // umask may have altered the permissions so test against what they really were.
+ // This reduces the coverage of the test but there isn't much we can do
+ var perm = file.permissions & 0xfff;
+ if (TESTS[i].permission != perm) {
+ dump("File permissions for " + TESTS[i].name + " were " + perm.toString(8) + "\n");
+ TESTS[i].permission = perm;
+ }
+
+ zipW.addEntryFile(TESTS[i].name, Ci.nsIZipWriter.COMPRESSION_NONE, file, false);
+ do_check_eq(zipW.getEntry(TESTS[i].name).permissions, TESTS[i].permission | 0o400);
+ file.permissions = 0o600;
+ file.remove(true);
+ }
+ zipW.close();
+
+ zipW.open(tmpFile, PR_RDWR);
+ for (let i = 0; i < TESTS.length; i++) {
+ dump("Testing zipwriter file permissions for " + TESTS[i].name + "\n");
+ do_check_eq(zipW.getEntry(TESTS[i].name).permissions, TESTS[i].permission | 0o400);
+ }
+ zipW.close();
+
+ var zipR = new ZipReader(tmpFile);
+ for (let i = 0; i < TESTS.length; i++) {
+ dump("Testing zipreader file permissions for " + TESTS[i].name + "\n");
+ do_check_eq(zipR.getEntry(TESTS[i].name).permissions, TESTS[i].permission | 0o400);
+ dump("Testing extracted file permissions for " + TESTS[i].name + "\n");
+ zipR.extract(TESTS[i].name, file);
+ do_check_eq(file.permissions & 0xfff, TESTS[i].permission);
+ do_check_false(file.isDirectory());
+ file.permissions = 0o600;
+ file.remove(true);
+ }
+ zipR.close();
+
+ tmp.remove(true);
+}
diff --git a/modules/libjar/zipwriter/test/unit/xpcshell.ini b/modules/libjar/zipwriter/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..a0a4a3ce12
--- /dev/null
+++ b/modules/libjar/zipwriter/test/unit/xpcshell.ini
@@ -0,0 +1,37 @@
+[DEFAULT]
+head = head_zipwriter.js
+tail = tail_zipwriter.js
+support-files =
+ data/test_bug446708/thumbs/st14-1.tiff
+ data/emptyfile.txt
+ data/smallfile.txt
+ data/test.png
+ data/test.txt
+ data/test.zip
+ data/test_bug399727.html
+ data/test_bug399727.zlib
+ data/test_bug717061.gz
+ data/test_bug717061.html
+
+[test_asyncadd.js]
+[test_asyncbadadd.js]
+[test_asyncbadremove.js]
+[test_asyncremove.js]
+[test_bug399727.js]
+[test_bug419769_1.js]
+[test_bug419769_2.js]
+[test_bug425768.js]
+[test_bug433248.js]
+[test_bug446708.js]
+[test_bug467740.js]
+[test_createempty.js]
+[test_deflatedata.js]
+[test_directory.js]
+[test_editexisting.js]
+[test_storedata.js]
+[test_sync.js]
+[test_undochange.js]
+[test_zipcomment.js]
+[test_zippermissions.js]
+[test_bug717061.js]
+[test_alignment.js]