[svn-inject] Installing original source of libarchive-zip-perl
Ernesto Hernández-Novich
14 years ago
0 | Revision history for Perl extension Archive::Zip. | |
1 | ||
2 | 1.18 Wed 25 Oct 2006 - Adam Kennedy | |
3 | - Changing to a production version for final release | |
4 | - No other changes of any kind | |
5 | ||
6 | 1.17_05 Tue 19 Sep 2006 - Adam Kennedy | |
7 | - Seperated the classes from the main file into seperate packages. | |
8 | - Merged the Zip.pod into the main Zip.pm file. | |
9 | - Applied default Perl::Tidy to all of the source files, to improve | |
10 | the readability and maintainability of the files. | |
11 | - Added license in Makefile.PL | |
12 | - Added some additional entries to the realclean files | |
13 | ||
14 | 1.17_03 Sat 16 Sep 2006 - Adam Kennedy | |
15 | - Adding dependency on File::Which to deal with problems on systems | |
16 | that lack zip and unzip programs. This really should be a build-time | |
17 | dependency only, but ExtUtils::MakeMaker lacks that capability. | |
18 | - Builds and tests cleanly on Win32 now. | |
19 | ||
20 | 1.17_02 Sun 7 May 2006 - Adam Kennedy | |
21 | - Renamed the test scripts to the more conventional 01_name.t style | |
22 | - Upgraded all test scripts from Test.pm to Test::More (removing Test.pm dependency) | |
23 | - Various other miscellaneous cleanups of the test scripts | |
24 | - Removed MANIFEST and pod.t from repository (will be auto-generated) | |
25 | - Some cleaning up of the POD documentation for readability | |
26 | - Added SUPPORT section to docs | |
27 | - Merged external TODO file into the POD as a more-common TO DO section | |
28 | - Added a BUGS section to the docs | |
29 | ||
30 | 1.17_01 Sun 30 Apr 2006 - Adam Kennedy | |
31 | - Imported Archive::Zip into http://svn.phase-n.com/svn/cpan/ orphanage. | |
32 | If you have a CPAN login and have released a module, ask ADAMK about an | |
33 | account and you can repair your bug directly in the repository. | |
34 | - Removed the revision comments from the old CVS repository | |
35 | - DOS DateTime Format doesn't support dates before 1980 and goes crazy when | |
36 | decoding back to unix time. If we don't get passed a time at all | |
37 | (0 or undef) we now throw an error. | |
38 | - DOS DateTime Format doesn't support dates before 1980, so if we find any | |
39 | we warn and use Jan 1 12:01pm 1980 if we encounter any | |
40 | - Win32 doesn't support directory modification times. | |
41 | Tentatively use the current time as the mod-time to prevent sending | |
42 | null times to the unix2dos converter (and the resulting error) | |
43 | - Reformat the expected empty zip warning in the output to add a note that | |
44 | the warning is entirely normal. Would be nice if some time later we can | |
45 | suppress it altogether, but I don't have the cross-platform STDERR-fu | |
46 | without adding a dependency to IPC::Run3 (which would be bad). | |
47 | - Adding a proper $VERSION to all classes, and synchronising them to the | |
48 | same value. | |
49 | - Adding a BEGIN block around the require 5.003_96 so it works at | |
50 | compile-time instead of post-compile. | |
51 | - Moved crc32 to bin/crc32 in line with package layout conventions | |
52 | ||
53 | 1.16 Mon Jul 04 12:49:30 CDT 2005 | |
54 | - Grrrr...removed test that fails when installing under CPANPLUS. | |
55 | ||
56 | 1.15 Wed Jun 22 10:24:25 CDT 2005 | |
57 | - added fix for RT #12771 Minor nit: warning in Archive::Zip::DirectoryMember::contents() | |
58 | - added fix for RT #13327 Formatting problem in Archive::Zip::Tree manpage | |
59 | ||
60 | 1.15_02 Sat Mar 12 09:16:30 CST 2005 | |
61 | - fixed dates in previous entry! | |
62 | - began the process of migrating from the monolithic t/test.t to | |
63 | smaller scripts using Test::More. | |
64 | - started work on improving Archive::Zip's test coverage. Coverage | |
65 | is now up to just over 80%. | |
66 | - added error handling to writeToFileHandle | |
67 | - fixed small bug in extractMember from previous version | |
68 | ||
69 | 1.15_01 Wed Mar 9 22:26:52 CST 2005 | |
70 | - added fix for RT #11818 extractMember method corrupts archive | |
71 | - added t/pod.t to test for pod correctness | |
72 | ||
73 | 1.10 Thu Mar 25 06:24:17 PST 2004 | |
74 | - Fixed documentation of setErrorHandler() | |
75 | - Fixed link to Japanese translation of docs | |
76 | - Added Compress::Zlib Bufsize patch from Yeasah Pell that was supposed to | |
77 | have been added in 1.02 | |
78 | - Fixed problems with backup filenames for zips with no extension | |
79 | - Fixed problems with undef volume names in _asLocalName() | |
80 | ||
81 | 1.09 Wed Nov 26 17:43:49 PST 2003 | |
82 | - Fixed handling of inserted garbage (as from viruses) | |
83 | - Always check for local header signatures before using them | |
84 | - Added updateMember() and updateTree() functions | |
85 | - Added examples/mailZip.pl | |
86 | - Added examples/updateTree.pl | |
87 | - Fixed some potential but unreported bugs with function parameters like '0' | |
88 | - Removed stray warn() call | |
89 | - Caught undef second arg to replaceMember() | |
90 | - Fixed test suite run with spaces in build dir name (ticket 4214) | |
91 | ||
92 | 1.08 Tue Oct 21 07:01:29 PDT 2003 | |
93 | - test noise fix from Michael Schwern (ticket 4174) | |
94 | - FAQ NAME fix from Michael Schwern (ticket 4175) | |
95 | ||
96 | 1.07 Mon Oct 20 06:48:41 PDT 2003 | |
97 | - Added file attribute code by Maurice Aubrey | |
98 | - Added FAQ about RedHat 9 | |
99 | - Added check for empty filenames | |
100 | ||
101 | 1.06 Thu Jul 17 11:06:18 PDT 2003 | |
102 | - Fixed seek use with IO::Scalar and IO::String | |
103 | - Fixed use of binmode with pseudo-file handles | |
104 | - Removed qr{} form for older Perl versions | |
105 | - Changed rel2abs logic in _asLocalName() if there is a volume | |
106 | - Fixed errors with making directories in extractMember() when none provided | |
107 | - Return AZ_OK in extractMemberWithoutPaths() if member is a directory | |
108 | - Fixed problem in extractTree with blank directory becoming "." prefix | |
109 | - Added examples/writeScalar2.pl to show how to use IO::String as destination of Zip write | |
110 | - Edited docs and FAQ to recommend against using absolute path names in zip files. | |
111 | ||
112 | 1.05 Wed Sep 11 12:31:20 PDT 2002 | |
113 | - fixed untaint from 1.04 | |
114 | ||
115 | 1.04 Wed Sep 11 07:22:04 PDT 2002 | |
116 | - added untaint of lastModFileDateTime | |
117 | ||
118 | 1.03 Mon Sep 2 20:42:43 PDT 2002 | |
119 | - Removed dependency on IO::Scalar | |
120 | - Set required version of File::Spec to 0.8 | |
121 | - Removed tests of examples that needed IO::Scalar | |
122 | - Added binmode() call to read/writeScalar examples | |
123 | - Fixed addTree() for 5.005 compatibility (still untested with 5.004) | |
124 | - Fixed mkdir() calls for 5.005 | |
125 | - Clarified documentation of tree operations | |
126 | ||
127 | 1.02 Fri Aug 23 17:07:22 PDT 2002 | |
128 | - Many changes for cross-platform use (use File::Spec everywhere) | |
129 | - Separated POD from Perl | |
130 | - Moved Archive::Zip::Tree contents into Archive::Zip | |
131 | A::Z::Tree is now deprecated and will warn with -w | |
132 | - Reorganized docs | |
133 | - Added FAQ | |
134 | - Added chunkSize() call to report current chunk size | |
135 | and added C::Z BufSize patch from Yeasah Pell. | |
136 | - Added fileName() to report last read zip file name | |
137 | - Added capability to prepend data, like for SFX files | |
138 | - Added examples/selfex.pl for self-extracting archives creation | |
139 | - Added examples/zipcheck.pl for validity testing | |
140 | - Made extractToFileNamed() set access/modification times | |
141 | - Added t/testTree.t to test A::Z::Tree | |
142 | - Fix/speed up memberNamed() | |
143 | - Added Archive::Zip::MemberRead by Sreeji K. Das | |
144 | - Added tempFile(), tempName() | |
145 | - Added overwrite() and overwriteAs() to allow read/modify/write of zip | |
146 | - added examples/updateZip.pl to show how to read/modify/write | |
147 | ||
148 | 1.01 Tue Apr 30 10:34:44 PDT 2002 | |
149 | - Changed mkpath call for directories to work with BSD/OS | |
150 | - Changed tests to work with BSD/OS | |
151 | ||
152 | 1.00 Sun Apr 28 2002 | |
153 | - Added several examples: | |
154 | - examples/calcSizes.pl | |
155 | How to find out how big a zip file will be before writing it | |
156 | - examples/readScalar.pl | |
157 | shows how to use IO::Scalar as the source of a zip read | |
158 | - examples/unzipAll.pl | |
159 | uses Archive::Zip::Tree to unzip an entire zip | |
160 | - examples/writeScalar.pl | |
161 | shows how to use IO::Scalar as the destination of a zip write | |
162 | - examples/zipGrep.pl | |
163 | Searches for text in zip files | |
164 | - Changed required version of Compress::Zlib to 1.08 | |
165 | - Added detection and repair of zips with added garbage (as caused by | |
166 | the Sircam worm) | |
167 | - Added more documentation for FAQ-type questions, though few seem to | |
168 | actually read the documentation. | |
169 | - Fixed problem with stat vs lstat | |
170 | - Changed version number to 1.00 for PHB compatibility | |
171 | ||
172 | 0.12 Wed May 23 17:48:21 PDT 2001 | |
173 | - Added writeScalar.pl and readScalar.pl to show use of IO::Scalar | |
174 | - Fixed docs | |
175 | - Fixed bug with EOCD signature on block boundary | |
176 | - Made it work with IO::Scalar as file handles | |
177 | - added readFromFileHandle() | |
178 | - remove guess at seekability for Windows compatibility | |
179 | ||
180 | 0.11 Tue Jan 9 11:40:10 PST 2001 | |
181 | - Added examples/ziprecent.pl (by Rudi Farkas) | |
182 | - Fixed up documentation in Archive::Zip::Tree | |
183 | - Added to documentation in Archive::Zip::Tree | |
184 | - Fixed bugs in Archive::Zip::Tree that kept predicates from working | |
185 | - Detected file not existing errors in addFile | |
186 | ||
187 | 0.10 Tue Aug 8 13:50:19 PDT 2000 | |
188 | - Several bug fixes | |
189 | - More robust new file handle logic can (again) | |
190 | take opened file handles | |
191 | - Detect attempts to overwrite zip file when members | |
192 | depend on it | |
193 | ||
194 | 0.09 Tue May 9 13:27:35 PDT 2000 | |
195 | - Added fix for bug in contents() | |
196 | - removed system("rm") call in t/test.t for Windows. | |
197 | ||
198 | 0.08 March 27 2000 (unreleased) | |
199 | - Fixed documentation | |
200 | - Used IO::File instead of FileHandle, allowed for use of almost anything as | |
201 | a file handle. | |
202 | - Extra filenames can be passed to extractMember(), | |
203 | extractMemberWithoutPaths(), addFile(), addDirectory() | |
204 | - Added work-around for WinZip bug with 0-length DEFLATED files | |
205 | - Added Archive::Zip::Tree module for adding/extracting hierarchies | |
206 | ||
207 | 0.07 Fri Mar 24 10:26:51 PST 2000 | |
208 | - Added copyright | |
209 | - Added desiredCompressionLevel() and documentation | |
210 | - Made writeToFileHandle() detect seekability by default | |
211 | - Allowed Archive::Zip->new() to take filename for read() | |
212 | - Added crc32String() to Archive::Zip::Member | |
213 | - Changed requirement in Makefile.PL to Compress::Zip | |
214 | version 1.06 or later (bug in earlier versions can truncate data) | |
215 | - Moved BufferedFileHandle and MockFileHandle into | |
216 | Archive::Zip namespace | |
217 | - Allowed changing error printing routine | |
218 | - Factored out reading of signatures | |
219 | - Made re-read of local header for directory members | |
220 | depend on file handle seekability | |
221 | - Added ability to change member contents | |
222 | - Fixed a possible truncation bug in contents() method | |
223 | ||
224 | 0.06 Tue Mar 21 15:28:22 PST 2000 | |
225 | - first release to CPAN | |
226 | ||
227 | 0.01 Sun Mar 12 18:59:55 2000 | |
228 | - original version; created by h2xs 1.19 |
0 | To make and test, do the following: | |
1 | perl Makefile.PL | |
2 | make | |
3 | make test | |
4 | ||
5 | To copy the libs and docs to the right places, | |
6 | do this (as superuser or administrator): | |
7 | make install | |
8 | ||
9 | If you're using Windows, you probably have to first get nmake to make this | |
10 | work. If you have the ActiveState distribution, read the part about installing | |
11 | modules from CPAN. | |
12 | ||
13 | Then: | |
14 | perl Makefile.PL | |
15 | nmake | |
16 | nmake test | |
17 | nmake install | |
18 | ||
19 | ||
20 | If you don't have the Info-Zip tools zip and unzip installed, you may | |
21 | get warnings from "make test" or "nmake test". Don't worry about these. | |
22 | ||
23 | Actually, if you're using ActiveState's ActivePerl on Windows | |
24 | you should first check for up to date .PPM versions and use their PPM tool | |
25 | to install them. | |
26 | ||
27 | If you are having install problems on RedHat 8 or 9 with Perl 5.8.0, please | |
28 | read this (from the FAQ in lib/Archive/Zip/FAQ.pod): | |
29 | ||
30 | Q: Archive::Zip won't install on my RedHat 9 system! It's broke! | |
31 | ||
32 | A: This has become something of a FAQ. Basically, RedHat broke some | |
33 | versions of Perl by setting LANG to UTF8. They apparently have a fixed | |
34 | version out as an update. | |
35 | ||
36 | You might try running CPAN or creating your Makefile after exporting the | |
37 | LANG environment variable as | |
38 | ||
39 | "LANG=C" | |
40 | ||
41 | <https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=87682> | |
42 |
0 | ||
1 | Terms of Perl itself | |
2 | ||
3 | a) the GNU General Public License as published by the Free | |
4 | Software Foundation; either version 1, or (at your option) any | |
5 | later version, or | |
6 | b) the "Artistic License" | |
7 | ||
8 | ---------------------------------------------------------------------------- | |
9 | ||
10 | The General Public License (GPL) | |
11 | Version 2, June 1991 | |
12 | ||
13 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, | |
14 | Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute | |
15 | verbatim copies of this license document, but changing it is not allowed. | |
16 | ||
17 | Preamble | |
18 | ||
19 | The licenses for most software are designed to take away your freedom to share | |
20 | and change it. By contrast, the GNU General Public License is intended to | |
21 | guarantee your freedom to share and change free software--to make sure the | |
22 | software is free for all its users. This General Public License applies to most of | |
23 | the Free Software Foundation's software and to any other program whose | |
24 | authors commit to using it. (Some other Free Software Foundation software is | |
25 | covered by the GNU Library General Public License instead.) You can apply it to | |
26 | your programs, too. | |
27 | ||
28 | When we speak of free software, we are referring to freedom, not price. Our | |
29 | General Public Licenses are designed to make sure that you have the freedom | |
30 | to distribute copies of free software (and charge for this service if you wish), that | |
31 | you receive source code or can get it if you want it, that you can change the | |
32 | software or use pieces of it in new free programs; and that you know you can do | |
33 | these things. | |
34 | ||
35 | To protect your rights, we need to make restrictions that forbid anyone to deny | |
36 | you these rights or to ask you to surrender the rights. These restrictions | |
37 | translate to certain responsibilities for you if you distribute copies of the | |
38 | software, or if you modify it. | |
39 | ||
40 | For example, if you distribute copies of such a program, whether gratis or for a | |
41 | fee, you must give the recipients all the rights that you have. You must make | |
42 | sure that they, too, receive or can get the source code. And you must show | |
43 | them these terms so they know their rights. | |
44 | ||
45 | We protect your rights with two steps: (1) copyright the software, and (2) offer | |
46 | you this license which gives you legal permission to copy, distribute and/or | |
47 | modify the software. | |
48 | ||
49 | Also, for each author's protection and ours, we want to make certain that | |
50 | everyone understands that there is no warranty for this free software. If the | |
51 | software is modified by someone else and passed on, we want its recipients to | |
52 | know that what they have is not the original, so that any problems introduced by | |
53 | others will not reflect on the original authors' reputations. | |
54 | ||
55 | Finally, any free program is threatened constantly by software patents. We wish | |
56 | to avoid the danger that redistributors of a free program will individually obtain | |
57 | patent licenses, in effect making the program proprietary. To prevent this, we | |
58 | have made it clear that any patent must be licensed for everyone's free use or | |
59 | not licensed at all. | |
60 | ||
61 | The precise terms and conditions for copying, distribution and modification | |
62 | follow. | |
63 | ||
64 | GNU GENERAL PUBLIC LICENSE | |
65 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND | |
66 | MODIFICATION | |
67 | ||
68 | 0. This License applies to any program or other work which contains a notice | |
69 | placed by the copyright holder saying it may be distributed under the terms of | |
70 | this General Public License. The "Program", below, refers to any such program | |
71 | or work, and a "work based on the Program" means either the Program or any | |
72 | derivative work under copyright law: that is to say, a work containing the | |
73 | Program or a portion of it, either verbatim or with modifications and/or translated | |
74 | into another language. (Hereinafter, translation is included without limitation in | |
75 | the term "modification".) Each licensee is addressed as "you". | |
76 | ||
77 | Activities other than copying, distribution and modification are not covered by | |
78 | this License; they are outside its scope. The act of running the Program is not | |
79 | restricted, and the output from the Program is covered only if its contents | |
80 | constitute a work based on the Program (independent of having been made by | |
81 | running the Program). Whether that is true depends on what the Program does. | |
82 | ||
83 | 1. You may copy and distribute verbatim copies of the Program's source code as | |
84 | you receive it, in any medium, provided that you conspicuously and appropriately | |
85 | publish on each copy an appropriate copyright notice and disclaimer of warranty; | |
86 | keep intact all the notices that refer to this License and to the absence of any | |
87 | warranty; and give any other recipients of the Program a copy of this License | |
88 | along with the Program. | |
89 | ||
90 | You may charge a fee for the physical act of transferring a copy, and you may at | |
91 | your option offer warranty protection in exchange for a fee. | |
92 | ||
93 | 2. You may modify your copy or copies of the Program or any portion of it, thus | |
94 | forming a work based on the Program, and copy and distribute such | |
95 | modifications or work under the terms of Section 1 above, provided that you also | |
96 | meet all of these conditions: | |
97 | ||
98 | a) You must cause the modified files to carry prominent notices stating that you | |
99 | changed the files and the date of any change. | |
100 | ||
101 | b) You must cause any work that you distribute or publish, that in whole or in | |
102 | part contains or is derived from the Program or any part thereof, to be licensed | |
103 | as a whole at no charge to all third parties under the terms of this License. | |
104 | ||
105 | c) If the modified program normally reads commands interactively when run, you | |
106 | must cause it, when started running for such interactive use in the most ordinary | |
107 | way, to print or display an announcement including an appropriate copyright | |
108 | notice and a notice that there is no warranty (or else, saying that you provide a | |
109 | warranty) and that users may redistribute the program under these conditions, | |
110 | and telling the user how to view a copy of this License. (Exception: if the | |
111 | Program itself is interactive but does not normally print such an announcement, | |
112 | your work based on the Program is not required to print an announcement.) | |
113 | ||
114 | These requirements apply to the modified work as a whole. If identifiable | |
115 | sections of that work are not derived from the Program, and can be reasonably | |
116 | considered independent and separate works in themselves, then this License, | |
117 | and its terms, do not apply to those sections when you distribute them as | |
118 | separate works. But when you distribute the same sections as part of a whole | |
119 | which is a work based on the Program, the distribution of the whole must be on | |
120 | the terms of this License, whose permissions for other licensees extend to the | |
121 | entire whole, and thus to each and every part regardless of who wrote it. | |
122 | ||
123 | Thus, it is not the intent of this section to claim rights or contest your rights to | |
124 | work written entirely by you; rather, the intent is to exercise the right to control | |
125 | the distribution of derivative or collective works based on the Program. | |
126 | ||
127 | In addition, mere aggregation of another work not based on the Program with the | |
128 | Program (or with a work based on the Program) on a volume of a storage or | |
129 | distribution medium does not bring the other work under the scope of this | |
130 | License. | |
131 | ||
132 | 3. You may copy and distribute the Program (or a work based on it, under | |
133 | Section 2) in object code or executable form under the terms of Sections 1 and 2 | |
134 | above provided that you also do one of the following: | |
135 | ||
136 | a) Accompany it with the complete corresponding machine-readable source | |
137 | code, which must be distributed under the terms of Sections 1 and 2 above on a | |
138 | medium customarily used for software interchange; or, | |
139 | ||
140 | b) Accompany it with a written offer, valid for at least three years, to give any | |
141 | third party, for a charge no more than your cost of physically performing source | |
142 | distribution, a complete machine-readable copy of the corresponding source | |
143 | code, to be distributed under the terms of Sections 1 and 2 above on a medium | |
144 | customarily used for software interchange; or, | |
145 | ||
146 | c) Accompany it with the information you received as to the offer to distribute | |
147 | corresponding source code. (This alternative is allowed only for noncommercial | |
148 | distribution and only if you received the program in object code or executable | |
149 | form with such an offer, in accord with Subsection b above.) | |
150 | ||
151 | The source code for a work means the preferred form of the work for making | |
152 | modifications to it. For an executable work, complete source code means all the | |
153 | source code for all modules it contains, plus any associated interface definition | |
154 | files, plus the scripts used to control compilation and installation of the | |
155 | executable. However, as a special exception, the source code distributed need | |
156 | not include anything that is normally distributed (in either source or binary form) | |
157 | with the major components (compiler, kernel, and so on) of the operating system | |
158 | on which the executable runs, unless that component itself accompanies the | |
159 | executable. | |
160 | ||
161 | If distribution of executable or object code is made by offering access to copy | |
162 | from a designated place, then offering equivalent access to copy the source | |
163 | code from the same place counts as distribution of the source code, even though | |
164 | third parties are not compelled to copy the source along with the object code. | |
165 | ||
166 | 4. You may not copy, modify, sublicense, or distribute the Program except as | |
167 | expressly provided under this License. Any attempt otherwise to copy, modify, | |
168 | sublicense or distribute the Program is void, and will automatically terminate | |
169 | your rights under this License. However, parties who have received copies, or | |
170 | rights, from you under this License will not have their licenses terminated so long | |
171 | as such parties remain in full compliance. | |
172 | ||
173 | 5. You are not required to accept this License, since you have not signed it. | |
174 | However, nothing else grants you permission to modify or distribute the Program | |
175 | or its derivative works. These actions are prohibited by law if you do not accept | |
176 | this License. Therefore, by modifying or distributing the Program (or any work | |
177 | based on the Program), you indicate your acceptance of this License to do so, | |
178 | and all its terms and conditions for copying, distributing or modifying the | |
179 | Program or works based on it. | |
180 | ||
181 | 6. Each time you redistribute the Program (or any work based on the Program), | |
182 | the recipient automatically receives a license from the original licensor to copy, | |
183 | distribute or modify the Program subject to these terms and conditions. You | |
184 | may not impose any further restrictions on the recipients' exercise of the rights | |
185 | granted herein. You are not responsible for enforcing compliance by third parties | |
186 | to this License. | |
187 | ||
188 | 7. If, as a consequence of a court judgment or allegation of patent infringement | |
189 | or for any other reason (not limited to patent issues), conditions are imposed on | |
190 | you (whether by court order, agreement or otherwise) that contradict the | |
191 | conditions of this License, they do not excuse you from the conditions of this | |
192 | License. If you cannot distribute so as to satisfy simultaneously your obligations | |
193 | under this License and any other pertinent obligations, then as a consequence | |
194 | you may not distribute the Program at all. For example, if a patent license would | |
195 | not permit royalty-free redistribution of the Program by all those who receive | |
196 | copies directly or indirectly through you, then the only way you could satisfy | |
197 | both it and this License would be to refrain entirely from distribution of the | |
198 | Program. | |
199 | ||
200 | If any portion of this section is held invalid or unenforceable under any particular | |
201 | circumstance, the balance of the section is intended to apply and the section as | |
202 | a whole is intended to apply in other circumstances. | |
203 | ||
204 | It is not the purpose of this section to induce you to infringe any patents or other | |
205 | property right claims or to contest validity of any such claims; this section has | |
206 | the sole purpose of protecting the integrity of the free software distribution | |
207 | system, which is implemented by public license practices. Many people have | |
208 | made generous contributions to the wide range of software distributed through | |
209 | that system in reliance on consistent application of that system; it is up to the | |
210 | author/donor to decide if he or she is willing to distribute software through any | |
211 | other system and a licensee cannot impose that choice. | |
212 | ||
213 | This section is intended to make thoroughly clear what is believed to be a | |
214 | consequence of the rest of this License. | |
215 | ||
216 | 8. If the distribution and/or use of the Program is restricted in certain countries | |
217 | either by patents or by copyrighted interfaces, the original copyright holder who | |
218 | places the Program under this License may add an explicit geographical | |
219 | distribution limitation excluding those countries, so that distribution is permitted | |
220 | only in or among countries not thus excluded. In such case, this License | |
221 | incorporates the limitation as if written in the body of this License. | |
222 | ||
223 | 9. The Free Software Foundation may publish revised and/or new versions of the | |
224 | General Public License from time to time. Such new versions will be similar in | |
225 | spirit to the present version, but may differ in detail to address new problems or | |
226 | concerns. | |
227 | ||
228 | Each version is given a distinguishing version number. If the Program specifies a | |
229 | version number of this License which applies to it and "any later version", you | |
230 | have the option of following the terms and conditions either of that version or of | |
231 | any later version published by the Free Software Foundation. If the Program does | |
232 | not specify a version number of this License, you may choose any version ever | |
233 | published by the Free Software Foundation. | |
234 | ||
235 | 10. If you wish to incorporate parts of the Program into other free programs | |
236 | whose distribution conditions are different, write to the author to ask for | |
237 | permission. For software which is copyrighted by the Free Software Foundation, | |
238 | write to the Free Software Foundation; we sometimes make exceptions for this. | |
239 | Our decision will be guided by the two goals of preserving the free status of all | |
240 | derivatives of our free software and of promoting the sharing and reuse of | |
241 | software generally. | |
242 | ||
243 | NO WARRANTY | |
244 | ||
245 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS | |
246 | NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | |
247 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE | |
248 | COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM | |
249 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR | |
250 | IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
251 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE | |
252 | ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE | |
253 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, | |
254 | YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR | |
255 | CORRECTION. | |
256 | ||
257 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED | |
258 | TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY | |
259 | WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS | |
260 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | |
261 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES | |
262 | ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM | |
263 | (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING | |
264 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | |
265 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY | |
266 | OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS | |
267 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. | |
268 | ||
269 | END OF TERMS AND CONDITIONS | |
270 | ||
271 | ||
272 | ---------------------------------------------------------------------------- | |
273 | ||
274 | The Artistic License | |
275 | ||
276 | Preamble | |
277 | ||
278 | The intent of this document is to state the conditions under which a Package | |
279 | may be copied, such that the Copyright Holder maintains some semblance of | |
280 | artistic control over the development of the package, while giving the users of the | |
281 | package the right to use and distribute the Package in a more-or-less customary | |
282 | fashion, plus the right to make reasonable modifications. | |
283 | ||
284 | Definitions: | |
285 | ||
286 | - "Package" refers to the collection of files distributed by the Copyright | |
287 | Holder, and derivatives of that collection of files created through textual | |
288 | modification. | |
289 | - "Standard Version" refers to such a Package if it has not been modified, | |
290 | or has been modified in accordance with the wishes of the Copyright | |
291 | Holder. | |
292 | - "Copyright Holder" is whoever is named in the copyright or copyrights for | |
293 | the package. | |
294 | - "You" is you, if you're thinking about copying or distributing this Package. | |
295 | - "Reasonable copying fee" is whatever you can justify on the basis of | |
296 | media cost, duplication charges, time of people involved, and so on. (You | |
297 | will not be required to justify it to the Copyright Holder, but only to the | |
298 | computing community at large as a market that must bear the fee.) | |
299 | - "Freely Available" means that no fee is charged for the item itself, though | |
300 | there may be fees involved in handling the item. It also means that | |
301 | recipients of the item may redistribute it under the same conditions they | |
302 | received it. | |
303 | ||
304 | 1. You may make and give away verbatim copies of the source form of the | |
305 | Standard Version of this Package without restriction, provided that you duplicate | |
306 | all of the original copyright notices and associated disclaimers. | |
307 | ||
308 | 2. You may apply bug fixes, portability fixes and other modifications derived from | |
309 | the Public Domain or from the Copyright Holder. A Package modified in such a | |
310 | way shall still be considered the Standard Version. | |
311 | ||
312 | 3. You may otherwise modify your copy of this Package in any way, provided | |
313 | that you insert a prominent notice in each changed file stating how and when | |
314 | you changed that file, and provided that you do at least ONE of the following: | |
315 | ||
316 | a) place your modifications in the Public Domain or otherwise | |
317 | make them Freely Available, such as by posting said modifications | |
318 | to Usenet or an equivalent medium, or placing the modifications on | |
319 | a major archive site such as ftp.uu.net, or by allowing the | |
320 | Copyright Holder to include your modifications in the Standard | |
321 | Version of the Package. | |
322 | ||
323 | b) use the modified Package only within your corporation or | |
324 | organization. | |
325 | ||
326 | c) rename any non-standard executables so the names do not | |
327 | conflict with standard executables, which must also be provided, | |
328 | and provide a separate manual page for each non-standard | |
329 | executable that clearly documents how it differs from the Standard | |
330 | Version. | |
331 | ||
332 | d) make other distribution arrangements with the Copyright Holder. | |
333 | ||
334 | 4. You may distribute the programs of this Package in object code or executable | |
335 | form, provided that you do at least ONE of the following: | |
336 | ||
337 | a) distribute a Standard Version of the executables and library | |
338 | files, together with instructions (in the manual page or equivalent) | |
339 | on where to get the Standard Version. | |
340 | ||
341 | b) accompany the distribution with the machine-readable source of | |
342 | the Package with your modifications. | |
343 | ||
344 | c) accompany any non-standard executables with their | |
345 | corresponding Standard Version executables, giving the | |
346 | non-standard executables non-standard names, and clearly | |
347 | documenting the differences in manual pages (or equivalent), | |
348 | together with instructions on where to get the Standard Version. | |
349 | ||
350 | d) make other distribution arrangements with the Copyright Holder. | |
351 | ||
352 | 5. You may charge a reasonable copying fee for any distribution of this Package. | |
353 | You may charge any fee you choose for support of this Package. You may not | |
354 | charge a fee for this Package itself. However, you may distribute this Package in | |
355 | aggregate with other (possibly commercial) programs as part of a larger | |
356 | (possibly commercial) software distribution provided that you do not advertise | |
357 | this Package as a product of your own. | |
358 | ||
359 | 6. The scripts and library files supplied as input to or produced as output from | |
360 | the programs of this Package do not automatically fall under the copyright of this | |
361 | Package, but belong to whomever generated them, and may be sold | |
362 | commercially, and may be aggregated with this Package. | |
363 | ||
364 | 7. C or perl subroutines supplied by you and linked into this Package shall not | |
365 | be considered part of this Package. | |
366 | ||
367 | 8. The name of the Copyright Holder may not be used to endorse or promote | |
368 | products derived from this software without specific prior written permission. | |
369 | ||
370 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR | |
371 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED | |
372 | WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR | |
373 | PURPOSE. | |
374 | ||
375 | The End | |
376 | ||
377 |
0 | bin/crc32 | |
1 | Changes | |
2 | docs/appnote.iz | |
3 | docs/Appnote.txt | |
4 | docs/Archive-Zip.pdf | |
5 | docs/ideas.txt | |
6 | examples/calcSizes.pl | |
7 | examples/copy.pl | |
8 | examples/extract.pl | |
9 | examples/mailZip.pl | |
10 | examples/mfh.pl | |
11 | examples/readScalar.pl | |
12 | examples/selfex.pl | |
13 | examples/unzipAll.pl | |
14 | examples/updateTree.pl | |
15 | examples/updateZip.pl | |
16 | examples/writeScalar.pl | |
17 | examples/writeScalar2.pl | |
18 | examples/zip.pl | |
19 | examples/zipcheck.pl | |
20 | examples/zipGrep.pl | |
21 | examples/zipinfo.pl | |
22 | examples/ziprecent.pl | |
23 | examples/ziptest.pl | |
24 | INSTALL | |
25 | lib/Archive/Zip.pm | |
26 | lib/Archive/Zip/Archive.pm | |
27 | lib/Archive/Zip/BufferedFileHandle.pm | |
28 | lib/Archive/Zip/DirectoryMember.pm | |
29 | lib/Archive/Zip/FAQ.pod | |
30 | lib/Archive/Zip/FileMember.pm | |
31 | lib/Archive/Zip/Member.pm | |
32 | lib/Archive/Zip/MemberRead.pm | |
33 | lib/Archive/Zip/MockFileHandle.pm | |
34 | lib/Archive/Zip/NewFileMember.pm | |
35 | lib/Archive/Zip/StringMember.pm | |
36 | lib/Archive/Zip/Tree.pm | |
37 | lib/Archive/Zip/ZipFileMember.pm | |
38 | LICENSE | |
39 | Makefile.PL | |
40 | MANIFEST This list of files | |
41 | README | |
42 | t/01_compile.t | |
43 | t/02_main.t | |
44 | t/03_ex.t | |
45 | t/04_readmember.t | |
46 | t/05_tree.t | |
47 | t/06_update.t | |
48 | t/99_author.t | |
49 | t/badjpeg/expected.jpg | |
50 | t/badjpeg/source.zip | |
51 | t/common.pl | |
52 | META.yml Module meta-data (added by MakeMaker) |
0 | --- #YAML:1.0 | |
1 | name: Archive-Zip | |
2 | version: 1.18 | |
3 | abstract: Provide an interface to ZIP archive files. | |
4 | license: perl | |
5 | generated_by: ExtUtils::MakeMaker version 6.31 | |
6 | distribution_type: module | |
7 | requires: | |
8 | Carp: 0 | |
9 | Compress::Zlib: 1.14 | |
10 | File::Basename: 0 | |
11 | File::Copy: 0 | |
12 | File::Find: 0 | |
13 | File::Path: 0 | |
14 | File::Spec: 0.80 | |
15 | File::Temp: 0 | |
16 | File::Which: 0.05 | |
17 | IO::File: 0 | |
18 | IO::Handle: 0 | |
19 | IO::Seekable: 0 | |
20 | Test::More: 0.42 | |
21 | Time::Local: 0 | |
22 | meta-spec: | |
23 | url: http://module-build.sourceforge.net/META-spec-v1.2.html | |
24 | version: 1.2 | |
25 | author: | |
26 | - Ned Konz <perl@bike-nomad.com> |
0 | require 5.003_96; | |
1 | ||
2 | use Config; | |
3 | use ExtUtils::MakeMaker; | |
4 | ||
5 | WriteMakefile( | |
6 | NAME => 'Archive::Zip', | |
7 | VERSION_FROM => 'lib/Archive/Zip.pm', # finds $VERSION | |
8 | ($] < 5.005 ? () : | |
9 | ( | |
10 | BINARY_LOCATION => $Config{'archname'} . "/\$(DISTVNAME)-PPD.tar\$(SUFFIX)", | |
11 | AUTHOR => 'Ned Konz <perl@bike-nomad.com>', | |
12 | ABSTRACT_FROM => 'lib/Archive/Zip.pm', | |
13 | )), | |
14 | PREREQ_PM => { | |
15 | 'Compress::Zlib' => '1.14', | |
16 | 'Carp' => 0, | |
17 | # 'Data::Dumper' => 0, # examples/zipinfo.pl | |
18 | 'File::Path' => 0, | |
19 | 'File::Find' => 0, | |
20 | 'File::Basename' => 0, | |
21 | 'File::Spec' => '0.80', # need splitpath() | |
22 | 'File::Copy' => 0, | |
23 | 'File::Temp' => 0, | |
24 | 'File::Which' => '0.05', # Should really be build_requires | |
25 | # 'Getopt::Std' => 0, # examples/extract.pl | |
26 | 'IO::File' => 0, | |
27 | 'IO::Handle' => 0, | |
28 | 'IO::Seekable' => 0, | |
29 | 'Test::More' => '0.42', | |
30 | 'Time::Local' => 0, | |
31 | }, | |
32 | EXE_FILES => [ 'bin/crc32' ], | |
33 | clean => { | |
34 | FILES => 'test.log testdir/* testdir/ extracted/testdir/* extracted/testdir extracted/ testin.zip testout.zip test2.zip' | |
35 | }, | |
36 | dist => { | |
37 | COMPRESS => 'gzip', | |
38 | SUFFIX => '.gz', | |
39 | ZIP => 'zip', | |
40 | ZIPFLAGS => '-r' | |
41 | }, | |
42 | ($ExtUtils::MakeMaker::VERSION ge '6.30_00'? | |
43 | ('LICENSE' => 'perl', ) : ()), | |
44 | ); |
0 | The Archive::Zip module allows a Perl program to create, manipulate, | |
1 | read, and write Zip archive files. | |
2 | ||
3 | Zip archives can be created, or you can read from existing zip files. | |
4 | Once created, they can be written to files, streams, or strings. | |
5 | ||
6 | Members can be added, removed, extracted, replaced, rearranged, and | |
7 | enumerated. They can also be renamed or have their dates, comments, or | |
8 | other attributes queried or modified. Their data can be compressed or | |
9 | uncompressed as needed. Members can be created from members in existing | |
10 | Zip files, or from existing directories, files, or strings. | |
11 | ||
12 | This module uses the Compress::Zlib library to read and write the | |
13 | compressed streams inside the files. | |
14 | ||
15 | Examples and helper libraries are given to show how: | |
16 | * zip files can be written to strings | |
17 | * zip files can be written in chunks to arbitrary functions | |
18 | ||
19 | Documentation will be installed as man pages for: | |
20 | Archive::Zip | |
21 | Archive::Zip::FAQ | |
22 | Archive::Zip::Tree | |
23 | Archive::Zip::MemberRead | |
24 | ||
25 | Copyright (c) 2000-2003 Ned Konz. All rights reserved. This program is free | |
26 | software; you can redistribute it and/or modify it under the same terms | |
27 | as Perl itself. |
0 | #! /usr/bin/perl -w | |
1 | # computes and prints to stdout the CRC-32 values of the given files | |
2 | use lib qw( blib/lib lib ); | |
3 | use Archive::Zip; | |
4 | use FileHandle; | |
5 | ||
6 | my $totalFiles = scalar(@ARGV); | |
7 | foreach my $file (@ARGV) { | |
8 | if ( -d $file ) { | |
9 | warn "$0: ${file}: Is a directory\n"; | |
10 | next; | |
11 | } | |
12 | my $fh = FileHandle->new(); | |
13 | if ( !$fh->open( $file, 'r' ) ) { | |
14 | warn "$0: $!\n"; | |
15 | next; | |
16 | } | |
17 | binmode($fh); | |
18 | my $buffer; | |
19 | my $bytesRead; | |
20 | my $crc = 0; | |
21 | while ( $bytesRead = $fh->read( $buffer, 32768 ) ) { | |
22 | $crc = Archive::Zip::computeCRC32( $buffer, $crc ); | |
23 | } | |
24 | printf( "%08x", $crc ); | |
25 | print("\t$file") if ( $totalFiles > 1 ); | |
26 | print("\n"); | |
27 | } |
Binary diff not shown
0 | Newsgroups: comp.lang.perl.modules | |
1 | Subject: Best form for allowing module extension? | |
2 | Reply-To: | |
3 | Followup-To: | |
4 | Keywords: | |
5 | Summary: | |
6 | ||
7 | I am writing a module (Archive::Zip) that implements the basic read and | |
8 | write functionality for Zip archive files. These files have provisions | |
9 | for extensions for specific platforms: each member has an 'extra field' | |
10 | that can contain OS-specific (or, indeed, any member-specific) data. The | |
11 | overall format of this data is specified (<Header ID>, <count>, <data>), | |
12 | but the actual contents depends on the Header ID. | |
13 | ||
14 | Because I'm only working in a couple of operating environments, and | |
15 | because I'm not trying to write a full "unzip" or "PKZIP" replacement, I | |
16 | don't really want to try to interpret all of these formats. | |
17 | ||
18 | From the PKWARE Appnote.txt file: | |
19 | ||
20 | The current Header ID mappings defined by PKWARE are: | |
21 | ||
22 | 0x0007 AV Info | |
23 | 0x0009 OS/2 | |
24 | 0x000a NTFS | |
25 | 0x000c VAX/VMS | |
26 | 0x000d Unix | |
27 | 0x000f Patch Descriptor | |
28 | ||
29 | Several third party mappings commonly used are: | |
30 | ||
31 | 0x4b46 FWKCS MD5 (see below) | |
32 | 0x07c8 Macintosh | |
33 | 0x4341 Acorn/SparkFS | |
34 | 0x4453 Windows NT security descriptor (binary ACL) | |
35 | 0x4704 VM/CMS | |
36 | 0x470f MVS | |
37 | 0x4c41 OS/2 access control list (text ACL) | |
38 | 0x4d49 Info-ZIP VMS (VAX or Alpha) | |
39 | 0x5455 extended timestamp | |
40 | 0x5855 Info-ZIP Unix (original, also OS/2, NT, etc) | |
41 | 0x6542 BeOS/BeBox | |
42 | 0x756e ASi Unix | |
43 | 0x7855 Info-ZIP Unix (new) | |
44 | 0xfd4a SMS/QDOS | |
45 | ||
46 | I want to make it easy for other people to provide this support without | |
47 | changing my code. | |
48 | ||
49 | Note that not all of these extensions have anything to do with file | |
50 | permissions, although it may be helpful to provide one or more hooks for | |
51 | extracting files: | |
52 | ||
53 | * supply OS-specific filename | |
54 | * open file for write (set permissions) | |
55 | * after closing file (to set ownership, timestamps, etc.) | |
56 | ||
57 | I can provide generic support for these extra fields, so that each | |
58 | member can have 0 or more extra fields, each with a type tag and | |
59 | uninterpreted data. | |
60 | ||
61 | I have seen File::Spec and File::Spec::Unix, etc., and don't think that | |
62 | this scheme is appropriate, since you could have a zip file that was | |
63 | produced on one operating system being extracted by another. | |
64 | ||
65 | Also, it is possible to have multiple types of extra fields in a single | |
66 | zip file. | |
67 | ||
68 | What I have thought about is this: a user who wants to interpret the | |
69 | extended information in the zip members can include the appropriate | |
70 | extension modules: | |
71 | ||
72 | # ==================== in user's code ==================== | |
73 | use Archive::Zip; # basic functionality | |
74 | use Archive::Zip::Unix; # to interpret Unix file permissions, etc. | |
75 | use Archive::Zip::MD5; # to interpret MD5 extended info | |
76 | ||
77 | my $zip = Archive::Zip->new(); | |
78 | $zip->read('ZIPFILE.ZIP'); | |
79 | foreach my $member ($zip->members()) | |
80 | { | |
81 | foreach my $extraField ($member->extraFields()) | |
82 | { | |
83 | print $extraField->info() . "\n"; | |
84 | } | |
85 | ||
86 | $member->extract(); | |
87 | } | |
88 | # ==================== end user's code ==================== | |
89 | ||
90 | I can make an extensible class for writers of OS-specific modules to | |
91 | inherit from: | |
92 | ||
93 | # ==================== in my code ==================== | |
94 | package Archive::Zip::ExtraField; | |
95 | my %Handlers; | |
96 | ||
97 | # Each subclass must call this with their class name and tag ID. | |
98 | sub registerType | |
99 | { | |
100 | my ($class, $tag) = @_; | |
101 | $Handlers{ $tag } = $class; | |
102 | } | |
103 | ||
104 | # Overrideable methods | |
105 | sub info | |
106 | { | |
107 | my $self = shift; | |
108 | ref($self) . " " . $self->{tag} . " " . $self->{dataLength}; | |
109 | } | |
110 | ||
111 | # Provide OS-specific name if any or undef | |
112 | sub preferredFileName { undef } | |
113 | ||
114 | # Returns numeric arg for open() call or undef | |
115 | sub openPermissions { undef } | |
116 | ||
117 | # Hook for doing things after file is extracted | |
118 | # Called as: $extraField->afterClosingExtractedFile($fileName) | |
119 | sub afterClosingExtractedFile { } | |
120 | ||
121 | package Archive::Zip::Member; | |
122 | ||
123 | # return array of extra fields | |
124 | sub extraFields() { ... } | |
125 | ||
126 | sub extract | |
127 | { | |
128 | my $self = shift; | |
129 | my ($preferredFileName) = | |
130 | grep { $_ } | |
131 | (map { $_->preferredFileName() } $self->extraFields()); | |
132 | my $fileName = $preferredFileName || $self->fileName(); | |
133 | # ... similar things for open permissions ... | |
134 | my $fh = FileHandle->new($fileName, $openPermissions); | |
135 | # ... extract data to fh ... | |
136 | $fh->close(); | |
137 | map { $_->afterClosingExtractedFile($fileName) } | |
138 | $self->extraFields(); | |
139 | } | |
140 | # ==================== end my code ==================== | |
141 | ||
142 | ||
143 | Does this seem like a good way to go? Any other suggestions? | |
144 | ||
145 | -- | |
146 | Ned Konz | |
147 | currently: Stanwood, WA | |
148 | email: ned@bike-nomad.com | |
149 | homepage: http://www.bike-nomad.com |
0 | # Example of how to compute compressed sizes | |
1 | # $Revision: 1.2 $ | |
2 | use strict; | |
3 | use Archive::Zip qw(:ERROR_CODES); | |
4 | use File::Spec; | |
5 | my $zip = Archive::Zip->new(); | |
6 | my $blackHoleDevice = File::Spec->devnull(); | |
7 | ||
8 | $zip->addFile($_) foreach (<*.pl>); | |
9 | ||
10 | # Write and throw the data away. | |
11 | # after members are written, the writeOffset will be set | |
12 | # to the compressed size. | |
13 | $zip->writeToFileNamed($blackHoleDevice); | |
14 | ||
15 | my $totalSize = 0; | |
16 | my $totalCompressedSize = 0; | |
17 | foreach my $member ($zip->members()) | |
18 | { | |
19 | $totalSize += $member->uncompressedSize; | |
20 | $totalCompressedSize += $member->_writeOffset; | |
21 | print "Member ", $member->externalFileName, | |
22 | " size=", $member->uncompressedSize, | |
23 | ", writeOffset=", $member->_writeOffset, | |
24 | ", compressed=", $member->compressedSize, | |
25 | "\n"; | |
26 | } | |
27 | ||
28 | print "Total Size=", $totalSize, ", total compressed=", $totalCompressedSize, "\n"; | |
29 | ||
30 | $zip->writeToFileNamed('test.zip'); |
0 | # Copies a zip file to another. | |
1 | # Usage: | |
2 | # perl copy.pl input.zip output.zip | |
3 | # $Revision: 1.4 $ | |
4 | ||
5 | use Archive::Zip qw(:ERROR_CODES); | |
6 | ||
7 | die "usage: perl copy.pl input.zip output.zip\n" | |
8 | if scalar(@ARGV) != 2; | |
9 | ||
10 | my $zip = Archive::Zip->new(); | |
11 | ||
12 | my $status = $zip->read($ARGV[0]); | |
13 | die("read $ARGV[0] failed: $status\n") if $status != AZ_OK; | |
14 | ||
15 | $status = $zip->writeToFileNamed($ARGV[1]); | |
16 | die("writeToFileNamed $ARGV[1] failed: $status\n") if $status != AZ_OK; |
0 | #!/bin/perl -w | |
1 | # Extracts the named files into 'extractTest' subdir | |
2 | # usage: | |
3 | # perl extract.pl [-j] zipfile.zip filename [...] | |
4 | # if -j option given, discards paths. | |
5 | # | |
6 | # $Revision: 1.5 $ | |
7 | # | |
8 | use strict; | |
9 | ||
10 | my $dirName = 'extractTest'; | |
11 | ||
12 | use vars qw( $opt_j ); | |
13 | use Archive::Zip qw(:ERROR_CODES); | |
14 | use Getopt::Std; | |
15 | ||
16 | $opt_j = 0; | |
17 | getopts('j'); | |
18 | ||
19 | if (@ARGV < 2) | |
20 | { | |
21 | die <<EOF | |
22 | usage: perl extract.pl [-j] zipfile.zip filename [...] | |
23 | if -j option given, discards paths. | |
24 | EOF | |
25 | } | |
26 | ||
27 | my $zip = Archive::Zip->new(); | |
28 | my $zipName = shift(@ARGV); | |
29 | my $status = $zip->read( $zipName ); | |
30 | die "Read of $zipName failed\n" if $status != AZ_OK; | |
31 | ||
32 | foreach my $memberName (@ARGV) | |
33 | { | |
34 | print "Extracting $memberName\n"; | |
35 | $status = $opt_j | |
36 | ? $zip->extractMemberWithoutPaths($memberName) | |
37 | : $zip->extractMember($memberName); | |
38 | die "Extracting $memberName from $zipName failed\n" if $status != AZ_OK; | |
39 | } |
0 | #!/usr/bin/perl -w | |
1 | # Requires the following to be installed: | |
2 | # File::Path | |
3 | # File::Spec | |
4 | # IO::Scalar, ... from the IO-stringy distribution | |
5 | # MIME::Base64 | |
6 | # MIME::QuotedPrint | |
7 | # Net::SMTP | |
8 | # Mail::Internet, ... from the MailTools distribution. | |
9 | # MIME::Tools | |
10 | ||
11 | use strict; | |
12 | use Archive::Zip qw(:CONSTANTS :ERROR_CODES); | |
13 | use IO::Scalar; | |
14 | use MIME::Entity; # part of MIME::Tools package | |
15 | ||
16 | my $zipContents = ''; | |
17 | my $SH = IO::Scalar->new( \$zipContents ); | |
18 | ||
19 | my $zip = Archive::Zip->new(); | |
20 | my $member; | |
21 | ||
22 | # add a string as a member: | |
23 | my $stringMember = '<html><head></head><body><h1>Testing</h1></body></html>'; | |
24 | $member = $zip->addString($stringMember, 'whatever.html'); | |
25 | # $member->desiredCompressionMethod(COMPRESSION_STORED); | |
26 | ||
27 | # write it to the scalar | |
28 | my $status = $zip->writeToFileHandle($SH); | |
29 | $SH->close; | |
30 | ||
31 | print STDERR "zip is ". length($zipContents). " bytes long\n"; | |
32 | ||
33 | ### Create an entity: | |
34 | my $top = MIME::Entity->build( | |
35 | Type => 'multipart/mixed', | |
36 | From => 'ned@bike-nomad.com', | |
37 | To => 'billnevin@tricom.net', | |
38 | Subject => "Your zip", | |
39 | ); | |
40 | ||
41 | # attach the message | |
42 | $top->attach( | |
43 | Encoding => '7bit', | |
44 | Data => "here is the zip you ordered\n" | |
45 | ); | |
46 | ||
47 | # attach the zip | |
48 | $top->attach( | |
49 | Data => \$zipContents, | |
50 | Type => "application/x-zip", | |
51 | Encoding => "base64", | |
52 | Disposition => 'attachment', | |
53 | Filename => 'your.zip' | |
54 | ); | |
55 | ||
56 | # attach this code | |
57 | $top->attach( | |
58 | Encoding => '8bit', | |
59 | Type => 'text/plain', | |
60 | Path => $0, | |
61 | # Data => 'whatever', | |
62 | Disposition => 'inline' | |
63 | ); | |
64 | ||
65 | # and print it out to stdout | |
66 | $top->print( \*STDOUT ); |
0 | # Prints messages on every chunk write. | |
1 | # Usage: | |
2 | # perl mfh.pl zipfile.zip | |
3 | # $Revision: 1.4 $ | |
4 | use strict; | |
5 | use Archive::Zip qw(:ERROR_CODES); | |
6 | use Archive::Zip::MockFileHandle; | |
7 | ||
8 | package NedsFileHandle; | |
9 | use vars qw(@ISA); | |
10 | @ISA = qw( Archive::Zip::MockFileHandle ); | |
11 | ||
12 | sub writeHook | |
13 | { | |
14 | my $self = shift; | |
15 | my $bytes = shift; | |
16 | my $length = length($bytes); | |
17 | printf "write %d bytes (position now %d)\n", $length, $self->tell(); | |
18 | return $length; | |
19 | } | |
20 | ||
21 | package main; | |
22 | ||
23 | my $zip = Archive::Zip->new(); | |
24 | my $status = $zip->read($ARGV[0]); | |
25 | exit $status if $status != AZ_OK; | |
26 | ||
27 | my $fh = NedsFileHandle->new(); | |
28 | $zip->writeToFileHandle($fh, 0); |
0 | #!/usr/bin/perl -w | |
1 | # Demonstrates reading a zip from an IO::Scalar | |
2 | # $Revision: 1.4 $ | |
3 | use strict; | |
4 | use Archive::Zip qw(:CONSTANTS :ERROR_CODES); | |
5 | use IO::Scalar; | |
6 | use IO::File; | |
7 | ||
8 | # test reading from a scalar | |
9 | my $file = IO::File->new('testin.zip', 'r'); | |
10 | my $zipContents; | |
11 | binmode($file); | |
12 | $file->read($zipContents, 20000); | |
13 | $file->close(); | |
14 | printf "Read %d bytes\n", length($zipContents); | |
15 | ||
16 | my $SH = IO::Scalar->new(\$zipContents); | |
17 | ||
18 | my $zip = Archive::Zip->new(); | |
19 | $zip->readFromFileHandle( $SH ); | |
20 | my $member = $zip->addString('c' x 300, 'bunchOfCs.txt'); | |
21 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
22 | $member = $zip->addString('d' x 300, 'bunchOfDs.txt'); | |
23 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
24 | ||
25 | $zip->writeToFileNamed('test2.zip'); |
0 | #/usr/bin/perl -w | |
1 | # | |
2 | # Shows one way to write a self-extracting archive file. | |
3 | # This is not intended for production use, and it always extracts to a | |
4 | # subdirectory with a fixed name. | |
5 | # Plus, it requires Perl and A::Z to be installed first. | |
6 | # | |
7 | # In general, you want to provide a stub that is platform-specific. | |
8 | # You can use 'unzipsfx' that it provided with the Info-Zip unzip program. | |
9 | # Get this from http://www.info-zip.org . | |
10 | # | |
11 | # $Revision: 1.6 $ | |
12 | # | |
13 | use strict; | |
14 | ||
15 | use Archive::Zip; | |
16 | use IO::File; | |
17 | ||
18 | # Make a self-extracting Zip file. | |
19 | ||
20 | die "usage: $0 sfxname file [...]\n" unless @ARGV > 1; | |
21 | ||
22 | my $outputName = shift(); | |
23 | ||
24 | my $zip = Archive::Zip->new(); | |
25 | ||
26 | foreach my $file (@ARGV) | |
27 | { | |
28 | $zip->addFileOrDirectory($file); | |
29 | } | |
30 | ||
31 | my $fh = IO::File->new( $outputName, O_CREAT | O_WRONLY | O_TRUNC, 0777 ) | |
32 | or die "Can't open $outputName\: $!\n"; | |
33 | binmode($fh); | |
34 | ||
35 | # add self-extracting Perl code | |
36 | ||
37 | while (<DATA>) | |
38 | { | |
39 | $fh->print($_) | |
40 | } | |
41 | ||
42 | $zip->writeToFileHandle($fh); | |
43 | ||
44 | $fh->close(); | |
45 | ||
46 | # below the __DATA__ line is the extraction stub: | |
47 | __DATA__ | |
48 | #!/usr/local/bin/perl | |
49 | # Self-extracting Zip file extraction stub | |
50 | # Copyright (C) 2002 Ned Konz | |
51 | ||
52 | use Archive::Zip qw(:ERROR_CODES); | |
53 | use IO::File; | |
54 | use File::Spec; | |
55 | ||
56 | my $dir = 'extracted'; | |
57 | my $zip = Archive::Zip->new(); | |
58 | my $fh = IO::File->new($0) or die "Can't open $0\: $!\n"; | |
59 | die "Zip read error\n" unless $zip->readFromFileHandle($fh) == AZ_OK; | |
60 | ||
61 | (mkdir($dir, 0777) or die "Can't create directory $dir\: $!\n") unless -d $dir; | |
62 | ||
63 | for my $member ( $zip->members ) | |
64 | { | |
65 | $member->extractToFileNamed( File::Spec->catfile($dir,$member->fileName) ); | |
66 | } | |
67 | __DATA__ |
0 | #!/bin/perl -w | |
1 | # Extracts all files from the given zip | |
2 | # $Revision: 1.3 $ | |
3 | # usage: | |
4 | # perl unzipAll.pl [-j] zipfile.zip | |
5 | # if -j option given, discards paths. | |
6 | # | |
7 | use strict; | |
8 | ||
9 | use vars qw( $opt_j ); | |
10 | use Archive::Zip qw(:ERROR_CODES); | |
11 | use Getopt::Std; | |
12 | ||
13 | $opt_j = 0; | |
14 | getopts('j'); | |
15 | ||
16 | if (@ARGV < 1) | |
17 | { | |
18 | die <<EOF | |
19 | usage: perl $0 [-j] zipfile.zip | |
20 | if -j option given, discards paths. | |
21 | EOF | |
22 | } | |
23 | ||
24 | my $zip = Archive::Zip->new(); | |
25 | my $zipName = shift(@ARGV); | |
26 | my $status = $zip->read( $zipName ); | |
27 | die "Read of $zipName failed\n" if $status != AZ_OK; | |
28 | ||
29 | $zip->extractTree(); |
0 | # Shows how to update a Zip in place using a temp file. | |
1 | # | |
2 | # usage: | |
3 | # perl [-m] examples/updateTree.pl zipfile.zip dirname | |
4 | # | |
5 | # -m means to mirror | |
6 | # | |
7 | # $Id: updateTree.pl,v 1.2 2003/11/27 17:03:51 ned Exp $ | |
8 | # | |
9 | use Archive::Zip qw(:ERROR_CODES); | |
10 | ||
11 | my $mirror = 0; | |
12 | if ( $ARGV[0] eq '-m' ) { shift; $mirror = 1; } | |
13 | ||
14 | my $zipName = shift || die 'must provide a zip name'; | |
15 | my $dirName = shift || die 'must provide a directory name'; | |
16 | ||
17 | # Read the zip | |
18 | my $zip = Archive::Zip->new(); | |
19 | ||
20 | if ( -f $zipName ) | |
21 | { | |
22 | die "can't read $zipName\n" unless $zip->read($zipName) == AZ_OK; | |
23 | ||
24 | # Update the zip | |
25 | $zip->updateTree($dirName, undef, undef, $mirror); | |
26 | ||
27 | # Now the zip is updated. Write it back via a temp file. | |
28 | exit( $zip->overwrite() ); | |
29 | } | |
30 | else # new zip | |
31 | { | |
32 | $zip->addTree($dirName); | |
33 | exit( $zip->writeToFileNamed($zipName) ); | |
34 | } |
0 | # Shows how to update a Zip in place using a temp file. | |
1 | # $Revision: 1.1 $ | |
2 | # | |
3 | use Archive::Zip qw(:ERROR_CODES); | |
4 | use File::Copy(); | |
5 | ||
6 | my $zipName = shift || die 'must provide a zip name'; | |
7 | my @fileNames = @ARGV; | |
8 | die 'must provide file names' unless scalar(@fileNames); | |
9 | ||
10 | # Read the zip | |
11 | my $zip = Archive::Zip->new(); | |
12 | die "can't read $zipName\n" unless $zip->read($zipName) == AZ_OK; | |
13 | ||
14 | # Update the zip | |
15 | foreach my $file (@fileNames) | |
16 | { | |
17 | $zip->removeMember($file); | |
18 | if ( -r $file ) | |
19 | { | |
20 | if ( -f $file ) | |
21 | { | |
22 | $zip->addFile($file) or die "Can't add $file to zip!\n"; | |
23 | } | |
24 | elsif ( -d $file ) | |
25 | { | |
26 | $zip->addDirectory($file) or die "Can't add $file to zip!\n"; | |
27 | } | |
28 | else | |
29 | { | |
30 | warn "Don't know how to add $file\n"; | |
31 | } | |
32 | } | |
33 | else | |
34 | { | |
35 | warn "Can't read $file\n"; | |
36 | } | |
37 | } | |
38 | ||
39 | # Now the zip is updated. Write it back via a temp file. | |
40 | ||
41 | exit( $zip->overwrite() ); |
0 | #!/usr/bin/perl -w | |
1 | use strict; | |
2 | use Archive::Zip qw(:CONSTANTS :ERROR_CODES); | |
3 | use IO::Scalar; | |
4 | use IO::File; | |
5 | ||
6 | # test writing to a scalar | |
7 | my $zipContents = ''; | |
8 | my $SH = IO::Scalar->new(\$zipContents); | |
9 | ||
10 | my $zip = Archive::Zip->new(); | |
11 | my $member = $zip->addString('a' x 300, 'bunchOfAs.txt'); | |
12 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
13 | $member = $zip->addString('b' x 300, 'bunchOfBs.txt'); | |
14 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
15 | my $status = $zip->writeToFileHandle( $SH ); | |
16 | ||
17 | my $file = IO::File->new('test.zip', 'w'); | |
18 | binmode($file); | |
19 | $file->print($zipContents); | |
20 | $file->close(); | |
21 |
0 | #!/usr/bin/perl -w | |
1 | use strict; | |
2 | use Archive::Zip qw(:CONSTANTS :ERROR_CODES); | |
3 | use IO::String; | |
4 | use IO::File; | |
5 | ||
6 | # test writing to a scalar | |
7 | my $zipContents = ''; | |
8 | my $SH = IO::String->new($zipContents); | |
9 | ||
10 | my $zip = Archive::Zip->new(); | |
11 | my $member = $zip->addString('a' x 300, 'bunchOfAs.txt'); | |
12 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
13 | $member = $zip->addString('b' x 300, 'bunchOfBs.txt'); | |
14 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
15 | my $status = $zip->writeToFileHandle( $SH ); | |
16 | ||
17 | my $file = IO::File->new('test.zip', 'w'); | |
18 | binmode($file); | |
19 | $file->print($zipContents); | |
20 | $file->close(); | |
21 |
0 | #!/bin/perl -w | |
1 | # Creates a zip file, adding the given directories and files. | |
2 | # Usage: | |
3 | # perl zip.pl zipfile.zip file [...] | |
4 | ||
5 | use strict; | |
6 | use Archive::Zip qw(:ERROR_CODES :CONSTANTS); | |
7 | ||
8 | die "usage: $0 zipfile.zip file [...]\n" | |
9 | if (scalar(@ARGV) < 2); | |
10 | ||
11 | my $zipName = shift(@ARGV); | |
12 | my $zip = Archive::Zip->new(); | |
13 | ||
14 | foreach my $memberName (map { glob } @ARGV) | |
15 | { | |
16 | if (-d $memberName ) | |
17 | { | |
18 | warn "Can't add tree $memberName\n" | |
19 | if $zip->addTree( $memberName, $memberName ) != AZ_OK; | |
20 | } | |
21 | else | |
22 | { | |
23 | $zip->addFile( $memberName ) | |
24 | or warn "Can't add file $memberName\n"; | |
25 | } | |
26 | } | |
27 | ||
28 | my $status = $zip->writeToFileNamed($zipName); | |
29 | exit $status; |
0 | #!/usr/bin/perl -w | |
1 | # This program searches for the given Perl regular expression in a Zip archive. | |
2 | # Archive is assumed to contain text files. | |
3 | # By Ned Konz, perl@bike-nomad.com | |
4 | # Usage: | |
5 | # perl zipGrep.pl 'pattern' myZip.zip | |
6 | # | |
7 | use strict; | |
8 | use Archive::Zip qw(:CONSTANTS :ERROR_CODES); | |
9 | ||
10 | if ( @ARGV != 2 ) | |
11 | { | |
12 | print <<EOF; | |
13 | This program searches for the given Perl regular expression in a Zip archive. | |
14 | Archive is assumed to contain text files. | |
15 | Usage: | |
16 | perl $0 'pattern' myZip.zip | |
17 | EOF | |
18 | exit 1; | |
19 | } | |
20 | ||
21 | my $pattern = shift; | |
22 | $pattern = qr{$pattern}; # compile the regular expression | |
23 | my $zipName = shift; | |
24 | ||
25 | my $zip = Archive::Zip->new(); | |
26 | if ( $zip->read($zipName) != AZ_OK ) | |
27 | { | |
28 | die "Read error reading $zipName\n"; | |
29 | } | |
30 | ||
31 | foreach my $member ( $zip->members() ) | |
32 | { | |
33 | my ( $bufferRef, $status, $lastChunk ); | |
34 | my $memberName = $member->fileName(); | |
35 | my $lineNumber = 1; | |
36 | $lastChunk = ''; | |
37 | $member->desiredCompressionMethod(COMPRESSION_STORED); | |
38 | $status = $member->rewindData(); | |
39 | die "rewind error $status" if $status != AZ_OK; | |
40 | ||
41 | while ( !$member->readIsDone() ) | |
42 | { | |
43 | ( $bufferRef, $status ) = $member->readChunk(); | |
44 | die "readChunk error $status" | |
45 | if $status != AZ_OK && $status != AZ_STREAM_END; | |
46 | ||
47 | my $buffer = $lastChunk . $$bufferRef; | |
48 | while ( $buffer =~ m{(.*$pattern.*\n)}mg ) | |
49 | { | |
50 | print "$memberName:$1"; | |
51 | } | |
52 | ($lastChunk) = $$bufferRef =~ m{([^\n\r]+)\z}; | |
53 | } | |
54 | ||
55 | $member->endRead(); | |
56 | } |
0 | #!/bin/perl -w | |
1 | # usage: valid zipname.zip | |
2 | # exits with non-zero status if invalid zip | |
3 | # status = 1: invalid arguments | |
4 | # status = 2: generic error somewhere | |
5 | # status = 3: format error | |
6 | # status = 4: IO error | |
7 | use strict; | |
8 | use Archive::Zip qw(:ERROR_CODES); | |
9 | use IO::Handle; | |
10 | use File::Spec; | |
11 | ||
12 | # instead of stack dump: | |
13 | Archive::Zip::setErrorHandler( sub { warn shift() } ); | |
14 | ||
15 | my $nullFileName = File::Spec->devnull(); | |
16 | my $zip = Archive::Zip->new(); | |
17 | my $zipName = shift(@ARGV) || exit 1; | |
18 | eval | |
19 | { | |
20 | my $status = $zip->read( $zipName ); | |
21 | exit $status if $status != AZ_OK; | |
22 | }; | |
23 | if ($@) { warn 'error reading zip:', $@, "\n"; exit 2 } | |
24 | ||
25 | eval | |
26 | { | |
27 | foreach my $member ($zip->members) | |
28 | { | |
29 | my $fh = IO::File->new(); | |
30 | $fh->open(">$nullFileName") || die "can't open $nullFileName\: $!\n"; | |
31 | my $status = $member->extractToFileHandle($fh); | |
32 | if ($status != AZ_OK) | |
33 | { | |
34 | warn "Extracting ", $member->fileName(), " from $zipName failed\n"; | |
35 | exit $status; | |
36 | } | |
37 | } | |
38 | } |
0 | #! /usr/bin/perl -w | |
1 | # Print out information about a ZIP file. | |
2 | # Note that this buffers the entire file into memory! | |
3 | # usage: | |
4 | # perl examples/zipinfo.pl zipfile.zip | |
5 | ||
6 | use strict; | |
7 | ||
8 | use Data::Dumper (); | |
9 | use FileHandle; | |
10 | use Archive::Zip qw(:ERROR_CODES :CONSTANTS :PKZIP_CONSTANTS); | |
11 | use Archive::Zip::BufferedFileHandle; | |
12 | ||
13 | $| = 1; | |
14 | ||
15 | ### Workaround for a bug in version of Data::Dumper bundled | |
16 | ### with some versions of Perl, which causes warnings when | |
17 | ### calling ->Seen below. | |
18 | if ( defined &Data::Dumper::init_refaddr_format ) { | |
19 | Data::Dumper::init_refaddr_format(); | |
20 | } | |
21 | ||
22 | # use constant END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING; | |
23 | use constant CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE_STRING => pack( SIGNATURE_FORMAT, | |
24 | CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE ); | |
25 | use constant LOCAL_FILE_HEADER_SIGNATURE_STRING => pack( SIGNATURE_FORMAT, | |
26 | LOCAL_FILE_HEADER_SIGNATURE ); | |
27 | ||
28 | $Data::Dumper::Useqq = 1; # enable double-quotes for string values | |
29 | $Data::Dumper::Indent = 1; | |
30 | ||
31 | my $zip = Archive::Zip->new(); | |
32 | my $zipFileName = shift(@ARGV); | |
33 | ||
34 | my $fh = Archive::Zip::BufferedFileHandle->new(); | |
35 | $fh->readFromFile($zipFileName) or exit($!); | |
36 | ||
37 | my $status = $zip->_findEndOfCentralDirectory($fh); | |
38 | die("can't find EOCD\n") if $status != AZ_OK; | |
39 | ||
40 | my $eocdPosition = $fh->tell( ); | |
41 | ||
42 | $status = $zip->_readEndOfCentralDirectory($fh); | |
43 | die("can't read EOCD\n") if $status != AZ_OK; | |
44 | ||
45 | my $zipDumper = Data::Dumper->new([$zip], ['ZIP']); | |
46 | $zipDumper->Seen({ ref($fh), $fh }); | |
47 | print $zipDumper->Dump(), "\n"; | |
48 | ||
49 | my $expectedEOCDPosition = $zip->centralDirectoryOffsetWRTStartingDiskNumber() | |
50 | + $zip->centralDirectorySize(); | |
51 | ||
52 | my $eocdOffset = $zip->{eocdOffset} = $eocdPosition - $expectedEOCDPosition; | |
53 | ||
54 | if ($eocdOffset) | |
55 | { | |
56 | printf "Expected EOCD at %d (0x%x) but found it at %d (0x%x)\n", | |
57 | ($expectedEOCDPosition) x 2, ($eocdPosition) x 2; | |
58 | } | |
59 | else | |
60 | { | |
61 | printf("Found EOCD at %d (0x%x)\n\n", ($eocdPosition) x 2); | |
62 | } | |
63 | ||
64 | my $contents = $fh->contents(); | |
65 | my $offset = $eocdPosition + $eocdOffset - 1; | |
66 | my $cdPos; | |
67 | my @members; | |
68 | my $numberOfMembers = $zip->numberOfCentralDirectoriesOnThisDisk(); | |
69 | foreach my $n (0 .. $numberOfMembers - 1) | |
70 | { | |
71 | my $index = $numberOfMembers - $n; | |
72 | $cdPos = rindex($contents, | |
73 | CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE_STRING, $offset); | |
74 | if ($cdPos < 0) | |
75 | { | |
76 | print "No central directory found for member #$index\n"; | |
77 | last; | |
78 | } | |
79 | else | |
80 | { | |
81 | print "Found central directory for member #$index at $cdPos\n"; | |
82 | $fh->seek($cdPos + SIGNATURE_LENGTH, 0); # SEEK_SET | |
83 | my $newMember = $zip->ZIPMEMBERCLASS->_newFromZipFile( | |
84 | $fh, "($zipFileName)" ); | |
85 | $status = $newMember->_readCentralDirectoryFileHeader(); | |
86 | if ($status != AZ_OK and $status != AZ_STREAM_END) | |
87 | { | |
88 | printf "read CD header status=%d\n", $status; | |
89 | last; | |
90 | } | |
91 | unshift(@members, $newMember); | |
92 | ||
93 | my $memberDumper = Data::Dumper->new([$newMember], ['CDMEMBER' . $index ]); | |
94 | $memberDumper->Seen({ ref($fh), $fh }); | |
95 | print $memberDumper->Dump(), "\n"; | |
96 | } | |
97 | $offset = $cdPos - 1; | |
98 | } | |
99 | ||
100 | if ($cdPos >= 0 and | |
101 | $cdPos != $zip->centralDirectoryOffsetWRTStartingDiskNumber()) | |
102 | { | |
103 | printf "Expected to find central directory at %d (0x%x), but found it at %d (0x%x)\n", | |
104 | ($zip->centralDirectoryOffsetWRTStartingDiskNumber()) x 2, | |
105 | ($cdPos) x 2; | |
106 | } | |
107 | ||
108 | print "\n"; | |
109 | ||
110 | # Now read the local headers | |
111 | ||
112 | foreach my $n (0 .. $#members) | |
113 | { | |
114 | my $member = $members[$n]; | |
115 | $fh->seek($member->localHeaderRelativeOffset() + $eocdOffset + SIGNATURE_LENGTH, 0); | |
116 | $status = $member->_readLocalFileHeader(); | |
117 | if ($status != AZ_OK and $status != AZ_STREAM_END) | |
118 | { | |
119 | printf "member %d read header status=%d\n", $n+1, $status; | |
120 | last; | |
121 | } | |
122 | ||
123 | my $memberDumper = Data::Dumper->new([$member], ['LHMEMBER' . ($n + 1)]); | |
124 | $memberDumper->Seen({ ref($fh), $fh }); | |
125 | print $memberDumper->Dump(), "\n"; | |
126 | ||
127 | my $endOfMember = $member->localHeaderRelativeOffset() | |
128 | + $member->_localHeaderSize() | |
129 | + $member->compressedSize(); | |
130 | ||
131 | if ($endOfMember > $cdPos | |
132 | or ($n < $#members and | |
133 | $endOfMember > $members[$n+1]->localHeaderRelativeOffset())) | |
134 | { | |
135 | print "Error: "; | |
136 | } | |
137 | printf("End of member: %d, CD at %d", $endOfMember, $cdPos); | |
138 | if ( $n < $#members ) | |
139 | { | |
140 | printf(", next member starts at %d", | |
141 | $members[$n+1]->localHeaderRelativeOffset()); | |
142 | } | |
143 | print("\n\n"); | |
144 | } | |
145 | ||
146 | # vim: ts=4 sw=4 |
0 | #!/usr/bin/perl -w | |
1 | # Makes a zip file of the most recent files in a specified directory. | |
2 | # By Rudi Farkas, rudif@bluemail.ch, 9 December 2000 | |
3 | # Usage: | |
4 | # ziprecent <dir> -d <ageDays> [-e <ext> ...]> [-h] [-msvc] [-q] [<zippath>] | |
5 | # Zips files in source directory and its subdirectories | |
6 | # whose file extension is in specified extensions (default: any extension). | |
7 | # -d <days> max age (days) for files to be zipped (default: 1 day) | |
8 | # <dir> source directory | |
9 | # -e <ext> one or more space-separated extensions | |
10 | # -h print help text and exit | |
11 | # -msvc may be given instead of -e and will zip all msvc source files | |
12 | # -q query only (list files but don't zip) | |
13 | # <zippath>.zip path to zipfile to be created (or updated if it exists) | |
14 | # | |
15 | # $Revision: 1.2 $ | |
16 | ||
17 | use strict; | |
18 | ||
19 | use Archive::Zip qw(:ERROR_CODES :CONSTANTS); | |
20 | use Cwd; | |
21 | use File::Basename; | |
22 | use File::Copy; | |
23 | use File::Find; | |
24 | use File::Path; | |
25 | ||
26 | # argument and variable defaults | |
27 | # | |
28 | my $maxFileAgeDays = 1; | |
29 | my $defaultzipdir = 'h:/zip/_homework'; | |
30 | my ($sourcedir, $zipdir, $zippath, @extensions, $query); | |
31 | ||
32 | ||
33 | # usage | |
34 | # | |
35 | my $scriptname = basename $0; | |
36 | my $usage = <<ENDUSAGE; | |
37 | $scriptname <dir> -d <ageDays> [-e <ext> ...]> [-h] [-msvc] [-q] [<zippath>] | |
38 | Zips files in source directory and its subdirectories | |
39 | whose file extension is in specified extensions (default: any extension). | |
40 | -d <days> max age (days) for files to be zipped (default: 1 day) | |
41 | <dir> source directory | |
42 | -e <ext> one or more space-separated extensions | |
43 | -h print help text and exit | |
44 | -msvc may be given instead of -e and will zip all msvc source files | |
45 | -q query only (list files but don't zip) | |
46 | <zippath>.zip path to zipfile to be created (or updated if it exists) | |
47 | ENDUSAGE | |
48 | ||
49 | ||
50 | # parse arguments | |
51 | # | |
52 | while (@ARGV) { | |
53 | my $arg = shift; | |
54 | ||
55 | if ($arg eq '-d') { | |
56 | $maxFileAgeDays = shift; | |
57 | $maxFileAgeDays = 0.0 if $maxFileAgeDays < 0.0; | |
58 | } | |
59 | elsif ($arg eq '-e') { | |
60 | while ($ARGV[0] && $ARGV[0] !~ /^-/) { | |
61 | push @extensions, shift; | |
62 | } | |
63 | } | |
64 | elsif ($arg eq '-msvc') { | |
65 | push @extensions, qw / bmp c cpp def dlg dsp dsw h ico idl mak odl rc rc2 rgs /; | |
66 | } | |
67 | elsif ($arg eq '-q') { | |
68 | $query = 1; | |
69 | } | |
70 | elsif ($arg eq '-h') { | |
71 | print STDERR $usage; | |
72 | exit; | |
73 | } | |
74 | elsif (-d $arg) { | |
75 | $sourcedir = $arg; | |
76 | } | |
77 | elsif ($arg eq '-z') { | |
78 | if ($ARGV[0]) { | |
79 | $zipdir = shift; | |
80 | } | |
81 | } | |
82 | elsif ($arg =~ /\.zip$/) { | |
83 | $zippath = $arg; | |
84 | } | |
85 | else { | |
86 | errorExit("Unknown option or argument: $arg"); | |
87 | } | |
88 | } | |
89 | ||
90 | # process arguments | |
91 | # | |
92 | errorExit("Please specify an existing source directory") unless defined($sourcedir) && -d $sourcedir; | |
93 | ||
94 | my $extensions; | |
95 | if (@extensions) { | |
96 | $extensions = join "|", @extensions; | |
97 | } | |
98 | else { | |
99 | $extensions = ".*"; | |
100 | } | |
101 | ||
102 | # change '\' to '/' (avoids trouble in substitution on Win2k) | |
103 | # | |
104 | $sourcedir =~ s|\\|/|g; | |
105 | $zippath =~ s|\\|/|g if defined($zippath); | |
106 | ||
107 | ||
108 | # find files | |
109 | # | |
110 | my @files; | |
111 | cwd $sourcedir; | |
112 | find(\&listFiles, $sourcedir); | |
113 | printf STDERR "Found %d file(s)\n", scalar @files; | |
114 | ||
115 | ||
116 | # exit ? | |
117 | # | |
118 | exit if $query; | |
119 | exit if @files <= 0; | |
120 | ||
121 | ||
122 | # prepare zip directory | |
123 | # | |
124 | if (defined($zippath)) { | |
125 | # deduce directory from zip path | |
126 | $zipdir = dirname($zippath); | |
127 | $zipdir = '.' unless length $zipdir; | |
128 | } | |
129 | else { | |
130 | $zipdir= $defaultzipdir; | |
131 | } | |
132 | ||
133 | # make sure that zip directory exists | |
134 | # | |
135 | mkpath $zipdir unless -d $zipdir; | |
136 | -d $zipdir or die "Can't find/make directory $zipdir\n"; | |
137 | ||
138 | ||
139 | ||
140 | # create the zip object | |
141 | # | |
142 | my $zip = Archive::Zip->new(); | |
143 | ||
144 | ||
145 | # read-in the existing zip file if any | |
146 | # | |
147 | if (defined $zippath && -f $zippath) { | |
148 | my $status = $zip->read($zippath); | |
149 | warn "Read $zippath failed\n" if $status != AZ_OK; | |
150 | } | |
151 | ||
152 | # add files | |
153 | # | |
154 | foreach my $memberName (@files) | |
155 | { | |
156 | if (-d $memberName ) | |
157 | { | |
158 | warn "Can't add tree $memberName\n" | |
159 | if $zip->addTree( $memberName, $memberName ) != AZ_OK; | |
160 | } | |
161 | else | |
162 | { | |
163 | $zip->addFile( $memberName ) | |
164 | or warn "Can't add file $memberName\n"; | |
165 | } | |
166 | } | |
167 | ||
168 | ||
169 | # prepare the new zip path | |
170 | # | |
171 | my $newzipfile = genfilename(); | |
172 | my $newzippath = "$zipdir/$newzipfile"; | |
173 | ||
174 | ||
175 | # write the new zip file | |
176 | # | |
177 | my $status = $zip->writeToFileNamed($newzippath); | |
178 | if ($status == AZ_OK) { | |
179 | # rename (and overwrite the old zip file if any)? | |
180 | # | |
181 | if (defined $zippath) { | |
182 | my $res = rename $newzippath, $zippath; | |
183 | if ($res) { | |
184 | print STDERR "Updated file $zippath\n"; | |
185 | } | |
186 | else { | |
187 | print STDERR "Created file $newzippath, failed to rename to $zippath\n"; | |
188 | } | |
189 | } | |
190 | else { | |
191 | print STDERR "Created file $newzippath\n"; | |
192 | } | |
193 | } | |
194 | else { | |
195 | print STDERR "Failed to create file $newzippath\n"; | |
196 | } | |
197 | ||
198 | ||
199 | ||
200 | # subroutines | |
201 | # | |
202 | ||
203 | sub listFiles { | |
204 | if (/\.($extensions)$/) { | |
205 | cwd $File::Find::dir; | |
206 | return if -d $File::Find::name; # skip directories | |
207 | my $fileagedays = fileAgeDays($_); | |
208 | if ($fileagedays < $maxFileAgeDays) { | |
209 | printf STDERR "$File::Find::name (%.3g)\n", $fileagedays; | |
210 | (my $filename = $File::Find::name) =~ s/^[a-zA-Z]://; # remove the leading drive letter: | |
211 | push @files, $filename; | |
212 | } | |
213 | } | |
214 | } | |
215 | ||
216 | sub errorExit { | |
217 | printf STDERR "*** %s ***\n$usage\n", shift; | |
218 | exit; | |
219 | } | |
220 | ||
221 | sub mtime { | |
222 | (stat shift)[9]; | |
223 | } | |
224 | ||
225 | sub fileAgeDays { | |
226 | (time() - mtime(shift)) / 86400; | |
227 | } | |
228 | ||
229 | sub genfilename { | |
230 | my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); | |
231 | sprintf "%04d%02d%02d-%02d%02d%02d.zip", $year+1900, $mon+1, $mday, $hour, $min, $sec; | |
232 | } | |
233 | ||
234 | __END__ | |
235 | ||
236 | =head1 NAME | |
237 | ||
238 | ziprecent.pl | |
239 | ||
240 | =head1 SYNOPSIS | |
241 | ||
242 | ziprecent h:/myperl | |
243 | ||
244 | ziprecent h:/myperl -e pl pm -d 365 | |
245 | ||
246 | ziprecent h:/myperl -q | |
247 | ||
248 | ziprecent h:/myperl h:/temp/zip/file1.zip | |
249 | ||
250 | ||
251 | =head1 DESCRIPTION | |
252 | ||
253 | =over 4 | |
254 | ||
255 | This script helps to collect recently modified files in a source directory | |
256 | into a zip file (new or existing). | |
257 | ||
258 | It uses Archive::Zip. | |
259 | ||
260 | =item C< ziprecent h:/myperl > | |
261 | ||
262 | Lists and zips all files more recent than 1 day (24 hours) | |
263 | in directory h:/myperl and it's subdirectories, | |
264 | and places the zip file into default zip directory. | |
265 | The generated zip file name is based on local time (e.g. 20001208-231237.zip). | |
266 | ||
267 | ||
268 | =item C< ziprecent h:/myperl -e pl pm -d 365 > | |
269 | ||
270 | Zips only .pl and .pm files more recent than one year. | |
271 | ||
272 | ||
273 | =item C< ziprecent h:/myperl -msvc > | |
274 | ||
275 | Zips source files found in a typical MSVC project. | |
276 | ||
277 | ||
278 | =item C< ziprecent h:/myperl -q > | |
279 | ||
280 | Lists files that should be zipped. | |
281 | ||
282 | ||
283 | =item C< ziprecent h:/myperl h:/temp/zip/file1.zip > | |
284 | ||
285 | Updates file named h:/temp/zip/file1.zip | |
286 | (overwrites an existing file if writable). | |
287 | ||
288 | ||
289 | =item C< ziprecent -h > | |
290 | ||
291 | Prints the help text and exits. | |
292 | ||
293 | ziprecent.pl <dir> -d <days> [-e <ext> ...]> [-h] [-msvc] [-q] [<zippath>] | |
294 | Zips files in source directory and its subdirectories | |
295 | whose file extension is in specified extensions (default: any extension). | |
296 | -d <days> max age (days) for files to be zipped (default: 1 day) | |
297 | <dir> source directory | |
298 | -e <ext> one or more space-separated extensions | |
299 | -h print help text and exit | |
300 | -msvc may be given instead of -e and will zip all msvc source files | |
301 | -q query only (list files but don't zip) | |
302 | <zippath>.zip path to zipfile to be created (or updated if it exists) | |
303 | ||
304 | =back | |
305 | ||
306 | ||
307 | =head1 BUGS | |
308 | ||
309 | Tested only on Win2k. | |
310 | ||
311 | Does not handle filenames without extension. | |
312 | ||
313 | Does not accept more than one source directory (workaround: invoke separately | |
314 | for each directory, specifying the same zip file). | |
315 | ||
316 | ||
317 | =head1 AUTHOR | |
318 | ||
319 | Rudi Farkas rudif@lecroy.com rudif@bluemail.ch | |
320 | ||
321 | =head1 SEE ALSO | |
322 | ||
323 | perl ;-) | |
324 | ||
325 | =cut | |
326 | ||
327 | ||
328 |
0 | #!/bin/perl -w | |
1 | # $Revision: 1.7 $ | |
2 | # Lists the zipfile given as a first argument and tests CRC's. | |
3 | # Usage: | |
4 | # perl ziptest.pl zipfile.zip | |
5 | ||
6 | use strict; | |
7 | ||
8 | use Archive::Zip qw(:ERROR_CODES :CONSTANTS); | |
9 | ||
10 | package CRCComputingFileHandle; | |
11 | use Archive::Zip::MockFileHandle; | |
12 | ||
13 | use vars qw( @ISA ); | |
14 | @ISA = qw( Archive::Zip::MockFileHandle ); | |
15 | ||
16 | my $crc; | |
17 | ||
18 | sub writeHook | |
19 | { | |
20 | my $self = shift; | |
21 | my $bytes = shift; | |
22 | my $length = length($bytes); | |
23 | $crc = Archive::Zip::computeCRC32($bytes, $crc); | |
24 | } | |
25 | ||
26 | sub resetCRC { $crc = 0 } | |
27 | ||
28 | sub crc { $crc } | |
29 | ||
30 | package main; | |
31 | ||
32 | die "usage: $0 zipfile.zip\n" | |
33 | if (scalar(@ARGV) != 1); | |
34 | ||
35 | my $zip = Archive::Zip->new(); | |
36 | my $status = $zip->read( $ARGV[0] ); | |
37 | exit $status if $status != AZ_OK; | |
38 | ||
39 | print " Length Size Last Modified CRC-32 Name\n"; | |
40 | print "-------- -------- ------------------------ -------- ----\n"; | |
41 | ||
42 | my $fh = CRCComputingFileHandle->new(); | |
43 | my @errors; | |
44 | ||
45 | foreach my $member ($zip->members()) | |
46 | { | |
47 | my $compressedSize = $member->compressedSize(); | |
48 | $fh->resetCRC(); | |
49 | $member->desiredCompressionMethod(COMPRESSION_STORED); | |
50 | $status = $member->extractToFileHandle($fh); | |
51 | exit $status if $status != AZ_OK; | |
52 | my $crc = $fh->crc(); | |
53 | ||
54 | my $ct = scalar(localtime($member->lastModTime())); | |
55 | chomp($ct); | |
56 | ||
57 | printf("%8d %8d %s %08x %s\n", | |
58 | $member->uncompressedSize(), | |
59 | $compressedSize, | |
60 | $ct, | |
61 | $member->crc32(), | |
62 | $member->fileName() | |
63 | ); | |
64 | ||
65 | if ($member->crc32() != $crc) | |
66 | { | |
67 | push(@errors, | |
68 | sprintf("Member %s CRC error: file says %08x computed: %08x\n", | |
69 | $member->fileName(), $member->crc32(), $crc)); | |
70 | } | |
71 | } | |
72 | ||
73 | if (scalar(@errors)) | |
74 | { | |
75 | print join("\n", @errors); | |
76 | die "CRC errors found\n"; | |
77 | } | |
78 | else | |
79 | { | |
80 | print "All CRCs check OK\n"; | |
81 | } |
0 | package Archive::Zip::Archive; | |
1 | ||
2 | # Represents a generic ZIP archive | |
3 | ||
4 | use strict; | |
5 | use File::Path; | |
6 | use File::Find (); | |
7 | use File::Spec (); | |
8 | use File::Copy (); | |
9 | use File::Basename; | |
10 | use Cwd; | |
11 | ||
12 | use vars qw( $VERSION @ISA ); | |
13 | ||
14 | BEGIN { | |
15 | $VERSION = '1.18'; | |
16 | @ISA = qw( Archive::Zip ); | |
17 | } | |
18 | ||
19 | use Archive::Zip qw( | |
20 | :CONSTANTS | |
21 | :ERROR_CODES | |
22 | :PKZIP_CONSTANTS | |
23 | :UTILITY_METHODS | |
24 | ); | |
25 | ||
26 | # Note that this returns undef on read errors, else new zip object. | |
27 | ||
28 | sub new { | |
29 | my $class = shift; | |
30 | my $self = bless( | |
31 | { | |
32 | 'diskNumber' => 0, | |
33 | 'diskNumberWithStartOfCentralDirectory' => 0, | |
34 | 'numberOfCentralDirectoriesOnThisDisk' => 0, # shld be # of members | |
35 | 'numberOfCentralDirectories' => 0, # shld be # of members | |
36 | 'centralDirectorySize' => 0, # must re-compute on write | |
37 | 'centralDirectoryOffsetWRTStartingDiskNumber' => | |
38 | 0, # must re-compute | |
39 | 'writeEOCDOffset' => 0, | |
40 | 'writeCentralDirectoryOffset' => 0, | |
41 | 'zipfileComment' => '', | |
42 | 'eocdOffset' => 0, | |
43 | 'fileName' => '' | |
44 | }, | |
45 | $class | |
46 | ); | |
47 | $self->{'members'} = []; | |
48 | if (@_) { | |
49 | my $status = $self->read(@_); | |
50 | return $status == AZ_OK ? $self : undef; | |
51 | } | |
52 | return $self; | |
53 | } | |
54 | ||
55 | sub members { | |
56 | @{ shift->{'members'} }; | |
57 | } | |
58 | ||
59 | sub numberOfMembers { | |
60 | scalar( shift->members() ); | |
61 | } | |
62 | ||
63 | sub memberNames { | |
64 | my $self = shift; | |
65 | return map { $_->fileName() } $self->members(); | |
66 | } | |
67 | ||
68 | # return ref to member with given name or undef | |
69 | sub memberNamed { | |
70 | my ( $self, $fileName ) = @_; | |
71 | foreach my $member ( $self->members() ) { | |
72 | return $member if $member->fileName() eq $fileName; | |
73 | } | |
74 | return undef; | |
75 | } | |
76 | ||
77 | sub membersMatching { | |
78 | my ( $self, $pattern ) = @_; | |
79 | return grep { $_->fileName() =~ /$pattern/ } $self->members(); | |
80 | } | |
81 | ||
82 | sub diskNumber { | |
83 | shift->{'diskNumber'}; | |
84 | } | |
85 | ||
86 | sub diskNumberWithStartOfCentralDirectory { | |
87 | shift->{'diskNumberWithStartOfCentralDirectory'}; | |
88 | } | |
89 | ||
90 | sub numberOfCentralDirectoriesOnThisDisk { | |
91 | shift->{'numberOfCentralDirectoriesOnThisDisk'}; | |
92 | } | |
93 | ||
94 | sub numberOfCentralDirectories { | |
95 | shift->{'numberOfCentralDirectories'}; | |
96 | } | |
97 | ||
98 | sub centralDirectorySize { | |
99 | shift->{'centralDirectorySize'}; | |
100 | } | |
101 | ||
102 | sub centralDirectoryOffsetWRTStartingDiskNumber { | |
103 | shift->{'centralDirectoryOffsetWRTStartingDiskNumber'}; | |
104 | } | |
105 | ||
106 | sub zipfileComment { | |
107 | my $self = shift; | |
108 | my $comment = $self->{'zipfileComment'}; | |
109 | if (@_) { | |
110 | $self->{'zipfileComment'} = pack( 'C0a*', shift() ); # avoid unicode | |
111 | } | |
112 | return $comment; | |
113 | } | |
114 | ||
115 | sub eocdOffset { | |
116 | shift->{'eocdOffset'}; | |
117 | } | |
118 | ||
119 | # Return the name of the file last read. | |
120 | sub fileName { | |
121 | shift->{'fileName'}; | |
122 | } | |
123 | ||
124 | sub removeMember { | |
125 | my ( $self, $member ) = @_; | |
126 | $member = $self->memberNamed($member) unless ref($member); | |
127 | return undef unless $member; | |
128 | my @newMembers = grep { $_ != $member } $self->members(); | |
129 | $self->{'members'} = \@newMembers; | |
130 | return $member; | |
131 | } | |
132 | ||
133 | sub replaceMember { | |
134 | my ( $self, $oldMember, $newMember ) = @_; | |
135 | $oldMember = $self->memberNamed($oldMember) unless ref($oldMember); | |
136 | return undef unless $oldMember; | |
137 | return undef unless $newMember; | |
138 | my @newMembers = | |
139 | map { ( $_ == $oldMember ) ? $newMember : $_ } $self->members(); | |
140 | $self->{'members'} = \@newMembers; | |
141 | return $oldMember; | |
142 | } | |
143 | ||
144 | sub extractMember { | |
145 | my $self = shift; | |
146 | my $member = shift; | |
147 | $member = $self->memberNamed($member) unless ref($member); | |
148 | return _error('member not found') unless $member; | |
149 | my $originalSize = $member->compressedSize(); | |
150 | my $name = shift; # local FS name if given | |
151 | my ( $volumeName, $dirName, $fileName ); | |
152 | if ( defined($name) ) { | |
153 | ( $volumeName, $dirName, $fileName ) = File::Spec->splitpath($name); | |
154 | $dirName = File::Spec->catpath( $volumeName, $dirName, '' ); | |
155 | } | |
156 | else { | |
157 | $name = $member->fileName(); | |
158 | ( $dirName = $name ) =~ s{[^/]*$}{}; | |
159 | $dirName = Archive::Zip::_asLocalName($dirName); | |
160 | $name = Archive::Zip::_asLocalName($name); | |
161 | } | |
162 | if ( $dirName && !-d $dirName ) { | |
163 | mkpath($dirName); | |
164 | return _ioError("can't create dir $dirName") if ( !-d $dirName ); | |
165 | } | |
166 | my $rc = $member->extractToFileNamed( $name, @_ ); | |
167 | ||
168 | # TODO refactor this fix into extractToFileNamed() | |
169 | $member->{'compressedSize'} = $originalSize; | |
170 | return $rc; | |
171 | } | |
172 | ||
173 | sub extractMemberWithoutPaths { | |
174 | my $self = shift; | |
175 | my $member = shift; | |
176 | $member = $self->memberNamed($member) unless ref($member); | |
177 | return _error('member not found') unless $member; | |
178 | my $originalSize = $member->compressedSize(); | |
179 | return AZ_OK if $member->isDirectory(); | |
180 | my $name = shift; | |
181 | unless ($name) { | |
182 | $name = $member->fileName(); | |
183 | $name =~ s{.*/}{}; # strip off directories, if any | |
184 | $name = Archive::Zip::_asLocalName($name); | |
185 | } | |
186 | my $rc = $member->extractToFileNamed( $name, @_ ); | |
187 | $member->{'compressedSize'} = $originalSize; | |
188 | return $rc; | |
189 | } | |
190 | ||
191 | sub addMember { | |
192 | my ( $self, $newMember ) = @_; | |
193 | push( @{ $self->{'members'} }, $newMember ) if $newMember; | |
194 | return $newMember; | |
195 | } | |
196 | ||
197 | sub addFile { | |
198 | my $self = shift; | |
199 | my $fileName = shift; | |
200 | my $newName = shift; | |
201 | my $newMember = $self->ZIPMEMBERCLASS->newFromFile( $fileName, $newName ); | |
202 | $self->addMember($newMember) if defined($newMember); | |
203 | return $newMember; | |
204 | } | |
205 | ||
206 | sub addString { | |
207 | my $self = shift; | |
208 | my $newMember = $self->ZIPMEMBERCLASS->newFromString(@_); | |
209 | return $self->addMember($newMember); | |
210 | } | |
211 | ||
212 | sub addDirectory { | |
213 | my ( $self, $name, $newName ) = @_; | |
214 | my $newMember = $self->ZIPMEMBERCLASS->newDirectoryNamed( $name, $newName ); | |
215 | $self->addMember($newMember); | |
216 | return $newMember; | |
217 | } | |
218 | ||
219 | # add either a file or a directory. | |
220 | ||
221 | sub addFileOrDirectory { | |
222 | my ( $self, $name, $newName ) = @_; | |
223 | if ( -f $name ) { | |
224 | ( $newName =~ s{/$}{} ) if $newName; | |
225 | return $self->addFile( $name, $newName ); | |
226 | } | |
227 | elsif ( -d $name ) { | |
228 | ( $newName =~ s{[^/]$}{&/} ) if $newName; | |
229 | return $self->addDirectory( $name, $newName ); | |
230 | } | |
231 | else { | |
232 | return _error("$name is neither a file nor a directory"); | |
233 | } | |
234 | } | |
235 | ||
236 | sub contents { | |
237 | my ( $self, $member, $newContents ) = @_; | |
238 | return _error('No member name given') unless $member; | |
239 | $member = $self->memberNamed($member) unless ref($member); | |
240 | return undef unless $member; | |
241 | return $member->contents($newContents); | |
242 | } | |
243 | ||
244 | sub writeToFileNamed { | |
245 | my $self = shift; | |
246 | my $fileName = shift; # local FS format | |
247 | foreach my $member ( $self->members() ) { | |
248 | if ( $member->_usesFileNamed($fileName) ) { | |
249 | return _error( "$fileName is needed by member " | |
250 | . $member->fileName() | |
251 | . "; consider using overwrite() or overwriteAs() instead." ); | |
252 | } | |
253 | } | |
254 | my ( $status, $fh ) = _newFileHandle( $fileName, 'w' ); | |
255 | return _ioError("Can't open $fileName for write") unless $status; | |
256 | my $retval = $self->writeToFileHandle( $fh, 1 ); | |
257 | $fh->close(); | |
258 | $fh = undef; | |
259 | ||
260 | return $retval; | |
261 | } | |
262 | ||
263 | # It is possible to write data to the FH before calling this, | |
264 | # perhaps to make a self-extracting archive. | |
265 | sub writeToFileHandle { | |
266 | my $self = shift; | |
267 | my $fh = shift; | |
268 | return _error('No filehandle given') unless $fh; | |
269 | return _ioError('filehandle not open') unless $fh->opened(); | |
270 | ||
271 | my $fhIsSeekable = @_ ? shift: _isSeekable($fh); | |
272 | _binmode($fh); | |
273 | ||
274 | # Find out where the current position is. | |
275 | my $offset = $fhIsSeekable ? $fh->tell() : 0; | |
276 | $offset = 0 if $offset < 0; | |
277 | ||
278 | foreach my $member ( $self->members() ) { | |
279 | my $retval = $member->_writeToFileHandle( $fh, $fhIsSeekable, $offset ); | |
280 | $member->endRead(); | |
281 | return $retval if $retval != AZ_OK; | |
282 | $offset += $member->_localHeaderSize() + $member->_writeOffset(); | |
283 | $offset += | |
284 | $member->hasDataDescriptor() | |
285 | ? DATA_DESCRIPTOR_LENGTH + SIGNATURE_LENGTH | |
286 | : 0; | |
287 | ||
288 | # changed this so it reflects the last successful position | |
289 | $self->{'writeCentralDirectoryOffset'} = $offset; | |
290 | } | |
291 | return $self->writeCentralDirectory($fh); | |
292 | } | |
293 | ||
294 | # Write zip back to the original file, | |
295 | # as safely as possible. | |
296 | # Returns AZ_OK if successful. | |
297 | sub overwrite { | |
298 | my $self = shift; | |
299 | return $self->overwriteAs( $self->{'fileName'} ); | |
300 | } | |
301 | ||
302 | # Write zip to the specified file, | |
303 | # as safely as possible. | |
304 | # Returns AZ_OK if successful. | |
305 | sub overwriteAs { | |
306 | my $self = shift; | |
307 | my $zipName = shift; | |
308 | return _error("no filename in overwriteAs()") unless defined($zipName); | |
309 | ||
310 | my ( $fh, $tempName ) = Archive::Zip::tempFile(); | |
311 | return _error( "Can't open temp file", $! ) unless $fh; | |
312 | ||
313 | ( my $backupName = $zipName ) =~ s{(\.[^.]*)?$}{.zbk}; | |
314 | ||
315 | my $status = $self->writeToFileHandle($fh); | |
316 | $fh->close(); | |
317 | $fh = undef; | |
318 | ||
319 | if ( $status != AZ_OK ) { | |
320 | unlink($tempName); | |
321 | _printError("Can't write to $tempName"); | |
322 | return $status; | |
323 | } | |
324 | ||
325 | my $err; | |
326 | ||
327 | # rename the zip | |
328 | if ( -f $zipName && !rename( $zipName, $backupName ) ) { | |
329 | $err = $!; | |
330 | unlink($tempName); | |
331 | return _error( "Can't rename $zipName as $backupName", $err ); | |
332 | } | |
333 | ||
334 | # move the temp to the original name (possibly copying) | |
335 | unless ( File::Copy::move( $tempName, $zipName ) ) { | |
336 | $err = $!; | |
337 | rename( $backupName, $zipName ); | |
338 | unlink($tempName); | |
339 | return _error( "Can't move $tempName to $zipName", $err ); | |
340 | } | |
341 | ||
342 | # unlink the backup | |
343 | if ( -f $backupName && !unlink($backupName) ) { | |
344 | $err = $!; | |
345 | return _error( "Can't unlink $backupName", $err ); | |
346 | } | |
347 | ||
348 | return AZ_OK; | |
349 | } | |
350 | ||
351 | # Used only during writing | |
352 | sub _writeCentralDirectoryOffset { | |
353 | shift->{'writeCentralDirectoryOffset'}; | |
354 | } | |
355 | ||
356 | sub _writeEOCDOffset { | |
357 | shift->{'writeEOCDOffset'}; | |
358 | } | |
359 | ||
360 | # Expects to have _writeEOCDOffset() set | |
361 | sub _writeEndOfCentralDirectory { | |
362 | my ( $self, $fh ) = @_; | |
363 | ||
364 | $fh->print(END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING) | |
365 | or return _ioError('writing EOCD Signature'); | |
366 | my $zipfileCommentLength = length( $self->zipfileComment() ); | |
367 | ||
368 | my $header = pack( | |
369 | END_OF_CENTRAL_DIRECTORY_FORMAT, | |
370 | 0, # {'diskNumber'}, | |
371 | 0, # {'diskNumberWithStartOfCentralDirectory'}, | |
372 | $self->numberOfMembers(), # {'numberOfCentralDirectoriesOnThisDisk'}, | |
373 | $self->numberOfMembers(), # {'numberOfCentralDirectories'}, | |
374 | $self->_writeEOCDOffset() - $self->_writeCentralDirectoryOffset(), | |
375 | $self->_writeCentralDirectoryOffset(), | |
376 | $zipfileCommentLength | |
377 | ); | |
378 | $fh->print($header) | |
379 | or return _ioError('writing EOCD header'); | |
380 | if ($zipfileCommentLength) { | |
381 | $fh->print( $self->zipfileComment() ) | |
382 | or return _ioError('writing zipfile comment'); | |
383 | } | |
384 | return AZ_OK; | |
385 | } | |
386 | ||
387 | # $offset can be specified to truncate a zip file. | |
388 | sub writeCentralDirectory { | |
389 | my ( $self, $fh, $offset ) = @_; | |
390 | ||
391 | if ( defined($offset) ) { | |
392 | $self->{'writeCentralDirectoryOffset'} = $offset; | |
393 | $fh->seek( $offset, IO::Seekable::SEEK_SET ) | |
394 | or return _ioError('seeking to write central directory'); | |
395 | } | |
396 | else { | |
397 | $offset = $self->_writeCentralDirectoryOffset(); | |
398 | } | |
399 | ||
400 | foreach my $member ( $self->members() ) { | |
401 | my $status = $member->_writeCentralDirectoryFileHeader($fh); | |
402 | return $status if $status != AZ_OK; | |
403 | $offset += $member->_centralDirectoryHeaderSize(); | |
404 | $self->{'writeEOCDOffset'} = $offset; | |
405 | } | |
406 | return $self->_writeEndOfCentralDirectory($fh); | |
407 | } | |
408 | ||
409 | sub read { | |
410 | my $self = shift; | |
411 | my $fileName = shift; | |
412 | return _error('No filename given') unless $fileName; | |
413 | my ( $status, $fh ) = _newFileHandle( $fileName, 'r' ); | |
414 | return _ioError("opening $fileName for read") unless $status; | |
415 | ||
416 | $status = $self->readFromFileHandle( $fh, $fileName ); | |
417 | return $status if $status != AZ_OK; | |
418 | ||
419 | $fh->close(); | |
420 | $self->{'fileName'} = $fileName; | |
421 | return AZ_OK; | |
422 | } | |
423 | ||
424 | sub readFromFileHandle { | |
425 | my $self = shift; | |
426 | my $fh = shift; | |
427 | my $fileName = shift; | |
428 | $fileName = $fh unless defined($fileName); | |
429 | return _error('No filehandle given') unless $fh; | |
430 | return _ioError('filehandle not open') unless $fh->opened(); | |
431 | ||
432 | _binmode($fh); | |
433 | $self->{'fileName'} = "$fh"; | |
434 | ||
435 | # TODO: how to support non-seekable zips? | |
436 | return _error('file not seekable') | |
437 | unless _isSeekable($fh); | |
438 | ||
439 | $fh->seek( 0, 0 ); # rewind the file | |
440 | ||
441 | my $status = $self->_findEndOfCentralDirectory($fh); | |
442 | return $status if $status != AZ_OK; | |
443 | ||
444 | my $eocdPosition = $fh->tell(); | |
445 | ||
446 | $status = $self->_readEndOfCentralDirectory($fh); | |
447 | return $status if $status != AZ_OK; | |
448 | ||
449 | $fh->seek( $eocdPosition - $self->centralDirectorySize(), | |
450 | IO::Seekable::SEEK_SET ) | |
451 | or return _ioError("Can't seek $fileName"); | |
452 | ||
453 | # Try to detect garbage at beginning of archives | |
454 | # This should be 0 | |
455 | $self->{'eocdOffset'} = $eocdPosition - $self->centralDirectorySize() # here | |
456 | - $self->centralDirectoryOffsetWRTStartingDiskNumber(); | |
457 | ||
458 | for ( ; ; ) { | |
459 | my $newMember = | |
460 | $self->ZIPMEMBERCLASS->_newFromZipFile( $fh, $fileName, | |
461 | $self->eocdOffset() ); | |
462 | my $signature; | |
463 | ( $status, $signature ) = _readSignature( $fh, $fileName ); | |
464 | return $status if $status != AZ_OK; | |
465 | last if $signature == END_OF_CENTRAL_DIRECTORY_SIGNATURE; | |
466 | $status = $newMember->_readCentralDirectoryFileHeader(); | |
467 | return $status if $status != AZ_OK; | |
468 | $status = $newMember->endRead(); | |
469 | return $status if $status != AZ_OK; | |
470 | $newMember->_becomeDirectoryIfNecessary(); | |
471 | push( @{ $self->{'members'} }, $newMember ); | |
472 | } | |
473 | ||
474 | return AZ_OK; | |
475 | } | |
476 | ||
477 | # Read EOCD, starting from position before signature. | |
478 | # Return AZ_OK on success. | |
479 | sub _readEndOfCentralDirectory { | |
480 | my $self = shift; | |
481 | my $fh = shift; | |
482 | ||
483 | # Skip past signature | |
484 | $fh->seek( SIGNATURE_LENGTH, IO::Seekable::SEEK_CUR ) | |
485 | or return _ioError("Can't seek past EOCD signature"); | |
486 | ||
487 | my $header = ''; | |
488 | my $bytesRead = $fh->read( $header, END_OF_CENTRAL_DIRECTORY_LENGTH ); | |
489 | if ( $bytesRead != END_OF_CENTRAL_DIRECTORY_LENGTH ) { | |
490 | return _ioError("reading end of central directory"); | |
491 | } | |
492 | ||
493 | my $zipfileCommentLength; | |
494 | ( | |
495 | $self->{'diskNumber'}, | |
496 | $self->{'diskNumberWithStartOfCentralDirectory'}, | |
497 | $self->{'numberOfCentralDirectoriesOnThisDisk'}, | |
498 | $self->{'numberOfCentralDirectories'}, | |
499 | $self->{'centralDirectorySize'}, | |
500 | $self->{'centralDirectoryOffsetWRTStartingDiskNumber'}, | |
501 | $zipfileCommentLength | |
502 | ) = unpack( END_OF_CENTRAL_DIRECTORY_FORMAT, $header ); | |
503 | ||
504 | if ($zipfileCommentLength) { | |
505 | my $zipfileComment = ''; | |
506 | $bytesRead = $fh->read( $zipfileComment, $zipfileCommentLength ); | |
507 | if ( $bytesRead != $zipfileCommentLength ) { | |
508 | return _ioError("reading zipfile comment"); | |
509 | } | |
510 | $self->{'zipfileComment'} = $zipfileComment; | |
511 | } | |
512 | ||
513 | return AZ_OK; | |
514 | } | |
515 | ||
516 | # Seek in my file to the end, then read backwards until we find the | |
517 | # signature of the central directory record. Leave the file positioned right | |
518 | # before the signature. Returns AZ_OK if success. | |
519 | sub _findEndOfCentralDirectory { | |
520 | my $self = shift; | |
521 | my $fh = shift; | |
522 | my $data = ''; | |
523 | $fh->seek( 0, IO::Seekable::SEEK_END ) | |
524 | or return _ioError("seeking to end"); | |
525 | ||
526 | my $fileLength = $fh->tell(); | |
527 | if ( $fileLength < END_OF_CENTRAL_DIRECTORY_LENGTH + 4 ) { | |
528 | return _formatError("file is too short"); | |
529 | } | |
530 | ||
531 | my $seekOffset = 0; | |
532 | my $pos = -1; | |
533 | for ( ; ; ) { | |
534 | $seekOffset += 512; | |
535 | $seekOffset = $fileLength if ( $seekOffset > $fileLength ); | |
536 | $fh->seek( -$seekOffset, IO::Seekable::SEEK_END ) | |
537 | or return _ioError("seek failed"); | |
538 | my $bytesRead = $fh->read( $data, $seekOffset ); | |
539 | if ( $bytesRead != $seekOffset ) { | |
540 | return _ioError("read failed"); | |
541 | } | |
542 | $pos = rindex( $data, END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING ); | |
543 | last | |
544 | if ( $pos >= 0 | |
545 | or $seekOffset == $fileLength | |
546 | or $seekOffset >= $Archive::Zip::ChunkSize ); | |
547 | } | |
548 | ||
549 | if ( $pos >= 0 ) { | |
550 | $fh->seek( $pos - $seekOffset, IO::Seekable::SEEK_CUR ) | |
551 | or return _ioError("seeking to EOCD"); | |
552 | return AZ_OK; | |
553 | } | |
554 | else { | |
555 | return _formatError("can't find EOCD signature"); | |
556 | } | |
557 | } | |
558 | ||
559 | # Used to avoid taint problems when chdir'ing. | |
560 | # Not intended to increase security in any way; just intended to shut up the -T | |
561 | # complaints. If your Cwd module is giving you unreliable returns from cwd() | |
562 | # you have bigger problems than this. | |
563 | sub _untaintDir { | |
564 | my $dir = shift; | |
565 | $dir =~ m/\A(.+)\z/s; | |
566 | return $1; | |
567 | } | |
568 | ||
569 | sub addTree { | |
570 | my $self = shift; | |
571 | my $root = shift or return _error("root arg missing in call to addTree()"); | |
572 | my $dest = shift; | |
573 | $dest = '' unless defined($dest); | |
574 | my $pred = shift || sub { -r }; | |
575 | my @files; | |
576 | my $startDir = _untaintDir( cwd() ); | |
577 | ||
578 | return _error( 'undef returned by _untaintDir on cwd ', cwd() ) | |
579 | unless $startDir; | |
580 | ||
581 | # This avoids chdir'ing in Find, in a way compatible with older | |
582 | # versions of File::Find. | |
583 | my $wanted = sub { | |
584 | local $main::_ = $File::Find::name; | |
585 | my $dir = _untaintDir($File::Find::dir); | |
586 | chdir($startDir); | |
587 | push( @files, $File::Find::name ) if (&$pred); | |
588 | chdir($dir); | |
589 | }; | |
590 | ||
591 | File::Find::find( $wanted, $root ); | |
592 | ||
593 | my $rootZipName = _asZipDirName( $root, 1 ); # with trailing slash | |
594 | my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E"; | |
595 | ||
596 | $dest = _asZipDirName( $dest, 1 ); # with trailing slash | |
597 | ||
598 | foreach my $fileName (@files) { | |
599 | my $isDir = -d $fileName; | |
600 | ||
601 | # normalize, remove leading ./ | |
602 | my $archiveName = _asZipDirName( $fileName, $isDir ); | |
603 | if ( $archiveName eq $rootZipName ) { $archiveName = $dest } | |
604 | else { $archiveName =~ s{$pattern}{$dest} } | |
605 | next if $archiveName =~ m{^\.?/?$}; # skip current dir | |
606 | my $member = $isDir | |
607 | ? $self->addDirectory( $fileName, $archiveName ) | |
608 | : $self->addFile( $fileName, $archiveName ); | |
609 | return _error("add $fileName failed in addTree()") if !$member; | |
610 | } | |
611 | return AZ_OK; | |
612 | } | |
613 | ||
614 | sub addTreeMatching { | |
615 | my $self = shift; | |
616 | my $root = shift | |
617 | or return _error("root arg missing in call to addTreeMatching()"); | |
618 | my $dest = shift; | |
619 | $dest = '' unless defined($dest); | |
620 | my $pattern = shift | |
621 | or return _error("pattern missing in call to addTreeMatching()"); | |
622 | my $pred = shift; | |
623 | my $matcher = | |
624 | $pred ? sub { m{$pattern} && &$pred } : sub { m{$pattern} && -r }; | |
625 | return $self->addTree( $root, $dest, $matcher ); | |
626 | } | |
627 | ||
628 | # $zip->extractTree( $root, $dest [, $volume] ); | |
629 | # | |
630 | # $root and $dest are Unix-style. | |
631 | # $volume is in local FS format. | |
632 | # | |
633 | sub extractTree { | |
634 | my $self = shift; | |
635 | my $root = shift; # Zip format | |
636 | $root = '' unless defined($root); | |
637 | my $dest = shift; # Zip format | |
638 | $dest = './' unless defined($dest); | |
639 | my $volume = shift; # optional | |
640 | my $pattern = "^\Q$root"; | |
641 | my @members = $self->membersMatching($pattern); | |
642 | ||
643 | foreach my $member (@members) { | |
644 | my $fileName = $member->fileName(); # in Unix format | |
645 | $fileName =~ s{$pattern}{$dest}; # in Unix format | |
646 | # convert to platform format: | |
647 | $fileName = Archive::Zip::_asLocalName( $fileName, $volume ); | |
648 | my $status = $member->extractToFileNamed($fileName); | |
649 | return $status if $status != AZ_OK; | |
650 | } | |
651 | return AZ_OK; | |
652 | } | |
653 | ||
654 | # $zip->updateMember( $memberOrName, $fileName ); | |
655 | # Returns (possibly updated) member, if any; undef on errors. | |
656 | ||
657 | sub updateMember { | |
658 | my $self = shift; | |
659 | my $oldMember = shift; | |
660 | my $fileName = shift; | |
661 | ||
662 | if ( !defined($fileName) ) { | |
663 | _error("updateMember(): missing fileName argument"); | |
664 | return undef; | |
665 | } | |
666 | ||
667 | my @newStat = stat($fileName); | |
668 | if ( !@newStat ) { | |
669 | _ioError("Can't stat $fileName"); | |
670 | return undef; | |
671 | } | |
672 | ||
673 | my $isDir = -d _; | |
674 | ||
675 | my $memberName; | |
676 | ||
677 | if ( ref($oldMember) ) { | |
678 | $memberName = $oldMember->fileName(); | |
679 | } | |
680 | else { | |
681 | $oldMember = $self->memberNamed( $memberName = $oldMember ) | |
682 | || $self->memberNamed( $memberName = | |
683 | _asZipDirName( $oldMember, $isDir ) ); | |
684 | } | |
685 | ||
686 | unless ( defined($oldMember) | |
687 | && $oldMember->lastModTime() == $newStat[9] | |
688 | && $oldMember->isDirectory() == $isDir | |
689 | && ( $isDir || ( $oldMember->uncompressedSize() == $newStat[7] ) ) ) | |
690 | { | |
691 | ||
692 | # create the new member | |
693 | my $newMember = $isDir | |
694 | ? $self->ZIPMEMBERCLASS->newDirectoryNamed( $fileName, $memberName ) | |
695 | : $self->ZIPMEMBERCLASS->newFromFile( $fileName, $memberName ); | |
696 | ||
697 | unless ( defined($newMember) ) { | |
698 | _error("creation of member $fileName failed in updateMember()"); | |
699 | return undef; | |
700 | } | |
701 | ||
702 | # replace old member or append new one | |
703 | if ( defined($oldMember) ) { | |
704 | $self->replaceMember( $oldMember, $newMember ); | |
705 | } | |
706 | else { $self->addMember($newMember); } | |
707 | ||
708 | return $newMember; | |
709 | } | |
710 | ||
711 | return $oldMember; | |
712 | } | |
713 | ||
714 | # $zip->updateTree( $root, [ $dest, [ $pred [, $mirror]]] ); | |
715 | # | |
716 | # This takes the same arguments as addTree, but first checks to see | |
717 | # whether the file or directory already exists in the zip file. | |
718 | # | |
719 | # If the fourth argument $mirror is true, then delete all my members | |
720 | # if corresponding files weren't found. | |
721 | ||
722 | sub updateTree { | |
723 | my $self = shift; | |
724 | my $root = shift | |
725 | or return _error("root arg missing in call to updateTree()"); | |
726 | my $dest = shift; | |
727 | $dest = '' unless defined($dest); | |
728 | $dest = _asZipDirName( $dest, 1 ); | |
729 | my $pred = shift || sub { -r }; | |
730 | my $mirror = shift; | |
731 | ||
732 | my $rootZipName = _asZipDirName( $root, 1 ); # with trailing slash | |
733 | my $pattern = $rootZipName eq './' ? '^' : "^\Q$rootZipName\E"; | |
734 | ||
735 | my @files; | |
736 | my $startDir = _untaintDir( cwd() ); | |
737 | ||
738 | return _error( 'undef returned by _untaintDir on cwd ', cwd() ) | |
739 | unless $startDir; | |
740 | ||
741 | # This avoids chdir'ing in Find, in a way compatible with older | |
742 | # versions of File::Find. | |
743 | my $wanted = sub { | |
744 | local $main::_ = $File::Find::name; | |
745 | my $dir = _untaintDir($File::Find::dir); | |
746 | chdir($startDir); | |
747 | push( @files, $File::Find::name ) if (&$pred); | |
748 | chdir($dir); | |
749 | }; | |
750 | ||
751 | File::Find::find( $wanted, $root ); | |
752 | ||
753 | # Now @files has all the files that I could potentially be adding to | |
754 | # the zip. Only add the ones that are necessary. | |
755 | # For each file (updated or not), add its member name to @done. | |
756 | my %done; | |
757 | foreach my $fileName (@files) { | |
758 | my @newStat = stat($fileName); | |
759 | my $isDir = -d _; | |
760 | ||
761 | # normalize, remove leading ./ | |
762 | my $memberName = _asZipDirName( $fileName, $isDir ); | |
763 | if ( $memberName eq $rootZipName ) { $memberName = $dest } | |
764 | else { $memberName =~ s{$pattern}{$dest} } | |
765 | next if $memberName =~ m{^\.?/?$}; # skip current dir | |
766 | ||
767 | $done{$memberName} = 1; | |
768 | my $changedMember = $self->updateMember( $memberName, $fileName ); | |
769 | return _error("updateTree failed to update $fileName") | |
770 | unless ref($changedMember); | |
771 | } | |
772 | ||
773 | # @done now has the archive names corresponding to all the found files. | |
774 | # If we're mirroring, delete all those members that aren't in @done. | |
775 | if ($mirror) { | |
776 | foreach my $member ( $self->members() ) { | |
777 | $self->removeMember($member) | |
778 | unless $done{ $member->fileName() }; | |
779 | } | |
780 | } | |
781 | ||
782 | return AZ_OK; | |
783 | } | |
784 | ||
785 | 1; |
0 | package Archive::Zip::BufferedFileHandle; | |
1 | ||
2 | # File handle that uses a string internally and can seek | |
3 | # This is given as a demo for getting a zip file written | |
4 | # to a string. | |
5 | # I probably should just use IO::Scalar instead. | |
6 | # Ned Konz, March 2000 | |
7 | ||
8 | use strict; | |
9 | use IO::File; | |
10 | use Carp; | |
11 | ||
12 | use vars qw{$VERSION}; | |
13 | ||
14 | BEGIN { | |
15 | $VERSION = '1.18'; | |
16 | $VERSION = eval $VERSION; | |
17 | } | |
18 | ||
19 | sub new { | |
20 | my $class = shift || __PACKAGE__; | |
21 | $class = ref($class) || $class; | |
22 | my $self = bless( | |
23 | { | |
24 | content => '', | |
25 | position => 0, | |
26 | size => 0 | |
27 | }, | |
28 | $class | |
29 | ); | |
30 | return $self; | |
31 | } | |
32 | ||
33 | # Utility method to read entire file | |
34 | sub readFromFile { | |
35 | my $self = shift; | |
36 | my $fileName = shift; | |
37 | my $fh = IO::File->new( $fileName, "r" ); | |
38 | CORE::binmode($fh); | |
39 | if ( !$fh ) { | |
40 | Carp::carp("Can't open $fileName: $!\n"); | |
41 | return undef; | |
42 | } | |
43 | local $/ = undef; | |
44 | $self->{content} = <$fh>; | |
45 | $self->{size} = length( $self->{content} ); | |
46 | return $self; | |
47 | } | |
48 | ||
49 | sub contents { | |
50 | my $self = shift; | |
51 | if (@_) { | |
52 | $self->{content} = shift; | |
53 | $self->{size} = length( $self->{content} ); | |
54 | } | |
55 | return $self->{content}; | |
56 | } | |
57 | ||
58 | sub binmode { 1 } | |
59 | ||
60 | sub close { 1 } | |
61 | ||
62 | sub opened { 1 } | |
63 | ||
64 | sub eof { | |
65 | my $self = shift; | |
66 | return $self->{position} >= $self->{size}; | |
67 | } | |
68 | ||
69 | sub seek { | |
70 | my $self = shift; | |
71 | my $pos = shift; | |
72 | my $whence = shift; | |
73 | ||
74 | # SEEK_SET | |
75 | if ( $whence == 0 ) { $self->{position} = $pos; } | |
76 | ||
77 | # SEEK_CUR | |
78 | elsif ( $whence == 1 ) { $self->{position} += $pos; } | |
79 | ||
80 | # SEEK_END | |
81 | elsif ( $whence == 2 ) { $self->{position} = $self->{size} + $pos; } | |
82 | else { return 0; } | |
83 | ||
84 | return 1; | |
85 | } | |
86 | ||
87 | sub tell { return shift->{position}; } | |
88 | ||
89 | # Copy my data to given buffer | |
90 | sub read { | |
91 | my $self = shift; | |
92 | my $buf = \( $_[0] ); | |
93 | shift; | |
94 | my $len = shift; | |
95 | my $offset = shift || 0; | |
96 | ||
97 | $$buf = '' if not defined($$buf); | |
98 | my $bytesRead = | |
99 | ( $self->{position} + $len > $self->{size} ) | |
100 | ? ( $self->{size} - $self->{position} ) | |
101 | : $len; | |
102 | substr( $$buf, $offset, $bytesRead ) = | |
103 | substr( $self->{content}, $self->{position}, $bytesRead ); | |
104 | $self->{position} += $bytesRead; | |
105 | return $bytesRead; | |
106 | } | |
107 | ||
108 | # Copy given buffer to me | |
109 | sub write { | |
110 | my $self = shift; | |
111 | my $buf = \( $_[0] ); | |
112 | shift; | |
113 | my $len = shift; | |
114 | my $offset = shift || 0; | |
115 | ||
116 | $$buf = '' if not defined($$buf); | |
117 | my $bufLen = length($$buf); | |
118 | my $bytesWritten = | |
119 | ( $offset + $len > $bufLen ) | |
120 | ? $bufLen - $offset | |
121 | : $len; | |
122 | substr( $self->{content}, $self->{position}, $bytesWritten ) = | |
123 | substr( $$buf, $offset, $bytesWritten ); | |
124 | $self->{size} = length( $self->{content} ); | |
125 | return $bytesWritten; | |
126 | } | |
127 | ||
128 | sub clearerr() { 1 } | |
129 | ||
130 | 1; |
0 | package Archive::Zip::DirectoryMember; | |
1 | ||
2 | use strict; | |
3 | use File::Path; | |
4 | ||
5 | use vars qw( $VERSION @ISA ); | |
6 | ||
7 | BEGIN { | |
8 | $VERSION = '1.18'; | |
9 | @ISA = qw( Archive::Zip::Member ); | |
10 | } | |
11 | ||
12 | use Archive::Zip qw( | |
13 | :ERROR_CODES | |
14 | :UTILITY_METHODS | |
15 | ); | |
16 | ||
17 | sub _newNamed { | |
18 | my $class = shift; | |
19 | my $fileName = shift; # FS name | |
20 | my $newName = shift; # Zip name | |
21 | $newName = _asZipDirName($fileName) unless $newName; | |
22 | my $self = $class->new(@_); | |
23 | $self->{'externalFileName'} = $fileName; | |
24 | $self->fileName($newName); | |
25 | ||
26 | if ( -e $fileName ) { | |
27 | ||
28 | # -e does NOT do a full stat, so we need to do one now | |
29 | if ( -d _ ) { | |
30 | my @stat = stat(_); | |
31 | $self->unixFileAttributes( $stat[2] ); | |
32 | my $mod_t = $stat[9]; | |
33 | if ( $^O eq 'MSWin32' and !$mod_t ) { | |
34 | $mod_t = time(); | |
35 | } | |
36 | $self->setLastModFileDateTimeFromUnix($mod_t); | |
37 | ||
38 | } | |
39 | else { # hmm.. trying to add a non-directory? | |
40 | _error( $fileName, ' exists but is not a directory' ); | |
41 | return undef; | |
42 | } | |
43 | } | |
44 | else { | |
45 | $self->unixFileAttributes( $self->DEFAULT_DIRECTORY_PERMISSIONS ); | |
46 | $self->setLastModFileDateTimeFromUnix( time() ); | |
47 | } | |
48 | return $self; | |
49 | } | |
50 | ||
51 | sub externalFileName { | |
52 | shift->{'externalFileName'}; | |
53 | } | |
54 | ||
55 | sub isDirectory { | |
56 | return 1; | |
57 | } | |
58 | ||
59 | sub extractToFileNamed { | |
60 | my $self = shift; | |
61 | my $name = shift; # local FS name | |
62 | my $attribs = $self->unixFileAttributes() & 07777; | |
63 | mkpath( $name, 0, $attribs ); # croaks on error | |
64 | utime( $self->lastModTime(), $self->lastModTime(), $name ); | |
65 | return AZ_OK; | |
66 | } | |
67 | ||
68 | sub fileName { | |
69 | my $self = shift; | |
70 | my $newName = shift; | |
71 | $newName =~ s{/?$}{/} if defined($newName); | |
72 | return $self->SUPER::fileName($newName); | |
73 | } | |
74 | ||
75 | # So people don't get too confused. This way it looks like the problem | |
76 | # is in their code... | |
77 | sub contents { | |
78 | return wantarray ? ( undef, AZ_OK ) : undef; | |
79 | } | |
80 | ||
81 | 1; |
0 | ||
1 | =head1 NAME | |
2 | ||
3 | ||
4 | Archive::Zip::FAQ - Answers to a few frequently asked questions about Archive::Zip | |
5 | ||
6 | =head1 DESCRIPTION | |
7 | ||
8 | ||
9 | It seems that I keep answering the same questions over and over again. I | |
10 | assume that this is because my documentation is deficient, rather than that | |
11 | people don't read the documentation. | |
12 | ||
13 | ||
14 | So this FAQ is an attempt to cut down on the number of personal answers I have | |
15 | to give. At least I can now say "You I<did> read the FAQ, right?". | |
16 | ||
17 | ||
18 | The questions are not in any particular order. The answers assume the current | |
19 | version of Archive::Zip; some of the answers depend on newly added/fixed | |
20 | functionality. | |
21 | ||
22 | =head1 Install problems on RedHat 8 or 9 with Perl 5.8.0 | |
23 | ||
24 | ||
25 | B<Q:> Archive::Zip won't install on my RedHat 9 system! It's broke! | |
26 | ||
27 | ||
28 | B<A:> This has become something of a FAQ. | |
29 | Basically, RedHat broke some versions of Perl by setting LANG to UTF8. | |
30 | They apparently have a fixed version out as an update. | |
31 | ||
32 | You might try running CPAN or creating your Makefile after exporting the LANG | |
33 | environment variable as | |
34 | ||
35 | C<LANG=C> | |
36 | ||
37 | L<https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=87682> | |
38 | ||
39 | ||
40 | =head1 Why is my zip file so big? | |
41 | ||
42 | ||
43 | B<Q:> My zip file is actually bigger than what I stored in it! Why? | |
44 | ||
45 | ||
46 | B<A:> Some things to make sure of: | |
47 | ||
48 | =over 4 | |
49 | ||
50 | =item Make sure that you are requesting COMPRESSION_DEFLATED if you are storing strings. | |
51 | ||
52 | ||
53 | ||
54 | ||
55 | $member->desiredCompressionMethod( COMPRESSION_DEFLATED ); | |
56 | ||
57 | ||
58 | =item Don't make lots of little files if you can help it. | |
59 | ||
60 | ||
61 | ||
62 | Since zip computes the compression tables for each member, small | |
63 | members without much entropy won't compress well. Instead, if you've | |
64 | got lots of repeated strings in your data, try to combine them into | |
65 | one big member. | |
66 | ||
67 | ||
68 | =item Make sure that you are requesting COMPRESSION_STORED if you are storing things that are already compressed. | |
69 | ||
70 | ||
71 | ||
72 | If you're storing a .zip, .jpg, .mp3, or other compressed file in a zip, | |
73 | then don't compress them again. They'll get bigger. | |
74 | ||
75 | =back | |
76 | ||
77 | =head1 Sample code? | |
78 | ||
79 | ||
80 | B<Q:> Can you send me code to do (whatever)? | |
81 | ||
82 | ||
83 | B<A:> Have you looked in the C<examples/> directory yet? It contains: | |
84 | ||
85 | =over 4 | |
86 | ||
87 | =item examples/calcSizes.pl -- How to find out how big a Zip file will be before writing it | |
88 | ||
89 | ||
90 | ||
91 | =item examples/copy.pl -- Copies one Zip file to another | |
92 | ||
93 | ||
94 | ||
95 | =item examples/extract.pl -- extract file(s) from a Zip | |
96 | ||
97 | ||
98 | ||
99 | =item examples/mailZip.pl -- make and mail a zip file | |
100 | ||
101 | ||
102 | ||
103 | =item examples/mfh.pl -- demo for use of MockFileHandle | |
104 | ||
105 | ||
106 | ||
107 | =item examples/readScalar.pl -- shows how to use IO::Scalar as the source of a Zip read | |
108 | ||
109 | ||
110 | ||
111 | =item examples/selfex.pl -- a brief example of a self-extracting Zip | |
112 | ||
113 | ||
114 | ||
115 | =item examples/unzipAll.pl -- uses Archive::Zip::Tree to unzip an entire Zip | |
116 | ||
117 | ||
118 | ||
119 | =item examples/updateZip.pl -- shows how to read/modify/write a Zip | |
120 | ||
121 | ||
122 | ||
123 | =item examples/updateTree.pl -- shows how to update a Zip in place | |
124 | ||
125 | ||
126 | ||
127 | =item examples/writeScalar.pl -- shows how to use IO::Scalar as the destination of a Zip write | |
128 | ||
129 | ||
130 | ||
131 | =item examples/writeScalar2.pl -- shows how to use IO::String as the destination of a Zip write | |
132 | ||
133 | ||
134 | ||
135 | =item examples/zip.pl -- Constructs a Zip file | |
136 | ||
137 | ||
138 | ||
139 | =item examples/zipcheck.pl -- One way to check a Zip file for validity | |
140 | ||
141 | ||
142 | ||
143 | =item examples/zipinfo.pl -- Prints out information about a Zip archive file | |
144 | ||
145 | ||
146 | ||
147 | =item examples/zipGrep.pl -- Searches for text in Zip files | |
148 | ||
149 | ||
150 | ||
151 | =item examples/ziptest.pl -- Lists a Zip file and checks member CRCs | |
152 | ||
153 | ||
154 | ||
155 | =item examples/ziprecent.pl -- Puts recent files into a zipfile | |
156 | ||
157 | ||
158 | ||
159 | =item examples/ziptest.pl -- Another way to check a Zip file for validity | |
160 | ||
161 | ||
162 | ||
163 | =back | |
164 | ||
165 | =head1 Can't Read/modify/write same Zip file | |
166 | ||
167 | ||
168 | B<Q:> Why can't I open a Zip file, add a member, and write it back? I get an | |
169 | error message when I try. | |
170 | ||
171 | ||
172 | B<A:> Because Archive::Zip doesn't (and can't, generally) read file contents into memory, | |
173 | the original Zip file is required to stay around until the writing of the new | |
174 | file is completed. | |
175 | ||
176 | ||
177 | The best way to do this is to write the Zip to a temporary file and then | |
178 | rename the temporary file to have the old name (possibly after deleting the | |
179 | old one). | |
180 | ||
181 | ||
182 | Archive::Zip v1.02 added the archive methods C<overwrite()> and | |
183 | C<overwriteAs()> to do this simply and carefully. | |
184 | ||
185 | ||
186 | See C<examples/updateZip.pl> for an example of this technique. | |
187 | ||
188 | =head1 File creation time not set | |
189 | ||
190 | ||
191 | B<Q:> Upon extracting files, I see that their modification (and access) times are | |
192 | set to the time in the Zip archive. However, their creation time is not set to | |
193 | the same time. Why? | |
194 | ||
195 | ||
196 | B<A:> Mostly because Perl doesn't give cross-platform access to I<creation time>. | |
197 | Indeed, many systems (like Unix) don't support such a concept. | |
198 | However, if yours does, you can easily set it. Get the modification time from | |
199 | the member using C<lastModTime()>. | |
200 | ||
201 | =head1 Can't use Archive::Zip on gzip files | |
202 | ||
203 | ||
204 | B<Q:> Can I use Archive::Zip to extract Unix gzip files? | |
205 | ||
206 | ||
207 | B<A:> No. | |
208 | ||
209 | ||
210 | There is a distinction between Unix gzip files, and Zip archives that | |
211 | also can use the gzip compression. | |
212 | ||
213 | ||
214 | Depending on the format of the gzip file, you can use L<Compress::Zlib>, or | |
215 | L<Archive::Tar> to decompress it (and de-archive it in the case of Tar files). | |
216 | ||
217 | ||
218 | You can unzip PKZIP/WinZip/etc/ archives using Archive::Zip (that's what | |
219 | it's for) as long as any compressed members are compressed using | |
220 | Deflate compression. | |
221 | ||
222 | =head1 Add a directory/tree to a Zip | |
223 | ||
224 | ||
225 | B<Q:> How can I add a directory (or tree) full of files to a Zip? | |
226 | ||
227 | ||
228 | B<A:> You can use the Archive::Zip::addTree*() methods: | |
229 | ||
230 | use Archive::Zip; | |
231 | my $zip = Archive::Zip->new(); | |
232 | # add all readable files and directories below . as xyz/* | |
233 | $zip->addTree( '.', 'xyz' ); | |
234 | # add all readable plain files below /abc as def/* | |
235 | $zip->addTree( '/abc', 'def', sub { -f && -r } ); | |
236 | # add all .c files below /tmp as stuff/* | |
237 | $zip->addTreeMatching( '/tmp', 'stuff', '\.c$' ); | |
238 | # add all .o files below /tmp as stuff/* if they aren't writable | |
239 | $zip->addTreeMatching( '/tmp', 'stuff', '\.o$', sub { ! -w } ); | |
240 | # add all .so files below /tmp that are smaller than 200 bytes as stuff/* | |
241 | $zip->addTreeMatching( '/tmp', 'stuff', '\.o$', sub { -s < 200 } ); | |
242 | # and write them into a file | |
243 | $zip->writeToFileNamed('xxx.zip'); | |
244 | ||
245 | =head1 Extract a directory/tree | |
246 | ||
247 | ||
248 | B<Q:> How can I extract some (or all) files from a Zip into a different | |
249 | directory? | |
250 | ||
251 | ||
252 | B<A:> You can use the Archive::Zip::extractTree() method: | |
253 | ??? || | |
254 | ||
255 | ||
256 | # now extract the same files into /tmpx | |
257 | $zip->extractTree( 'stuff', '/tmpx' ); | |
258 | ||
259 | =head1 Update a directory/tree | |
260 | ||
261 | ||
262 | B<Q:> How can I update a Zip from a directory tree, adding or replacing only | |
263 | the newer files? | |
264 | ||
265 | ||
266 | B<A:> You can use the Archive::Zip::updateTree() method that was added in version 1.09. | |
267 | ||
268 | =head1 Zip times might be off by 1 second | |
269 | ||
270 | ||
271 | B<Q:> It bothers me greatly that my file times are wrong by one second about half | |
272 | the time. Why don't you do something about it? | |
273 | ||
274 | ||
275 | B<A:> Get over it. This is a result of the Zip format storing times in DOS | |
276 | format, which has a resolution of only two seconds. | |
277 | ||
278 | =head1 Zip times don't include time zone information | |
279 | ||
280 | ||
281 | B<Q:> My file times don't respect time zones. What gives? | |
282 | ||
283 | ||
284 | B<A:> If this is important to you, please submit patches to read the various | |
285 | Extra Fields that encode times with time zones. I'm just using the DOS | |
286 | Date/Time, which doesn't have a time zone. | |
287 | ||
288 | =head1 How do I make a self-extracting Zip | |
289 | ||
290 | ||
291 | B<Q:> I want to make a self-extracting Zip file. Can I do this? | |
292 | ||
293 | ||
294 | B<A:> Yes. You can write a self-extracting archive stub (that is, a version of | |
295 | unzip) to the output filehandle that you pass to writeToFileHandle(). See | |
296 | examples/selfex.pl for how to write a self-extracting archive. | |
297 | ||
298 | ||
299 | However, you should understand that this will only work on one kind of | |
300 | platform (the one for which the stub was compiled). | |
301 | ||
302 | =head1 How can I deal with Zips with prepended garbage (i.e. from Sircam) | |
303 | ||
304 | ||
305 | B<Q:> How can I tell if a Zip has been damaged by adding garbage to the | |
306 | beginning or inside the file? | |
307 | ||
308 | ||
309 | B<A:> I added code for this for the Amavis virus scanner. You can query archives | |
310 | for their 'eocdOffset' property, which should be 0: | |
311 | ||
312 | ||
313 | if ($zip->eocdOffset > 0) | |
314 | { warn($zip->eocdOffset . " bytes of garbage at beginning or within Zip") } | |
315 | ||
316 | ||
317 | When members are extracted, this offset will be used to adjust the start of | |
318 | the member if necessary. | |
319 | ||
320 | =head1 Can't extract Shrunk files | |
321 | ||
322 | ||
323 | B<Q:> I'm trying to extract a file out of a Zip produced by PKZIP, and keep | |
324 | getting this error message: | |
325 | ||
326 | ||
327 | error: Unsupported compression combination: read 6, write 0 | |
328 | ||
329 | ||
330 | B<A:> You can't uncompress this archive member. Archive::Zip only supports uncompressed | |
331 | members, and compressed members that are compressed using the compression | |
332 | supported by Compress::Zlib. That means only Deflated and Stored members. | |
333 | ||
334 | ||
335 | Your file is compressed using the Shrink format, which isn't supported by | |
336 | Compress::Zlib. | |
337 | ||
338 | ||
339 | You could, perhaps, use a command-line UnZip program (like the Info-Zip | |
340 | one) to extract this. | |
341 | ||
342 | =head1 Can't do decryption | |
343 | ||
344 | ||
345 | B<Q:> How do I decrypt encrypted Zip members? | |
346 | ||
347 | ||
348 | B<A:> With some other program or library. Archive::Zip doesn't support decryption, | |
349 | and probably never will (unless I<you> write it). | |
350 | ||
351 | =head1 How to test file integrity? | |
352 | ||
353 | ||
354 | B<Q:> How can Archive::Zip can test the validity of a Zip file? | |
355 | ||
356 | ||
357 | B<A:> If you try to decompress the file, the gzip streams will report errors | |
358 | if you have garbage. Most of the time. | |
359 | ||
360 | If you try to open the file and a central directory structure can't be | |
361 | found, an error will be reported. | |
362 | ||
363 | When a file is being read, if we can't find a proper PK.. signature in | |
364 | the right places we report a format error. | |
365 | ||
366 | If there is added garbage at the beginning of a Zip file (as inserted | |
367 | by some viruses), you can find out about it, but Archive::Zip will ignore it, | |
368 | and you can still use the archive. When it gets written back out the | |
369 | added stuff will be gone. | |
370 | ||
371 | ||
372 | There are two ready-to-use utilities in the examples directory that can | |
373 | be used to test file integrity, or that you can use as examples | |
374 | for your own code: | |
375 | ||
376 | =over 4 | |
377 | ||
378 | =item examples/zipcheck.pl shows how to use an attempted extraction to test a file. | |
379 | ||
380 | ||
381 | ||
382 | =item examples/ziptest.pl shows how to test CRCs in a file. | |
383 | ||
384 | ||
385 | ||
386 | =back | |
387 | ||
388 | =head1 Duplicate files in Zip? | |
389 | ||
390 | ||
391 | B<Q:> Archive::Zip let me put the same file in my Zip twice! Why don't you prevent this? | |
392 | ||
393 | ||
394 | B<A:> As far as I can tell, this is not disallowed by the Zip spec. If you | |
395 | think it's a bad idea, check for it yourself: | |
396 | ||
397 | ||
398 | $zip->addFile($someFile, $someName) unless $zip->memberNamed($someName); | |
399 | ||
400 | ||
401 | I can even imagine cases where this might be useful (for instance, multiple | |
402 | versions of files). | |
403 | ||
404 | =head1 File ownership/permissions/ACLS/etc | |
405 | ||
406 | ||
407 | B<Q:> Why doesn't Archive::Zip deal with file ownership, ACLs, etc.? | |
408 | ||
409 | ||
410 | B<A:> There is no standard way to represent these in the Zip file format. If | |
411 | you want to send me code to properly handle the various extra fields that | |
412 | have been used to represent these through the years, I'll look at it. | |
413 | ||
414 | =head1 I can't compile but ActiveState only has an old version of Archive::Zip | |
415 | ||
416 | ||
417 | B<Q:> I've only installed modules using ActiveState's PPM program and | |
418 | repository. But they have a much older version of Archive::Zip than is in CPAN. Will | |
419 | you send me a newer PPM? | |
420 | ||
421 | ||
422 | B<A:> Probably not, unless I get lots of extra time. But there's no reason you | |
423 | can't install the version from CPAN. Archive::Zip is pure Perl, so all you need is | |
424 | NMAKE, which you can get for free from Microsoft (see the FAQ in the | |
425 | ActiveState documentation for details on how to install CPAN modules). | |
426 | ||
427 | =head1 My JPEGs (or MP3's) don't compress when I put them into Zips! | |
428 | ||
429 | ||
430 | B<Q:> How come my JPEGs and MP3's don't compress much when I put them into Zips? | |
431 | ||
432 | ||
433 | B<A:> Because they're already compressed. | |
434 | ||
435 | =head1 Under Windows, things lock up/get damaged | |
436 | ||
437 | ||
438 | B<Q:> I'm using Windows. When I try to use Archive::Zip, my machine locks up/makes | |
439 | funny sounds/displays a BSOD/corrupts data. How can I fix this? | |
440 | ||
441 | ||
442 | B<A:> First, try the newest version of Compress::Zlib. I know of | |
443 | Windows-related problems prior to v1.14 of that library. | |
444 | ||
445 | ||
446 | If that doesn't get rid of the problem, fix your computer or get rid of | |
447 | Windows. | |
448 | ||
449 | =head1 Zip contents in a scalar | |
450 | ||
451 | ||
452 | B<Q:> I want to read a Zip file from (or write one to) a scalar variable instead | |
453 | of a file. How can I do this? | |
454 | ||
455 | ||
456 | B<A:> Use C<IO::Scalar> and the C<readFromFileHandle()> and | |
457 | C<writeToFileHandle()> methods. | |
458 | See C<examples/readScalar.pl> and C<examples/writeScalar.pl>. | |
459 | ||
460 | =head1 Reading from streams | |
461 | ||
462 | ||
463 | B<Q:> How do I read from a stream (like for the Info-Zip C<funzip> program)? | |
464 | ||
465 | ||
466 | B<A:> This isn't currently supported, though writing to a stream is. |
0 | package Archive::Zip::FileMember; | |
1 | ||
2 | use strict; | |
3 | use vars qw( $VERSION @ISA ); | |
4 | ||
5 | BEGIN { | |
6 | $VERSION = '1.18'; | |
7 | @ISA = qw ( Archive::Zip::Member ); | |
8 | } | |
9 | ||
10 | use Archive::Zip qw( | |
11 | :UTILITY_METHODS | |
12 | ); | |
13 | ||
14 | sub externalFileName { | |
15 | shift->{'externalFileName'}; | |
16 | } | |
17 | ||
18 | # Return true if I depend on the named file | |
19 | sub _usesFileNamed { | |
20 | my $self = shift; | |
21 | my $fileName = shift; | |
22 | my $xfn = $self->externalFileName(); | |
23 | return undef if ref($xfn); | |
24 | return $xfn eq $fileName; | |
25 | } | |
26 | ||
27 | sub fh { | |
28 | my $self = shift; | |
29 | $self->_openFile() | |
30 | if !defined( $self->{'fh'} ) || !$self->{'fh'}->opened(); | |
31 | return $self->{'fh'}; | |
32 | } | |
33 | ||
34 | # opens my file handle from my file name | |
35 | sub _openFile { | |
36 | my $self = shift; | |
37 | my ( $status, $fh ) = _newFileHandle( $self->externalFileName(), 'r' ); | |
38 | if ( !$status ) { | |
39 | _ioError( "Can't open", $self->externalFileName() ); | |
40 | return undef; | |
41 | } | |
42 | $self->{'fh'} = $fh; | |
43 | _binmode($fh); | |
44 | return $fh; | |
45 | } | |
46 | ||
47 | # Make sure I close my file handle | |
48 | sub endRead { | |
49 | my $self = shift; | |
50 | undef $self->{'fh'}; # _closeFile(); | |
51 | return $self->SUPER::endRead(@_); | |
52 | } | |
53 | ||
54 | sub _become { | |
55 | my $self = shift; | |
56 | my $newClass = shift; | |
57 | return $self if ref($self) eq $newClass; | |
58 | delete( $self->{'externalFileName'} ); | |
59 | delete( $self->{'fh'} ); | |
60 | return $self->SUPER::_become($newClass); | |
61 | } | |
62 | ||
63 | 1; |
0 | package Archive::Zip::Member; | |
1 | ||
2 | # A generic membet of an archive | |
3 | ||
4 | use strict; | |
5 | use vars qw( $VERSION @ISA ); | |
6 | ||
7 | BEGIN { | |
8 | $VERSION = '1.18'; | |
9 | @ISA = qw( Archive::Zip ); | |
10 | } | |
11 | ||
12 | use Archive::Zip qw( | |
13 | :CONSTANTS | |
14 | :MISC_CONSTANTS | |
15 | :ERROR_CODES | |
16 | :PKZIP_CONSTANTS | |
17 | :UTILITY_METHODS | |
18 | ); | |
19 | ||
20 | use Time::Local (); | |
21 | use Compress::Zlib qw( Z_OK Z_STREAM_END MAX_WBITS ); | |
22 | use File::Path; | |
23 | use File::Basename; | |
24 | ||
25 | use constant ZIPFILEMEMBERCLASS => 'Archive::Zip::ZipFileMember'; | |
26 | use constant NEWFILEMEMBERCLASS => 'Archive::Zip::NewFileMember'; | |
27 | use constant STRINGMEMBERCLASS => 'Archive::Zip::StringMember'; | |
28 | use constant DIRECTORYMEMBERCLASS => 'Archive::Zip::DirectoryMember'; | |
29 | ||
30 | # Unix perms for default creation of files/dirs. | |
31 | use constant DEFAULT_DIRECTORY_PERMISSIONS => 040755; | |
32 | use constant DEFAULT_FILE_PERMISSIONS => 0100666; | |
33 | use constant DIRECTORY_ATTRIB => 040000; | |
34 | use constant FILE_ATTRIB => 0100000; | |
35 | ||
36 | # Returns self if successful, else undef | |
37 | # Assumes that fh is positioned at beginning of central directory file header. | |
38 | # Leaves fh positioned immediately after file header or EOCD signature. | |
39 | sub _newFromZipFile { | |
40 | my $class = shift; | |
41 | my $self = $class->ZIPFILEMEMBERCLASS->_newFromZipFile(@_); | |
42 | return $self; | |
43 | } | |
44 | ||
45 | sub newFromString { | |
46 | my $class = shift; | |
47 | my $self = $class->STRINGMEMBERCLASS->_newFromString(@_); | |
48 | return $self; | |
49 | } | |
50 | ||
51 | sub newFromFile { | |
52 | my $class = shift; | |
53 | my $self = $class->NEWFILEMEMBERCLASS->_newFromFileNamed(@_); | |
54 | return $self; | |
55 | } | |
56 | ||
57 | sub newDirectoryNamed { | |
58 | my $class = shift; | |
59 | my $self = $class->DIRECTORYMEMBERCLASS->_newNamed(@_); | |
60 | return $self; | |
61 | } | |
62 | ||
63 | sub new { | |
64 | my $class = shift; | |
65 | my $self = { | |
66 | 'lastModFileDateTime' => 0, | |
67 | 'fileAttributeFormat' => FA_UNIX, | |
68 | 'versionMadeBy' => 20, | |
69 | 'versionNeededToExtract' => 20, | |
70 | 'bitFlag' => 0, | |
71 | 'compressionMethod' => COMPRESSION_STORED, | |
72 | 'desiredCompressionMethod' => COMPRESSION_STORED, | |
73 | 'desiredCompressionLevel' => COMPRESSION_LEVEL_NONE, | |
74 | 'internalFileAttributes' => 0, | |
75 | 'externalFileAttributes' => 0, # set later | |
76 | 'fileName' => '', | |
77 | 'cdExtraField' => '', | |
78 | 'localExtraField' => '', | |
79 | 'fileComment' => '', | |
80 | 'crc32' => 0, | |
81 | 'compressedSize' => 0, | |
82 | 'uncompressedSize' => 0, | |
83 | @_ | |
84 | }; | |
85 | bless( $self, $class ); | |
86 | $self->unixFileAttributes( $self->DEFAULT_FILE_PERMISSIONS ); | |
87 | return $self; | |
88 | } | |
89 | ||
90 | sub _becomeDirectoryIfNecessary { | |
91 | my $self = shift; | |
92 | $self->_become(DIRECTORYMEMBERCLASS) | |
93 | if $self->isDirectory(); | |
94 | return $self; | |
95 | } | |
96 | ||
97 | # Morph into given class (do whatever cleanup I need to do) | |
98 | sub _become { | |
99 | return bless( $_[0], $_[1] ); | |
100 | } | |
101 | ||
102 | sub versionMadeBy { | |
103 | shift->{'versionMadeBy'}; | |
104 | } | |
105 | ||
106 | sub fileAttributeFormat { | |
107 | ( $#_ > 0 ) | |
108 | ? ( $_[0]->{'fileAttributeFormat'} = $_[1] ) | |
109 | : $_[0]->{'fileAttributeFormat'}; | |
110 | } | |
111 | ||
112 | sub versionNeededToExtract { | |
113 | shift->{'versionNeededToExtract'}; | |
114 | } | |
115 | ||
116 | sub bitFlag { | |
117 | shift->{'bitFlag'}; | |
118 | } | |
119 | ||
120 | sub compressionMethod { | |
121 | shift->{'compressionMethod'}; | |
122 | } | |
123 | ||
124 | sub desiredCompressionMethod { | |
125 | my $self = shift; | |
126 | my $newDesiredCompressionMethod = shift; | |
127 | my $oldDesiredCompressionMethod = $self->{'desiredCompressionMethod'}; | |
128 | if ( defined($newDesiredCompressionMethod) ) { | |
129 | $self->{'desiredCompressionMethod'} = $newDesiredCompressionMethod; | |
130 | if ( $newDesiredCompressionMethod == COMPRESSION_STORED ) { | |
131 | $self->{'desiredCompressionLevel'} = 0; | |
132 | } | |
133 | elsif ( $oldDesiredCompressionMethod == COMPRESSION_STORED ) { | |
134 | $self->{'desiredCompressionLevel'} = COMPRESSION_LEVEL_DEFAULT; | |
135 | } | |
136 | } | |
137 | return $oldDesiredCompressionMethod; | |
138 | } | |
139 | ||
140 | sub desiredCompressionLevel { | |
141 | my $self = shift; | |
142 | my $newDesiredCompressionLevel = shift; | |
143 | my $oldDesiredCompressionLevel = $self->{'desiredCompressionLevel'}; | |
144 | if ( defined($newDesiredCompressionLevel) ) { | |
145 | $self->{'desiredCompressionLevel'} = $newDesiredCompressionLevel; | |
146 | $self->{'desiredCompressionMethod'} = ( | |
147 | $newDesiredCompressionLevel | |
148 | ? COMPRESSION_DEFLATED | |
149 | : COMPRESSION_STORED | |
150 | ); | |
151 | } | |
152 | return $oldDesiredCompressionLevel; | |
153 | } | |
154 | ||
155 | sub fileName { | |
156 | my $self = shift; | |
157 | my $newName = shift; | |
158 | if ($newName) { | |
159 | $newName =~ s{[\\/]+}{/}g; # deal with dos/windoze problems | |
160 | $self->{'fileName'} = $newName; | |
161 | } | |
162 | return $self->{'fileName'}; | |
163 | } | |
164 | ||
165 | sub lastModFileDateTime { | |
166 | my $modTime = shift->{'lastModFileDateTime'}; | |
167 | $modTime =~ m/^(\d+)$/; # untaint | |
168 | return $1; | |
169 | } | |
170 | ||
171 | sub lastModTime { | |
172 | my $self = shift; | |
173 | return _dosToUnixTime( $self->lastModFileDateTime() ); | |
174 | } | |
175 | ||
176 | sub setLastModFileDateTimeFromUnix { | |
177 | my $self = shift; | |
178 | my $time_t = shift; | |
179 | $self->{'lastModFileDateTime'} = _unixToDosTime($time_t); | |
180 | } | |
181 | ||
182 | sub internalFileAttributes { | |
183 | shift->{'internalFileAttributes'}; | |
184 | } | |
185 | ||
186 | sub externalFileAttributes { | |
187 | shift->{'externalFileAttributes'}; | |
188 | } | |
189 | ||
190 | # Convert UNIX permissions into proper value for zip file | |
191 | # NOT A METHOD! | |
192 | sub _mapPermissionsFromUnix { | |
193 | my $perms = shift; | |
194 | return $perms << 16; | |
195 | ||
196 | # TODO: map MS-DOS perms too (RHSA?) | |
197 | } | |
198 | ||
199 | # Convert ZIP permissions into Unix ones | |
200 | # | |
201 | # This was taken from Info-ZIP group's portable UnZip | |
202 | # zipfile-extraction program, version 5.50. | |
203 | # http://www.info-zip.org/pub/infozip/ | |
204 | # | |
205 | # See the mapattr() function in unix/unix.c | |
206 | # See the attribute format constants in unzpriv.h | |
207 | # | |
208 | # XXX Note that there's one situation that isn't implemented | |
209 | # yet that depends on the "extra field." | |
210 | sub _mapPermissionsToUnix { | |
211 | my $self = shift; | |
212 | ||
213 | my $format = $self->{'fileAttributeFormat'}; | |
214 | my $attribs = $self->{'externalFileAttributes'}; | |
215 | ||
216 | my $mode = 0; | |
217 | ||
218 | if ( $format == FA_AMIGA ) { | |
219 | $attribs = $attribs >> 17 & 7; # Amiga RWE bits | |
220 | $mode = $attribs << 6 | $attribs << 3 | $attribs; | |
221 | return $mode; | |
222 | } | |
223 | ||
224 | if ( $format == FA_THEOS ) { | |
225 | $attribs &= 0xF1FFFFFF; | |
226 | if ( ( $attribs & 0xF0000000 ) != 0x40000000 ) { | |
227 | $attribs &= 0x01FFFFFF; # not a dir, mask all ftype bits | |
228 | } | |
229 | else { | |
230 | $attribs &= 0x41FFFFFF; # leave directory bit as set | |
231 | } | |
232 | } | |
233 | ||
234 | if ( $format == FA_UNIX | |
235 | || $format == FA_VAX_VMS | |
236 | || $format == FA_ACORN | |
237 | || $format == FA_ATARI_ST | |
238 | || $format == FA_BEOS | |
239 | || $format == FA_QDOS | |
240 | || $format == FA_TANDEM ) | |
241 | { | |
242 | $mode = $attribs >> 16; | |
243 | return $mode if $mode != 0 or not $self->localExtraField; | |
244 | ||
245 | # warn("local extra field is: ", $self->localExtraField, "\n"); | |
246 | ||
247 | # XXX This condition is not implemented | |
248 | # I'm just including the comments from the info-zip section for now. | |
249 | ||
250 | # Some (non-Info-ZIP) implementations of Zip for Unix and | |
251 | # VMS (and probably others ??) leave 0 in the upper 16-bit | |
252 | # part of the external_file_attributes field. Instead, they | |
253 | # store file permission attributes in some extra field. | |
254 | # As a work-around, we search for the presence of one of | |
255 | # these extra fields and fall back to the MSDOS compatible | |
256 | # part of external_file_attributes if one of the known | |
257 | # e.f. types has been detected. | |
258 | # Later, we might implement extraction of the permission | |
259 | # bits from the VMS extra field. But for now, the work-around | |
260 | # should be sufficient to provide "readable" extracted files. | |
261 | # (For ASI Unix e.f., an experimental remap from the e.f. | |
262 | # mode value IS already provided!) | |
263 | } | |
264 | ||
265 | # PKWARE's PKZip for Unix marks entries as FA_MSDOS, but stores the | |
266 | # Unix attributes in the upper 16 bits of the external attributes | |
267 | # field, just like Info-ZIP's Zip for Unix. We try to use that | |
268 | # value, after a check for consistency with the MSDOS attribute | |
269 | # bits (see below). | |
270 | if ( $format == FA_MSDOS ) { | |
271 | $mode = $attribs >> 16; | |
272 | } | |
273 | ||
274 | # FA_MSDOS, FA_OS2_HPFS, FA_WINDOWS_NTFS, FA_MACINTOSH, FA_TOPS20 | |
275 | $attribs = !( $attribs & 1 ) << 1 | ( $attribs & 0x10 ) >> 4; | |
276 | ||
277 | # keep previous $mode setting when its "owner" | |
278 | # part appears to be consistent with DOS attribute flags! | |
279 | return $mode if ( $mode & 0700 ) == ( 0400 | $attribs << 6 ); | |
280 | $mode = 0444 | $attribs << 6 | $attribs << 3 | $attribs; | |
281 | return $mode; | |
282 | } | |
283 | ||
284 | sub unixFileAttributes { | |
285 | my $self = shift; | |
286 | my $oldPerms = $self->_mapPermissionsToUnix(); | |
287 | if (@_) { | |
288 | my $perms = shift; | |
289 | if ( $self->isDirectory() ) { | |
290 | $perms &= ~FILE_ATTRIB; | |
291 | $perms |= DIRECTORY_ATTRIB; | |
292 | } | |
293 | else { | |
294 | $perms &= ~DIRECTORY_ATTRIB; | |
295 | $perms |= FILE_ATTRIB; | |
296 | } | |
297 | $self->{'externalFileAttributes'} = _mapPermissionsFromUnix($perms); | |
298 | } | |
299 | return $oldPerms; | |
300 | } | |
301 | ||
302 | sub localExtraField { | |
303 | ( $#_ > 0 ) | |
304 | ? ( $_[0]->{'localExtraField'} = $_[1] ) | |
305 | : $_[0]->{'localExtraField'}; | |
306 | } | |
307 | ||
308 | sub cdExtraField { | |
309 | ( $#_ > 0 ) ? ( $_[0]->{'cdExtraField'} = $_[1] ) : $_[0]->{'cdExtraField'}; | |
310 | } | |
311 | ||
312 | sub extraFields { | |
313 | my $self = shift; | |
314 | return $self->localExtraField() . $self->cdExtraField(); | |
315 | } | |
316 | ||
317 | sub fileComment { | |
318 | ( $#_ > 0 ) | |
319 | ? ( $_[0]->{'fileComment'} = pack( 'C0a*', $_[1] ) ) | |
320 | : $_[0]->{'fileComment'}; | |
321 | } | |
322 | ||
323 | sub hasDataDescriptor { | |
324 | my $self = shift; | |
325 | if (@_) { | |
326 | my $shouldHave = shift; | |
327 | if ($shouldHave) { | |
328 | $self->{'bitFlag'} |= GPBF_HAS_DATA_DESCRIPTOR_MASK; | |
329 | } | |
330 | else { | |
331 | $self->{'bitFlag'} &= ~GPBF_HAS_DATA_DESCRIPTOR_MASK; | |
332 | } | |
333 | } | |
334 | return $self->{'bitFlag'} & GPBF_HAS_DATA_DESCRIPTOR_MASK; | |
335 | } | |
336 | ||
337 | sub crc32 { | |
338 | shift->{'crc32'}; | |
339 | } | |
340 | ||
341 | sub crc32String { | |
342 | sprintf( "%08x", shift->{'crc32'} ); | |
343 | } | |
344 | ||
345 | sub compressedSize { | |
346 | shift->{'compressedSize'}; | |
347 | } | |
348 | ||
349 | sub uncompressedSize { | |
350 | shift->{'uncompressedSize'}; | |
351 | } | |
352 | ||
353 | sub isEncrypted { | |
354 | shift->bitFlag() & GPBF_ENCRYPTED_MASK; | |
355 | } | |
356 | ||
357 | sub isTextFile { | |
358 | my $self = shift; | |
359 | my $bit = $self->internalFileAttributes() & IFA_TEXT_FILE_MASK; | |
360 | if (@_) { | |
361 | my $flag = shift; | |
362 | $self->{'internalFileAttributes'} &= ~IFA_TEXT_FILE_MASK; | |
363 | $self->{'internalFileAttributes'} |= | |
364 | ( $flag ? IFA_TEXT_FILE: IFA_BINARY_FILE ); | |
365 | } | |
366 | return $bit == IFA_TEXT_FILE; | |
367 | } | |
368 | ||
369 | sub isBinaryFile { | |
370 | my $self = shift; | |
371 | my $bit = $self->internalFileAttributes() & IFA_TEXT_FILE_MASK; | |
372 | if (@_) { | |
373 | my $flag = shift; | |
374 | $self->{'internalFileAttributes'} &= ~IFA_TEXT_FILE_MASK; | |
375 | $self->{'internalFileAttributes'} |= | |
376 | ( $flag ? IFA_BINARY_FILE: IFA_TEXT_FILE ); | |
377 | } | |
378 | return $bit == IFA_BINARY_FILE; | |
379 | } | |
380 | ||
381 | sub extractToFileNamed { | |
382 | my $self = shift; | |
383 | my $name = shift; # local FS name | |
384 | return _error("encryption unsupported") if $self->isEncrypted(); | |
385 | mkpath( dirname($name) ); # croaks on error | |
386 | my ( $status, $fh ) = _newFileHandle( $name, 'w' ); | |
387 | return _ioError("Can't open file $name for write") unless $status; | |
388 | my $retval = $self->extractToFileHandle($fh); | |
389 | $fh->close(); | |
390 | utime( $self->lastModTime(), $self->lastModTime(), $name ); | |
391 | return $retval; | |
392 | } | |
393 | ||
394 | sub isDirectory { | |
395 | return 0; | |
396 | } | |
397 | ||
398 | sub externalFileName { | |
399 | return undef; | |
400 | } | |
401 | ||
402 | # The following are used when copying data | |
403 | sub _writeOffset { | |
404 | shift->{'writeOffset'}; | |
405 | } | |
406 | ||
407 | sub _readOffset { | |
408 | shift->{'readOffset'}; | |
409 | } | |
410 | ||
411 | sub writeLocalHeaderRelativeOffset { | |
412 | shift->{'writeLocalHeaderRelativeOffset'}; | |
413 | } | |
414 | ||
415 | sub wasWritten { shift->{'wasWritten'} } | |
416 | ||
417 | sub _dataEnded { | |
418 | shift->{'dataEnded'}; | |
419 | } | |
420 | ||
421 | sub _readDataRemaining { | |
422 | shift->{'readDataRemaining'}; | |
423 | } | |
424 | ||
425 | sub _inflater { | |
426 | shift->{'inflater'}; | |
427 | } | |
428 | ||
429 | sub _deflater { | |
430 | shift->{'deflater'}; | |
431 | } | |
432 | ||
433 | # Return the total size of my local header | |
434 | sub _localHeaderSize { | |
435 | my $self = shift; | |
436 | return SIGNATURE_LENGTH + LOCAL_FILE_HEADER_LENGTH + | |
437 | length( $self->fileName() ) + length( $self->localExtraField() ); | |
438 | } | |
439 | ||
440 | # Return the total size of my CD header | |
441 | sub _centralDirectoryHeaderSize { | |
442 | my $self = shift; | |
443 | return SIGNATURE_LENGTH + CENTRAL_DIRECTORY_FILE_HEADER_LENGTH + | |
444 | length( $self->fileName() ) + length( $self->cdExtraField() ) + | |
445 | length( $self->fileComment() ); | |
446 | } | |
447 | ||
448 | # DOS date/time format | |
449 | # 0-4 (5) Second divided by 2 | |
450 | # 5-10 (6) Minute (0-59) | |
451 | # 11-15 (5) Hour (0-23 on a 24-hour clock) | |
452 | # 16-20 (5) Day of the month (1-31) | |
453 | # 21-24 (4) Month (1 = January, 2 = February, etc.) | |
454 | # 25-31 (7) Year offset from 1980 (add 1980 to get actual year) | |
455 | ||
456 | # Convert DOS date/time format to unix time_t format | |
457 | # NOT AN OBJECT METHOD! | |
458 | sub _dosToUnixTime { | |
459 | my $dt = shift; | |
460 | return time() unless defined($dt); | |
461 | ||
462 | my $year = ( ( $dt >> 25 ) & 0x7f ) + 80; | |
463 | my $mon = ( ( $dt >> 21 ) & 0x0f ) - 1; | |
464 | my $mday = ( ( $dt >> 16 ) & 0x1f ); | |
465 | ||
466 | my $hour = ( ( $dt >> 11 ) & 0x1f ); | |
467 | my $min = ( ( $dt >> 5 ) & 0x3f ); | |
468 | my $sec = ( ( $dt << 1 ) & 0x3e ); | |
469 | ||
470 | # catch errors | |
471 | my $time_t = | |
472 | eval { Time::Local::timelocal( $sec, $min, $hour, $mday, $mon, $year ); }; | |
473 | return time() if ($@); | |
474 | return $time_t; | |
475 | } | |
476 | ||
477 | # Note, this isn't exactly UTC 1980, it's 1980 + 12 hours and 1 | |
478 | # minute so that nothing timezoney can muck us up. | |
479 | my $safe_epoch = 315576060; | |
480 | ||
481 | # convert a unix time to DOS date/time | |
482 | # NOT AN OBJECT METHOD! | |
483 | sub _unixToDosTime { | |
484 | my $time_t = shift; | |
485 | unless ($time_t) { | |
486 | _error("Tried to add member with zero or undef value for time"); | |
487 | $time_t = $safe_epoch; | |
488 | } | |
489 | if ( $time_t < $safe_epoch ) { | |
490 | _ioError("Unsupported date before 1980 encountered, moving to 1980"); | |
491 | $time_t = $safe_epoch; | |
492 | } | |
493 | my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time_t); | |
494 | my $dt = 0; | |
495 | $dt += ( $sec >> 1 ); | |
496 | $dt += ( $min << 5 ); | |
497 | $dt += ( $hour << 11 ); | |
498 | $dt += ( $mday << 16 ); | |
499 | $dt += ( ( $mon + 1 ) << 21 ); | |
500 | $dt += ( ( $year - 80 ) << 25 ); | |
501 | return $dt; | |
502 | } | |
503 | ||
504 | # Write my local header to a file handle. | |
505 | # Stores the offset to the start of the header in my | |
506 | # writeLocalHeaderRelativeOffset member. | |
507 | # Returns AZ_OK on success. | |
508 | sub _writeLocalFileHeader { | |
509 | my $self = shift; | |
510 | my $fh = shift; | |
511 | ||
512 | my $signatureData = pack( SIGNATURE_FORMAT, LOCAL_FILE_HEADER_SIGNATURE ); | |
513 | $fh->print($signatureData) | |
514 | or return _ioError("writing local header signature"); | |
515 | ||
516 | my $header = pack( | |
517 | LOCAL_FILE_HEADER_FORMAT, | |
518 | $self->versionNeededToExtract(), | |
519 | $self->bitFlag(), | |
520 | $self->desiredCompressionMethod(), | |
521 | $self->lastModFileDateTime(), | |
522 | $self->crc32(), | |
523 | $self->compressedSize(), # may need to be re-written later | |
524 | $self->uncompressedSize(), | |
525 | length( $self->fileName() ), | |
526 | length( $self->localExtraField() ) | |
527 | ); | |
528 | ||
529 | $fh->print($header) or return _ioError("writing local header"); | |
530 | if ( $self->fileName() ) { | |
531 | $fh->print( $self->fileName() ) | |
532 | or return _ioError("writing local header filename"); | |
533 | } | |
534 | if ( $self->localExtraField() ) { | |
535 | $fh->print( $self->localExtraField() ) | |
536 | or return _ioError("writing local extra field"); | |
537 | } | |
538 | ||
539 | return AZ_OK; | |
540 | } | |
541 | ||
542 | sub _writeCentralDirectoryFileHeader { | |
543 | my $self = shift; | |
544 | my $fh = shift; | |
545 | ||
546 | my $sigData = | |
547 | pack( SIGNATURE_FORMAT, CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE ); | |
548 | $fh->print($sigData) | |
549 | or return _ioError("writing central directory header signature"); | |
550 | ||
551 | my $fileNameLength = length( $self->fileName() ); | |
552 | my $extraFieldLength = length( $self->cdExtraField() ); | |
553 | my $fileCommentLength = length( $self->fileComment() ); | |
554 | ||
555 | my $header = pack( | |
556 | CENTRAL_DIRECTORY_FILE_HEADER_FORMAT, | |
557 | $self->versionMadeBy(), | |
558 | $self->fileAttributeFormat(), | |
559 | $self->versionNeededToExtract(), | |
560 | $self->bitFlag(), | |
561 | $self->desiredCompressionMethod(), | |
562 | $self->lastModFileDateTime(), | |
563 | $self->crc32(), # these three fields should have been updated | |
564 | $self->_writeOffset(), # by writing the data stream out | |
565 | $self->uncompressedSize(), # | |
566 | $fileNameLength, | |
567 | $extraFieldLength, | |
568 | $fileCommentLength, | |
569 | 0, # {'diskNumberStart'}, | |
570 | $self->internalFileAttributes(), | |
571 | $self->externalFileAttributes(), | |
572 | $self->writeLocalHeaderRelativeOffset() | |
573 | ); | |
574 | ||
575 | $fh->print($header) | |
576 | or return _ioError("writing central directory header"); | |
577 | if ($fileNameLength) { | |
578 | $fh->print( $self->fileName() ) | |
579 | or return _ioError("writing central directory header signature"); | |
580 | } | |
581 | if ($extraFieldLength) { | |
582 | $fh->print( $self->cdExtraField() ) | |
583 | or return _ioError("writing central directory extra field"); | |
584 | } | |
585 | if ($fileCommentLength) { | |
586 | $fh->print( $self->fileComment() ) | |
587 | or return _ioError("writing central directory file comment"); | |
588 | } | |
589 | ||
590 | return AZ_OK; | |
591 | } | |
592 | ||
593 | # This writes a data descriptor to the given file handle. | |
594 | # Assumes that crc32, writeOffset, and uncompressedSize are | |
595 | # set correctly (they should be after a write). | |
596 | # Further, the local file header should have the | |
597 | # GPBF_HAS_DATA_DESCRIPTOR_MASK bit set. | |
598 | sub _writeDataDescriptor { | |
599 | my $self = shift; | |
600 | my $fh = shift; | |
601 | my $header = pack( | |
602 | SIGNATURE_FORMAT . DATA_DESCRIPTOR_FORMAT, | |
603 | DATA_DESCRIPTOR_SIGNATURE, | |
604 | $self->crc32(), | |
605 | $self->_writeOffset(), # compressed size | |
606 | $self->uncompressedSize() | |
607 | ); | |
608 | ||
609 | $fh->print($header) | |
610 | or return _ioError("writing data descriptor"); | |
611 | return AZ_OK; | |
612 | } | |
613 | ||
614 | # Re-writes the local file header with new crc32 and compressedSize fields. | |
615 | # To be called after writing the data stream. | |
616 | # Assumes that filename and extraField sizes didn't change since last written. | |
617 | sub _refreshLocalFileHeader { | |
618 | my $self = shift; | |
619 | my $fh = shift; | |
620 | ||
621 | my $here = $fh->tell(); | |
622 | $fh->seek( $self->writeLocalHeaderRelativeOffset() + SIGNATURE_LENGTH, | |
623 | IO::Seekable::SEEK_SET ) | |
624 | or return _ioError("seeking to rewrite local header"); | |
625 | ||
626 | my $header = pack( | |
627 | LOCAL_FILE_HEADER_FORMAT, | |
628 | $self->versionNeededToExtract(), | |
629 | $self->bitFlag(), | |
630 | $self->desiredCompressionMethod(), | |
631 | $self->lastModFileDateTime(), | |
632 | $self->crc32(), | |
633 | $self->_writeOffset(), # compressed size | |
634 | $self->uncompressedSize(), | |
635 | length( $self->fileName() ), | |
636 | length( $self->localExtraField() ) | |
637 | ); | |
638 | ||
639 | $fh->print($header) | |
640 | or return _ioError("re-writing local header"); | |
641 | $fh->seek( $here, IO::Seekable::SEEK_SET ) | |
642 | or return _ioError("seeking after rewrite of local header"); | |
643 | ||
644 | return AZ_OK; | |
645 | } | |
646 | ||
647 | sub readChunk { | |
648 | my ( $self, $chunkSize ) = @_; | |
649 | ||
650 | if ( $self->readIsDone() ) { | |
651 | $self->endRead(); | |
652 | my $dummy = ''; | |
653 | return ( \$dummy, AZ_STREAM_END ); | |
654 | } | |
655 | ||
656 | $chunkSize = $Archive::Zip::ChunkSize if not defined($chunkSize); | |
657 | $chunkSize = $self->_readDataRemaining() | |
658 | if $chunkSize > $self->_readDataRemaining(); | |
659 | ||
660 | my $buffer = ''; | |
661 | my $outputRef; | |
662 | my ( $bytesRead, $status ) = $self->_readRawChunk( \$buffer, $chunkSize ); | |
663 | return ( \$buffer, $status ) unless $status == AZ_OK; | |
664 | ||
665 | $self->{'readDataRemaining'} -= $bytesRead; | |
666 | $self->{'readOffset'} += $bytesRead; | |
667 | ||
668 | if ( $self->compressionMethod() == COMPRESSION_STORED ) { | |
669 | $self->{'crc32'} = $self->computeCRC32( $buffer, $self->{'crc32'} ); | |
670 | } | |
671 | ||
672 | ( $outputRef, $status ) = &{ $self->{'chunkHandler'} }( $self, \$buffer ); | |
673 | $self->{'writeOffset'} += length($$outputRef); | |
674 | ||
675 | $self->endRead() | |
676 | if $self->readIsDone(); | |
677 | ||
678 | return ( $outputRef, $status ); | |
679 | } | |
680 | ||
681 | # Read the next raw chunk of my data. Subclasses MUST implement. | |
682 | # my ( $bytesRead, $status) = $self->_readRawChunk( \$buffer, $chunkSize ); | |
683 | sub _readRawChunk { | |
684 | my $self = shift; | |
685 | return $self->_subclassResponsibility(); | |
686 | } | |
687 | ||
688 | # A place holder to catch rewindData errors if someone ignores | |
689 | # the error code. | |
690 | sub _noChunk { | |
691 | my $self = shift; | |
692 | return ( \undef, _error("trying to copy chunk when init failed") ); | |
693 | } | |
694 | ||
695 | # Basically a no-op so that I can have a consistent interface. | |
696 | # ( $outputRef, $status) = $self->_copyChunk( \$buffer ); | |
697 | sub _copyChunk { | |
698 | my ( $self, $dataRef ) = @_; | |
699 | return ( $dataRef, AZ_OK ); | |
700 | } | |
701 | ||
702 | # ( $outputRef, $status) = $self->_deflateChunk( \$buffer ); | |
703 | sub _deflateChunk { | |
704 | my ( $self, $buffer ) = @_; | |
705 | my ( $out, $status ) = $self->_deflater()->deflate($buffer); | |
706 | ||
707 | if ( $self->_readDataRemaining() == 0 ) { | |
708 | my $extraOutput; | |
709 | ( $extraOutput, $status ) = $self->_deflater()->flush(); | |
710 | $out .= $extraOutput; | |
711 | $self->endRead(); | |
712 | return ( \$out, AZ_STREAM_END ); | |
713 | } | |
714 | elsif ( $status == Z_OK ) { | |
715 | return ( \$out, AZ_OK ); | |
716 | } | |
717 | else { | |
718 | $self->endRead(); | |
719 | my $retval = _error( 'deflate error', $status ); | |
720 | my $dummy = ''; | |
721 | return ( \$dummy, $retval ); | |
722 | } | |
723 | } | |
724 | ||
725 | # ( $outputRef, $status) = $self->_inflateChunk( \$buffer ); | |
726 | sub _inflateChunk { | |
727 | my ( $self, $buffer ) = @_; | |
728 | my ( $out, $status ) = $self->_inflater()->inflate($buffer); | |
729 | my $retval; | |
730 | $self->endRead() unless $status == Z_OK; | |
731 | if ( $status == Z_OK || $status == Z_STREAM_END ) { | |
732 | $retval = ( $status == Z_STREAM_END ) ? AZ_STREAM_END: AZ_OK; | |
733 | return ( \$out, $retval ); | |
734 | } | |
735 | else { | |
736 | $retval = _error( 'inflate error', $status ); | |
737 | my $dummy = ''; | |
738 | return ( \$dummy, $retval ); | |
739 | } | |
740 | } | |
741 | ||
742 | sub rewindData { | |
743 | my $self = shift; | |
744 | my $status; | |
745 | ||
746 | # set to trap init errors | |
747 | $self->{'chunkHandler'} = $self->can('_noChunk'); | |
748 | ||
749 | # Work around WinZip bug with 0-length DEFLATED files | |
750 | $self->desiredCompressionMethod(COMPRESSION_STORED) | |
751 | if $self->uncompressedSize() == 0; | |
752 | ||
753 | # assume that we're going to read the whole file, and compute the CRC anew. | |
754 | $self->{'crc32'} = 0 | |
755 | if ( $self->compressionMethod() == COMPRESSION_STORED ); | |
756 | ||
757 | # These are the only combinations of methods we deal with right now. | |
758 | if ( $self->compressionMethod() == COMPRESSION_STORED | |
759 | and $self->desiredCompressionMethod() == COMPRESSION_DEFLATED ) | |
760 | { | |
761 | ( $self->{'deflater'}, $status ) = Compress::Zlib::deflateInit( | |
762 | '-Level' => $self->desiredCompressionLevel(), | |
763 | '-WindowBits' => -MAX_WBITS(), # necessary magic | |
764 | '-Bufsize' => $Archive::Zip::ChunkSize, | |
765 | @_ | |
766 | ); # pass additional options | |
767 | return _error( 'deflateInit error:', $status ) | |
768 | unless $status == Z_OK; | |
769 | $self->{'chunkHandler'} = $self->can('_deflateChunk'); | |
770 | } | |
771 | elsif ( $self->compressionMethod() == COMPRESSION_DEFLATED | |
772 | and $self->desiredCompressionMethod() == COMPRESSION_STORED ) | |
773 | { | |
774 | ( $self->{'inflater'}, $status ) = Compress::Zlib::inflateInit( | |
775 | '-WindowBits' => -MAX_WBITS(), # necessary magic | |
776 | '-Bufsize' => $Archive::Zip::ChunkSize, | |
777 | @_ | |
778 | ); # pass additional options | |
779 | return _error( 'inflateInit error:', $status ) | |
780 | unless $status == Z_OK; | |
781 | $self->{'chunkHandler'} = $self->can('_inflateChunk'); | |
782 | } | |
783 | elsif ( $self->compressionMethod() == $self->desiredCompressionMethod() ) { | |
784 | $self->{'chunkHandler'} = $self->can('_copyChunk'); | |
785 | } | |
786 | else { | |
787 | return _error( | |
788 | sprintf( | |
789 | "Unsupported compression combination: read %d, write %d", | |
790 | $self->compressionMethod(), | |
791 | $self->desiredCompressionMethod() | |
792 | ) | |
793 | ); | |
794 | } | |
795 | ||
796 | $self->{'readDataRemaining'} = | |
797 | ( $self->compressionMethod() == COMPRESSION_STORED ) | |
798 | ? $self->uncompressedSize() | |
799 | : $self->compressedSize(); | |
800 | $self->{'dataEnded'} = 0; | |
801 | $self->{'readOffset'} = 0; | |
802 | ||
803 | return AZ_OK; | |
804 | } | |
805 | ||
806 | sub endRead { | |
807 | my $self = shift; | |
808 | delete $self->{'inflater'}; | |
809 | delete $self->{'deflater'}; | |
810 | $self->{'dataEnded'} = 1; | |
811 | $self->{'readDataRemaining'} = 0; | |
812 | return AZ_OK; | |
813 | } | |
814 | ||
815 | sub readIsDone { | |
816 | my $self = shift; | |
817 | return ( $self->_dataEnded() or !$self->_readDataRemaining() ); | |
818 | } | |
819 | ||
820 | sub contents { | |
821 | my $self = shift; | |
822 | my $newContents = shift; | |
823 | ||
824 | if ( defined($newContents) ) { | |
825 | ||
826 | # change our type and call the subclass contents method. | |
827 | $self->_become(STRINGMEMBERCLASS); | |
828 | return $self->contents( pack( 'C0a*', $newContents ) ) | |
829 | ; # in case of Unicode | |
830 | } | |
831 | else { | |
832 | my $oldCompression = | |
833 | $self->desiredCompressionMethod(COMPRESSION_STORED); | |
834 | my $status = $self->rewindData(@_); | |
835 | if ( $status != AZ_OK ) { | |
836 | $self->endRead(); | |
837 | return $status; | |
838 | } | |
839 | my $retval = ''; | |
840 | while ( $status == AZ_OK ) { | |
841 | my $ref; | |
842 | ( $ref, $status ) = $self->readChunk( $self->_readDataRemaining() ); | |
843 | ||
844 | # did we get it in one chunk? | |
845 | if ( length($$ref) == $self->uncompressedSize() ) { | |
846 | $retval = $$ref; | |
847 | } | |
848 | else { $retval .= $$ref } | |
849 | } | |
850 | $self->desiredCompressionMethod($oldCompression); | |
851 | $self->endRead(); | |
852 | $status = AZ_OK if $status == AZ_STREAM_END; | |
853 | $retval = undef unless $status == AZ_OK; | |
854 | return wantarray ? ( $retval, $status ) : $retval; | |
855 | } | |
856 | } | |
857 | ||
858 | sub extractToFileHandle { | |
859 | my $self = shift; | |
860 | return _error("encryption unsupported") if $self->isEncrypted(); | |
861 | my $fh = shift; | |
862 | _binmode($fh); | |
863 | my $oldCompression = $self->desiredCompressionMethod(COMPRESSION_STORED); | |
864 | my $status = $self->rewindData(@_); | |
865 | $status = $self->_writeData($fh) if $status == AZ_OK; | |
866 | $self->desiredCompressionMethod($oldCompression); | |
867 | $self->endRead(); | |
868 | return $status; | |
869 | } | |
870 | ||
871 | # write local header and data stream to file handle | |
872 | sub _writeToFileHandle { | |
873 | my $self = shift; | |
874 | my $fh = shift; | |
875 | my $fhIsSeekable = shift; | |
876 | my $offset = shift; | |
877 | ||
878 | return _error("no member name given for $self") | |
879 | unless $self->fileName(); | |
880 | ||
881 | $self->{'writeLocalHeaderRelativeOffset'} = $offset; | |
882 | $self->{'wasWritten'} = 0; | |
883 | ||
884 | # Determine if I need to write a data descriptor | |
885 | # I need to do this if I can't refresh the header | |
886 | # and I don't know compressed size or crc32 fields. | |
887 | my $headerFieldsUnknown = ( | |
888 | ( $self->uncompressedSize() > 0 ) | |
889 | and ($self->compressionMethod() == COMPRESSION_STORED | |
890 | or $self->desiredCompressionMethod() == COMPRESSION_DEFLATED ) | |
891 | ); | |
892 | ||
893 | my $shouldWriteDataDescriptor = | |
894 | ( $headerFieldsUnknown and not $fhIsSeekable ); | |
895 | ||
896 | $self->hasDataDescriptor(1) | |
897 | if ($shouldWriteDataDescriptor); | |
898 | ||
899 | $self->{'writeOffset'} = 0; | |
900 | ||
901 | my $status = $self->rewindData(); | |
902 | ( $status = $self->_writeLocalFileHeader($fh) ) | |
903 | if $status == AZ_OK; | |
904 | ( $status = $self->_writeData($fh) ) | |
905 | if $status == AZ_OK; | |
906 | if ( $status == AZ_OK ) { | |
907 | $self->{'wasWritten'} = 1; | |
908 | if ( $self->hasDataDescriptor() ) { | |
909 | $status = $self->_writeDataDescriptor($fh); | |
910 | } | |
911 | elsif ($headerFieldsUnknown) { | |
912 | $status = $self->_refreshLocalFileHeader($fh); | |
913 | } | |
914 | } | |
915 | ||
916 | return $status; | |
917 | } | |
918 | ||
919 | # Copy my (possibly compressed) data to given file handle. | |
920 | # Returns C<AZ_OK> on success | |
921 | sub _writeData { | |
922 | my $self = shift; | |
923 | my $writeFh = shift; | |
924 | ||
925 | return AZ_OK if ( $self->uncompressedSize() == 0 ); | |
926 | my $status; | |
927 | my $chunkSize = $Archive::Zip::ChunkSize; | |
928 | while ( $self->_readDataRemaining() > 0 ) { | |
929 | my $outRef; | |
930 | ( $outRef, $status ) = $self->readChunk($chunkSize); | |
931 | return $status if ( $status != AZ_OK and $status != AZ_STREAM_END ); | |
932 | ||
933 | if ( length($$outRef) > 0 ) { | |
934 | $writeFh->print($$outRef) | |
935 | or return _ioError("write error during copy"); | |
936 | } | |
937 | ||
938 | last if $status == AZ_STREAM_END; | |
939 | } | |
940 | $self->{'compressedSize'} = $self->_writeOffset(); | |
941 | return AZ_OK; | |
942 | } | |
943 | ||
944 | # Return true if I depend on the named file | |
945 | sub _usesFileNamed { | |
946 | return 0; | |
947 | } | |
948 | ||
949 | 1; |
0 | package Archive::Zip::MemberRead; | |
1 | ||
2 | # Copyright (c) 2002 Sreeji K. Das. All rights reserved. This program is free | |
3 | # software; you can redistribute it and/or modify it under the same terms | |
4 | # as Perl itself. | |
5 | ||
6 | =head1 NAME | |
7 | ||
8 | Archive::Zip::MemberRead - A wrapper that lets you read Zip archive members as if they were files. | |
9 | ||
10 | =cut | |
11 | ||
12 | =head1 SYNOPSIS | |
13 | ||
14 | use Archive::Zip; | |
15 | use Archive::Zip::MemberRead; | |
16 | $zip = new Archive::Zip("file.zip"); | |
17 | $fh = new Archive::Zip::MemberRead($zip, "subdir/abc.txt"); | |
18 | while (defined($line = $fh->getline())) | |
19 | { | |
20 | print $fh->input_line_number . "#: $line\n"; | |
21 | } | |
22 | ||
23 | $read = $fh->read($buffer, 32*1024); | |
24 | print "Read $read bytes as :$buffer:\n"; | |
25 | ||
26 | =head1 DESCRIPTION | |
27 | ||
28 | The Archive::Zip::MemberRead module lets you read Zip archive member data | |
29 | just like you read data from files. | |
30 | ||
31 | =head1 METHODS | |
32 | ||
33 | =over 4 | |
34 | ||
35 | =cut | |
36 | ||
37 | use strict; | |
38 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); | |
39 | ||
40 | use vars qw{$VERSION}; | |
41 | ||
42 | BEGIN { | |
43 | $VERSION = '1.18'; | |
44 | $VERSION = eval $VERSION; | |
45 | } | |
46 | ||
47 | =item Archive::Zip::Member::readFileHandle() | |
48 | ||
49 | You can get a C<Archive::Zip::MemberRead> from an archive member by | |
50 | calling C<readFileHandle()>: | |
51 | ||
52 | my $member = $zip->memberNamed('abc/def.c'); | |
53 | my $fh = $member->readFileHandle(); | |
54 | while (defined($line = $fh->getline())) | |
55 | { | |
56 | # ... | |
57 | } | |
58 | $fh->close(); | |
59 | ||
60 | =cut | |
61 | ||
62 | sub Archive::Zip::Member::readFileHandle { | |
63 | return Archive::Zip::MemberRead->new( shift() ); | |
64 | } | |
65 | ||
66 | =item Archive::Zip::MemberRead->new($zip, $fileName) | |
67 | ||
68 | =item Archive::Zip::MemberRead->new($zip, $member) | |
69 | ||
70 | =item Archive::Zip::MemberRead->new($member) | |
71 | ||
72 | Construct a new Archive::Zip::MemberRead on the specified member. | |
73 | ||
74 | my $fh = Archive::Zip::MemberRead->new($zip, 'fred.c') | |
75 | ||
76 | =cut | |
77 | ||
78 | sub new { | |
79 | my ( $class, $zip, $file ) = @_; | |
80 | my ( $self, $member ); | |
81 | ||
82 | if ( $zip && $file ) # zip and filename, or zip and member | |
83 | { | |
84 | $member = ref($file) ? $file : $zip->memberNamed($file); | |
85 | } | |
86 | elsif ( $zip && !$file && ref($zip) ) # just member | |
87 | { | |
88 | $member = $zip; | |
89 | } | |
90 | else { | |
91 | die( | |
92 | 'Archive::Zip::MemberRead::new needs a zip and filename, zip and member, or member' | |
93 | ); | |
94 | } | |
95 | ||
96 | $self = {}; | |
97 | bless( $self, $class ); | |
98 | $self->set_member($member); | |
99 | return $self; | |
100 | } | |
101 | ||
102 | sub set_member { | |
103 | my ( $self, $member ) = @_; | |
104 | ||
105 | $self->{member} = $member; | |
106 | $self->set_compression(COMPRESSION_STORED); | |
107 | $self->rewind(); | |
108 | } | |
109 | ||
110 | sub set_compression { | |
111 | my ( $self, $compression ) = @_; | |
112 | $self->{member}->desiredCompressionMethod($compression) if $self->{member}; | |
113 | } | |
114 | ||
115 | =item rewind() | |
116 | ||
117 | Rewinds an C<Archive::Zip::MemberRead> so that you can read from it again | |
118 | starting at the beginning. | |
119 | ||
120 | =cut | |
121 | ||
122 | sub rewind { | |
123 | my $self = shift; | |
124 | ||
125 | $self->_reset_vars(); | |
126 | $self->{member}->rewindData() if $self->{member}; | |
127 | } | |
128 | ||
129 | sub _reset_vars { | |
130 | my $self = shift; | |
131 | $self->{lines} = []; | |
132 | $self->{partial} = 0; | |
133 | $self->{line_no} = 0; | |
134 | } | |
135 | ||
136 | =item input_line_number() | |
137 | ||
138 | Returns the current line number, but only if you're using C<getline()>. | |
139 | Using C<read()> will not update the line number. | |
140 | ||
141 | =cut | |
142 | ||
143 | sub input_line_number { | |
144 | my $self = shift; | |
145 | return $self->{line_no}; | |
146 | } | |
147 | ||
148 | =item close() | |
149 | ||
150 | Closes the given file handle. | |
151 | ||
152 | =cut | |
153 | ||
154 | sub close { | |
155 | my $self = shift; | |
156 | ||
157 | $self->_reset_vars(); | |
158 | $self->{member}->endRead(); | |
159 | } | |
160 | ||
161 | =item buffer_size([ $size ]) | |
162 | ||
163 | Gets or sets the buffer size used for reads. | |
164 | Default is the chunk size used by Archive::Zip. | |
165 | ||
166 | =cut | |
167 | ||
168 | sub buffer_size { | |
169 | my ( $self, $size ) = @_; | |
170 | ||
171 | if ( !$size ) { | |
172 | return $self->{chunkSize} || Archive::Zip::chunkSize(); | |
173 | } | |
174 | else { | |
175 | $self->{chunkSize} = $size; | |
176 | } | |
177 | } | |
178 | ||
179 | =item getline() | |
180 | ||
181 | Returns the next line from the currently open member. | |
182 | Makes sense only for text files. | |
183 | A read error is considered fatal enough to die. | |
184 | Returns undef on eof. All subsequent calls would return undef, | |
185 | unless a rewind() is called. | |
186 | Note: The line returned has the newline removed. | |
187 | ||
188 | =cut | |
189 | ||
190 | # $self->{partial} flags whether the last line in the buffer is partial or not. | |
191 | # A line is treated as partial if it does not ends with \n | |
192 | sub getline { | |
193 | my $self = shift; | |
194 | my ( $temp, $status, $size, $buffer, @lines ); | |
195 | ||
196 | $status = AZ_OK; | |
197 | $size = $self->buffer_size(); | |
198 | $temp = \$status; | |
199 | while ( $$temp !~ /\n/ && $status != AZ_STREAM_END ) { | |
200 | ( $temp, $status ) = $self->{member}->readChunk($size); | |
201 | if ( $status != AZ_OK && $status != AZ_STREAM_END ) { | |
202 | die "ERROR: Error reading chunk from archive - $status\n"; | |
203 | } | |
204 | ||
205 | $buffer .= $$temp; | |
206 | } | |
207 | ||
208 | @lines = split( /\n/, $buffer ); | |
209 | $self->{line_no}++; | |
210 | if ( $#lines == -1 ) { | |
211 | return ( $#{ $self->{lines} } == -1 ) | |
212 | ? undef | |
213 | : shift( @{ $self->{lines} } ); | |
214 | } | |
215 | ||
216 | $self->{lines}->[ $#{ $self->{lines} } ] .= shift(@lines) | |
217 | if $self->{partial}; | |
218 | ||
219 | splice( @{ $self->{lines} }, @{ $self->{lines} }, 0, @lines ); | |
220 | $self->{partial} = !( $buffer =~ /\n$/ ); | |
221 | return shift( @{ $self->{lines} } ); | |
222 | } | |
223 | ||
224 | =item read($buffer, $num_bytes_to_read) | |
225 | ||
226 | Simulates a normal C<read()> system call. | |
227 | Returns the no. of bytes read. C<undef> on error, 0 on eof, I<e.g.>: | |
228 | ||
229 | $fh = new Archive::Zip::MemberRead($zip, "sreeji/secrets.bin"); | |
230 | while (1) | |
231 | { | |
232 | $read = $fh->read($buffer, 1024); | |
233 | die "FATAL ERROR reading my secrets !\n" if (!defined($read)); | |
234 | last if (!$read); | |
235 | # Do processing. | |
236 | .... | |
237 | } | |
238 | ||
239 | =cut | |
240 | ||
241 | # | |
242 | # All these $_ are required to emulate read(). | |
243 | # | |
244 | sub read { | |
245 | my $self = $_[0]; | |
246 | my $size = $_[2]; | |
247 | my ( $temp, $status, $ret ); | |
248 | ||
249 | ( $temp, $status ) = $self->{member}->readChunk($size); | |
250 | if ( $status != AZ_OK && $status != AZ_STREAM_END ) { | |
251 | $_[1] = undef; | |
252 | $ret = undef; | |
253 | } | |
254 | else { | |
255 | $_[1] = $$temp; | |
256 | $ret = length($$temp); | |
257 | } | |
258 | return $ret; | |
259 | } | |
260 | ||
261 | 1; | |
262 | ||
263 | =back | |
264 | ||
265 | =head1 AUTHOR | |
266 | ||
267 | Sreeji K. Das, <sreeji_k@yahoo.com> | |
268 | See L<Archive::Zip> by Ned Konz without which this module does not make | |
269 | any sense! | |
270 | ||
271 | Minor mods by Ned Konz. | |
272 | ||
273 | =head1 COPYRIGHT | |
274 | ||
275 | Copyright (c) 2002 Sreeji K. Das. All rights reserved. This program is free | |
276 | software; you can redistribute it and/or modify it under the same terms | |
277 | as Perl itself. | |
278 | ||
279 | =cut |
0 | package Archive::Zip::MockFileHandle; | |
1 | ||
2 | # Output file handle that calls a custom write routine | |
3 | # Ned Konz, March 2000 | |
4 | # This is provided to help with writing zip files | |
5 | # when you have to process them a chunk at a time. | |
6 | ||
7 | use strict; | |
8 | ||
9 | use vars qw{$VERSION}; | |
10 | ||
11 | BEGIN { | |
12 | $VERSION = '1.18'; | |
13 | $VERSION = eval $VERSION; | |
14 | } | |
15 | ||
16 | sub new { | |
17 | my $class = shift || __PACKAGE__; | |
18 | $class = ref($class) || $class; | |
19 | my $self = bless( | |
20 | { | |
21 | 'position' => 0, | |
22 | 'size' => 0 | |
23 | }, | |
24 | $class | |
25 | ); | |
26 | return $self; | |
27 | } | |
28 | ||
29 | sub eof { | |
30 | my $self = shift; | |
31 | return $self->{'position'} >= $self->{'size'}; | |
32 | } | |
33 | ||
34 | # Copy given buffer to me | |
35 | sub print { | |
36 | my $self = shift; | |
37 | my $bytes = join( '', @_ ); | |
38 | my $bytesWritten = $self->writeHook($bytes); | |
39 | if ( $self->{'position'} + $bytesWritten > $self->{'size'} ) { | |
40 | $self->{'size'} = $self->{'position'} + $bytesWritten; | |
41 | } | |
42 | $self->{'position'} += $bytesWritten; | |
43 | return $bytesWritten; | |
44 | } | |
45 | ||
46 | # Called on each write. | |
47 | # Override in subclasses. | |
48 | # Return number of bytes written (0 on error). | |
49 | sub writeHook { | |
50 | my $self = shift; | |
51 | my $bytes = shift; | |
52 | return length($bytes); | |
53 | } | |
54 | ||
55 | sub binmode { 1 } | |
56 | ||
57 | sub close { 1 } | |
58 | ||
59 | sub clearerr { 1 } | |
60 | ||
61 | # I'm write-only! | |
62 | sub read { 0 } | |
63 | ||
64 | sub tell { return shift->{'position'} } | |
65 | ||
66 | sub opened { 1 } | |
67 | ||
68 | 1; |
0 | package Archive::Zip::NewFileMember; | |
1 | ||
2 | use strict; | |
3 | use vars qw( $VERSION @ISA ); | |
4 | ||
5 | BEGIN { | |
6 | $VERSION = '1.18'; | |
7 | @ISA = qw ( Archive::Zip::FileMember ); | |
8 | } | |
9 | ||
10 | use Archive::Zip qw( | |
11 | :CONSTANTS | |
12 | :ERROR_CODES | |
13 | :UTILITY_METHODS | |
14 | ); | |
15 | ||
16 | # Given a file name, set up for eventual writing. | |
17 | sub _newFromFileNamed { | |
18 | my $class = shift; | |
19 | my $fileName = shift; # local FS format | |
20 | my $newName = shift; | |
21 | $newName = _asZipDirName($fileName) unless defined($newName); | |
22 | return undef unless ( stat($fileName) && -r _ && !-d _ ); | |
23 | my $self = $class->new(@_); | |
24 | $self->fileName($newName); | |
25 | $self->{'externalFileName'} = $fileName; | |
26 | $self->{'compressionMethod'} = COMPRESSION_STORED; | |
27 | my @stat = stat(_); | |
28 | $self->{'compressedSize'} = $self->{'uncompressedSize'} = $stat[7]; | |
29 | $self->desiredCompressionMethod( | |
30 | ( $self->compressedSize() > 0 ) | |
31 | ? COMPRESSION_DEFLATED | |
32 | : COMPRESSION_STORED | |
33 | ); | |
34 | $self->unixFileAttributes( $stat[2] ); | |
35 | $self->setLastModFileDateTimeFromUnix( $stat[9] ); | |
36 | $self->isTextFile( -T _ ); | |
37 | return $self; | |
38 | } | |
39 | ||
40 | sub rewindData { | |
41 | my $self = shift; | |
42 | ||
43 | my $status = $self->SUPER::rewindData(@_); | |
44 | return $status unless $status == AZ_OK; | |
45 | ||
46 | return AZ_IO_ERROR unless $self->fh(); | |
47 | $self->fh()->clearerr(); | |
48 | $self->fh()->seek( 0, IO::Seekable::SEEK_SET ) | |
49 | or return _ioError( "rewinding", $self->externalFileName() ); | |
50 | return AZ_OK; | |
51 | } | |
52 | ||
53 | # Return bytes read. Note that first parameter is a ref to a buffer. | |
54 | # my $data; | |
55 | # my ( $bytesRead, $status) = $self->readRawChunk( \$data, $chunkSize ); | |
56 | sub _readRawChunk { | |
57 | my ( $self, $dataRef, $chunkSize ) = @_; | |
58 | return ( 0, AZ_OK ) unless $chunkSize; | |
59 | my $bytesRead = $self->fh()->read( $$dataRef, $chunkSize ) | |
60 | or return ( 0, _ioError("reading data") ); | |
61 | return ( $bytesRead, AZ_OK ); | |
62 | } | |
63 | ||
64 | # If I already exist, extraction is a no-op. | |
65 | sub extractToFileNamed { | |
66 | my $self = shift; | |
67 | my $name = shift; # local FS name | |
68 | if ( File::Spec->rel2abs($name) eq | |
69 | File::Spec->rel2abs( $self->externalFileName() ) and -r $name ) | |
70 | { | |
71 | return AZ_OK; | |
72 | } | |
73 | else { | |
74 | return $self->SUPER::extractToFileNamed( $name, @_ ); | |
75 | } | |
76 | } | |
77 | ||
78 | 1; |
0 | package Archive::Zip::StringMember; | |
1 | ||
2 | use strict; | |
3 | use vars qw( $VERSION @ISA ); | |
4 | ||
5 | BEGIN { | |
6 | $VERSION = '1.18'; | |
7 | @ISA = qw( Archive::Zip::Member ); | |
8 | } | |
9 | ||
10 | use Archive::Zip qw( | |
11 | :CONSTANTS | |
12 | :ERROR_CODES | |
13 | ); | |
14 | ||
15 | # Create a new string member. Default is COMPRESSION_STORED. | |
16 | # Can take a ref to a string as well. | |
17 | sub _newFromString { | |
18 | my $class = shift; | |
19 | my $string = shift; | |
20 | my $name = shift; | |
21 | my $self = $class->new(@_); | |
22 | $self->contents($string); | |
23 | $self->fileName($name) if defined($name); | |
24 | ||
25 | # Set the file date to now | |
26 | $self->setLastModFileDateTimeFromUnix( time() ); | |
27 | $self->unixFileAttributes( $self->DEFAULT_FILE_PERMISSIONS ); | |
28 | return $self; | |
29 | } | |
30 | ||
31 | sub _become { | |
32 | my $self = shift; | |
33 | my $newClass = shift; | |
34 | return $self if ref($self) eq $newClass; | |
35 | delete( $self->{'contents'} ); | |
36 | return $self->SUPER::_become($newClass); | |
37 | } | |
38 | ||
39 | # Get or set my contents. Note that we do not call the superclass | |
40 | # version of this, because it calls us. | |
41 | sub contents { | |
42 | my $self = shift; | |
43 | my $string = shift; | |
44 | if ( defined($string) ) { | |
45 | $self->{'contents'} = | |
46 | pack( 'C0a*', ( ref($string) eq 'SCALAR' ) ? $$string : $string ); | |
47 | $self->{'uncompressedSize'} = $self->{'compressedSize'} = | |
48 | length( $self->{'contents'} ); | |
49 | $self->{'compressionMethod'} = COMPRESSION_STORED; | |
50 | } | |
51 | return $self->{'contents'}; | |
52 | } | |
53 | ||
54 | # Return bytes read. Note that first parameter is a ref to a buffer. | |
55 | # my $data; | |
56 | # my ( $bytesRead, $status) = $self->readRawChunk( \$data, $chunkSize ); | |
57 | sub _readRawChunk { | |
58 | my ( $self, $dataRef, $chunkSize ) = @_; | |
59 | $$dataRef = substr( $self->contents(), $self->_readOffset(), $chunkSize ); | |
60 | return ( length($$dataRef), AZ_OK ); | |
61 | } | |
62 | ||
63 | 1; |
0 | use Archive::Zip; | |
1 | ||
2 | warn( | |
3 | "Archive::Zip::Tree is deprecated; its methods have been moved into Archive::Zip." | |
4 | ) if $^W; | |
5 | ||
6 | 1; | |
7 | ||
8 | __END__ | |
9 | ||
10 | =head1 NAME | |
11 | ||
12 | Archive::Zip::Tree - (DEPRECATED) methods for adding/extracting trees using Archive::Zip | |
13 | ||
14 | =head1 SYNOPSIS | |
15 | ||
16 | =head1 DESCRIPTION | |
17 | ||
18 | This module is deprecated, because all its methods were moved into the main | |
19 | Archive::Zip module. | |
20 | ||
21 | It is included in the distribution merely to avoid breaking old code. | |
22 | ||
23 | See L<Archive::Zip>. | |
24 | ||
25 | =head1 AUTHOR | |
26 | ||
27 | Ned Konz, perl@bike-nomad.com | |
28 | ||
29 | =head1 COPYRIGHT | |
30 | ||
31 | Copyright (c) 2000-2002 Ned Konz. All rights reserved. This program is free | |
32 | software; you can redistribute it and/or modify it under the same terms | |
33 | as Perl itself. | |
34 | ||
35 | =head1 SEE ALSO | |
36 | ||
37 | L<Archive::Zip> | |
38 | ||
39 | =cut |
0 | package Archive::Zip::ZipFileMember; | |
1 | ||
2 | use strict; | |
3 | use vars qw( $VERSION @ISA ); | |
4 | ||
5 | BEGIN { | |
6 | $VERSION = '1.18'; | |
7 | @ISA = qw ( Archive::Zip::FileMember ); | |
8 | } | |
9 | ||
10 | use Archive::Zip qw( | |
11 | :CONSTANTS | |
12 | :ERROR_CODES | |
13 | :PKZIP_CONSTANTS | |
14 | :UTILITY_METHODS | |
15 | ); | |
16 | ||
17 | # Create a new Archive::Zip::ZipFileMember | |
18 | # given a filename and optional open file handle | |
19 | # | |
20 | sub _newFromZipFile { | |
21 | my $class = shift; | |
22 | my $fh = shift; | |
23 | my $externalFileName = shift; | |
24 | my $possibleEocdOffset = shift; # normally 0 | |
25 | ||
26 | my $self = $class->new( | |
27 | 'crc32' => 0, | |
28 | 'diskNumberStart' => 0, | |
29 | 'localHeaderRelativeOffset' => 0, | |
30 | 'dataOffset' => 0, # localHeaderRelativeOffset + header length | |
31 | @_ | |
32 | ); | |
33 | $self->{'externalFileName'} = $externalFileName; | |
34 | $self->{'fh'} = $fh; | |
35 | $self->{'possibleEocdOffset'} = $possibleEocdOffset; | |
36 | return $self; | |
37 | } | |
38 | ||
39 | sub isDirectory { | |
40 | my $self = shift; | |
41 | return ( substr( $self->fileName(), -1, 1 ) eq '/' | |
42 | and $self->uncompressedSize() == 0 ); | |
43 | } | |
44 | ||
45 | # Seek to the beginning of the local header, just past the signature. | |
46 | # Verify that the local header signature is in fact correct. | |
47 | # Update the localHeaderRelativeOffset if necessary by adding the possibleEocdOffset. | |
48 | # Returns status. | |
49 | ||
50 | sub _seekToLocalHeader { | |
51 | my $self = shift; | |
52 | my $where = shift; # optional | |
53 | my $previousWhere = shift; # optional | |
54 | ||
55 | $where = $self->localHeaderRelativeOffset() unless defined($where); | |
56 | ||
57 | # avoid loop on certain corrupt files (from Julian Field) | |
58 | return _formatError("corrupt zip file") | |
59 | if defined($previousWhere) && $where == $previousWhere; | |
60 | ||
61 | my $status; | |
62 | my $signature; | |
63 | ||
64 | $status = $self->fh()->seek( $where, IO::Seekable::SEEK_SET ); | |
65 | return _ioError("seeking to local header") unless $status; | |
66 | ||
67 | ( $status, $signature ) = | |
68 | _readSignature( $self->fh(), $self->externalFileName(), | |
69 | LOCAL_FILE_HEADER_SIGNATURE ); | |
70 | return $status if $status == AZ_IO_ERROR; | |
71 | ||
72 | # retry with EOCD offset if any was given. | |
73 | if ( $status == AZ_FORMAT_ERROR && $self->{'possibleEocdOffset'} ) { | |
74 | $status = $self->_seekToLocalHeader( | |
75 | $self->localHeaderRelativeOffset() + $self->{'possibleEocdOffset'}, | |
76 | $where | |
77 | ); | |
78 | if ( $status == AZ_OK ) { | |
79 | $self->{'localHeaderRelativeOffset'} += | |
80 | $self->{'possibleEocdOffset'}; | |
81 | $self->{'possibleEocdOffset'} = 0; | |
82 | } | |
83 | } | |
84 | ||
85 | return $status; | |
86 | } | |
87 | ||
88 | # Because I'm going to delete the file handle, read the local file | |
89 | # header if the file handle is seekable. If it isn't, I assume that | |
90 | # I've already read the local header. | |
91 | # Return ( $status, $self ) | |
92 | ||
93 | sub _become { | |
94 | my $self = shift; | |
95 | my $newClass = shift; | |
96 | return $self if ref($self) eq $newClass; | |
97 | ||
98 | my $status = AZ_OK; | |
99 | ||
100 | if ( _isSeekable( $self->fh() ) ) { | |
101 | my $here = $self->fh()->tell(); | |
102 | $status = $self->_seekToLocalHeader(); | |
103 | $status = $self->_readLocalFileHeader() if $status == AZ_OK; | |
104 | $self->fh()->seek( $here, IO::Seekable::SEEK_SET ); | |
105 | return $status unless $status == AZ_OK; | |
106 | } | |
107 | ||
108 | delete( $self->{'eocdCrc32'} ); | |
109 | delete( $self->{'diskNumberStart'} ); | |
110 | delete( $self->{'localHeaderRelativeOffset'} ); | |
111 | delete( $self->{'dataOffset'} ); | |
112 | ||
113 | return $self->SUPER::_become($newClass); | |
114 | } | |
115 | ||
116 | sub diskNumberStart { | |
117 | shift->{'diskNumberStart'}; | |
118 | } | |
119 | ||
120 | sub localHeaderRelativeOffset { | |
121 | shift->{'localHeaderRelativeOffset'}; | |
122 | } | |
123 | ||
124 | sub dataOffset { | |
125 | shift->{'dataOffset'}; | |
126 | } | |
127 | ||
128 | # Skip local file header, updating only extra field stuff. | |
129 | # Assumes that fh is positioned before signature. | |
130 | sub _skipLocalFileHeader { | |
131 | my $self = shift; | |
132 | my $header; | |
133 | my $bytesRead = $self->fh()->read( $header, LOCAL_FILE_HEADER_LENGTH ); | |
134 | if ( $bytesRead != LOCAL_FILE_HEADER_LENGTH ) { | |
135 | return _ioError("reading local file header"); | |
136 | } | |
137 | my $fileNameLength; | |
138 | my $extraFieldLength; | |
139 | my $bitFlag; | |
140 | ( | |
141 | undef, # $self->{'versionNeededToExtract'}, | |
142 | $bitFlag, | |
143 | undef, # $self->{'compressionMethod'}, | |
144 | undef, # $self->{'lastModFileDateTime'}, | |
145 | undef, # $crc32, | |
146 | undef, # $compressedSize, | |
147 | undef, # $uncompressedSize, | |
148 | $fileNameLength, | |
149 | $extraFieldLength | |
150 | ) = unpack( LOCAL_FILE_HEADER_FORMAT, $header ); | |
151 | ||
152 | if ($fileNameLength) { | |
153 | $self->fh()->seek( $fileNameLength, IO::Seekable::SEEK_CUR ) | |
154 | or return _ioError("skipping local file name"); | |
155 | } | |
156 | ||
157 | if ($extraFieldLength) { | |
158 | $bytesRead = | |
159 | $self->fh()->read( $self->{'localExtraField'}, $extraFieldLength ); | |
160 | if ( $bytesRead != $extraFieldLength ) { | |
161 | return _ioError("reading local extra field"); | |
162 | } | |
163 | } | |
164 | ||
165 | $self->{'dataOffset'} = $self->fh()->tell(); | |
166 | ||
167 | if ( $bitFlag & GPBF_HAS_DATA_DESCRIPTOR_MASK ) { | |
168 | ||
169 | # Read the crc32, compressedSize, and uncompressedSize from the | |
170 | # extended data descriptor, which directly follows the compressed data. | |
171 | # | |
172 | # Skip over the compressed file data (assumes that EOCD compressedSize | |
173 | # was correct) | |
174 | $self->fh()->seek( $self->{'compressedSize'}, IO::Seekable::SEEK_CUR ) | |
175 | or return _ioError("seeking to extended local header"); | |
176 | ||
177 | # these values should be set correctly from before. | |
178 | my $oldCrc32 = $self->{'eocdCrc32'}; | |
179 | my $oldCompressedSize = $self->{'compressedSize'}; | |
180 | my $oldUncompressedSize = $self->{'uncompressedSize'}; | |
181 | ||
182 | my $status = $self->_readDataDescriptor(); | |
183 | return $status unless $status == AZ_OK; | |
184 | ||
185 | return _formatError( | |
186 | "CRC or size mismatch while skipping data descriptor") | |
187 | if ( $oldCrc32 != $self->{'crc32'} | |
188 | || $oldUncompressedSize != $self->{'uncompressedSize'} ); | |
189 | } | |
190 | ||
191 | return AZ_OK; | |
192 | } | |
193 | ||
194 | # Read from a local file header into myself. Returns AZ_OK if successful. | |
195 | # Assumes that fh is positioned after signature. | |
196 | # Note that crc32, compressedSize, and uncompressedSize will be 0 if | |
197 | # GPBF_HAS_DATA_DESCRIPTOR_MASK is set in the bitFlag. | |
198 | ||
199 | sub _readLocalFileHeader { | |
200 | my $self = shift; | |
201 | my $header; | |
202 | my $bytesRead = $self->fh()->read( $header, LOCAL_FILE_HEADER_LENGTH ); | |
203 | if ( $bytesRead != LOCAL_FILE_HEADER_LENGTH ) { | |
204 | return _ioError("reading local file header"); | |
205 | } | |
206 | my $fileNameLength; | |
207 | my $crc32; | |
208 | my $compressedSize; | |
209 | my $uncompressedSize; | |
210 | my $extraFieldLength; | |
211 | ( | |
212 | $self->{'versionNeededToExtract'}, $self->{'bitFlag'}, | |
213 | $self->{'compressionMethod'}, $self->{'lastModFileDateTime'}, | |
214 | $crc32, $compressedSize, | |
215 | $uncompressedSize, $fileNameLength, | |
216 | $extraFieldLength | |
217 | ) = unpack( LOCAL_FILE_HEADER_FORMAT, $header ); | |
218 | ||
219 | if ($fileNameLength) { | |
220 | my $fileName; | |
221 | $bytesRead = $self->fh()->read( $fileName, $fileNameLength ); | |
222 | if ( $bytesRead != $fileNameLength ) { | |
223 | return _ioError("reading local file name"); | |
224 | } | |
225 | $self->fileName($fileName); | |
226 | } | |
227 | ||
228 | if ($extraFieldLength) { | |
229 | $bytesRead = | |
230 | $self->fh()->read( $self->{'localExtraField'}, $extraFieldLength ); | |
231 | if ( $bytesRead != $extraFieldLength ) { | |
232 | return _ioError("reading local extra field"); | |
233 | } | |
234 | } | |
235 | ||
236 | $self->{'dataOffset'} = $self->fh()->tell(); | |
237 | ||
238 | if ( $self->hasDataDescriptor() ) { | |
239 | ||
240 | # Read the crc32, compressedSize, and uncompressedSize from the | |
241 | # extended data descriptor. | |
242 | # Skip over the compressed file data (assumes that EOCD compressedSize | |
243 | # was correct) | |
244 | $self->fh()->seek( $self->{'compressedSize'}, IO::Seekable::SEEK_CUR ) | |
245 | or return _ioError("seeking to extended local header"); | |
246 | ||
247 | my $status = $self->_readDataDescriptor(); | |
248 | return $status unless $status == AZ_OK; | |
249 | } | |
250 | else { | |
251 | return _formatError( | |
252 | "CRC or size mismatch after reading data descriptor") | |
253 | if ( $self->{'crc32'} != $crc32 | |
254 | || $self->{'uncompressedSize'} != $uncompressedSize ); | |
255 | } | |
256 | ||
257 | return AZ_OK; | |
258 | } | |
259 | ||
260 | # This will read the data descriptor, which is after the end of compressed file | |
261 | # data in members that that have GPBF_HAS_DATA_DESCRIPTOR_MASK set in their | |
262 | # bitFlag. | |
263 | # The only reliable way to find these is to rely on the EOCD compressedSize. | |
264 | # Assumes that file is positioned immediately after the compressed data. | |
265 | # Returns status; sets crc32, compressedSize, and uncompressedSize. | |
266 | sub _readDataDescriptor { | |
267 | my $self = shift; | |
268 | my $signatureData; | |
269 | my $header; | |
270 | my $crc32; | |
271 | my $compressedSize; | |
272 | my $uncompressedSize; | |
273 | ||
274 | my $bytesRead = $self->fh()->read( $signatureData, SIGNATURE_LENGTH ); | |
275 | return _ioError("reading header signature") | |
276 | if $bytesRead != SIGNATURE_LENGTH; | |
277 | my $signature = unpack( SIGNATURE_FORMAT, $signatureData ); | |
278 | ||
279 | # unfortunately, the signature appears to be optional. | |
280 | if ( $signature == DATA_DESCRIPTOR_SIGNATURE | |
281 | && ( $signature != $self->{'crc32'} ) ) | |
282 | { | |
283 | $bytesRead = $self->fh()->read( $header, DATA_DESCRIPTOR_LENGTH ); | |
284 | return _ioError("reading data descriptor") | |
285 | if $bytesRead != DATA_DESCRIPTOR_LENGTH; | |
286 | ||
287 | ( $crc32, $compressedSize, $uncompressedSize ) = | |
288 | unpack( DATA_DESCRIPTOR_FORMAT, $header ); | |
289 | } | |
290 | else { | |
291 | $bytesRead = | |
292 | $self->fh()->read( $header, DATA_DESCRIPTOR_LENGTH_NO_SIG ); | |
293 | return _ioError("reading data descriptor") | |
294 | if $bytesRead != DATA_DESCRIPTOR_LENGTH_NO_SIG; | |
295 | ||
296 | $crc32 = $signature; | |
297 | ( $compressedSize, $uncompressedSize ) = | |
298 | unpack( DATA_DESCRIPTOR_FORMAT_NO_SIG, $header ); | |
299 | } | |
300 | ||
301 | $self->{'eocdCrc32'} = $self->{'crc32'} | |
302 | unless defined( $self->{'eocdCrc32'} ); | |
303 | $self->{'crc32'} = $crc32; | |
304 | $self->{'compressedSize'} = $compressedSize; | |
305 | $self->{'uncompressedSize'} = $uncompressedSize; | |
306 | ||
307 | return AZ_OK; | |
308 | } | |
309 | ||
310 | # Read a Central Directory header. Return AZ_OK on success. | |
311 | # Assumes that fh is positioned right after the signature. | |
312 | ||
313 | sub _readCentralDirectoryFileHeader { | |
314 | my $self = shift; | |
315 | my $fh = $self->fh(); | |
316 | my $header = ''; | |
317 | my $bytesRead = $fh->read( $header, CENTRAL_DIRECTORY_FILE_HEADER_LENGTH ); | |
318 | if ( $bytesRead != CENTRAL_DIRECTORY_FILE_HEADER_LENGTH ) { | |
319 | return _ioError("reading central dir header"); | |
320 | } | |
321 | my ( $fileNameLength, $extraFieldLength, $fileCommentLength ); | |
322 | ( | |
323 | $self->{'versionMadeBy'}, | |
324 | $self->{'fileAttributeFormat'}, | |
325 | $self->{'versionNeededToExtract'}, | |
326 | $self->{'bitFlag'}, | |
327 | $self->{'compressionMethod'}, | |
328 | $self->{'lastModFileDateTime'}, | |
329 | $self->{'crc32'}, | |
330 | $self->{'compressedSize'}, | |
331 | $self->{'uncompressedSize'}, | |
332 | $fileNameLength, | |
333 | $extraFieldLength, | |
334 | $fileCommentLength, | |
335 | $self->{'diskNumberStart'}, | |
336 | $self->{'internalFileAttributes'}, | |
337 | $self->{'externalFileAttributes'}, | |
338 | $self->{'localHeaderRelativeOffset'} | |
339 | ) = unpack( CENTRAL_DIRECTORY_FILE_HEADER_FORMAT, $header ); | |
340 | ||
341 | $self->{'eocdCrc32'} = $self->{'crc32'}; | |
342 | ||
343 | if ($fileNameLength) { | |
344 | $bytesRead = $fh->read( $self->{'fileName'}, $fileNameLength ); | |
345 | if ( $bytesRead != $fileNameLength ) { | |
346 | _ioError("reading central dir filename"); | |
347 | } | |
348 | } | |
349 | if ($extraFieldLength) { | |
350 | $bytesRead = $fh->read( $self->{'cdExtraField'}, $extraFieldLength ); | |
351 | if ( $bytesRead != $extraFieldLength ) { | |
352 | return _ioError("reading central dir extra field"); | |
353 | } | |
354 | } | |
355 | if ($fileCommentLength) { | |
356 | $bytesRead = $fh->read( $self->{'fileComment'}, $fileCommentLength ); | |
357 | if ( $bytesRead != $fileCommentLength ) { | |
358 | return _ioError("reading central dir file comment"); | |
359 | } | |
360 | } | |
361 | ||
362 | # NK 10/21/04: added to avoid problems with manipulated headers | |
363 | if ( $self->{'uncompressedSize'} != $self->{'compressedSize'} | |
364 | and $self->{'compressionMethod'} == COMPRESSION_STORED ) | |
365 | { | |
366 | $self->{'uncompressedSize'} = $self->{'compressedSize'}; | |
367 | } | |
368 | ||
369 | $self->desiredCompressionMethod( $self->compressionMethod() ); | |
370 | ||
371 | return AZ_OK; | |
372 | } | |
373 | ||
374 | sub rewindData { | |
375 | my $self = shift; | |
376 | ||
377 | my $status = $self->SUPER::rewindData(@_); | |
378 | return $status unless $status == AZ_OK; | |
379 | ||
380 | return AZ_IO_ERROR unless $self->fh(); | |
381 | ||
382 | $self->fh()->clearerr(); | |
383 | ||
384 | # Seek to local file header. | |
385 | # The only reason that I'm doing this this way is that the extraField | |
386 | # length seems to be different between the CD header and the LF header. | |
387 | $status = $self->_seekToLocalHeader(); | |
388 | return $status unless $status == AZ_OK; | |
389 | ||
390 | # skip local file header | |
391 | $status = $self->_skipLocalFileHeader(); | |
392 | return $status unless $status == AZ_OK; | |
393 | ||
394 | # Seek to beginning of file data | |
395 | $self->fh()->seek( $self->dataOffset(), IO::Seekable::SEEK_SET ) | |
396 | or return _ioError("seeking to beginning of file data"); | |
397 | ||
398 | return AZ_OK; | |
399 | } | |
400 | ||
401 | # Return bytes read. Note that first parameter is a ref to a buffer. | |
402 | # my $data; | |
403 | # my ( $bytesRead, $status) = $self->readRawChunk( \$data, $chunkSize ); | |
404 | sub _readRawChunk { | |
405 | my ( $self, $dataRef, $chunkSize ) = @_; | |
406 | return ( 0, AZ_OK ) unless $chunkSize; | |
407 | my $bytesRead = $self->fh()->read( $$dataRef, $chunkSize ) | |
408 | or return ( 0, _ioError("reading data") ); | |
409 | return ( $bytesRead, AZ_OK ); | |
410 | } | |
411 | ||
412 | 1; |
0 | package Archive::Zip; | |
1 | ||
2 | # Copyright 2000 - 2002 Ned Konz. All rights reserved. This program is free | |
3 | # software; you can redistribute it and/or modify it under the same terms as | |
4 | # Perl itself. | |
5 | ||
6 | # ---------------------------------------------------------------------- | |
7 | # class Archive::Zip | |
8 | # Note that the package Archive::Zip exists only for exporting and | |
9 | # sharing constants. Everything else is in another package | |
10 | # in this file. | |
11 | # Creation of a new Archive::Zip object actually creates a new object | |
12 | # of class Archive::Zip::Archive. | |
13 | # ---------------------------------------------------------------------- | |
14 | ||
15 | BEGIN { | |
16 | require 5.003_96; | |
17 | } | |
18 | use strict; | |
19 | use UNIVERSAL (); | |
20 | use Carp (); | |
21 | use IO::File (); | |
22 | use IO::Seekable (); | |
23 | use Compress::Zlib (); | |
24 | use File::Spec (); | |
25 | use File::Temp (); | |
26 | ||
27 | use vars qw( $VERSION @ISA ); | |
28 | BEGIN { | |
29 | $VERSION = '1.18'; | |
30 | $VERSION = eval $VERSION; | |
31 | ||
32 | require Exporter; | |
33 | @ISA = qw( Exporter ); | |
34 | } | |
35 | ||
36 | use vars qw( $ChunkSize $ErrorHandler ); | |
37 | ||
38 | # This is the size we'll try to read, write, and (de)compress. | |
39 | # You could set it to something different if you had lots of memory | |
40 | # and needed more speed. | |
41 | $ChunkSize = 32768; | |
42 | ||
43 | $ErrorHandler = \&Carp::carp; | |
44 | ||
45 | # BEGIN block is necessary here so that other modules can use the constants. | |
46 | use vars qw( @EXPORT_OK %EXPORT_TAGS ); | |
47 | BEGIN { | |
48 | @EXPORT_OK = ('computeCRC32'); | |
49 | %EXPORT_TAGS = ( | |
50 | CONSTANTS => [ qw( | |
51 | FA_MSDOS | |
52 | FA_UNIX | |
53 | GPBF_ENCRYPTED_MASK | |
54 | GPBF_DEFLATING_COMPRESSION_MASK | |
55 | GPBF_HAS_DATA_DESCRIPTOR_MASK | |
56 | COMPRESSION_STORED | |
57 | COMPRESSION_DEFLATED | |
58 | COMPRESSION_LEVEL_NONE | |
59 | COMPRESSION_LEVEL_DEFAULT | |
60 | COMPRESSION_LEVEL_FASTEST | |
61 | COMPRESSION_LEVEL_BEST_COMPRESSION | |
62 | IFA_TEXT_FILE_MASK | |
63 | IFA_TEXT_FILE | |
64 | IFA_BINARY_FILE | |
65 | ) ], | |
66 | ||
67 | MISC_CONSTANTS => [ qw( | |
68 | FA_AMIGA | |
69 | FA_VAX_VMS | |
70 | FA_VM_CMS | |
71 | FA_ATARI_ST | |
72 | FA_OS2_HPFS | |
73 | FA_MACINTOSH | |
74 | FA_Z_SYSTEM | |
75 | FA_CPM | |
76 | FA_TOPS20 | |
77 | FA_WINDOWS_NTFS | |
78 | FA_QDOS | |
79 | FA_ACORN | |
80 | FA_VFAT | |
81 | FA_MVS | |
82 | FA_BEOS | |
83 | FA_TANDEM | |
84 | FA_THEOS | |
85 | GPBF_IMPLODING_8K_SLIDING_DICTIONARY_MASK | |
86 | GPBF_IMPLODING_3_SHANNON_FANO_TREES_MASK | |
87 | GPBF_IS_COMPRESSED_PATCHED_DATA_MASK | |
88 | COMPRESSION_SHRUNK | |
89 | DEFLATING_COMPRESSION_NORMAL | |
90 | DEFLATING_COMPRESSION_MAXIMUM | |
91 | DEFLATING_COMPRESSION_FAST | |
92 | DEFLATING_COMPRESSION_SUPER_FAST | |
93 | COMPRESSION_REDUCED_1 | |
94 | COMPRESSION_REDUCED_2 | |
95 | COMPRESSION_REDUCED_3 | |
96 | COMPRESSION_REDUCED_4 | |
97 | COMPRESSION_IMPLODED | |
98 | COMPRESSION_TOKENIZED | |
99 | COMPRESSION_DEFLATED_ENHANCED | |
100 | COMPRESSION_PKWARE_DATA_COMPRESSION_LIBRARY_IMPLODED | |
101 | ) ], | |
102 | ||
103 | ERROR_CODES => [ qw( | |
104 | AZ_OK | |
105 | AZ_STREAM_END | |
106 | AZ_ERROR | |
107 | AZ_FORMAT_ERROR | |
108 | AZ_IO_ERROR | |
109 | ) ], | |
110 | ||
111 | # For Internal Use Only | |
112 | PKZIP_CONSTANTS => [ qw( | |
113 | SIGNATURE_FORMAT | |
114 | SIGNATURE_LENGTH | |
115 | LOCAL_FILE_HEADER_SIGNATURE | |
116 | LOCAL_FILE_HEADER_FORMAT | |
117 | LOCAL_FILE_HEADER_LENGTH | |
118 | CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE | |
119 | DATA_DESCRIPTOR_FORMAT | |
120 | DATA_DESCRIPTOR_LENGTH | |
121 | DATA_DESCRIPTOR_SIGNATURE | |
122 | DATA_DESCRIPTOR_FORMAT_NO_SIG | |
123 | DATA_DESCRIPTOR_LENGTH_NO_SIG | |
124 | CENTRAL_DIRECTORY_FILE_HEADER_FORMAT | |
125 | CENTRAL_DIRECTORY_FILE_HEADER_LENGTH | |
126 | END_OF_CENTRAL_DIRECTORY_SIGNATURE | |
127 | END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING | |
128 | END_OF_CENTRAL_DIRECTORY_FORMAT | |
129 | END_OF_CENTRAL_DIRECTORY_LENGTH | |
130 | ) ], | |
131 | ||
132 | # For Internal Use Only | |
133 | UTILITY_METHODS => [ qw( | |
134 | _error | |
135 | _printError | |
136 | _ioError | |
137 | _formatError | |
138 | _subclassResponsibility | |
139 | _binmode | |
140 | _isSeekable | |
141 | _newFileHandle | |
142 | _readSignature | |
143 | _asZipDirName | |
144 | ) ], | |
145 | ); | |
146 | ||
147 | # Add all the constant names and error code names to @EXPORT_OK | |
148 | Exporter::export_ok_tags( qw( | |
149 | CONSTANTS | |
150 | ERROR_CODES | |
151 | PKZIP_CONSTANTS | |
152 | UTILITY_METHODS | |
153 | MISC_CONSTANTS | |
154 | ) ); | |
155 | ||
156 | } | |
157 | ||
158 | # Error codes | |
159 | use constant AZ_OK => 0; | |
160 | use constant AZ_STREAM_END => 1; | |
161 | use constant AZ_ERROR => 2; | |
162 | use constant AZ_FORMAT_ERROR => 3; | |
163 | use constant AZ_IO_ERROR => 4; | |
164 | ||
165 | # File types | |
166 | # Values of Archive::Zip::Member->fileAttributeFormat() | |
167 | ||
168 | use constant FA_MSDOS => 0; | |
169 | use constant FA_AMIGA => 1; | |
170 | use constant FA_VAX_VMS => 2; | |
171 | use constant FA_UNIX => 3; | |
172 | use constant FA_VM_CMS => 4; | |
173 | use constant FA_ATARI_ST => 5; | |
174 | use constant FA_OS2_HPFS => 6; | |
175 | use constant FA_MACINTOSH => 7; | |
176 | use constant FA_Z_SYSTEM => 8; | |
177 | use constant FA_CPM => 9; | |
178 | use constant FA_TOPS20 => 10; | |
179 | use constant FA_WINDOWS_NTFS => 11; | |
180 | use constant FA_QDOS => 12; | |
181 | use constant FA_ACORN => 13; | |
182 | use constant FA_VFAT => 14; | |
183 | use constant FA_MVS => 15; | |
184 | use constant FA_BEOS => 16; | |
185 | use constant FA_TANDEM => 17; | |
186 | use constant FA_THEOS => 18; | |
187 | ||
188 | # general-purpose bit flag masks | |
189 | # Found in Archive::Zip::Member->bitFlag() | |
190 | ||
191 | use constant GPBF_ENCRYPTED_MASK => 1 << 0; | |
192 | use constant GPBF_DEFLATING_COMPRESSION_MASK => 3 << 1; | |
193 | use constant GPBF_HAS_DATA_DESCRIPTOR_MASK => 1 << 3; | |
194 | ||
195 | # deflating compression types, if compressionMethod == COMPRESSION_DEFLATED | |
196 | # ( Archive::Zip::Member->bitFlag() & GPBF_DEFLATING_COMPRESSION_MASK ) | |
197 | ||
198 | use constant DEFLATING_COMPRESSION_NORMAL => 0 << 1; | |
199 | use constant DEFLATING_COMPRESSION_MAXIMUM => 1 << 1; | |
200 | use constant DEFLATING_COMPRESSION_FAST => 2 << 1; | |
201 | use constant DEFLATING_COMPRESSION_SUPER_FAST => 3 << 1; | |
202 | ||
203 | # compression method | |
204 | ||
205 | # these two are the only ones supported in this module | |
206 | use constant COMPRESSION_STORED => 0; # file is stored (no compression) | |
207 | use constant COMPRESSION_DEFLATED => 8; # file is Deflated | |
208 | use constant COMPRESSION_LEVEL_NONE => 0; | |
209 | use constant COMPRESSION_LEVEL_DEFAULT => -1; | |
210 | use constant COMPRESSION_LEVEL_FASTEST => 1; | |
211 | use constant COMPRESSION_LEVEL_BEST_COMPRESSION => 9; | |
212 | ||
213 | # internal file attribute bits | |
214 | # Found in Archive::Zip::Member::internalFileAttributes() | |
215 | ||
216 | use constant IFA_TEXT_FILE_MASK => 1; | |
217 | use constant IFA_TEXT_FILE => 1; | |
218 | use constant IFA_BINARY_FILE => 0; | |
219 | ||
220 | # PKZIP file format miscellaneous constants (for internal use only) | |
221 | use constant SIGNATURE_FORMAT => "V"; | |
222 | use constant SIGNATURE_LENGTH => 4; | |
223 | ||
224 | # these lengths are without the signature. | |
225 | use constant LOCAL_FILE_HEADER_SIGNATURE => 0x04034b50; | |
226 | use constant LOCAL_FILE_HEADER_FORMAT => "v3 V4 v2"; | |
227 | use constant LOCAL_FILE_HEADER_LENGTH => 26; | |
228 | ||
229 | # PKZIP docs don't mention the signature, but Info-Zip writes it. | |
230 | use constant DATA_DESCRIPTOR_SIGNATURE => 0x08074b50; | |
231 | use constant DATA_DESCRIPTOR_FORMAT => "V3"; | |
232 | use constant DATA_DESCRIPTOR_LENGTH => 12; | |
233 | ||
234 | # but the signature is apparently optional. | |
235 | use constant DATA_DESCRIPTOR_FORMAT_NO_SIG => "V2"; | |
236 | use constant DATA_DESCRIPTOR_LENGTH_NO_SIG => 8; | |
237 | ||
238 | use constant CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE => 0x02014b50; | |
239 | use constant CENTRAL_DIRECTORY_FILE_HEADER_FORMAT => "C2 v3 V4 v5 V2"; | |
240 | use constant CENTRAL_DIRECTORY_FILE_HEADER_LENGTH => 42; | |
241 | ||
242 | use constant END_OF_CENTRAL_DIRECTORY_SIGNATURE => 0x06054b50; | |
243 | use constant END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING => | |
244 | pack( "V", END_OF_CENTRAL_DIRECTORY_SIGNATURE ); | |
245 | use constant END_OF_CENTRAL_DIRECTORY_FORMAT => "v4 V2 v"; | |
246 | use constant END_OF_CENTRAL_DIRECTORY_LENGTH => 18; | |
247 | ||
248 | use constant GPBF_IMPLODING_8K_SLIDING_DICTIONARY_MASK => 1 << 1; | |
249 | use constant GPBF_IMPLODING_3_SHANNON_FANO_TREES_MASK => 1 << 2; | |
250 | use constant GPBF_IS_COMPRESSED_PATCHED_DATA_MASK => 1 << 5; | |
251 | ||
252 | # the rest of these are not supported in this module | |
253 | use constant COMPRESSION_SHRUNK => 1; # file is Shrunk | |
254 | use constant COMPRESSION_REDUCED_1 => 2; # file is Reduced CF=1 | |
255 | use constant COMPRESSION_REDUCED_2 => 3; # file is Reduced CF=2 | |
256 | use constant COMPRESSION_REDUCED_3 => 4; # file is Reduced CF=3 | |
257 | use constant COMPRESSION_REDUCED_4 => 5; # file is Reduced CF=4 | |
258 | use constant COMPRESSION_IMPLODED => 6; # file is Imploded | |
259 | use constant COMPRESSION_TOKENIZED => 7; # reserved for Tokenizing compr. | |
260 | use constant COMPRESSION_DEFLATED_ENHANCED => 9; # reserved for enh. Deflating | |
261 | use constant COMPRESSION_PKWARE_DATA_COMPRESSION_LIBRARY_IMPLODED => 10; | |
262 | ||
263 | # Load the various required classes | |
264 | require Archive::Zip::Archive; | |
265 | require Archive::Zip::Member; | |
266 | require Archive::Zip::FileMember; | |
267 | require Archive::Zip::DirectoryMember; | |
268 | require Archive::Zip::ZipFileMember; | |
269 | require Archive::Zip::NewFileMember; | |
270 | require Archive::Zip::StringMember; | |
271 | ||
272 | use constant ZIPARCHIVECLASS => 'Archive::Zip::Archive'; | |
273 | use constant ZIPMEMBERCLASS => 'Archive::Zip::Member'; | |
274 | ||
275 | sub new | |
276 | { | |
277 | my $class = shift; | |
278 | return $class->ZIPARCHIVECLASS->new(@_); | |
279 | } | |
280 | ||
281 | sub computeCRC32 | |
282 | { | |
283 | my $data = shift; | |
284 | $data = shift if ref($data); # allow calling as an obj method | |
285 | my $crc = shift; | |
286 | return Compress::Zlib::crc32( $data, $crc ); | |
287 | } | |
288 | ||
289 | # Report or change chunk size used for reading and writing. | |
290 | # Also sets Zlib's default buffer size (eventually). | |
291 | sub setChunkSize | |
292 | { | |
293 | my $chunkSize = shift; | |
294 | $chunkSize = shift if ref($chunkSize); # object method on zip? | |
295 | my $oldChunkSize = $Archive::Zip::ChunkSize; | |
296 | $Archive::Zip::ChunkSize = $chunkSize if ($chunkSize); | |
297 | return $oldChunkSize; | |
298 | } | |
299 | ||
300 | sub chunkSize | |
301 | { | |
302 | return $Archive::Zip::ChunkSize; | |
303 | } | |
304 | ||
305 | sub setErrorHandler (&) | |
306 | { | |
307 | my $errorHandler = shift; | |
308 | $errorHandler = \&Carp::carp unless defined($errorHandler); | |
309 | my $oldErrorHandler = $Archive::Zip::ErrorHandler; | |
310 | $Archive::Zip::ErrorHandler = $errorHandler; | |
311 | return $oldErrorHandler; | |
312 | } | |
313 | ||
314 | # ---------------------------------------------------------------------- | |
315 | # Private utility functions (not methods). | |
316 | # ---------------------------------------------------------------------- | |
317 | ||
318 | sub _printError | |
319 | { | |
320 | my $string = join ( ' ', @_, "\n" ); | |
321 | my $oldCarpLevel = $Carp::CarpLevel; | |
322 | $Carp::CarpLevel += 2; | |
323 | &{$ErrorHandler} ($string); | |
324 | $Carp::CarpLevel = $oldCarpLevel; | |
325 | } | |
326 | ||
327 | # This is called on format errors. | |
328 | sub _formatError | |
329 | { | |
330 | shift if ref( $_[0] ); | |
331 | _printError( 'format error:', @_ ); | |
332 | return AZ_FORMAT_ERROR; | |
333 | } | |
334 | ||
335 | # This is called on IO errors. | |
336 | sub _ioError | |
337 | { | |
338 | shift if ref( $_[0] ); | |
339 | _printError( 'IO error:', @_, ':', $! ); | |
340 | return AZ_IO_ERROR; | |
341 | } | |
342 | ||
343 | # This is called on generic errors. | |
344 | sub _error | |
345 | { | |
346 | shift if ref( $_[0] ); | |
347 | _printError( 'error:', @_ ); | |
348 | return AZ_ERROR; | |
349 | } | |
350 | ||
351 | # Called when a subclass should have implemented | |
352 | # something but didn't | |
353 | sub _subclassResponsibility | |
354 | { | |
355 | Carp::croak("subclass Responsibility\n"); | |
356 | } | |
357 | ||
358 | # Try to set the given file handle or object into binary mode. | |
359 | sub _binmode | |
360 | { | |
361 | my $fh = shift; | |
362 | return UNIVERSAL::can( $fh, 'binmode' ) ? $fh->binmode() : binmode($fh); | |
363 | } | |
364 | ||
365 | # Attempt to guess whether file handle is seekable. | |
366 | # Because of problems with Windoze, this only returns true when | |
367 | # the file handle is a real file. | |
368 | sub _isSeekable | |
369 | { | |
370 | my $fh = shift; | |
371 | if ( UNIVERSAL::isa($fh, 'IO::Scalar') ) { | |
372 | return 0; | |
373 | } | |
374 | if ( UNIVERSAL::isa($fh, 'IO::String') ) { | |
375 | return 1; | |
376 | } | |
377 | if ( UNIVERSAL::isa($fh, 'IO::Seekable') ) { | |
378 | # Unfortunately, some things like FileHandle objects | |
379 | # return true for Seekable, but AREN'T!!!!! | |
380 | if ( UNIVERSAL::isa($fh, 'FileHandle') ) { | |
381 | return 0; | |
382 | } | |
383 | return 1; | |
384 | } | |
385 | if ( UNIVERSAL::can($fh, 'stat') ) { | |
386 | return -f $fh; | |
387 | } | |
388 | return ( | |
389 | UNIVERSAL::can($fh, 'seek') and UNIVERSAL::can($fh, 'tell') | |
390 | ) ? 1 : 0; | |
391 | } | |
392 | ||
393 | # Return an opened IO::Handle | |
394 | # my ( $status, fh ) = _newFileHandle( 'fileName', 'w' ); | |
395 | # Can take a filename, file handle, or ref to GLOB | |
396 | # Or, if given something that is a ref but not an IO::Handle, | |
397 | # passes back the same thing. | |
398 | sub _newFileHandle | |
399 | { | |
400 | my $fd = shift; | |
401 | my $status = 1; | |
402 | my $handle; | |
403 | ||
404 | if ( ref($fd) ) | |
405 | { | |
406 | if ( UNIVERSAL::isa( $fd, 'IO::Scalar' ) | |
407 | or UNIVERSAL::isa( $fd, 'IO::String' ) ) | |
408 | { | |
409 | $handle = $fd; | |
410 | } | |
411 | elsif ( UNIVERSAL::isa( $fd, 'IO::Handle' ) | |
412 | or UNIVERSAL::isa( $fd, 'GLOB' ) ) | |
413 | { | |
414 | $handle = IO::File->new(); | |
415 | $status = $handle->fdopen( $fd, @_ ); | |
416 | } | |
417 | else | |
418 | { | |
419 | $handle = $fd; | |
420 | } | |
421 | } | |
422 | else | |
423 | { | |
424 | $handle = IO::File->new(); | |
425 | $status = $handle->open( $fd, @_ ); | |
426 | } | |
427 | ||
428 | return ( $status, $handle ); | |
429 | } | |
430 | ||
431 | # Returns next signature from given file handle, leaves | |
432 | # file handle positioned afterwards. | |
433 | # In list context, returns ($status, $signature) | |
434 | # ( $status, $signature) = _readSignature( $fh, $fileName ); | |
435 | ||
436 | sub _readSignature | |
437 | { | |
438 | my $fh = shift; | |
439 | my $fileName = shift; | |
440 | my $expectedSignature = shift; # optional | |
441 | ||
442 | my $signatureData; | |
443 | my $bytesRead = $fh->read( $signatureData, SIGNATURE_LENGTH ); | |
444 | return _ioError("reading header signature") | |
445 | if $bytesRead != SIGNATURE_LENGTH; | |
446 | my $signature = unpack( SIGNATURE_FORMAT, $signatureData ); | |
447 | my $status = AZ_OK; | |
448 | ||
449 | # compare with expected signature, if any, or any known signature. | |
450 | if ( ( defined($expectedSignature) && $signature != $expectedSignature ) | |
451 | || ( !defined($expectedSignature) | |
452 | && $signature != CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE | |
453 | && $signature != LOCAL_FILE_HEADER_SIGNATURE | |
454 | && $signature != END_OF_CENTRAL_DIRECTORY_SIGNATURE | |
455 | && $signature != DATA_DESCRIPTOR_SIGNATURE ) ) | |
456 | { | |
457 | my $errmsg = sprintf( "bad signature: 0x%08x", $signature ); | |
458 | if ( _isSeekable($fh) ) | |
459 | { | |
460 | $errmsg .= | |
461 | sprintf( " at offset %d", $fh->tell() - SIGNATURE_LENGTH ); | |
462 | } | |
463 | ||
464 | $status = _formatError("$errmsg in file $fileName"); | |
465 | } | |
466 | ||
467 | return ( $status, $signature ); | |
468 | } | |
469 | ||
470 | # Utility method to make and open a temp file. | |
471 | # Will create $temp_dir if it doesn't exist. | |
472 | # Returns file handle and name: | |
473 | # | |
474 | # my ($fh, $name) = Archive::Zip::tempFile(); | |
475 | # my ($fh, $name) = Archive::Zip::tempFile('mytempdir'); | |
476 | # | |
477 | ||
478 | sub tempFile | |
479 | { | |
480 | my $dir = shift; | |
481 | my ( $fh, $filename ) = File::Temp::tempfile( | |
482 | SUFFIX => '.zip', | |
483 | UNLINK => 0, # we will delete it! | |
484 | $dir ? ( DIR => $dir ) : () | |
485 | ); | |
486 | return ( undef, undef ) unless $fh; | |
487 | my ( $status, $newfh ) = _newFileHandle( $fh, 'w+' ); | |
488 | return ( $newfh, $filename ); | |
489 | } | |
490 | ||
491 | # Return the normalized directory name as used in a zip file (path | |
492 | # separators become slashes, etc.). | |
493 | # Will translate internal slashes in path components (i.e. on Macs) to | |
494 | # underscores. Discards volume names. | |
495 | # When $forceDir is set, returns paths with trailing slashes (or arrays | |
496 | # with trailing blank members). | |
497 | # | |
498 | # If third argument is a reference, returns volume information there. | |
499 | # | |
500 | # input output | |
501 | # . ('.') '.' | |
502 | # ./a ('a') a | |
503 | # ./a/b ('a','b') a/b | |
504 | # ./a/b/ ('a','b') a/b | |
505 | # a/b/ ('a','b') a/b | |
506 | # /a/b/ ('','a','b') /a/b | |
507 | # c:\a\b\c.doc ('','a','b','c.doc') /a/b/c.doc # on Windoze | |
508 | # "i/o maps:whatever" ('i_o maps', 'whatever') "i_o maps/whatever" # on Macs | |
509 | sub _asZipDirName | |
510 | { | |
511 | my $name = shift; | |
512 | my $forceDir = shift; | |
513 | my $volReturn = shift; | |
514 | my ( $volume, $directories, $file ) = | |
515 | File::Spec->splitpath( File::Spec->canonpath($name), $forceDir ); | |
516 | $$volReturn = $volume if ( ref($volReturn) ); | |
517 | my @dirs = map { $_ =~ s{/}{_}g; $_ } File::Spec->splitdir($directories); | |
518 | if ( @dirs > 0 ) { pop (@dirs) unless $dirs[-1] } # remove empty component | |
519 | push ( @dirs, $file || '' ); | |
520 | #return wantarray ? @dirs : join ( '/', @dirs ); | |
521 | return join ( '/', @dirs ); | |
522 | } | |
523 | ||
524 | # Return an absolute local name for a zip name. | |
525 | # Assume a directory if zip name has trailing slash. | |
526 | # Takes an optional volume name in FS format (like 'a:'). | |
527 | # | |
528 | sub _asLocalName | |
529 | { | |
530 | my $name = shift; # zip format | |
531 | my $volume = shift; | |
532 | $volume = '' unless defined($volume); # local FS format | |
533 | ||
534 | my @paths = split ( /\//, $name ); | |
535 | my $filename = pop (@paths); | |
536 | $filename = '' unless defined($filename); | |
537 | my $localDirs = @paths?File::Spec->catdir(@paths):''; | |
538 | my $localName = File::Spec->catpath( $volume, $localDirs, $filename ); | |
539 | $localName = File::Spec->rel2abs($localName) unless $volume; | |
540 | return $localName; | |
541 | } | |
542 | ||
543 | 1; | |
544 | ||
545 | __END__ | |
546 | ||
547 | =pod | |
548 | ||
549 | =head1 NAME | |
550 | ||
551 | Archive::Zip - Provide an interface to ZIP archive files. | |
552 | ||
553 | =head1 SYNOPSIS | |
554 | ||
555 | # Create a Zip file | |
556 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); | |
557 | my $zip = Archive::Zip->new(); | |
558 | ||
559 | # Add a directory | |
560 | my $dir_member = $zip->addDirectory( 'dirname/' ); | |
561 | ||
562 | # Add a file from a string with compression | |
563 | my $string_member = $zip->addString( 'This is a test', 'stringMember.txt' ); | |
564 | $string_member->desiredCompressionMethod( COMPRESSION_DEFLATED ); | |
565 | ||
566 | # Add a file from disk | |
567 | my $file_member = $zip->addFile( 'xyz.pl', 'AnotherName.pl' ); | |
568 | ||
569 | # Save the Zip file | |
570 | unless ( $zip->writeToFileNamed('someZip.zip') == AZ_OK ) { | |
571 | die 'write error'; | |
572 | } | |
573 | ||
574 | # Read a Zip file | |
575 | my $somezip = Archive::Zip->new(); | |
576 | unless ( $somezip->read( 'someZip.zip' ) == AZ_OK ) { | |
577 | die 'read error'; | |
578 | } | |
579 | ||
580 | # Change the compression type for a file in the Zip | |
581 | my $member = $somezip->memberNamed( 'stringMember.txt' ); | |
582 | $member->desiredCompressionMethod( COMPRESSION_STORED ); | |
583 | unless ( $zip->writeToFileNamed( 'someOtherZip.zip' ) == AZ_OK ) { | |
584 | die 'write error'; | |
585 | } | |
586 | ||
587 | =head1 DESCRIPTION | |
588 | ||
589 | The Archive::Zip module allows a Perl program to create, manipulate, read, | |
590 | and write Zip archive files. | |
591 | ||
592 | Zip archives can be created, or you can read from existing zip files. | |
593 | ||
594 | Once created, they can be written to files, streams, or strings. Members | |
595 | can be added, removed, extracted, replaced, rearranged, and enumerated. | |
596 | They can also be renamed or have their dates, comments, or other attributes | |
597 | queried or modified. Their data can be compressed or uncompressed as needed. | |
598 | ||
599 | Members can be created from members in existing Zip files, or from existing | |
600 | directories, files, or strings. | |
601 | ||
602 | This module uses the L<Compress::Zlib> library to read and write the | |
603 | compressed streams inside the files. | |
604 | ||
605 | =head2 File Naming | |
606 | ||
607 | Regardless of what your local file system uses for file naming, names in a | |
608 | Zip file are in Unix format (I<forward> slashes (/) separating directory | |
609 | names, etc.). | |
610 | ||
611 | C<Archive::Zip> tries to be consistent with file naming conventions, and will | |
612 | translate back and forth between native and Zip file names. | |
613 | ||
614 | However, it can't guess which format names are in. So two rules control what | |
615 | kind of file name you must pass various routines: | |
616 | ||
617 | =over 4 | |
618 | ||
619 | =item Names of files are in local format. | |
620 | ||
621 | C<File::Spec> and C<File::Basename> are used for various file | |
622 | operations. When you're referring to a file on your system, use its | |
623 | file naming conventions. | |
624 | ||
625 | =item Names of archive members are in Unix format. | |
626 | ||
627 | This applies to every method that refers to an archive member, or | |
628 | provides a name for new archive members. The C<extract()> methods | |
629 | that can take one or two names will convert from local to zip names | |
630 | if you call them with a single name. | |
631 | ||
632 | =back | |
633 | ||
634 | =head2 Archive::Zip Object Model | |
635 | ||
636 | =head2 Overview | |
637 | ||
638 | Archive::Zip::Archive objects are what you ordinarily deal with. | |
639 | These maintain the structure of a zip file, without necessarily | |
640 | holding data. When a zip is read from a disk file, the (possibly | |
641 | compressed) data still lives in the file, not in memory. Archive | |
642 | members hold information about the individual members, but not | |
643 | (usually) the actual member data. When the zip is written to a | |
644 | (different) file, the member data is compressed or copied as needed. | |
645 | It is possible to make archive members whose data is held in a string | |
646 | in memory, but this is not done when a zip file is read. Directory | |
647 | members don't have any data. | |
648 | ||
649 | =head2 Inheritance | |
650 | ||
651 | Exporter | |
652 | Archive::Zip Common base class, has defs. | |
653 | Archive::Zip::Archive A Zip archive. | |
654 | Archive::Zip::Member Abstract superclass for all members. | |
655 | Archive::Zip::StringMember Member made from a string | |
656 | Archive::Zip::FileMember Member made from an external file | |
657 | Archive::Zip::ZipFileMember Member that lives in a zip file | |
658 | Archive::Zip::NewFileMember Member whose data is in a file | |
659 | Archive::Zip::DirectoryMember Member that is a directory | |
660 | ||
661 | =head1 EXPORTS | |
662 | ||
663 | =over 4 | |
664 | ||
665 | =item :CONSTANTS | |
666 | ||
667 | Exports the following constants: | |
668 | ||
669 | FA_MSDOS FA_UNIX GPBF_ENCRYPTED_MASK | |
670 | GPBF_DEFLATING_COMPRESSION_MASK GPBF_HAS_DATA_DESCRIPTOR_MASK | |
671 | COMPRESSION_STORED COMPRESSION_DEFLATED IFA_TEXT_FILE_MASK | |
672 | IFA_TEXT_FILE IFA_BINARY_FILE COMPRESSION_LEVEL_NONE | |
673 | COMPRESSION_LEVEL_DEFAULT COMPRESSION_LEVEL_FASTEST | |
674 | COMPRESSION_LEVEL_BEST_COMPRESSION | |
675 | ||
676 | =item :MISC_CONSTANTS | |
677 | ||
678 | Exports the following constants (only necessary for extending the | |
679 | module): | |
680 | ||
681 | FA_AMIGA FA_VAX_VMS FA_VM_CMS FA_ATARI_ST FA_OS2_HPFS | |
682 | FA_MACINTOSH FA_Z_SYSTEM FA_CPM FA_WINDOWS_NTFS | |
683 | GPBF_IMPLODING_8K_SLIDING_DICTIONARY_MASK | |
684 | GPBF_IMPLODING_3_SHANNON_FANO_TREES_MASK | |
685 | GPBF_IS_COMPRESSED_PATCHED_DATA_MASK COMPRESSION_SHRUNK | |
686 | DEFLATING_COMPRESSION_NORMAL DEFLATING_COMPRESSION_MAXIMUM | |
687 | DEFLATING_COMPRESSION_FAST DEFLATING_COMPRESSION_SUPER_FAST | |
688 | COMPRESSION_REDUCED_1 COMPRESSION_REDUCED_2 COMPRESSION_REDUCED_3 | |
689 | COMPRESSION_REDUCED_4 COMPRESSION_IMPLODED COMPRESSION_TOKENIZED | |
690 | COMPRESSION_DEFLATED_ENHANCED | |
691 | COMPRESSION_PKWARE_DATA_COMPRESSION_LIBRARY_IMPLODED | |
692 | ||
693 | =item :ERROR_CODES | |
694 | ||
695 | Explained below. Returned from most methods. | |
696 | ||
697 | AZ_OK AZ_STREAM_END AZ_ERROR AZ_FORMAT_ERROR AZ_IO_ERROR | |
698 | ||
699 | =back | |
700 | ||
701 | =head1 ERROR CODES | |
702 | ||
703 | Many of the methods in Archive::Zip return error codes. These are implemented | |
704 | as inline subroutines, using the C<use constant> pragma. They can be imported | |
705 | into your namespace using the C<:ERROR_CODES> tag: | |
706 | ||
707 | use Archive::Zip qw( :ERROR_CODES ); | |
708 | ||
709 | ... | |
710 | ||
711 | unless ( $zip->read( 'myfile.zip' ) == AZ_OK ) { | |
712 | die "whoops!"; | |
713 | } | |
714 | ||
715 | =over 4 | |
716 | ||
717 | =item AZ_OK (0) | |
718 | ||
719 | Everything is fine. | |
720 | ||
721 | =item AZ_STREAM_END (1) | |
722 | ||
723 | The read stream (or central directory) ended normally. | |
724 | ||
725 | =item AZ_ERROR (2) | |
726 | ||
727 | There was some generic kind of error. | |
728 | ||
729 | =item AZ_FORMAT_ERROR (3) | |
730 | ||
731 | There is a format error in a ZIP file being read. | |
732 | ||
733 | =item AZ_IO_ERROR (4) | |
734 | ||
735 | There was an IO error. | |
736 | ||
737 | =back | |
738 | ||
739 | =head2 Compression | |
740 | ||
741 | Archive::Zip allows each member of a ZIP file to be compressed (using the | |
742 | Deflate algorithm) or uncompressed. | |
743 | ||
744 | Other compression algorithms that some versions of ZIP have been able to | |
745 | produce are not supported. Each member has two compression methods: the | |
746 | one it's stored as (this is always COMPRESSION_STORED for string and external | |
747 | file members), and the one you desire for the member in the zip file. | |
748 | ||
749 | These can be different, of course, so you can make a zip member that is not | |
750 | compressed out of one that is, and vice versa. | |
751 | ||
752 | You can inquire about the current compression and set the desired | |
753 | compression method: | |
754 | ||
755 | my $member = $zip->memberNamed( 'xyz.txt' ); | |
756 | $member->compressionMethod(); # return current compression | |
757 | ||
758 | # set to read uncompressed | |
759 | $member->desiredCompressionMethod( COMPRESSION_STORED ); | |
760 | ||
761 | # set to read compressed | |
762 | $member->desiredCompressionMethod( COMPRESSION_DEFLATED ); | |
763 | ||
764 | There are two different compression methods: | |
765 | ||
766 | =over 4 | |
767 | ||
768 | =item COMPRESSION_STORED | |
769 | ||
770 | File is stored (no compression) | |
771 | ||
772 | =item COMPRESSION_DEFLATED | |
773 | ||
774 | File is Deflated | |
775 | ||
776 | =back | |
777 | ||
778 | =head2 Compression Levels | |
779 | ||
780 | If a member's desiredCompressionMethod is COMPRESSION_DEFLATED, you | |
781 | can choose different compression levels. This choice may affect the | |
782 | speed of compression and decompression, as well as the size of the | |
783 | compressed member data. | |
784 | ||
785 | $member->desiredCompressionLevel( 9 ); | |
786 | ||
787 | The levels given can be: | |
788 | ||
789 | =over 4 | |
790 | ||
791 | =item 0 or COMPRESSION_LEVEL_NONE | |
792 | ||
793 | This is the same as saying | |
794 | ||
795 | $member->desiredCompressionMethod( COMPRESSION_STORED ); | |
796 | ||
797 | =item 1 .. 9 | |
798 | ||
799 | 1 gives the best speed and worst compression, and 9 gives the | |
800 | best compression and worst speed. | |
801 | ||
802 | =item COMPRESSION_LEVEL_FASTEST | |
803 | ||
804 | This is a synonym for level 1. | |
805 | ||
806 | =item COMPRESSION_LEVEL_BEST_COMPRESSION | |
807 | ||
808 | This is a synonym for level 9. | |
809 | ||
810 | =item COMPRESSION_LEVEL_DEFAULT | |
811 | ||
812 | This gives a good compromise between speed and compression, | |
813 | and is currently equivalent to 6 (this is in the zlib code). | |
814 | This is the level that will be used if not specified. | |
815 | ||
816 | =back | |
817 | ||
818 | =head1 Archive::Zip Methods | |
819 | ||
820 | The Archive::Zip class (and its invisible subclass Archive::Zip::Archive) | |
821 | implement generic zip file functionality. Creating a new Archive::Zip object | |
822 | actually makes an Archive::Zip::Archive object, but you don't have to worry | |
823 | about this unless you're subclassing. | |
824 | ||
825 | =head2 Constructor | |
826 | ||
827 | =over 4 | |
828 | ||
829 | =item new( [$fileName] ) | |
830 | ||
831 | Make a new, empty zip archive. | |
832 | ||
833 | my $zip = Archive::Zip->new(); | |
834 | ||
835 | If an additional argument is passed, new() will call read() | |
836 | to read the contents of an archive: | |
837 | ||
838 | my $zip = Archive::Zip->new( 'xyz.zip' ); | |
839 | ||
840 | If a filename argument is passed and the read fails for any | |
841 | reason, new will return undef. For this reason, it may be | |
842 | better to call read separately. | |
843 | ||
844 | =back | |
845 | ||
846 | =head2 Zip Archive Utility Methods | |
847 | ||
848 | These Archive::Zip methods may be called as functions or as object | |
849 | methods. Do not call them as class methods: | |
850 | ||
851 | $zip = Archive::Zip->new(); | |
852 | $crc = Archive::Zip::computeCRC32( 'ghijkl' ); # OK | |
853 | $crc = $zip->computeCRC32( 'ghijkl' ); # also OK | |
854 | $crc = Archive::Zip->computeCRC32( 'ghijkl' ); # NOT OK | |
855 | ||
856 | =over 4 | |
857 | ||
858 | =item Archive::Zip::computeCRC32( $string [, $crc] ) | |
859 | ||
860 | This is a utility function that uses the Compress::Zlib CRC | |
861 | routine to compute a CRC-32. You can get the CRC of a string: | |
862 | ||
863 | $crc = Archive::Zip::computeCRC32( $string ); | |
864 | ||
865 | Or you can compute the running CRC: | |
866 | ||
867 | $crc = 0; | |
868 | $crc = Archive::Zip::computeCRC32( 'abcdef', $crc ); | |
869 | $crc = Archive::Zip::computeCRC32( 'ghijkl', $crc ); | |
870 | ||
871 | =item Archive::Zip::setChunkSize( $number ) | |
872 | ||
873 | Report or change chunk size used for reading and writing. | |
874 | This can make big differences in dealing with large files. | |
875 | Currently, this defaults to 32K. This also changes the chunk | |
876 | size used for Compress::Zlib. You must call setChunkSize() | |
877 | before reading or writing. This is not exportable, so you | |
878 | must call it like: | |
879 | ||
880 | Archive::Zip::setChunkSize( 4096 ); | |
881 | ||
882 | or as a method on a zip (though this is a global setting). | |
883 | Returns old chunk size. | |
884 | ||
885 | =item Archive::Zip::chunkSize() | |
886 | ||
887 | Returns the current chunk size: | |
888 | ||
889 | my $chunkSize = Archive::Zip::chunkSize(); | |
890 | ||
891 | =item Archive::Zip::setErrorHandler( \&subroutine ) | |
892 | ||
893 | Change the subroutine called with error strings. This | |
894 | defaults to \&Carp::carp, but you may want to change it to | |
895 | get the error strings. This is not exportable, so you must | |
896 | call it like: | |
897 | ||
898 | Archive::Zip::setErrorHandler( \&myErrorHandler ); | |
899 | ||
900 | If myErrorHandler is undef, resets handler to default. | |
901 | Returns old error handler. Note that if you call Carp::carp | |
902 | or a similar routine or if you're chaining to the default | |
903 | error handler from your error handler, you may want to | |
904 | increment the number of caller levels that are skipped (do | |
905 | not just set it to a number): | |
906 | ||
907 | $Carp::CarpLevel++; | |
908 | ||
909 | =item Archive::Zip::tempFile( [$tmpdir] ) | |
910 | ||
911 | Create a uniquely named temp file. It will be returned open | |
912 | for read/write. If C<$tmpdir> is given, it is used as the | |
913 | name of a directory to create the file in. If not given, | |
914 | creates the file using C<File::Spec::tmpdir()>. Generally, you can | |
915 | override this choice using the | |
916 | ||
917 | $ENV{TMPDIR} | |
918 | ||
919 | environment variable. But see the L<File::Spec|File::Spec> | |
920 | documentation for your system. Note that on many systems, if you're | |
921 | running in taint mode, then you must make sure that C<$ENV{TMPDIR}> is | |
922 | untainted for it to be used. | |
923 | Will I<NOT> create C<$tmpdir> if it doesn't exist (this is a change | |
924 | from prior versions!). Returns file handle and name: | |
925 | ||
926 | my ($fh, $name) = Archive::Zip::tempFile(); | |
927 | my ($fh, $name) = Archive::Zip::tempFile('myTempDir'); | |
928 | my $fh = Archive::Zip::tempFile(); # if you don't need the name | |
929 | ||
930 | =back | |
931 | ||
932 | =head2 Zip Archive Accessors | |
933 | ||
934 | =over 4 | |
935 | ||
936 | =item members() | |
937 | ||
938 | Return a copy of the members array | |
939 | ||
940 | my @members = $zip->members(); | |
941 | ||
942 | =item numberOfMembers() | |
943 | ||
944 | Return the number of members I have | |
945 | ||
946 | =item memberNames() | |
947 | ||
948 | Return a list of the (internal) file names of the zip members | |
949 | ||
950 | =item memberNamed( $string ) | |
951 | ||
952 | Return ref to member whose filename equals given filename or | |
953 | undef. C<$string> must be in Zip (Unix) filename format. | |
954 | ||
955 | =item membersMatching( $regex ) | |
956 | ||
957 | Return array of members whose filenames match given regular | |
958 | expression in list context. Returns number of matching | |
959 | members in scalar context. | |
960 | ||
961 | my @textFileMembers = $zip->membersMatching( '.*\.txt' ); | |
962 | # or | |
963 | my $numberOfTextFiles = $zip->membersMatching( '.*\.txt' ); | |
964 | ||
965 | =item diskNumber() | |
966 | ||
967 | Return the disk that I start on. Not used for writing zips, | |
968 | but might be interesting if you read a zip in. This should be | |
969 | 0, as Archive::Zip does not handle multi-volume archives. | |
970 | ||
971 | =item diskNumberWithStartOfCentralDirectory() | |
972 | ||
973 | Return the disk number that holds the beginning of the | |
974 | central directory. Not used for writing zips, but might be | |
975 | interesting if you read a zip in. This should be 0, as | |
976 | Archive::Zip does not handle multi-volume archives. | |
977 | ||
978 | =item numberOfCentralDirectoriesOnThisDisk() | |
979 | ||
980 | Return the number of CD structures in the zipfile last read in. | |
981 | Not used for writing zips, but might be interesting if you read a zip | |
982 | in. | |
983 | ||
984 | =item numberOfCentralDirectories() | |
985 | ||
986 | Return the number of CD structures in the zipfile last read in. | |
987 | Not used for writing zips, but might be interesting if you read a zip | |
988 | in. | |
989 | ||
990 | =item centralDirectorySize() | |
991 | ||
992 | Returns central directory size, as read from an external zip | |
993 | file. Not used for writing zips, but might be interesting if | |
994 | you read a zip in. | |
995 | ||
996 | =item centralDirectoryOffsetWRTStartingDiskNumber() | |
997 | ||
998 | Returns the offset into the zip file where the CD begins. Not | |
999 | used for writing zips, but might be interesting if you read a | |
1000 | zip in. | |
1001 | ||
1002 | =item zipfileComment( [$string] ) | |
1003 | ||
1004 | Get or set the zipfile comment. Returns the old comment. | |
1005 | ||
1006 | print $zip->zipfileComment(); | |
1007 | $zip->zipfileComment( 'New Comment' ); | |
1008 | ||
1009 | =item eocdOffset() | |
1010 | ||
1011 | Returns the (unexpected) number of bytes between where the | |
1012 | EOCD was found and where it expected to be. This is normally | |
1013 | 0, but would be positive if something (a virus, perhaps) had | |
1014 | added bytes somewhere before the EOCD. Not used for writing | |
1015 | zips, but might be interesting if you read a zip in. Here is | |
1016 | an example of how you can diagnose this: | |
1017 | ||
1018 | my $zip = Archive::Zip->new('somefile.zip'); | |
1019 | if ($zip->eocdOffset()) | |
1020 | { | |
1021 | warn "A virus has added ", $zip->eocdOffset, " bytes of garbage\n"; | |
1022 | } | |
1023 | ||
1024 | The C<eocdOffset()> is used to adjust the starting position of member | |
1025 | headers, if necessary. | |
1026 | ||
1027 | =item fileName() | |
1028 | ||
1029 | Returns the name of the file last read from. If nothing has | |
1030 | been read yet, returns an empty string; if read from a file | |
1031 | handle, returns the handle in string form. | |
1032 | ||
1033 | =back | |
1034 | ||
1035 | =head2 Zip Archive Member Operations | |
1036 | ||
1037 | Various operations on a zip file modify members. When a member is | |
1038 | passed as an argument, you can either use a reference to the member | |
1039 | itself, or the name of a member. Of course, using the name requires | |
1040 | that names be unique within a zip (this is not enforced). | |
1041 | ||
1042 | =over 4 | |
1043 | ||
1044 | =item removeMember( $memberOrName ) | |
1045 | ||
1046 | Remove and return the given member, or match its name and | |
1047 | remove it. Returns undef if member or name doesn't exist in this | |
1048 | Zip. No-op if member does not belong to this zip. | |
1049 | ||
1050 | =item replaceMember( $memberOrName, $newMember ) | |
1051 | ||
1052 | Remove and return the given member, or match its name and | |
1053 | remove it. Replace with new member. Returns undef if member or | |
1054 | name doesn't exist in this Zip, or if C<$newMember> is undefined. | |
1055 | ||
1056 | It is an (undiagnosed) error to provide a C<$newMember> that is a | |
1057 | member of the zip being modified. | |
1058 | ||
1059 | my $member1 = $zip->removeMember( 'xyz' ); | |
1060 | my $member2 = $zip->replaceMember( 'abc', $member1 ); | |
1061 | # now, $member2 (named 'abc') is not in $zip, | |
1062 | # and $member1 (named 'xyz') is, having taken $member2's place. | |
1063 | ||
1064 | =item extractMember( $memberOrName [, $extractedName ] ) | |
1065 | ||
1066 | Extract the given member, or match its name and extract it. | |
1067 | Returns undef if member doesn't exist in this Zip. If | |
1068 | optional second arg is given, use it as the name of the | |
1069 | extracted member. Otherwise, the internal filename of the | |
1070 | member is used as the name of the extracted file or | |
1071 | directory. | |
1072 | If you pass C<$extractedName>, it should be in the local file | |
1073 | system's format. | |
1074 | All necessary directories will be created. Returns C<AZ_OK> | |
1075 | on success. | |
1076 | ||
1077 | =item extractMemberWithoutPaths( $memberOrName [, $extractedName ] ) | |
1078 | ||
1079 | Extract the given member, or match its name and extract it. | |
1080 | Does not use path information (extracts into the current | |
1081 | directory). Returns undef if member doesn't exist in this | |
1082 | Zip. | |
1083 | If optional second arg is given, use it as the name of the | |
1084 | extracted member (its paths will be deleted too). Otherwise, | |
1085 | the internal filename of the member (minus paths) is used as | |
1086 | the name of the extracted file or directory. Returns C<AZ_OK> | |
1087 | on success. | |
1088 | ||
1089 | =item addMember( $member ) | |
1090 | ||
1091 | Append a member (possibly from another zip file) to the zip | |
1092 | file. Returns the new member. Generally, you will use | |
1093 | addFile(), addDirectory(), addFileOrDirectory(), addString(), | |
1094 | or read() to add members. | |
1095 | ||
1096 | # Move member named 'abc' to end of zip: | |
1097 | my $member = $zip->removeMember( 'abc' ); | |
1098 | $zip->addMember( $member ); | |
1099 | ||
1100 | =item updateMember( $memberOrName, $fileName ) | |
1101 | ||
1102 | Update a single member from the file or directory named C<$fileName>. | |
1103 | Returns the (possibly added or updated) member, if any; C<undef> on | |
1104 | errors. | |
1105 | The comparison is based on C<lastModTime()> and (in the case of a | |
1106 | non-directory) the size of the file. | |
1107 | ||
1108 | =item addFile( $fileName [, $newName ] ) | |
1109 | ||
1110 | Append a member whose data comes from an external file, | |
1111 | returning the member or undef. The member will have its file | |
1112 | name set to the name of the external file, and its | |
1113 | desiredCompressionMethod set to COMPRESSION_DEFLATED. The | |
1114 | file attributes and last modification time will be set from | |
1115 | the file. | |
1116 | If the name given does not represent a readable plain file or | |
1117 | symbolic link, undef will be returned. C<$fileName> must be | |
1118 | in the format required for the local file system. | |
1119 | The optional C<$newName> argument sets the internal file name | |
1120 | to something different than the given $fileName. C<$newName>, | |
1121 | if given, must be in Zip name format (i.e. Unix). | |
1122 | The text mode bit will be set if the contents appears to be | |
1123 | text (as returned by the C<-T> perl operator). | |
1124 | ||
1125 | ||
1126 | I<NOTE> that you shouldn't (generally) use absolute path names | |
1127 | in zip member names, as this will cause problems with some zip | |
1128 | tools as well as introduce a security hole and make the zip | |
1129 | harder to use. | |
1130 | ||
1131 | =item addDirectory( $directoryName [, $fileName ] ) | |
1132 | ||
1133 | ||
1134 | ||
1135 | Append a member created from the given directory name. The | |
1136 | directory name does not have to name an existing directory. | |
1137 | If the named directory exists, the file modification time and | |
1138 | permissions are set from the existing directory, otherwise | |
1139 | they are set to now and permissive default permissions. | |
1140 | C<$directoryName> must be in local file system format. | |
1141 | The optional second argument sets the name of the archive | |
1142 | member (which defaults to C<$directoryName>). If given, it | |
1143 | must be in Zip (Unix) format. | |
1144 | Returns the new member. | |
1145 | ||
1146 | =item addFileOrDirectory( $name [, $newName ] ) | |
1147 | ||
1148 | ||
1149 | ||
1150 | Append a member from the file or directory named $name. If | |
1151 | $newName is given, use it for the name of the new member. | |
1152 | Will add or remove trailing slashes from $newName as needed. | |
1153 | C<$name> must be in local file system format. | |
1154 | The optional second argument sets the name of the archive | |
1155 | member (which defaults to C<$name>). If given, it must be in | |
1156 | Zip (Unix) format. | |
1157 | ||
1158 | =item addString( $stringOrStringRef, $name ) | |
1159 | ||
1160 | ||
1161 | ||
1162 | Append a member created from the given string or string | |
1163 | reference. The name is given by the second argument. | |
1164 | Returns the new member. The last modification time will be | |
1165 | set to now, and the file attributes will be set to permissive | |
1166 | defaults. | |
1167 | ||
1168 | my $member = $zip->addString( 'This is a test', 'test.txt' ); | |
1169 | ||
1170 | =item contents( $memberOrMemberName [, $newContents ] ) | |
1171 | ||
1172 | ||
1173 | ||
1174 | Returns the uncompressed data for a particular member, or | |
1175 | undef. | |
1176 | ||
1177 | print "xyz.txt contains " . $zip->contents( 'xyz.txt' ); | |
1178 | ||
1179 | Also can change the contents of a member: | |
1180 | ||
1181 | $zip->contents( 'xyz.txt', 'This is the new contents' ); | |
1182 | ||
1183 | If called expecting an array as the return value, it will include | |
1184 | the status as the second value in the array. | |
1185 | ||
1186 | ($content, $status) = $zip->contents( 'xyz.txt'); | |
1187 | ||
1188 | =back | |
1189 | ||
1190 | =head2 Zip Archive I/O operations | |
1191 | ||
1192 | ||
1193 | A Zip archive can be written to a file or file handle, or read from | |
1194 | one. | |
1195 | ||
1196 | =over 4 | |
1197 | ||
1198 | =item writeToFileNamed( $fileName ) | |
1199 | ||
1200 | ||
1201 | ||
1202 | Write a zip archive to named file. Returns C<AZ_OK> on | |
1203 | success. | |
1204 | ||
1205 | my $status = $zip->writeToFileNamed( 'xx.zip' ); | |
1206 | die "error somewhere" if $status != AZ_OK; | |
1207 | ||
1208 | Note that if you use the same name as an existing zip file | |
1209 | that you read in, you will clobber ZipFileMembers. So | |
1210 | instead, write to a different file name, then delete the | |
1211 | original. | |
1212 | If you use the C<overwrite()> or C<overwriteAs()> methods, you can | |
1213 | re-write the original zip in this way. | |
1214 | C<$fileName> should be a valid file name on your system. | |
1215 | ||
1216 | =item writeToFileHandle( $fileHandle [, $seekable] ) | |
1217 | ||
1218 | Write a zip archive to a file handle. Return AZ_OK on | |
1219 | success. The optional second arg tells whether or not to try | |
1220 | to seek backwards to re-write headers. If not provided, it is | |
1221 | set if the Perl C<-f> test returns true. This could fail on | |
1222 | some operating systems, though. | |
1223 | ||
1224 | my $fh = IO::File->new( 'someFile.zip', 'w' ); | |
1225 | unless ( $zip->writeToFileHandle( $fh ) != AZ_OK ) { | |
1226 | # error handling | |
1227 | } | |
1228 | ||
1229 | If you pass a file handle that is not seekable (like if | |
1230 | you're writing to a pipe or a socket), pass a false second | |
1231 | argument: | |
1232 | ||
1233 | my $fh = IO::File->new( '| cat > somefile.zip', 'w' ); | |
1234 | $zip->writeToFileHandle( $fh, 0 ); # fh is not seekable | |
1235 | ||
1236 | If this method fails during the write of a member, that | |
1237 | member and all following it will return false from | |
1238 | C<wasWritten()>. See writeCentralDirectory() for a way to | |
1239 | deal with this. | |
1240 | If you want, you can write data to the file handle before | |
1241 | passing it to writeToFileHandle(); this could be used (for | |
1242 | instance) for making self-extracting archives. However, this | |
1243 | only works reliably when writing to a real file (as opposed | |
1244 | to STDOUT or some other possible non-file). | |
1245 | ||
1246 | See examples/selfex.pl for how to write a self-extracting | |
1247 | archive. | |
1248 | ||
1249 | =item writeCentralDirectory( $fileHandle [, $offset ] ) | |
1250 | ||
1251 | Writes the central directory structure to the given file | |
1252 | handle. | |
1253 | ||
1254 | Returns AZ_OK on success. If given an $offset, will | |
1255 | seek to that point before writing. This can be used for | |
1256 | recovery in cases where writeToFileHandle or writeToFileNamed | |
1257 | returns an IO error because of running out of space on the | |
1258 | destination file. | |
1259 | ||
1260 | You can truncate the zip by seeking backwards and then writing the | |
1261 | directory: | |
1262 | ||
1263 | my $fh = IO::File->new( 'someFile.zip', 'w' ); | |
1264 | my $retval = $zip->writeToFileHandle( $fh ); | |
1265 | if ( $retval == AZ_IO_ERROR ) { | |
1266 | my @unwritten = grep { not $_->wasWritten() } $zip->members(); | |
1267 | if (@unwritten) { | |
1268 | $zip->removeMember( $member ) foreach my $member ( @unwritten ); | |
1269 | $zip->writeCentralDirectory( $fh, | |
1270 | $unwritten[0]->writeLocalHeaderRelativeOffset()); | |
1271 | } | |
1272 | } | |
1273 | ||
1274 | =item overwriteAs( $newName ) | |
1275 | ||
1276 | Write the zip to the specified file, as safely as possible. | |
1277 | This is done by first writing to a temp file, then renaming | |
1278 | the original if it exists, then renaming the temp file, then | |
1279 | deleting the renamed original if it exists. Returns AZ_OK if | |
1280 | successful. | |
1281 | ||
1282 | =item overwrite() | |
1283 | ||
1284 | Write back to the original zip file. See overwriteAs() above. | |
1285 | If the zip was not ever read from a file, this generates an | |
1286 | error. | |
1287 | ||
1288 | =item read( $fileName ) | |
1289 | ||
1290 | Read zipfile headers from a zip file, appending new members. | |
1291 | Returns C<AZ_OK> or error code. | |
1292 | ||
1293 | my $zipFile = Archive::Zip->new(); | |
1294 | my $status = $zipFile->read( '/some/FileName.zip' ); | |
1295 | ||
1296 | =item readFromFileHandle( $fileHandle, $filename ) | |
1297 | ||
1298 | Read zipfile headers from an already-opened file handle, | |
1299 | appending new members. Does not close the file handle. | |
1300 | Returns C<AZ_OK> or error code. Note that this requires a | |
1301 | seekable file handle; reading from a stream is not yet | |
1302 | supported. | |
1303 | ||
1304 | my $fh = IO::File->new( '/some/FileName.zip', 'r' ); | |
1305 | my $zip1 = Archive::Zip->new(); | |
1306 | my $status = $zip1->readFromFileHandle( $fh ); | |
1307 | my $zip2 = Archive::Zip->new(); | |
1308 | $status = $zip2->readFromFileHandle( $fh ); | |
1309 | ||
1310 | =back | |
1311 | ||
1312 | =head2 Zip Archive Tree operations | |
1313 | ||
1314 | These used to be in Archive::Zip::Tree but got moved into | |
1315 | Archive::Zip. They enable operation on an entire tree of members or | |
1316 | files. | |
1317 | A usage example: | |
1318 | ||
1319 | use Archive::Zip; | |
1320 | my $zip = Archive::Zip->new(); | |
1321 | ||
1322 | # add all readable files and directories below . as xyz/* | |
1323 | $zip->addTree( '.', 'xyz' ); | |
1324 | ||
1325 | # add all readable plain files below /abc as def/* | |
1326 | $zip->addTree( '/abc', 'def', sub { -f && -r } ); | |
1327 | ||
1328 | # add all .c files below /tmp as stuff/* | |
1329 | $zip->addTreeMatching( '/tmp', 'stuff', '\.c$' ); | |
1330 | ||
1331 | # add all .o files below /tmp as stuff/* if they aren't writable | |
1332 | $zip->addTreeMatching( '/tmp', 'stuff', '\.o$', sub { ! -w } ); | |
1333 | ||
1334 | # add all .so files below /tmp that are smaller than 200 bytes as stuff/* | |
1335 | $zip->addTreeMatching( '/tmp', 'stuff', '\.o$', sub { -s < 200 } ); | |
1336 | ||
1337 | # and write them into a file | |
1338 | $zip->writeToFileNamed('xxx.zip'); | |
1339 | ||
1340 | # now extract the same files into /tmpx | |
1341 | $zip->extractTree( 'stuff', '/tmpx' ); | |
1342 | ||
1343 | =over 4 | |
1344 | ||
1345 | =item $zip->addTree( $root, $dest [,$pred] ) -- Add tree of files to a zip | |
1346 | ||
1347 | C<$root> is the root of the tree of files and directories to be | |
1348 | added. It is a valid directory name on your system. C<$dest> is | |
1349 | the name for the root in the zip file (undef or blank means | |
1350 | to use relative pathnames). It is a valid ZIP directory name | |
1351 | (that is, it uses forward slashes (/) for separating | |
1352 | directory components). C<$pred> is an optional subroutine | |
1353 | reference to select files: it is passed the name of the | |
1354 | prospective file or directory using C<$_>, and if it returns | |
1355 | true, the file or directory will be included. The default is | |
1356 | to add all readable files and directories. For instance, | |
1357 | using | |
1358 | ||
1359 | my $pred = sub { /\.txt/ }; | |
1360 | $zip->addTree( '.', '', $pred ); | |
1361 | ||
1362 | will add all the .txt files in and below the current | |
1363 | directory, using relative names, and making the names | |
1364 | identical in the zipfile: | |
1365 | ||
1366 | original name zip member name | |
1367 | ./xyz xyz | |
1368 | ./a/ a/ | |
1369 | ./a/b a/b | |
1370 | ||
1371 | To translate absolute to relative pathnames, just pass them | |
1372 | in: $zip->addTree( '/c/d', 'a' ); | |
1373 | ||
1374 | original name zip member name | |
1375 | /c/d/xyz a/xyz | |
1376 | /c/d/a/ a/a/ | |
1377 | /c/d/a/b a/a/b | |
1378 | ||
1379 | Returns AZ_OK on success. Note that this will not follow | |
1380 | symbolic links to directories. Note also that this does not | |
1381 | check for the validity of filenames. | |
1382 | ||
1383 | Note that you generally I<don't> want to make zip archive member names | |
1384 | absolute. | |
1385 | ||
1386 | =item $zip->addTreeMatching( $root, $dest, $pattern [,$pred] ) | |
1387 | ||
1388 | $root is the root of the tree of files and directories to be | |
1389 | added $dest is the name for the root in the zip file (undef | |
1390 | means to use relative pathnames) $pattern is a (non-anchored) | |
1391 | regular expression for filenames to match $pred is an | |
1392 | optional subroutine reference to select files: it is passed | |
1393 | the name of the prospective file or directory in C<$_>, and | |
1394 | if it returns true, the file or directory will be included. | |
1395 | The default is to add all readable files and directories. To | |
1396 | add all files in and below the current dirctory whose names | |
1397 | end in C<.pl>, and make them extract into a subdirectory | |
1398 | named C<xyz>, do this: | |
1399 | ||
1400 | $zip->addTreeMatching( '.', 'xyz', '\.pl$' ) | |
1401 | ||
1402 | To add all I<writable> files in and below the dirctory named | |
1403 | C</abc> whose names end in C<.pl>, and make them extract into | |
1404 | a subdirectory named C<xyz>, do this: | |
1405 | ||
1406 | $zip->addTreeMatching( '/abc', 'xyz', '\.pl$', sub { -w } ) | |
1407 | ||
1408 | Returns AZ_OK on success. Note that this will not follow | |
1409 | symbolic links to directories. | |
1410 | ||
1411 | =item $zip->updateTree( $root, [ $dest, [ $pred [, $mirror]]] ); | |
1412 | ||
1413 | ||
1414 | ||
1415 | Update a zip file from a directory tree. | |
1416 | ||
1417 | C<updateTree()> takes the same arguments as C<addTree()>, but first | |
1418 | checks to see whether the file or directory already exists in the zip | |
1419 | file, and whether it has been changed. | |
1420 | ||
1421 | If the fourth argument C<$mirror> is true, then delete all my members | |
1422 | if corresponding files weren't found. | |
1423 | ||
1424 | ||
1425 | Returns an error code or AZ_OK if all is well. | |
1426 | ||
1427 | =item $zip->extractTree() | |
1428 | ||
1429 | ||
1430 | ||
1431 | =item $zip->extractTree( $root ) | |
1432 | ||
1433 | ||
1434 | ||
1435 | =item $zip->extractTree( $root, $dest ) | |
1436 | ||
1437 | ||
1438 | ||
1439 | =item $zip->extractTree( $root, $dest, $volume ) | |
1440 | ||
1441 | ||
1442 | ||
1443 | If you don't give any arguments at all, will extract all the | |
1444 | files in the zip with their original names. | |
1445 | ||
1446 | ||
1447 | If you supply one argument for C<$root>, C<extractTree> will extract | |
1448 | all the members whose names start with C<$root> into the current | |
1449 | directory, stripping off C<$root> first. | |
1450 | C<$root> is in Zip (Unix) format. | |
1451 | For instance, | |
1452 | ||
1453 | $zip->extractTree( 'a' ); | |
1454 | ||
1455 | when applied to a zip containing the files: | |
1456 | a/x a/b/c ax/d/e d/e will extract: | |
1457 | ||
1458 | ||
1459 | a/x as ./x | |
1460 | ||
1461 | ||
1462 | a/b/c as ./b/c | |
1463 | ||
1464 | ||
1465 | If you give two arguments, C<extractTree> extracts all the members | |
1466 | whose names start with C<$root>. It will translate C<$root> into | |
1467 | C<$dest> to construct the destination file name. | |
1468 | C<$root> and C<$dest> are in Zip (Unix) format. | |
1469 | For instance, | |
1470 | ||
1471 | $zip->extractTree( 'a', 'd/e' ); | |
1472 | ||
1473 | when applied to a zip containing the files: | |
1474 | a/x a/b/c ax/d/e d/e will extract: | |
1475 | ||
1476 | ||
1477 | a/x to d/e/x | |
1478 | ||
1479 | ||
1480 | a/b/c to d/e/b/c and ignore ax/d/e and d/e | |
1481 | ||
1482 | ||
1483 | If you give three arguments, C<extractTree> extracts all the members | |
1484 | whose names start with C<$root>. It will translate C<$root> into | |
1485 | C<$dest> to construct the destination file name, and then it will | |
1486 | convert to local file system format, using C<$volume> as the name of | |
1487 | the destination volume. | |
1488 | ||
1489 | ||
1490 | C<$root> and C<$dest> are in Zip (Unix) format. | |
1491 | ||
1492 | ||
1493 | C<$volume> is in local file system format. | |
1494 | ||
1495 | ||
1496 | For instance, under Windows, | |
1497 | ||
1498 | $zip->extractTree( 'a', 'd/e', 'f:' ); | |
1499 | ||
1500 | when applied to a zip containing the files: | |
1501 | a/x a/b/c ax/d/e d/e will extract: | |
1502 | ||
1503 | ||
1504 | a/x to f:d/e/x | |
1505 | ||
1506 | ||
1507 | a/b/c to f:d/e/b/c and ignore ax/d/e and d/e | |
1508 | ||
1509 | ||
1510 | If you want absolute paths (the prior example used paths relative to | |
1511 | the current directory on the destination volume, you can specify these | |
1512 | in C<$dest>: | |
1513 | ||
1514 | $zip->extractTree( 'a', '/d/e', 'f:' ); | |
1515 | ||
1516 | when applied to a zip containing the files: | |
1517 | a/x a/b/c ax/d/e d/e will extract: | |
1518 | ||
1519 | ||
1520 | a/x to f:\d\e\x | |
1521 | ||
1522 | ||
1523 | a/b/c to f:\d\e\b\c and ignore ax/d/e and d/e | |
1524 | ||
1525 | Returns an error code or AZ_OK if everything worked OK. | |
1526 | ||
1527 | =back | |
1528 | ||
1529 | =head1 MEMBER OPERATIONS | |
1530 | ||
1531 | ||
1532 | =head2 Member Class Methods | |
1533 | ||
1534 | ||
1535 | Several constructors allow you to construct members without adding | |
1536 | them to a zip archive. These work the same as the addFile(), | |
1537 | addDirectory(), and addString() zip instance methods described above, | |
1538 | but they don't add the new members to a zip. | |
1539 | ||
1540 | =over 4 | |
1541 | ||
1542 | =item Archive::Zip::Member->newFromString( $stringOrStringRef [, $fileName] ) | |
1543 | ||
1544 | ||
1545 | ||
1546 | Construct a new member from the given string. Returns undef | |
1547 | on error. | |
1548 | ||
1549 | my $member = Archive::Zip::Member->newFromString( 'This is a test', | |
1550 | 'xyz.txt' ); | |
1551 | ||
1552 | =item newFromFile( $fileName ) | |
1553 | ||
1554 | ||
1555 | ||
1556 | Construct a new member from the given file. Returns undef on | |
1557 | error. | |
1558 | ||
1559 | my $member = Archive::Zip::Member->newFromFile( 'xyz.txt' ); | |
1560 | ||
1561 | =item newDirectoryNamed( $directoryName [, $zipname ] ) | |
1562 | ||
1563 | ||
1564 | ||
1565 | Construct a new member from the given directory. | |
1566 | C<$directoryName> must be a valid name on your file system; it doesn't | |
1567 | have to exist. | |
1568 | ||
1569 | ||
1570 | If given, C<$zipname> will be the name of the zip member; it must be a | |
1571 | valid Zip (Unix) name. If not given, it will be converted from | |
1572 | C<$directoryName>. | |
1573 | ||
1574 | ||
1575 | Returns undef on error. | |
1576 | ||
1577 | my $member = Archive::Zip::Member->newDirectoryNamed( 'CVS/' ); | |
1578 | ||
1579 | =back | |
1580 | ||
1581 | =head2 Member Simple accessors | |
1582 | ||
1583 | ||
1584 | These methods get (and/or set) member attribute values. | |
1585 | ||
1586 | =over 4 | |
1587 | ||
1588 | =item versionMadeBy() | |
1589 | ||
1590 | ||
1591 | ||
1592 | Gets the field from the member header. | |
1593 | ||
1594 | =item fileAttributeFormat( [$format] ) | |
1595 | ||
1596 | ||
1597 | ||
1598 | Gets or sets the field from the member header. These are | |
1599 | C<FA_*> values. | |
1600 | ||
1601 | =item versionNeededToExtract() | |
1602 | ||
1603 | ||
1604 | ||
1605 | Gets the field from the member header. | |
1606 | ||
1607 | =item bitFlag() | |
1608 | ||
1609 | ||
1610 | ||
1611 | Gets the general purpose bit field from the member header. | |
1612 | This is where the C<GPBF_*> bits live. | |
1613 | ||
1614 | =item compressionMethod() | |
1615 | ||
1616 | ||
1617 | ||
1618 | Returns the member compression method. This is the method | |
1619 | that is currently being used to compress the member data. | |
1620 | This will be COMPRESSION_STORED for added string or file | |
1621 | members, or any of the C<COMPRESSION_*> values for members | |
1622 | from a zip file. However, this module can only handle members | |
1623 | whose data is in COMPRESSION_STORED or COMPRESSION_DEFLATED | |
1624 | format. | |
1625 | ||
1626 | =item desiredCompressionMethod( [$method] ) | |
1627 | ||
1628 | ||
1629 | ||
1630 | Get or set the member's C<desiredCompressionMethod>. This is | |
1631 | the compression method that will be used when the member is | |
1632 | written. Returns prior desiredCompressionMethod. Only | |
1633 | COMPRESSION_DEFLATED or COMPRESSION_STORED are valid | |
1634 | arguments. Changing to COMPRESSION_STORED will change the | |
1635 | member desiredCompressionLevel to 0; changing to | |
1636 | COMPRESSION_DEFLATED will change the member | |
1637 | desiredCompressionLevel to COMPRESSION_LEVEL_DEFAULT. | |
1638 | ||
1639 | =item desiredCompressionLevel( [$method] ) | |
1640 | ||
1641 | ||
1642 | ||
1643 | Get or set the member's desiredCompressionLevel This is the | |
1644 | method that will be used to write. Returns prior | |
1645 | desiredCompressionLevel. Valid arguments are 0 through 9, | |
1646 | COMPRESSION_LEVEL_NONE, COMPRESSION_LEVEL_DEFAULT, | |
1647 | COMPRESSION_LEVEL_BEST_COMPRESSION, and | |
1648 | COMPRESSION_LEVEL_FASTEST. 0 or COMPRESSION_LEVEL_NONE will | |
1649 | change the desiredCompressionMethod to COMPRESSION_STORED. | |
1650 | All other arguments will change the desiredCompressionMethod | |
1651 | to COMPRESSION_DEFLATED. | |
1652 | ||
1653 | =item externalFileName() | |
1654 | ||
1655 | ||
1656 | ||
1657 | Return the member's external file name, if any, or undef. | |
1658 | ||
1659 | =item fileName() | |
1660 | ||
1661 | ||
1662 | ||
1663 | Get or set the member's internal filename. Returns the | |
1664 | (possibly new) filename. Names will have backslashes | |
1665 | converted to forward slashes, and will have multiple | |
1666 | consecutive slashes converted to single ones. | |
1667 | ||
1668 | =item lastModFileDateTime() | |
1669 | ||
1670 | ||
1671 | ||
1672 | Return the member's last modification date/time stamp in | |
1673 | MS-DOS format. | |
1674 | ||
1675 | =item lastModTime() | |
1676 | ||
1677 | ||
1678 | ||
1679 | Return the member's last modification date/time stamp, | |
1680 | converted to unix localtime format. | |
1681 | ||
1682 | print "Mod Time: " . scalar( localtime( $member->lastModTime() ) ); | |
1683 | ||
1684 | =item setLastModFileDateTimeFromUnix() | |
1685 | ||
1686 | Set the member's lastModFileDateTime from the given unix | |
1687 | time. | |
1688 | ||
1689 | $member->setLastModFileDateTimeFromUnix( time() ); | |
1690 | ||
1691 | =item internalFileAttributes() | |
1692 | ||
1693 | Return the internal file attributes field from the zip | |
1694 | header. This is only set for members read from a zip file. | |
1695 | ||
1696 | =item externalFileAttributes() | |
1697 | ||
1698 | Return member attributes as read from the ZIP file. Note that | |
1699 | these are NOT UNIX! | |
1700 | ||
1701 | =item unixFileAttributes( [$newAttributes] ) | |
1702 | ||
1703 | Get or set the member's file attributes using UNIX file | |
1704 | attributes. Returns old attributes. | |
1705 | ||
1706 | my $oldAttribs = $member->unixFileAttributes( 0666 ); | |
1707 | ||
1708 | Note that the return value has more than just the file | |
1709 | permissions, so you will have to mask off the lowest bits for | |
1710 | comparisions. | |
1711 | ||
1712 | =item localExtraField( [$newField] ) | |
1713 | ||
1714 | Gets or sets the extra field that was read from the local | |
1715 | header. This is not set for a member from a zip file until | |
1716 | after the member has been written out. The extra field must | |
1717 | be in the proper format. | |
1718 | ||
1719 | =item cdExtraField( [$newField] ) | |
1720 | ||
1721 | Gets or sets the extra field that was read from the central | |
1722 | directory header. The extra field must be in the proper | |
1723 | format. | |
1724 | ||
1725 | =item extraFields() | |
1726 | ||
1727 | Return both local and CD extra fields, concatenated. | |
1728 | ||
1729 | =item fileComment( [$newComment] ) | |
1730 | ||
1731 | Get or set the member's file comment. | |
1732 | ||
1733 | =item hasDataDescriptor() | |
1734 | ||
1735 | Get or set the data descriptor flag. If this is set, the | |
1736 | local header will not necessarily have the correct data | |
1737 | sizes. Instead, a small structure will be stored at the end | |
1738 | of the member data with these values. This should be | |
1739 | transparent in normal operation. | |
1740 | ||
1741 | =item crc32() | |
1742 | ||
1743 | Return the CRC-32 value for this member. This will not be set | |
1744 | for members that were constructed from strings or external | |
1745 | files until after the member has been written. | |
1746 | ||
1747 | =item crc32String() | |
1748 | ||
1749 | Return the CRC-32 value for this member as an 8 character | |
1750 | printable hex string. This will not be set for members that | |
1751 | were constructed from strings or external files until after | |
1752 | the member has been written. | |
1753 | ||
1754 | =item compressedSize() | |
1755 | ||
1756 | Return the compressed size for this member. This will not be | |
1757 | set for members that were constructed from strings or | |
1758 | external files until after the member has been written. | |
1759 | ||
1760 | =item uncompressedSize() | |
1761 | ||
1762 | Return the uncompressed size for this member. | |
1763 | ||
1764 | =item isEncrypted() | |
1765 | ||
1766 | Return true if this member is encrypted. The Archive::Zip | |
1767 | module does not currently create or extract encrypted | |
1768 | members. | |
1769 | ||
1770 | =item isTextFile( [$flag] ) | |
1771 | ||
1772 | Returns true if I am a text file. Also can set the status if | |
1773 | given an argument (then returns old state). Note that this | |
1774 | module does not currently do anything with this flag upon | |
1775 | extraction or storage. That is, bytes are stored in native | |
1776 | format whether or not they came from a text file. | |
1777 | ||
1778 | =item isBinaryFile() | |
1779 | ||
1780 | Returns true if I am a binary file. Also can set the status | |
1781 | if given an argument (then returns old state). Note that this | |
1782 | module does not currently do anything with this flag upon | |
1783 | extraction or storage. That is, bytes are stored in native | |
1784 | format whether or not they came from a text file. | |
1785 | ||
1786 | =item extractToFileNamed( $fileName ) | |
1787 | ||
1788 | Extract me to a file with the given name. The file will be | |
1789 | created with default modes. Directories will be created as | |
1790 | needed. | |
1791 | The C<$fileName> argument should be a valid file name on your | |
1792 | file system. | |
1793 | Returns AZ_OK on success. | |
1794 | ||
1795 | =item isDirectory() | |
1796 | ||
1797 | Returns true if I am a directory. | |
1798 | ||
1799 | =item writeLocalHeaderRelativeOffset() | |
1800 | ||
1801 | Returns the file offset in bytes the last time I was written. | |
1802 | ||
1803 | =item wasWritten() | |
1804 | ||
1805 | Returns true if I was successfully written. Reset at the | |
1806 | beginning of a write attempt. | |
1807 | ||
1808 | =back | |
1809 | ||
1810 | =head2 Low-level member data reading | |
1811 | ||
1812 | It is possible to use lower-level routines to access member data | |
1813 | streams, rather than the extract* methods and contents(). For | |
1814 | instance, here is how to print the uncompressed contents of a member | |
1815 | in chunks using these methods: | |
1816 | ||
1817 | my ( $member, $status, $bufferRef ); | |
1818 | $member = $zip->memberNamed( 'xyz.txt' ); | |
1819 | $member->desiredCompressionMethod( COMPRESSION_STORED ); | |
1820 | $status = $member->rewindData(); | |
1821 | die "error $status" unless $status == AZ_OK; | |
1822 | while ( ! $member->readIsDone() ) | |
1823 | { | |
1824 | ( $bufferRef, $status ) = $member->readChunk(); | |
1825 | die "error $status" | |
1826 | if $status != AZ_OK && $status != AZ_STREAM_END; | |
1827 | # do something with $bufferRef: | |
1828 | print $$bufferRef; | |
1829 | } | |
1830 | $member->endRead(); | |
1831 | ||
1832 | =over 4 | |
1833 | ||
1834 | =item readChunk( [$chunkSize] ) | |
1835 | ||
1836 | This reads the next chunk of given size from the member's | |
1837 | data stream and compresses or uncompresses it as necessary, | |
1838 | returning a reference to the bytes read and a status. If size | |
1839 | argument is not given, defaults to global set by | |
1840 | Archive::Zip::setChunkSize. Status is AZ_OK on success until | |
1841 | the last chunk, where it returns AZ_STREAM_END. Returns C<( | |
1842 | \$bytes, $status)>. | |
1843 | ||
1844 | my ( $outRef, $status ) = $self->readChunk(); | |
1845 | print $$outRef if $status != AZ_OK && $status != AZ_STREAM_END; | |
1846 | ||
1847 | =item rewindData() | |
1848 | ||
1849 | Rewind data and set up for reading data streams or writing | |
1850 | zip files. Can take options for C<inflateInit()> or | |
1851 | C<deflateInit()>, but this isn't likely to be necessary. | |
1852 | Subclass overrides should call this method. Returns C<AZ_OK> | |
1853 | on success. | |
1854 | ||
1855 | =item endRead() | |
1856 | ||
1857 | Reset the read variables and free the inflater or deflater. | |
1858 | Must be called to close files, etc. Returns AZ_OK on success. | |
1859 | ||
1860 | =item readIsDone() | |
1861 | ||
1862 | Return true if the read has run out of data or errored out. | |
1863 | ||
1864 | =item contents() | |
1865 | ||
1866 | Return the entire uncompressed member data or undef in scalar | |
1867 | context. When called in array context, returns C<( $string, | |
1868 | $status )>; status will be AZ_OK on success: | |
1869 | ||
1870 | my $string = $member->contents(); | |
1871 | # or | |
1872 | my ( $string, $status ) = $member->contents(); | |
1873 | die "error $status" unless $status == AZ_OK; | |
1874 | ||
1875 | Can also be used to set the contents of a member (this may | |
1876 | change the class of the member): | |
1877 | ||
1878 | $member->contents( "this is my new contents" ); | |
1879 | ||
1880 | =item extractToFileHandle( $fh ) | |
1881 | ||
1882 | Extract (and uncompress, if necessary) the member's contents | |
1883 | to the given file handle. Return AZ_OK on success. | |
1884 | ||
1885 | =back | |
1886 | ||
1887 | =head1 Archive::Zip::FileMember methods | |
1888 | ||
1889 | The Archive::Zip::FileMember class extends Archive::Zip::Member. It is the | |
1890 | base class for both ZipFileMember and NewFileMember classes. This class adds | |
1891 | an C<externalFileName> and an C<fh> member to keep track of the external | |
1892 | file. | |
1893 | ||
1894 | =over 4 | |
1895 | ||
1896 | =item externalFileName() | |
1897 | ||
1898 | Return the member's external filename. | |
1899 | ||
1900 | =item fh() | |
1901 | ||
1902 | Return the member's read file handle. Automatically opens file if | |
1903 | necessary. | |
1904 | ||
1905 | =back | |
1906 | ||
1907 | =head1 Archive::Zip::ZipFileMember methods | |
1908 | ||
1909 | The Archive::Zip::ZipFileMember class represents members that have been read | |
1910 | from external zip files. | |
1911 | ||
1912 | =over 4 | |
1913 | ||
1914 | =item diskNumberStart() | |
1915 | ||
1916 | Returns the disk number that the member's local header resides in. | |
1917 | Should be 0. | |
1918 | ||
1919 | =item localHeaderRelativeOffset() | |
1920 | ||
1921 | Returns the offset into the zip file where the member's local header | |
1922 | is. | |
1923 | ||
1924 | =item dataOffset() | |
1925 | ||
1926 | Returns the offset from the beginning of the zip file to the member's | |
1927 | data. | |
1928 | ||
1929 | =back | |
1930 | ||
1931 | =head1 REQUIRED MODULES | |
1932 | ||
1933 | L<Archive::Zip> requires several other modules: | |
1934 | ||
1935 | L<Carp> | |
1936 | ||
1937 | L<Compress::Zlib> | |
1938 | ||
1939 | L<Cwd> | |
1940 | ||
1941 | L<File::Basename> | |
1942 | ||
1943 | L<File::Copy> | |
1944 | ||
1945 | L<File::Find> | |
1946 | ||
1947 | L<File::Path> | |
1948 | ||
1949 | L<File::Spec> | |
1950 | ||
1951 | L<File::Spec> | |
1952 | ||
1953 | L<IO::File> | |
1954 | ||
1955 | L<IO::Seekable> | |
1956 | ||
1957 | L<Time::Local> | |
1958 | ||
1959 | =head1 BUGS AND CAVEATS | |
1960 | ||
1961 | =head2 When not to use Archive::Zip | |
1962 | ||
1963 | If you are just going to be extracting zips (and/or other archives) you | |
1964 | are recommended to look at using L<Archive::Extract> instead, as it is much | |
1965 | easier to use and factors out archive-specific functionality. | |
1966 | ||
1967 | =head2 Try to avoid IO::Scalar | |
1968 | ||
1969 | One of the most common ways to use Archive::Zip is to generate Zip files | |
1970 | in-memory. Most people have use L<IO::Scalar> for this purpose. | |
1971 | ||
1972 | Unfortunately, as of 1.11 this module no longer works with L<IO::Scalar> | |
1973 | as it incorrectly implements seeking. | |
1974 | ||
1975 | Anybody using L<IO::Scalar> should consider porting to L<IO::String>, | |
1976 | which is smaller, lighter, and is implemented to be perfectly compatible | |
1977 | with regular seekable filehandles. | |
1978 | ||
1979 | Support for L<IO::Scalar> most likely will B<not> be restored in the | |
1980 | future, as L<IO::Scalar> itself cannot change the way it is implemented | |
1981 | due to back-compatibility issues. | |
1982 | ||
1983 | =head1 TO DO | |
1984 | ||
1985 | * auto-choosing storing vs compression | |
1986 | ||
1987 | * extra field hooks (see notes.txt) | |
1988 | ||
1989 | * check for dups on addition/renaming? | |
1990 | ||
1991 | * Text file extraction (line end translation) | |
1992 | ||
1993 | * Reading zip files from non-seekable inputs | |
1994 | (Perhaps by proxying through IO::String?) | |
1995 | ||
1996 | * separate unused constants into separate module | |
1997 | ||
1998 | * cookbook style docs | |
1999 | ||
2000 | * Handle tainted paths correctly | |
2001 | ||
2002 | * Work on better compatability with other IO:: modules | |
2003 | ||
2004 | =head1 SUPPORT | |
2005 | ||
2006 | Bugs should be reported via the CPAN bug tracker | |
2007 | ||
2008 | L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Archive-Zip> | |
2009 | ||
2010 | For other issues contact the maintainer | |
2011 | ||
2012 | =head1 AUTHOR | |
2013 | ||
2014 | Adam Kennedy E<lt>adamk@cpan.orgE<gt> | |
2015 | ||
2016 | Previously maintained by Steve Peters E<lt>steve@fisharerojo.orgE<gt>. | |
2017 | ||
2018 | File attributes code by Maurice Aubrey E<lt>maurice@lovelyfilth.comE<gt>. | |
2019 | ||
2020 | Originally by Ned Konz E<lt>nedkonz@cpan.orgE<gt>. | |
2021 | ||
2022 | =head1 COPYRIGHT | |
2023 | ||
2024 | Copyright (c) 2000-2004 Ned Konz. All rights reserved. | |
2025 | ||
2026 | Some parts copyright (c) 2005 Steve Peters. All rights reserved. | |
2027 | ||
2028 | Some parts copyright (c) 2006 Adam Kennedy. All rights reserved. | |
2029 | ||
2030 | This program is free software; you can redistribute it and/or modify it | |
2031 | under the same terms as Perl itself. | |
2032 | ||
2033 | =head1 SEE ALSO | |
2034 | ||
2035 | L<Compress::Zlib>, L<Archive::Tar>, L<Archive::Extract> | |
2036 | ||
2037 | There is a Japanese translation of this | |
2038 | document at L<http://www.memb.jp/~deq/perl/doc-ja/Archive-Zip.html> that | |
2039 | was done by DEQ E<lt>deq@oct.zaq.ne.jpE<gt> . Thanks! | |
2040 | ||
2041 | =cut |
0 | #!/usr/bin/perl -w | |
1 | ||
2 | use Test::More tests => 2; | |
3 | ||
4 | use_ok( 'Archive::Zip' ); | |
5 | use_ok( 'Archive::Zip::MemberRead' ); |
0 | #!/usr/bin/perl -w | |
1 | ||
2 | # Main testing for Archive::Zip | |
3 | ||
4 | use strict; | |
5 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); | |
6 | use FileHandle; | |
7 | use File::Path; | |
8 | use File::Spec; | |
9 | ||
10 | use Test::More tests => 141; | |
11 | ||
12 | BEGIN { | |
13 | unshift @INC, "t/"; | |
14 | require( File::Spec->catfile('t', 'common.pl') ) | |
15 | or die "Can't load t/common.pl"; | |
16 | } | |
17 | ||
18 | ||
19 | ||
20 | ||
21 | ||
22 | ##################################################################### | |
23 | # Testing Utility Functions | |
24 | ||
25 | #--------- check CRC | |
26 | is(TESTSTRINGCRC, 0xac373f32, 'Testing CRC matches expected'); | |
27 | ||
28 | # Bad times die | |
29 | SCOPE: { | |
30 | my @errors = (); | |
31 | local $Archive::Zip::ErrorHandler = sub { push @errors, @_ }; | |
32 | eval { Archive::Zip::Member::_unixToDosTime( 0 ) }; | |
33 | ok( $errors[0] =~ /Tried to add member with zero or undef/, | |
34 | 'Got expected _unixToDosTime error' ); | |
35 | } | |
36 | ||
37 | #--------- check time conversion | |
38 | ||
39 | foreach my $unix_time ( | |
40 | 315576062, 315576064, 315580000, 315600000, | |
41 | 316000000, 320000000, 400000000, 500000000, | |
42 | 600000000, 700000000, 800000000, 900000000, | |
43 | 1000000000, 1100000000, 1200000000, | |
44 | int(time()/2)*2, | |
45 | ) { | |
46 | my $dos_time = Archive::Zip::Member::_unixToDosTime( $unix_time ); | |
47 | my $round_trip = Archive::Zip::Member::_dosToUnixTime( $dos_time ); | |
48 | is( $unix_time, $round_trip, 'Got expected DOS DateTime value' ); | |
49 | } | |
50 | ||
51 | ||
52 | ||
53 | ||
54 | ||
55 | ##################################################################### | |
56 | # Testing Archives | |
57 | ||
58 | #--------- empty file | |
59 | # new # Archive::Zip | |
60 | # new # Archive::Zip::Archive | |
61 | my $zip = Archive::Zip->new(); | |
62 | isa_ok( $zip, 'Archive::Zip' ); | |
63 | ||
64 | # members # Archive::Zip::Archive | |
65 | my @members = $zip->members; | |
66 | is(scalar(@members), 0, '->members is 0' ); | |
67 | ||
68 | # numberOfMembers # Archive::Zip::Archive | |
69 | my $numberOfMembers = $zip->numberOfMembers(); | |
70 | is($numberOfMembers, 0, '->numberofMembers is 0' ); | |
71 | ||
72 | # writeToFileNamed # Archive::Zip::Archive | |
73 | my $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
74 | is($status, AZ_OK, '->writeToFileNames ok' ); | |
75 | ||
76 | my $zipout; | |
77 | SKIP: { | |
78 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
79 | if ( $^O eq 'MSWin32' ) { | |
80 | print STDERR "\n# You might see an expected 'zipfile is empty' warning now.\n"; | |
81 | } | |
82 | ($status, $zipout) = testZip(); | |
83 | # STDERR->print("status= $status, out=$zipout\n"); | |
84 | ||
85 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
86 | ok( $status != 0 ); | |
87 | } | |
88 | # unzip -t returns error code=1 for warning on empty | |
89 | ||
90 | #--------- add a directory | |
91 | my $memberName = TESTDIR . '/'; | |
92 | my $dirName = TESTDIR; | |
93 | ||
94 | # addDirectory # Archive::Zip::Archive | |
95 | # new # Archive::Zip::Member | |
96 | my $member = $zip->addDirectory($memberName); | |
97 | ok(defined($member)); | |
98 | is($member->fileName(), $memberName); | |
99 | ||
100 | # On some (Windows systems) the modification time is | |
101 | # corrupted. Save this to check late. | |
102 | my $dir_time = $member->lastModFileDateTime(); | |
103 | ||
104 | # members # Archive::Zip::Archive | |
105 | @members = $zip->members(); | |
106 | is(scalar(@members), 1); | |
107 | is($members[0], $member); | |
108 | ||
109 | # numberOfMembers # Archive::Zip::Archive | |
110 | $numberOfMembers = $zip->numberOfMembers(); | |
111 | is($numberOfMembers, 1); | |
112 | ||
113 | # writeToFileNamed # Archive::Zip::Archive | |
114 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
115 | is($status, AZ_OK); | |
116 | ||
117 | # Does the modification time get corrupted? | |
118 | is( ($zip->members)[0]->lastModFileDateTime(), $dir_time ); | |
119 | ||
120 | SKIP: { | |
121 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
122 | ($status, $zipout) = testZip(); | |
123 | # STDERR->print("status= $status, out=$zipout\n"); | |
124 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
125 | is( $status, 0 ); | |
126 | } | |
127 | ||
128 | #--------- extract the directory by name | |
129 | rmtree([ TESTDIR ], 0, 0); | |
130 | $status = $zip->extractMember($memberName); | |
131 | is($status, AZ_OK); | |
132 | ok(-d $dirName); | |
133 | ||
134 | #--------- extract the directory by identity | |
135 | ok(rmdir($dirName)); # it's still empty | |
136 | $status = $zip->extractMember($member); | |
137 | is($status, AZ_OK); | |
138 | ok(-d $dirName); | |
139 | ||
140 | #--------- add a string member, uncompressed | |
141 | $memberName = TESTDIR . '/string.txt'; | |
142 | # addString # Archive::Zip::Archive | |
143 | # newFromString # Archive::Zip::Member | |
144 | $member = $zip->addString(TESTSTRING, $memberName); | |
145 | ok(defined($member)); | |
146 | ||
147 | is($member->fileName(), $memberName); | |
148 | ||
149 | # members # Archive::Zip::Archive | |
150 | @members = $zip->members(); | |
151 | is(scalar(@members), 2); | |
152 | is($members[1], $member); | |
153 | ||
154 | # numberOfMembers # Archive::Zip::Archive | |
155 | $numberOfMembers = $zip->numberOfMembers(); | |
156 | is($numberOfMembers, 2); | |
157 | ||
158 | # writeToFileNamed # Archive::Zip::Archive | |
159 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
160 | is($status, AZ_OK); | |
161 | ||
162 | SKIP: { | |
163 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
164 | ($status, $zipout) = testZip(); | |
165 | # STDERR->print("status= $status, out=$zipout\n"); | |
166 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
167 | is( $status, 0 ); | |
168 | } | |
169 | ||
170 | is($member->crc32(), TESTSTRINGCRC); | |
171 | ||
172 | is($member->crc32String(), sprintf("%08x", TESTSTRINGCRC)); | |
173 | ||
174 | #--------- extract it by name | |
175 | $status = $zip->extractMember($memberName); | |
176 | is($status, AZ_OK); | |
177 | ok(-f $memberName); | |
178 | is(fileCRC($memberName), TESTSTRINGCRC); | |
179 | ||
180 | #--------- now compress it and re-test | |
181 | my $oldCompressionMethod = | |
182 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
183 | is($oldCompressionMethod, COMPRESSION_STORED, 'old compression method OK'); | |
184 | ||
185 | # writeToFileNamed # Archive::Zip::Archive | |
186 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
187 | is($status, AZ_OK, 'writeToFileNamed returns AZ_OK'); | |
188 | is($member->crc32(), TESTSTRINGCRC); | |
189 | is($member->uncompressedSize(), TESTSTRINGLENGTH); | |
190 | ||
191 | SKIP: { | |
192 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
193 | ($status, $zipout) = testZip(); | |
194 | # STDERR->print("status= $status, out=$zipout\n"); | |
195 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
196 | is( $status, 0 ); | |
197 | } | |
198 | ||
199 | #--------- extract it by name | |
200 | $status = $zip->extractMember($memberName); | |
201 | is($status, AZ_OK); | |
202 | ok(-f $memberName); | |
203 | is(fileCRC($memberName), TESTSTRINGCRC); | |
204 | ||
205 | #--------- add a file member, compressed | |
206 | ok(rename($memberName, TESTDIR . '/file.txt')); | |
207 | $memberName = TESTDIR . '/file.txt'; | |
208 | ||
209 | # addFile # Archive::Zip::Archive | |
210 | # newFromFile # Archive::Zip::Member | |
211 | $member = $zip->addFile($memberName); | |
212 | ok(defined($member)); | |
213 | ||
214 | is($member->desiredCompressionMethod(), COMPRESSION_DEFLATED); | |
215 | ||
216 | # writeToFileNamed # Archive::Zip::Archive | |
217 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
218 | is($status, AZ_OK); | |
219 | is($member->crc32(), TESTSTRINGCRC); | |
220 | is($member->uncompressedSize(), TESTSTRINGLENGTH); | |
221 | ||
222 | SKIP: { | |
223 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
224 | ($status, $zipout) = testZip(); | |
225 | # STDERR->print("status= $status, out=$zipout\n"); | |
226 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
227 | is( $status, 0 ); | |
228 | } | |
229 | ||
230 | #--------- extract it by name (note we have to rename it first | |
231 | #--------- or we will clobber the original file | |
232 | my $newName = $memberName; | |
233 | $newName =~ s/\.txt/2.txt/; | |
234 | $status = $zip->extractMember($memberName, $newName); | |
235 | is($status, AZ_OK); | |
236 | ok(-f $newName); | |
237 | is(fileCRC($newName), TESTSTRINGCRC); | |
238 | ||
239 | #--------- now make it uncompressed and re-test | |
240 | $oldCompressionMethod = | |
241 | $member->desiredCompressionMethod(COMPRESSION_STORED); | |
242 | ||
243 | is($oldCompressionMethod, COMPRESSION_DEFLATED); | |
244 | ||
245 | # writeToFileNamed # Archive::Zip::Archive | |
246 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
247 | is($status, AZ_OK); | |
248 | is($member->crc32(), TESTSTRINGCRC); | |
249 | is($member->uncompressedSize(), TESTSTRINGLENGTH); | |
250 | ||
251 | SKIP: { | |
252 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
253 | ($status, $zipout) = testZip(); | |
254 | # STDERR->print("status= $status, out=$zipout\n"); | |
255 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
256 | is( $status, 0 ); | |
257 | } | |
258 | ||
259 | #--------- extract it by name | |
260 | $status = $zip->extractMember($memberName, $newName); | |
261 | is($status, AZ_OK); | |
262 | ok(-f $newName); | |
263 | is(fileCRC($newName), TESTSTRINGCRC); | |
264 | ||
265 | # Now, the contents of OUTPUTZIP are: | |
266 | # Length Method Size Ratio Date Time CRC-32 Name | |
267 | #-------- ------ ------- ----- ---- ---- ------ ---- | |
268 | # 0 Stored 0 0% 03-17-00 11:16 00000000 testDir/ | |
269 | # 300 Defl:N 146 51% 03-17-00 11:16 ac373f32 testDir/string.txt | |
270 | # 300 Stored 300 0% 03-17-00 11:16 ac373f32 testDir/file.txt | |
271 | #-------- ------- --- ------- | |
272 | # 600 446 26% 3 files | |
273 | ||
274 | # members # Archive::Zip::Archive | |
275 | @members = $zip->members(); | |
276 | is(scalar(@members), 3); | |
277 | is($members[2], $member); | |
278 | ||
279 | # memberNames # Archive::Zip::Archive | |
280 | my @memberNames = $zip->memberNames(); | |
281 | is(scalar(@memberNames), 3); | |
282 | is($memberNames[2], $memberName); | |
283 | ||
284 | # memberNamed # Archive::Zip::Archive | |
285 | is($zip->memberNamed($memberName), $member); | |
286 | ||
287 | # membersMatching # Archive::Zip::Archive | |
288 | @members = $zip->membersMatching('file'); | |
289 | is(scalar(@members), 1); | |
290 | is($members[0], $member); | |
291 | ||
292 | @members = $zip->membersMatching('.txt$'); | |
293 | is(scalar(@members), 2); | |
294 | is($members[1], $member); | |
295 | ||
296 | #--------- remove the string member and test the file | |
297 | # removeMember # Archive::Zip::Archive | |
298 | $member = $zip->removeMember($members[0]); | |
299 | is($member, $members[0]); | |
300 | ||
301 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
302 | is($status, AZ_OK); | |
303 | ||
304 | SKIP: { | |
305 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
306 | ($status, $zipout) = testZip(); | |
307 | # STDERR->print("status= $status, out=$zipout\n"); | |
308 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
309 | is( $status, 0 ); | |
310 | } | |
311 | ||
312 | #--------- add the string member at the end and test the file | |
313 | # addMember # Archive::Zip::Archive | |
314 | $zip->addMember($member); | |
315 | @members = $zip->members(); | |
316 | ||
317 | is(scalar(@members), 3); | |
318 | is($members[2], $member); | |
319 | ||
320 | # memberNames # Archive::Zip::Archive | |
321 | @memberNames = $zip->memberNames(); | |
322 | is(scalar(@memberNames), 3); | |
323 | is($memberNames[1], $memberName); | |
324 | ||
325 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
326 | is($status, AZ_OK); | |
327 | ||
328 | SKIP: { | |
329 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
330 | ($status, $zipout) = testZip(); | |
331 | # STDERR->print("status= $status, out=$zipout\n"); | |
332 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
333 | is( $status, 0 ); | |
334 | } | |
335 | ||
336 | #--------- remove the file member | |
337 | $member = $zip->removeMember($members[1]); | |
338 | is($member, $members[1]); | |
339 | is($zip->numberOfMembers(), 2); | |
340 | ||
341 | #--------- replace the string member with the file member | |
342 | # replaceMember # Archive::Zip::Archive | |
343 | $member = $zip->replaceMember($members[2], $member); | |
344 | is($member, $members[2]); | |
345 | is($zip->numberOfMembers(), 2); | |
346 | ||
347 | #--------- re-add the string member | |
348 | $zip->addMember($member); | |
349 | is($zip->numberOfMembers(), 3); | |
350 | ||
351 | @members = $zip->members(); | |
352 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
353 | is($status, AZ_OK); | |
354 | ||
355 | SKIP: { | |
356 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
357 | ($status, $zipout) = testZip(); | |
358 | # STDERR->print("status= $status, out=$zipout\n"); | |
359 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
360 | is( $status, 0 ); | |
361 | } | |
362 | ||
363 | #--------- add compressed file | |
364 | $member = $zip->addFile(File::Spec->catfile(TESTDIR, 'file.txt')); | |
365 | ok(defined($member)); | |
366 | $member->desiredCompressionMethod(COMPRESSION_DEFLATED); | |
367 | $member->fileName(TESTDIR . '/fileC.txt'); | |
368 | ||
369 | #--------- add uncompressed string | |
370 | $member = $zip->addString(TESTSTRING, TESTDIR . '/stringU.txt'); | |
371 | ok(defined($member)); | |
372 | $member->desiredCompressionMethod(COMPRESSION_STORED); | |
373 | ||
374 | # Now, the file looks like this: | |
375 | # Length Method Size Ratio Date Time CRC-32 Name | |
376 | #-------- ------ ------- ----- ---- ---- ------ ---- | |
377 | # 0 Stored 0 0% 03-17-00 12:30 00000000 testDir/ | |
378 | # 300 Stored 300 0% 03-17-00 12:30 ac373f32 testDir/file.txt | |
379 | # 300 Defl:N 146 51% 03-17-00 12:30 ac373f32 testDir/string.txt | |
380 | # 300 Stored 300 0% 03-17-00 12:30 ac373f32 testDir/stringU.txt | |
381 | # 300 Defl:N 146 51% 03-17-00 12:30 ac373f32 testDir/fileC.txt | |
382 | #-------- ------- --- ------- | |
383 | # 1200 892 26% 5 files | |
384 | ||
385 | @members = $zip->members(); | |
386 | $numberOfMembers = $zip->numberOfMembers(); | |
387 | is($numberOfMembers, 5); | |
388 | ||
389 | #--------- make sure the contents of the stored file member are OK. | |
390 | # contents # Archive::Zip::Archive | |
391 | is($zip->contents($members[1]), TESTSTRING); | |
392 | ||
393 | # contents # Archive::Zip::Member | |
394 | is($members[1]->contents(), TESTSTRING); | |
395 | ||
396 | #--------- make sure the contents of the compressed string member are OK. | |
397 | is($members[2]->contents(), TESTSTRING); | |
398 | ||
399 | #--------- make sure the contents of the stored string member are OK. | |
400 | is($members[3]->contents(), TESTSTRING); | |
401 | ||
402 | #--------- make sure the contents of the compressed file member are OK. | |
403 | is($members[4]->contents(), TESTSTRING); | |
404 | ||
405 | #--------- write to INPUTZIP | |
406 | $status = $zip->writeToFileNamed( INPUTZIP ); | |
407 | is($status, AZ_OK); | |
408 | ||
409 | SKIP: { | |
410 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
411 | ($status, $zipout) = testZip(INPUTZIP); | |
412 | # STDERR->print("status= $status, out=$zipout\n"); | |
413 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
414 | is( $status, 0 ); | |
415 | } | |
416 | ||
417 | #--------- read from INPUTZIP (appending its entries) | |
418 | # read # Archive::Zip::Archive | |
419 | $status = $zip->read(INPUTZIP); | |
420 | is($status, AZ_OK); | |
421 | is($zip->numberOfMembers(), 10); | |
422 | ||
423 | #--------- clean up duplicate names | |
424 | @members = $zip->members(); | |
425 | $member = $zip->removeMember($members[5]); | |
426 | is($member->fileName(), TESTDIR . '/'); | |
427 | ||
428 | SCOPE: { | |
429 | for my $i (6..9) | |
430 | { | |
431 | $memberName = $members[$i]->fileName(); | |
432 | $memberName =~ s/\.txt/2.txt/; | |
433 | $members[$i]->fileName($memberName); | |
434 | } | |
435 | } | |
436 | is(scalar($zip->membersMatching('2.txt')), 4); | |
437 | ||
438 | #--------- write zip out and test it. | |
439 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
440 | is($status, AZ_OK); | |
441 | ||
442 | SKIP: { | |
443 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
444 | ($status, $zipout) = testZip(); | |
445 | # STDERR->print("status= $status, out=$zipout\n"); | |
446 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
447 | is( $status, 0 ); | |
448 | } | |
449 | ||
450 | #--------- Make sure that we haven't renamed files (this happened!) | |
451 | is(scalar($zip->membersMatching('2\.txt$')), 4); | |
452 | ||
453 | #--------- Now try extracting everyone | |
454 | @members = $zip->members(); | |
455 | is($zip->extractMember($members[0]), AZ_OK); #DM | |
456 | is($zip->extractMember($members[1]), AZ_OK); #NFM | |
457 | is($zip->extractMember($members[2]), AZ_OK); | |
458 | is($zip->extractMember($members[3]), AZ_OK); #NFM | |
459 | is($zip->extractMember($members[4]), AZ_OK); | |
460 | is($zip->extractMember($members[5]), AZ_OK); | |
461 | is($zip->extractMember($members[6]), AZ_OK); | |
462 | is($zip->extractMember($members[7]), AZ_OK); | |
463 | is($zip->extractMember($members[8]), AZ_OK); | |
464 | ||
465 | #--------- count dirs | |
466 | { | |
467 | my @dirs = grep { $_->isDirectory() } @members; | |
468 | is(scalar(@dirs), 1); | |
469 | is($dirs[0], $members[0]); | |
470 | } | |
471 | ||
472 | #--------- count binary and text files | |
473 | { | |
474 | my @binaryFiles = grep { $_->isBinaryFile() } @members; | |
475 | my @textFiles = grep { $_->isTextFile() } @members; | |
476 | is(scalar(@binaryFiles), 5); | |
477 | is(scalar(@textFiles), 4); | |
478 | } | |
479 | ||
480 | #--------- Try writing zip file to file handle | |
481 | { | |
482 | my $fh; | |
483 | if ($catWorks) | |
484 | { | |
485 | unlink( OUTPUTZIP ); | |
486 | $fh = FileHandle->new( CATPIPE . OUTPUTZIP ); | |
487 | binmode($fh); | |
488 | } | |
489 | SKIP: { | |
490 | skip('cat does not work on this platform', 1) unless $catWorks; | |
491 | ok( $fh ); | |
492 | } | |
493 | # $status = $zip->writeToFileHandle($fh, 0) if ($catWorks); | |
494 | $status = $zip->writeToFileHandle($fh) if ($catWorks); | |
495 | SKIP: { | |
496 | skip('cat does not work on this platform', 1) unless $catWorks; | |
497 | is( $status, AZ_OK ); | |
498 | } | |
499 | $fh->close() if ($catWorks); | |
500 | SKIP: { | |
501 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
502 | ($status, $zipout) = testZip(); | |
503 | is($status, 0); | |
504 | } | |
505 | } | |
506 | ||
507 | #--------- Change the contents of a string member | |
508 | is(ref($members[2]), 'Archive::Zip::StringMember'); | |
509 | $members[2]->contents( "This is my new contents\n" ); | |
510 | ||
511 | #--------- write zip out and test it. | |
512 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
513 | is($status, AZ_OK); | |
514 | ||
515 | SKIP: { | |
516 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
517 | ($status, $zipout) = testZip(); | |
518 | # STDERR->print("status= $status, out=$zipout\n"); | |
519 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
520 | is( $status, 0 ); | |
521 | } | |
522 | ||
523 | #--------- Change the contents of a file member | |
524 | is(ref($members[1]), 'Archive::Zip::NewFileMember'); | |
525 | $members[1]->contents( "This is my new contents\n" ); | |
526 | ||
527 | #--------- write zip out and test it. | |
528 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
529 | is($status, AZ_OK); | |
530 | ||
531 | SKIP: { | |
532 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
533 | ($status, $zipout) = testZip(); | |
534 | # STDERR->print("status= $status, out=$zipout\n"); | |
535 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
536 | is( $status, 0 ); | |
537 | } | |
538 | ||
539 | #--------- Change the contents of a zip member | |
540 | ||
541 | is(ref($members[7]), 'Archive::Zip::ZipFileMember'); | |
542 | $members[7]->contents( "This is my new contents\n" ); | |
543 | ||
544 | #--------- write zip out and test it. | |
545 | $status = $zip->writeToFileNamed( OUTPUTZIP ); | |
546 | is($status, AZ_OK); | |
547 | ||
548 | SKIP: { | |
549 | skip( "No 'unzip' program to test against", 1 ) unless HAVEUNZIP; | |
550 | ($status, $zipout) = testZip(); | |
551 | # STDERR->print("status= $status, out=$zipout\n"); | |
552 | skip( "test zip doesn't work", 1 ) if $testZipDoesntWork; | |
553 | is( $status, 0 ); | |
554 | } | |
555 | ||
556 | ||
557 | #--------- now clean up | |
558 | # END { system("rm -rf " . TESTDIR . " " . OUTPUTZIP . " " . INPUTZIP) } | |
559 | ||
560 | #--------------------- STILL UNTESTED IN THIS SCRIPT --------------------- | |
561 | ||
562 | # sub setChunkSize # Archive::Zip | |
563 | # sub _formatError # Archive::Zip | |
564 | # sub _error # Archive::Zip | |
565 | # sub _subclassResponsibility # Archive::Zip | |
566 | # sub diskNumber # Archive::Zip::Archive | |
567 | # sub diskNumberWithStartOfCentralDirectory # Archive::Zip::Archive | |
568 | # sub numberOfCentralDirectoriesOnThisDisk # Archive::Zip::Archive | |
569 | # sub numberOfCentralDirectories # Archive::Zip::Archive | |
570 | # sub centralDirectoryOffsetWRTStartingDiskNumber # Archive::Zip::Archive | |
571 | # sub extraField # Archive::Zip::Member | |
572 | # sub isEncrypted # Archive::Zip::Member | |
573 | # sub isTextFile # Archive::Zip::Member | |
574 | # sub isBinaryFile # Archive::Zip::Member | |
575 | # sub isDirectory # Archive::Zip::Member | |
576 | # sub lastModTime # Archive::Zip::Member | |
577 | # sub _writeDataDescriptor # Archive::Zip::Member | |
578 | # sub isDirectory # Archive::Zip::DirectoryMember | |
579 | # sub _becomeDirectory # Archive::Zip::DirectoryMember | |
580 | # sub diskNumberStart # Archive::Zip::ZipFileMember |
0 | #!/usr/bin/perl -w | |
1 | ||
2 | use strict; | |
3 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); | |
4 | use File::Spec; | |
5 | use IO::File; | |
6 | ||
7 | use Test::More tests => 17; | |
8 | BEGIN { | |
9 | unshift @INC, "t/"; | |
10 | require( File::Spec->catfile('t', 'common.pl') ) | |
11 | or die "Can't load t/common.pl"; | |
12 | } | |
13 | ||
14 | sub runPerlCommand | |
15 | { | |
16 | my $libs = join ( ' -I', @INC ); | |
17 | my $cmd = "\"$^X\" \"-I$libs\" -w \"". join('" "', @_). '"'; | |
18 | my $output = `$cmd`; | |
19 | return wantarray ? ( $?, $output ) : $?; | |
20 | } | |
21 | ||
22 | use constant FILENAME => File::Spec->catpath( '', TESTDIR, 'testing.txt' ); | |
23 | use constant ZFILENAME => TESTDIR . "/testing.txt"; # name in zip | |
24 | ||
25 | my $zip = Archive::Zip->new(); | |
26 | isa_ok( $zip, 'Archive::Zip' ); | |
27 | $zip->addString( TESTSTRING, FILENAME ); | |
28 | $zip->writeToFileNamed(INPUTZIP); | |
29 | ||
30 | my ( $status, $output ); | |
31 | my $fh = IO::File->new( "test.log", "w" ); | |
32 | isa_ok( $fh, 'IO::File' ); | |
33 | ||
34 | is( runPerlCommand( 'examples/copy.pl', INPUTZIP, OUTPUTZIP ), 0 ); | |
35 | ||
36 | is( runPerlCommand( 'examples/extract.pl', OUTPUTZIP, ZFILENAME ), 0 ); | |
37 | ||
38 | is( runPerlCommand( 'examples/mfh.pl', INPUTZIP ), 0 ); | |
39 | ||
40 | is( runPerlCommand( 'examples/zip.pl', OUTPUTZIP, INPUTZIP, FILENAME ), 0 ); | |
41 | ||
42 | ( $status, $output ) = runPerlCommand( 'examples/zipinfo.pl', INPUTZIP ); | |
43 | is( $status, 0 ); | |
44 | $fh->print("zipinfo output:\n"); | |
45 | $fh->print($output); | |
46 | ||
47 | ( $status, $output ) = runPerlCommand( 'examples/ziptest.pl', INPUTZIP ); | |
48 | is( $status, 0 ); | |
49 | $fh->print("ziptest output:\n"); | |
50 | $fh->print($output); | |
51 | ||
52 | ( $status, $output ) = runPerlCommand( 'examples/zipGrep.pl', '100', INPUTZIP ); | |
53 | is( $status, 0 ); | |
54 | is( $output, ZFILENAME . ":100\n" ); | |
55 | ||
56 | # calcSizes.pl | |
57 | # creates test.zip, may be sensitive to /dev/null | |
58 | ||
59 | # removed because requires IO::Scalar | |
60 | # ok( runPerlCommand('examples/readScalar.pl'), 0 ); | |
61 | ||
62 | unlink(OUTPUTZIP); | |
63 | is( runPerlCommand( 'examples/selfex.pl', OUTPUTZIP, FILENAME ), 0 ); | |
64 | unlink(FILENAME); | |
65 | is( runPerlCommand(OUTPUTZIP), 0 ); | |
66 | my $fn = | |
67 | File::Spec->catpath( '', File::Spec->catdir( 'extracted', TESTDIR ), | |
68 | 'testing.txt' ); | |
69 | is( -f $fn, 1, "$fn exists" ); | |
70 | ||
71 | # unzipAll.pl | |
72 | # updateZip.pl | |
73 | # writeScalar.pl | |
74 | # zipcheck.pl | |
75 | # ziprecent.pl | |
76 | ||
77 | unlink(OUTPUTZIP); | |
78 | is( runPerlCommand( 'examples/updateTree.pl', OUTPUTZIP, TESTDIR ), 0, "updateTree.pl create" ); | |
79 | is( -f OUTPUTZIP, 1, "zip created" ); | |
80 | is( runPerlCommand( 'examples/updateTree.pl', OUTPUTZIP, TESTDIR ), 0, "updateTree.pl update" ); | |
81 | is( -f OUTPUTZIP, 1, "zip updated" ); | |
82 | unlink(OUTPUTZIP); |
0 | #!/usr/bin/perl -w | |
1 | ||
2 | use strict; | |
3 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); | |
4 | use Archive::Zip::MemberRead; | |
5 | ||
6 | use Test::More tests => 8; | |
7 | BEGIN { | |
8 | unshift @INC, "t/"; | |
9 | require( File::Spec->catfile('t', 'common.pl') ) | |
10 | or die "Can't load t/common.pl"; | |
11 | } | |
12 | ||
13 | use constant FILENAME => File::Spec->catfile(TESTDIR, 'member_read.zip'); | |
14 | ||
15 | my ($zip, $member, $fh, @data); | |
16 | $zip = new Archive::Zip; | |
17 | isa_ok( $zip, 'Archive::Zip' ); | |
18 | @data = ( 'Line 1', 'Line 2', '', 'Line 3', 'Line 4' ); | |
19 | ||
20 | $zip->addString(join("\n", @data), 'string.txt'); | |
21 | $zip->writeToFileNamed(FILENAME); | |
22 | ||
23 | $member = $zip->memberNamed('string.txt'); | |
24 | $fh = $member->readFileHandle(); | |
25 | ok( $fh ); | |
26 | ||
27 | my ($line, $not_ok, $ret, $buffer); | |
28 | while (defined($line = $fh->getline())) | |
29 | { | |
30 | $not_ok = 1 if ($line ne $data[$fh->input_line_number()-1]); | |
31 | } | |
32 | ok( !$not_ok ); | |
33 | ||
34 | $fh->rewind(); | |
35 | $ret = $fh->read($buffer, length($data[0])); | |
36 | ok( $ret == length($data[0]) ); | |
37 | ok( $buffer eq $data[0] ); | |
38 | $fh->close(); | |
39 | ||
40 | # | |
41 | # Different usages | |
42 | # | |
43 | $fh = new Archive::Zip::MemberRead($zip, 'string.txt'); | |
44 | ok($fh); | |
45 | ||
46 | $fh = new Archive::Zip::MemberRead($zip, $zip->memberNamed('string.txt')); | |
47 | ok($fh); | |
48 | ||
49 | $fh = new Archive::Zip::MemberRead($zip->memberNamed('string.txt')); | |
50 | ok($fh); |
0 | #!/usr/bin/perl -w | |
1 | ||
2 | use strict; | |
3 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); | |
4 | use FileHandle; | |
5 | use File::Spec; | |
6 | ||
7 | use Test::More tests =>6; | |
8 | BEGIN { | |
9 | unshift @INC, "t/"; | |
10 | require( File::Spec->catfile('t', 'common.pl') ) | |
11 | or die "Can't load t/common.pl"; | |
12 | } | |
13 | ||
14 | use constant FILENAME => File::Spec->catfile(TESTDIR, 'testing.txt'); | |
15 | ||
16 | my $zip; | |
17 | my @memberNames; | |
18 | ||
19 | sub makeZip | |
20 | { | |
21 | my ($src, $dest, $pred) = @_; | |
22 | $zip = Archive::Zip->new(); | |
23 | $zip->addTree($src, $dest, $pred); | |
24 | @memberNames = $zip->memberNames(); | |
25 | } | |
26 | ||
27 | sub makeZipAndLookFor | |
28 | { | |
29 | my ($src, $dest, $pred, $lookFor) = @_; | |
30 | makeZip($src, $dest, $pred); | |
31 | ok( @memberNames ); | |
32 | ok( (grep { $_ eq $lookFor } @memberNames) == 1 ) | |
33 | or print STDERR "Can't find $lookFor in (" . join(",", @memberNames) . ")\n"; | |
34 | } | |
35 | ||
36 | my ($testFileVolume, $testFileDirs, $testFileName) = File::Spec->splitpath($0); | |
37 | ||
38 | makeZipAndLookFor('.', '', sub { print "file $_\n"; -f && /\.t$/ }, 't/02_main.t' ); | |
39 | makeZipAndLookFor('.', 'e/', sub { -f && /\.t$/ }, 'e/t/02_main.t'); | |
40 | makeZipAndLookFor('./t', '', sub { -f && /\.t$/ }, '02_main.t' ); |
0 | #!/usr/bin/perl -w | |
1 | ||
2 | # Test Archive::Zip updating | |
3 | ||
4 | use strict; | |
5 | use File::Spec (); | |
6 | use IO::File (); | |
7 | use File::Find (); | |
8 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); | |
9 | ||
10 | use Test::More tests => 12; | |
11 | BEGIN { | |
12 | unshift @INC, "t/"; | |
13 | require( File::Spec->catfile('t', 'common.pl') ) | |
14 | or die "Can't load t/common.pl"; | |
15 | } | |
16 | ||
17 | my ($testFileVolume, $testFileDirs, $testFileName) = File::Spec->splitpath($0); | |
18 | ||
19 | my $zip = Archive::Zip->new(); | |
20 | my $testDir = File::Spec->catpath( $testFileVolume, $testFileDirs, '' ); | |
21 | ||
22 | my $numberOfMembers = 0; | |
23 | my @memberNames; | |
24 | sub countMembers { unless ($_ eq '.') | |
25 | { push(@memberNames, $_); $numberOfMembers++; } }; | |
26 | File::Find::find( \&countMembers, $testDir ); | |
27 | is( $numberOfMembers > 1, 1, 'not enough members to test'); | |
28 | ||
29 | # an initial updateTree() should act like an addTree() | |
30 | is( $zip->updateTree( $testDir ), AZ_OK, 'initial updateTree failed' ); | |
31 | is( scalar($zip->members()), $numberOfMembers, 'wrong number of members after create' ); | |
32 | ||
33 | my $firstFile = $memberNames[0]; | |
34 | my $firstMember = ($zip->members())[0]; | |
35 | ||
36 | is( $firstFile, $firstMember->fileName(), 'member name wrong'); | |
37 | ||
38 | # add a file to the directory | |
39 | $testFileName = File::Spec->catpath( $testFileVolume, $testFileDirs, 'xxxxxx' ); | |
40 | my $fh = IO::File->new( $testFileName, 'w'); | |
41 | $fh->print('xxxx'); | |
42 | undef($fh); | |
43 | is( -f $testFileName, 1, "creating $testFileName failed"); | |
44 | ||
45 | # Then update it. It should be added. | |
46 | is( $zip->updateTree( $testDir ), AZ_OK, 'updateTree failed' ); | |
47 | is( scalar($zip->members()), $numberOfMembers + 1, 'wrong number of members after update' ); | |
48 | ||
49 | # Delete the file. | |
50 | unlink($testFileName); | |
51 | is( -f $testFileName, undef, "deleting $testFileName failed"); | |
52 | ||
53 | # updating without the mirror option should keep the members | |
54 | is( $zip->updateTree( $testDir ), AZ_OK, 'updateTree failed' ); | |
55 | is( scalar($zip->members()), $numberOfMembers + 1, 'wrong number of members after update' ); | |
56 | ||
57 | # now try again with the mirror option; should delete the last file. | |
58 | is( $zip->updateTree( $testDir, undef, undef, 1 ), AZ_OK, 'updateTree failed' ); | |
59 | is( scalar($zip->members()), $numberOfMembers, 'wrong number of members after mirror' ); |
0 | #!/usr/bin/perl -w | |
1 | ||
2 | use strict; | |
3 | BEGIN { | |
4 | $| = 1; | |
5 | $^W = 1; | |
6 | } | |
7 | use Test::More; | |
8 | ||
9 | # Skip if doing a regular install | |
10 | unless ( $ENV{AUTOMATED_TESTING} ) { | |
11 | plan( skip_all => "Author tests not required for installation" ); | |
12 | } | |
13 | ||
14 | # Can we run the POD tests? | |
15 | eval "use Test::Pod 1.00"; | |
16 | if ( $@ ) { | |
17 | plan( skip_all => "Test::Pod 1.00 required for testing POD" ); | |
18 | } | |
19 | ||
20 | ||
21 | ||
22 | ||
23 | ||
24 | ##################################################################### | |
25 | # WARNING: INSANE BLACK MAGIC | |
26 | ##################################################################### | |
27 | ||
28 | # Hack Pod::Simple::BlackBox to ignore the Test::Inline | |
29 | # "Extended Begin" syntax. | |
30 | # For example, "=begin has more than one word errors" | |
31 | my $begin = \&Pod::Simple::BlackBox::_ponder_begin; | |
32 | sub mybegin { | |
33 | my $para = $_[1]; | |
34 | my $content = join ' ', splice @$para, 2; | |
35 | $content =~ s/^\s+//s; | |
36 | $content =~ s/\s+$//s; | |
37 | my @words = split /\s+/, $content; | |
38 | if ( $words[0] =~ /^test(?:ing)?\z/s ) { | |
39 | foreach ( 2 .. $#$para ) { | |
40 | $para->[$_] = ''; | |
41 | } | |
42 | $para->[2] = $words[0]; | |
43 | } | |
44 | ||
45 | # Continue as normal | |
46 | push @$para, @words; | |
47 | return &$begin(@_); | |
48 | } | |
49 | ||
50 | SCOPE: { | |
51 | local $^W = 0; | |
52 | *Pod::Simple::BlackBox::_ponder_begin = \&mybegin; | |
53 | } | |
54 | ||
55 | ##################################################################### | |
56 | # END BLACK MAGIC | |
57 | ##################################################################### | |
58 | ||
59 | # Test POD | |
60 | all_pod_files_ok(); |
Binary diff not shown
Binary diff not shown
0 | # Shared defs for test programs | |
1 | ||
2 | # Paths. Must make case-insensitive. | |
3 | use constant TESTDIR => 'testdir'; | |
4 | use constant INPUTZIP => 'testin.zip'; | |
5 | use constant OUTPUTZIP => 'testout.zip'; | |
6 | ||
7 | # Do we have the 'zip' and 'unzip' programs? | |
8 | use File::Which (); | |
9 | use constant HAVEZIP => !! File::Which::which('zip'); | |
10 | use constant HAVEUNZIP => !! File::Which::which('unzip'); | |
11 | ||
12 | use constant ZIP => 'zip '; | |
13 | use constant ZIPTEST => 'unzip -t '; | |
14 | ||
15 | # 300-character test string | |
16 | use constant TESTSTRING => join ( "\n", 1 .. 102 ) . "\n"; | |
17 | use constant TESTSTRINGLENGTH => length(TESTSTRING); | |
18 | ||
19 | # CRC-32 should be ac373f32 | |
20 | use constant TESTSTRINGCRC => Archive::Zip::computeCRC32(TESTSTRING); | |
21 | ||
22 | # This is so that it will work on other systems. | |
23 | use constant CAT => $^X . ' -pe "BEGIN{binmode(STDIN);binmode(STDOUT)}"'; | |
24 | use constant CATPIPE => '| ' . CAT . ' >'; | |
25 | ||
26 | use vars qw($zipWorks $testZipDoesntWork $catWorks); | |
27 | local ( $zipWorks, $testZipDoesntWork, $catWorks ); | |
28 | ||
29 | # Run ZIPTEST to test a zip file. | |
30 | sub testZip { | |
31 | my $zipName = shift || OUTPUTZIP; | |
32 | if ( $testZipDoesntWork ) { | |
33 | return wantarray ? ( 0, '' ) : 0; | |
34 | } | |
35 | my $cmd = ZIPTEST . $zipName . ( $^O eq 'MSWin32' ? '' : ' 2>&1' ); | |
36 | my $zipout = `$cmd`; | |
37 | return wantarray ? ( $?, $zipout ) : $?; | |
38 | } | |
39 | ||
40 | # Return the crc-32 of the given file (0 if empty or error) | |
41 | sub fileCRC { | |
42 | my $fileName = shift; | |
43 | local $/ = undef; | |
44 | my $fh = IO::File->new( $fileName, "r" ); | |
45 | binmode($fh); | |
46 | return 0 if not defined($fh); | |
47 | my $contents = <$fh>; | |
48 | return Archive::Zip::computeCRC32($contents); | |
49 | } | |
50 | ||
51 | #--------- check to see if cat works | |
52 | ||
53 | sub testCat { | |
54 | my $fh = IO::File->new( CATPIPE . OUTPUTZIP ); | |
55 | binmode($fh); | |
56 | my $testString = pack( 'C256', 0 .. 255 ); | |
57 | my $testCrc = Archive::Zip::computeCRC32($testString); | |
58 | $fh->write( $testString, length($testString) ) or return 0; | |
59 | $fh->close(); | |
60 | ( -f OUTPUTZIP ) or return 0; | |
61 | my @stat = stat(OUTPUTZIP); | |
62 | $stat[7] == length($testString) or return 0; | |
63 | fileCRC(OUTPUTZIP) == $testCrc or return 0; | |
64 | unlink(OUTPUTZIP); | |
65 | return 1; | |
66 | } | |
67 | ||
68 | BEGIN { | |
69 | $catWorks = testCat(); | |
70 | unless ( $catWorks ) { | |
71 | warn( 'warning: ', CAT, " doesn't seem to work, may skip some tests" ); | |
72 | } | |
73 | } | |
74 | ||
75 | #--------- check to see if zip works (and make INPUTZIP) | |
76 | ||
77 | BEGIN { | |
78 | unlink(INPUTZIP); | |
79 | ||
80 | # Do we have zip installed? | |
81 | if ( HAVEZIP ) { | |
82 | my $cmd = ZIP . INPUTZIP . ' *' . ( $^O eq 'MSWin32' ? '' : ' 2>&1' ); | |
83 | $zipout = `$cmd`; | |
84 | $zipWorks = not $?; | |
85 | unless ( $zipWorks ) { | |
86 | warn( 'warning: ', ZIP, " doesn't seem to work, may skip some tests" ); | |
87 | } | |
88 | } | |
89 | } | |
90 | ||
91 | #--------- check to see if unzip -t works | |
92 | ||
93 | BEGIN { | |
94 | $testZipDoesntWork = 0; | |
95 | if ( HAVEUNZIP ) { | |
96 | my ( $status, $zipout ) = testZip(INPUTZIP); | |
97 | $testZipDoesntWork = $status; | |
98 | ||
99 | # Again, on Win32 no big surprise if this doesn't work | |
100 | if ( $testZipDoesntWork ) { | |
101 | warn( 'warning: ', ZIPTEST, " doesn't seem to work, may skip some tests" ); | |
102 | } | |
103 | } | |
104 | } | |
105 | ||
106 | 1; |