Neurogami-roir 1.3.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (251) hide show
  1. data/History.txt +22 -0
  2. data/Manifest.txt +250 -0
  3. data/README.md +12 -0
  4. data/Rakefile +63 -0
  5. data/bin/roir +176 -0
  6. data/data/GenericJavaApp.icns +0 -0
  7. data/data/JavaApplicationStub +0 -0
  8. data/data/Rawr logo 1 large.png +0 -0
  9. data/data/Rawr logo 2 large.png +0 -0
  10. data/data/Rawr logo2.png +0 -0
  11. data/data/website/_notes/dwsync.xml +1 -0
  12. data/data/website/css/1.css +254 -0
  13. data/data/website/css/_notes/dwsync.xml +1 -0
  14. data/data/website/images/_notes/dwsync.xml +1 -0
  15. data/data/website/images/body_bg.jpg +0 -0
  16. data/data/website/images/bullet.gif +0 -0
  17. data/data/website/images/content.jpg +0 -0
  18. data/data/website/images/content_h2.gif +0 -0
  19. data/data/website/images/details.gif +0 -0
  20. data/data/website/images/details.jpg +0 -0
  21. data/data/website/images/header_bg.jpg +0 -0
  22. data/data/website/images/nav_a_hover.gif +0 -0
  23. data/data/website/images/rawr_logo.png +0 -0
  24. data/data/website/index.html +164 -0
  25. data/lib/app_bundler.rb +138 -0
  26. data/lib/bundler.rb +36 -0
  27. data/lib/command.rb +68 -0
  28. data/lib/exe_bundler.rb +106 -0
  29. data/lib/generator.rb +127 -0
  30. data/lib/jar_builder.rb +57 -0
  31. data/lib/jruby_batch_compiler.rb +127 -0
  32. data/lib/jruby_release.rb +104 -0
  33. data/lib/launch4j/LICENSE.txt +30 -0
  34. data/lib/launch4j/bin-linux/COPYING +340 -0
  35. data/lib/launch4j/bin-linux/ld +0 -0
  36. data/lib/launch4j/bin-linux/windres +0 -0
  37. data/lib/launch4j/bin-mac/COPYING +340 -0
  38. data/lib/launch4j/bin-mac/ld +0 -0
  39. data/lib/launch4j/bin-mac/readme.txt +1 -0
  40. data/lib/launch4j/bin-mac/windres +0 -0
  41. data/lib/launch4j/bin-win/COPYING +340 -0
  42. data/lib/launch4j/bin-win/ld.exe +0 -0
  43. data/lib/launch4j/bin-win/windres.exe +0 -0
  44. data/lib/launch4j/build.xml +62 -0
  45. data/lib/launch4j/demo/ConsoleApp/ConsoleApp.jar +0 -0
  46. data/lib/launch4j/demo/ConsoleApp/build.xml +57 -0
  47. data/lib/launch4j/demo/ConsoleApp/l4j/ConsoleApp.ico +0 -0
  48. data/lib/launch4j/demo/ConsoleApp/lib/readme.txt +8 -0
  49. data/lib/launch4j/demo/ConsoleApp/readme.txt +1 -0
  50. data/lib/launch4j/demo/ConsoleApp/src/net/sf/launch4j/example/ConsoleApp.java +72 -0
  51. data/lib/launch4j/demo/LICENSE.txt +30 -0
  52. data/lib/launch4j/demo/SimpleApp/SimpleApp.jar +0 -0
  53. data/lib/launch4j/demo/SimpleApp/build.xml +52 -0
  54. data/lib/launch4j/demo/SimpleApp/l4j/SimpleApp.ico +0 -0
  55. data/lib/launch4j/demo/SimpleApp/l4j/SimpleApp.xml +18 -0
  56. data/lib/launch4j/demo/SimpleApp/l4j/splash.bmp +0 -0
  57. data/lib/launch4j/demo/SimpleApp/lib/readme.txt +8 -0
  58. data/lib/launch4j/demo/SimpleApp/readme.txt +1 -0
  59. data/lib/launch4j/demo/SimpleApp/src/net/sf/launch4j/example/SimpleApp.java +104 -0
  60. data/lib/launch4j/demo/readme.txt +3 -0
  61. data/lib/launch4j/head/LICENSE.txt +23 -0
  62. data/lib/launch4j/head/consolehead.o +0 -0
  63. data/lib/launch4j/head/guihead.o +0 -0
  64. data/lib/launch4j/head/head.o +0 -0
  65. data/lib/launch4j/head_src/LICENSE.txt +23 -0
  66. data/lib/launch4j/head_src/consolehead/Makefile.win +33 -0
  67. data/lib/launch4j/head_src/consolehead/consolehead.c +65 -0
  68. data/lib/launch4j/head_src/consolehead/consolehead.dev +108 -0
  69. data/lib/launch4j/head_src/guihead/Makefile.win +33 -0
  70. data/lib/launch4j/head_src/guihead/guihead.c +185 -0
  71. data/lib/launch4j/head_src/guihead/guihead.dev +109 -0
  72. data/lib/launch4j/head_src/guihead/guihead.h +43 -0
  73. data/lib/launch4j/head_src/head.c +807 -0
  74. data/lib/launch4j/head_src/head.h +113 -0
  75. data/lib/launch4j/head_src/resource.h +71 -0
  76. data/lib/launch4j/launch4j +8 -0
  77. data/lib/launch4j/launch4j.jar +0 -0
  78. data/lib/launch4j/launch4j.jfpr +0 -0
  79. data/lib/launch4j/lib/JGoodies.Forms.LICENSE.txt +31 -0
  80. data/lib/launch4j/lib/JGoodies.Looks.LICENSE.txt +31 -0
  81. data/lib/launch4j/lib/Nuvola.Icon.Theme.LICENSE.txt +504 -0
  82. data/lib/launch4j/lib/XStream.LICENSE.txt +27 -0
  83. data/lib/launch4j/lib/commons-beanutils.jar +0 -0
  84. data/lib/launch4j/lib/commons-logging.jar +0 -0
  85. data/lib/launch4j/lib/commons.LICENSE.txt +50 -0
  86. data/lib/launch4j/lib/forms.jar +0 -0
  87. data/lib/launch4j/lib/formsrt.jar +0 -0
  88. data/lib/launch4j/lib/foxtrot.LICENSE.txt +25 -0
  89. data/lib/launch4j/lib/foxtrot.jar +0 -0
  90. data/lib/launch4j/lib/looks.jar +0 -0
  91. data/lib/launch4j/lib/xstream.jar +0 -0
  92. data/lib/launch4j/manifest/uac.exe.manifest +12 -0
  93. data/lib/launch4j/manifest/xp-themes.exe.manifest +14 -0
  94. data/lib/launch4j/src/images/asterix-o.gif +0 -0
  95. data/lib/launch4j/src/images/asterix.gif +0 -0
  96. data/lib/launch4j/src/images/build.png +0 -0
  97. data/lib/launch4j/src/images/button_ok.png +0 -0
  98. data/lib/launch4j/src/images/cancel16.png +0 -0
  99. data/lib/launch4j/src/images/down16.png +0 -0
  100. data/lib/launch4j/src/images/edit_add16.png +0 -0
  101. data/lib/launch4j/src/images/info.png +0 -0
  102. data/lib/launch4j/src/images/new.png +0 -0
  103. data/lib/launch4j/src/images/new16.png +0 -0
  104. data/lib/launch4j/src/images/ok16.png +0 -0
  105. data/lib/launch4j/src/images/open.png +0 -0
  106. data/lib/launch4j/src/images/open16.png +0 -0
  107. data/lib/launch4j/src/images/run.png +0 -0
  108. data/lib/launch4j/src/images/save.png +0 -0
  109. data/lib/launch4j/src/images/up16.png +0 -0
  110. data/lib/launch4j/src/launch4j.properties +2 -0
  111. data/lib/launch4j/src/net/sf/launch4j/Builder.java +207 -0
  112. data/lib/launch4j/src/net/sf/launch4j/BuilderException.java +52 -0
  113. data/lib/launch4j/src/net/sf/launch4j/ExecException.java +66 -0
  114. data/lib/launch4j/src/net/sf/launch4j/FileChooserFilter.java +76 -0
  115. data/lib/launch4j/src/net/sf/launch4j/Log.java +105 -0
  116. data/lib/launch4j/src/net/sf/launch4j/Main.java +99 -0
  117. data/lib/launch4j/src/net/sf/launch4j/Messages.java +78 -0
  118. data/lib/launch4j/src/net/sf/launch4j/OptionParser.java +71 -0
  119. data/lib/launch4j/src/net/sf/launch4j/RcBuilder.java +340 -0
  120. data/lib/launch4j/src/net/sf/launch4j/Util.java +197 -0
  121. data/lib/launch4j/src/net/sf/launch4j/ant/AntClassPath.java +61 -0
  122. data/lib/launch4j/src/net/sf/launch4j/ant/AntConfig.java +129 -0
  123. data/lib/launch4j/src/net/sf/launch4j/ant/AntJre.java +69 -0
  124. data/lib/launch4j/src/net/sf/launch4j/ant/Launch4jTask.java +162 -0
  125. data/lib/launch4j/src/net/sf/launch4j/ant/Messages.java +55 -0
  126. data/lib/launch4j/src/net/sf/launch4j/ant/StringWrapper.java +67 -0
  127. data/lib/launch4j/src/net/sf/launch4j/ant/messages.properties +35 -0
  128. data/lib/launch4j/src/net/sf/launch4j/ant/messages_es.properties +35 -0
  129. data/lib/launch4j/src/net/sf/launch4j/binding/Binding.java +62 -0
  130. data/lib/launch4j/src/net/sf/launch4j/binding/BindingException.java +52 -0
  131. data/lib/launch4j/src/net/sf/launch4j/binding/Bindings.java +317 -0
  132. data/lib/launch4j/src/net/sf/launch4j/binding/IValidatable.java +44 -0
  133. data/lib/launch4j/src/net/sf/launch4j/binding/InvariantViolationException.java +67 -0
  134. data/lib/launch4j/src/net/sf/launch4j/binding/JComboBoxBinding.java +119 -0
  135. data/lib/launch4j/src/net/sf/launch4j/binding/JListBinding.java +118 -0
  136. data/lib/launch4j/src/net/sf/launch4j/binding/JRadioButtonBinding.java +146 -0
  137. data/lib/launch4j/src/net/sf/launch4j/binding/JTextAreaBinding.java +123 -0
  138. data/lib/launch4j/src/net/sf/launch4j/binding/JTextComponentBinding.java +108 -0
  139. data/lib/launch4j/src/net/sf/launch4j/binding/JToggleButtonBinding.java +108 -0
  140. data/lib/launch4j/src/net/sf/launch4j/binding/Messages.java +78 -0
  141. data/lib/launch4j/src/net/sf/launch4j/binding/OptComponentBinding.java +119 -0
  142. data/lib/launch4j/src/net/sf/launch4j/binding/OptJTextAreaBinding.java +141 -0
  143. data/lib/launch4j/src/net/sf/launch4j/binding/Validator.java +259 -0
  144. data/lib/launch4j/src/net/sf/launch4j/binding/messages.properties +52 -0
  145. data/lib/launch4j/src/net/sf/launch4j/binding/messages_es.properties +51 -0
  146. data/lib/launch4j/src/net/sf/launch4j/config/ClassPath.java +87 -0
  147. data/lib/launch4j/src/net/sf/launch4j/config/Config.java +396 -0
  148. data/lib/launch4j/src/net/sf/launch4j/config/ConfigPersister.java +249 -0
  149. data/lib/launch4j/src/net/sf/launch4j/config/ConfigPersisterException.java +51 -0
  150. data/lib/launch4j/src/net/sf/launch4j/config/Jre.java +235 -0
  151. data/lib/launch4j/src/net/sf/launch4j/config/LdDefaults.java +62 -0
  152. data/lib/launch4j/src/net/sf/launch4j/config/Messages.java +78 -0
  153. data/lib/launch4j/src/net/sf/launch4j/config/Msg.java +111 -0
  154. data/lib/launch4j/src/net/sf/launch4j/config/SingleInstance.java +74 -0
  155. data/lib/launch4j/src/net/sf/launch4j/config/Splash.java +103 -0
  156. data/lib/launch4j/src/net/sf/launch4j/config/VersionInfo.java +168 -0
  157. data/lib/launch4j/src/net/sf/launch4j/config/messages.properties +93 -0
  158. data/lib/launch4j/src/net/sf/launch4j/config/messages_es.properties +75 -0
  159. data/lib/launch4j/src/net/sf/launch4j/form/BasicForm.java +283 -0
  160. data/lib/launch4j/src/net/sf/launch4j/form/BasicForm.jfrm +0 -0
  161. data/lib/launch4j/src/net/sf/launch4j/form/ClassPathForm.java +193 -0
  162. data/lib/launch4j/src/net/sf/launch4j/form/ClassPathForm.jfrm +0 -0
  163. data/lib/launch4j/src/net/sf/launch4j/form/ConfigForm.java +132 -0
  164. data/lib/launch4j/src/net/sf/launch4j/form/ConfigForm.jfrm +0 -0
  165. data/lib/launch4j/src/net/sf/launch4j/form/EnvironmentVarsForm.java +127 -0
  166. data/lib/launch4j/src/net/sf/launch4j/form/EnvironmentVarsForm.jfrm +0 -0
  167. data/lib/launch4j/src/net/sf/launch4j/form/HeaderForm.java +171 -0
  168. data/lib/launch4j/src/net/sf/launch4j/form/HeaderForm.jfrm +0 -0
  169. data/lib/launch4j/src/net/sf/launch4j/form/JreForm.java +266 -0
  170. data/lib/launch4j/src/net/sf/launch4j/form/JreForm.jfrm +0 -0
  171. data/lib/launch4j/src/net/sf/launch4j/form/Messages.java +55 -0
  172. data/lib/launch4j/src/net/sf/launch4j/form/MessagesForm.java +183 -0
  173. data/lib/launch4j/src/net/sf/launch4j/form/MessagesForm.jfrm +0 -0
  174. data/lib/launch4j/src/net/sf/launch4j/form/SingleInstanceForm.java +141 -0
  175. data/lib/launch4j/src/net/sf/launch4j/form/SingleInstanceForm.jfrm +0 -0
  176. data/lib/launch4j/src/net/sf/launch4j/form/SplashForm.java +166 -0
  177. data/lib/launch4j/src/net/sf/launch4j/form/SplashForm.jfrm +0 -0
  178. data/lib/launch4j/src/net/sf/launch4j/form/VersionInfoForm.java +232 -0
  179. data/lib/launch4j/src/net/sf/launch4j/form/VersionInfoForm.jfrm +0 -0
  180. data/lib/launch4j/src/net/sf/launch4j/form/messages.properties +146 -0
  181. data/lib/launch4j/src/net/sf/launch4j/form/messages_es.properties +118 -0
  182. data/lib/launch4j/src/net/sf/launch4j/formimpl/AbstractAcceptListener.java +75 -0
  183. data/lib/launch4j/src/net/sf/launch4j/formimpl/BasicFormImpl.java +101 -0
  184. data/lib/launch4j/src/net/sf/launch4j/formimpl/BrowseActionListener.java +79 -0
  185. data/lib/launch4j/src/net/sf/launch4j/formimpl/ClassPathFormImpl.java +222 -0
  186. data/lib/launch4j/src/net/sf/launch4j/formimpl/ConfigFormImpl.java +100 -0
  187. data/lib/launch4j/src/net/sf/launch4j/formimpl/EnvironmentVarsFormImpl.java +50 -0
  188. data/lib/launch4j/src/net/sf/launch4j/formimpl/FileChooser.java +65 -0
  189. data/lib/launch4j/src/net/sf/launch4j/formimpl/GlassPane.java +67 -0
  190. data/lib/launch4j/src/net/sf/launch4j/formimpl/HeaderFormImpl.java +102 -0
  191. data/lib/launch4j/src/net/sf/launch4j/formimpl/JreFormImpl.java +166 -0
  192. data/lib/launch4j/src/net/sf/launch4j/formimpl/MainFrame.java +358 -0
  193. data/lib/launch4j/src/net/sf/launch4j/formimpl/Messages.java +55 -0
  194. data/lib/launch4j/src/net/sf/launch4j/formimpl/MessagesFormImpl.java +58 -0
  195. data/lib/launch4j/src/net/sf/launch4j/formimpl/SingleInstanceFormImpl.java +54 -0
  196. data/lib/launch4j/src/net/sf/launch4j/formimpl/SplashFormImpl.java +61 -0
  197. data/lib/launch4j/src/net/sf/launch4j/formimpl/VersionInfoFormImpl.java +63 -0
  198. data/lib/launch4j/src/net/sf/launch4j/formimpl/messages.properties +74 -0
  199. data/lib/launch4j/src/net/sf/launch4j/formimpl/messages_es.properties +56 -0
  200. data/lib/launch4j/src/net/sf/launch4j/messages.properties +45 -0
  201. data/lib/launch4j/src/net/sf/launch4j/messages_es.properties +45 -0
  202. data/lib/launch4j/w32api/MinGW.LICENSE.txt +25 -0
  203. data/lib/launch4j/w32api/crt2.o +0 -0
  204. data/lib/launch4j/w32api/libadvapi32.a +0 -0
  205. data/lib/launch4j/w32api/libgcc.a +0 -0
  206. data/lib/launch4j/w32api/libkernel32.a +0 -0
  207. data/lib/launch4j/w32api/libmingw32.a +0 -0
  208. data/lib/launch4j/w32api/libmsvcrt.a +0 -0
  209. data/lib/launch4j/w32api/libshell32.a +0 -0
  210. data/lib/launch4j/w32api/libuser32.a +0 -0
  211. data/lib/launch4j/web/bullet.gif +0 -0
  212. data/lib/launch4j/web/changelog.html +356 -0
  213. data/lib/launch4j/web/docs.html +585 -0
  214. data/lib/launch4j/web/index.html +128 -0
  215. data/lib/launch4j/web/launch4j-use.gif +0 -0
  216. data/lib/launch4j/web/launch4j.gif +0 -0
  217. data/lib/launch4j/web/links.html +73 -0
  218. data/lib/launch4j/web/style.css +159 -0
  219. data/lib/options.rb +101 -0
  220. data/lib/platform.rb +28 -0
  221. data/lib/rawr.rb +178 -0
  222. data/lib/rawr_bundle.rb +26 -0
  223. data/lib/rawr_version.rb +3 -0
  224. data/lib/web_bundler.rb +114 -0
  225. data/lib/zip/ioextras.rb +155 -0
  226. data/lib/zip/stdrubyext.rb +111 -0
  227. data/lib/zip/tempfile_bugfixed.rb +195 -0
  228. data/lib/zip/zip.rb +1848 -0
  229. data/lib/zip/zipfilesystem.rb +609 -0
  230. data/lib/zip/ziprequire.rb +90 -0
  231. data/tasks/ann.rake +81 -0
  232. data/tasks/bones.rake +21 -0
  233. data/tasks/gem.rake +121 -0
  234. data/tasks/manifest.rake +49 -0
  235. data/tasks/notes.rake +28 -0
  236. data/tasks/post_load.rake +39 -0
  237. data/tasks/rdoc.rake +51 -0
  238. data/tasks/rubyforge.rake +57 -0
  239. data/tasks/setup.rb +269 -0
  240. data/tasks/spec.rake +63 -0
  241. data/tasks/svn.rake +48 -0
  242. data/tasks/test.rake +38 -0
  243. data/test/unit/app_bundler_spec.rb +65 -0
  244. data/test/unit/bundler_spec.rb +27 -0
  245. data/test/unit/generator_spec.rb +49 -0
  246. data/test/unit/jar_builder_spec.rb +5 -0
  247. data/test/unit/options_spec.rb +97 -0
  248. data/test/unit/platform_spec.rb +29 -0
  249. data/test/unit/spec_helpers.rb +22 -0
  250. data/test/unit/web_bundler_spec.rb +83 -0
  251. metadata +501 -0
data/lib/zip/zip.rb ADDED
@@ -0,0 +1,1848 @@
1
+ require 'delegate'
2
+ require 'singleton'
3
+ require 'tempfile'
4
+ require 'ftools'
5
+ require 'stringio'
6
+ require 'zlib'
7
+ require 'zip/stdrubyext'
8
+ require 'zip/ioextras'
9
+ require 'rbconfig'
10
+
11
+ if Tempfile.superclass == SimpleDelegator
12
+ require 'zip/tempfile_bugfixed'
13
+ Tempfile = BugFix::Tempfile
14
+ end
15
+
16
+ module Zlib #:nodoc:all
17
+ if ! const_defined? :MAX_WBITS
18
+ MAX_WBITS = Zlib::Deflate.MAX_WBITS
19
+ end
20
+ end
21
+
22
+ module Zip
23
+
24
+ VERSION = '0.9.1'
25
+
26
+ RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i
27
+
28
+ RUNNING_ON_WINDOWS = Config::CONFIG['host_os'] =~ /^win|mswin/i
29
+
30
+ # Ruby 1.7.x compatibility
31
+ # In ruby 1.6.x and 1.8.0 reading from an empty stream returns
32
+ # an empty string the first time and then nil.
33
+ # not so in 1.7.x
34
+ EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7
35
+
36
+ # ZipInputStream is the basic class for reading zip entries in a
37
+ # zip file. It is possible to create a ZipInputStream object directly,
38
+ # passing the zip file name to the constructor, but more often than not
39
+ # the ZipInputStream will be obtained from a ZipFile (perhaps using the
40
+ # ZipFileSystem interface) object for a particular entry in the zip
41
+ # archive.
42
+ #
43
+ # A ZipInputStream inherits IOExtras::AbstractInputStream in order
44
+ # to provide an IO-like interface for reading from a single zip
45
+ # entry. Beyond methods for mimicking an IO-object it contains
46
+ # the method get_next_entry for iterating through the entries of
47
+ # an archive. get_next_entry returns a ZipEntry object that describes
48
+ # the zip entry the ZipInputStream is currently reading from.
49
+ #
50
+ # Example that creates a zip archive with ZipOutputStream and reads it
51
+ # back again with a ZipInputStream.
52
+ #
53
+ # require 'zip/zip'
54
+ #
55
+ # Zip::ZipOutputStream::open("my.zip") {
56
+ # |io|
57
+ #
58
+ # io.put_next_entry("first_entry.txt")
59
+ # io.write "Hello world!"
60
+ #
61
+ # io.put_next_entry("adir/first_entry.txt")
62
+ # io.write "Hello again!"
63
+ # }
64
+ #
65
+ #
66
+ # Zip::ZipInputStream::open("my.zip") {
67
+ # |io|
68
+ #
69
+ # while (entry = io.get_next_entry)
70
+ # puts "Contents of #{entry.name}: '#{io.read}'"
71
+ # end
72
+ # }
73
+ #
74
+ # java.util.zip.ZipInputStream is the original inspiration for this
75
+ # class.
76
+
77
+ class ZipInputStream
78
+ include IOExtras::AbstractInputStream
79
+
80
+ # Opens the indicated zip file. An exception is thrown
81
+ # if the specified offset in the specified filename is
82
+ # not a local zip entry header.
83
+ def initialize(filename, offset = 0)
84
+ super()
85
+ @archiveIO = File.open(filename, "rb")
86
+ @archiveIO.seek(offset, IO::SEEK_SET)
87
+ @decompressor = NullDecompressor.instance
88
+ @currentEntry = nil
89
+ end
90
+
91
+ def close
92
+ @archiveIO.close
93
+ end
94
+
95
+ # Same as #initialize but if a block is passed the opened
96
+ # stream is passed to the block and closed when the block
97
+ # returns.
98
+ def ZipInputStream.open(filename)
99
+ return new(filename) unless block_given?
100
+
101
+ zio = new(filename)
102
+ yield zio
103
+ ensure
104
+ zio.close if zio
105
+ end
106
+
107
+ # Returns a ZipEntry object. It is necessary to call this
108
+ # method on a newly created ZipInputStream before reading from
109
+ # the first entry in the archive. Returns nil when there are
110
+ # no more entries.
111
+
112
+ def get_next_entry
113
+ @archiveIO.seek(@currentEntry.next_header_offset,
114
+ IO::SEEK_SET) if @currentEntry
115
+ open_entry
116
+ end
117
+
118
+ # Rewinds the stream to the beginning of the current entry
119
+ def rewind
120
+ return if @currentEntry.nil?
121
+ @lineno = 0
122
+ @archiveIO.seek(@currentEntry.localHeaderOffset,
123
+ IO::SEEK_SET)
124
+ open_entry
125
+ end
126
+
127
+ # Modeled after IO.sysread
128
+ def sysread(numberOfBytes = nil, buf = nil)
129
+ @decompressor.sysread(numberOfBytes, buf)
130
+ end
131
+
132
+ def eof
133
+ @outputBuffer.empty? && @decompressor.eof
134
+ end
135
+ alias :eof? :eof
136
+
137
+ protected
138
+
139
+ def open_entry
140
+ @currentEntry = ZipEntry.read_local_entry(@archiveIO)
141
+ if (@currentEntry == nil)
142
+ @decompressor = NullDecompressor.instance
143
+ elsif @currentEntry.compression_method == ZipEntry::STORED
144
+ @decompressor = PassThruDecompressor.new(@archiveIO,
145
+ @currentEntry.size)
146
+ elsif @currentEntry.compression_method == ZipEntry::DEFLATED
147
+ @decompressor = Inflater.new(@archiveIO)
148
+ else
149
+ raise ZipCompressionMethodError,
150
+ "Unsupported compression method #{@currentEntry.compression_method}"
151
+ end
152
+ flush
153
+ return @currentEntry
154
+ end
155
+
156
+ def produce_input
157
+ @decompressor.produce_input
158
+ end
159
+
160
+ def input_finished?
161
+ @decompressor.input_finished?
162
+ end
163
+ end
164
+
165
+
166
+
167
+ class Decompressor #:nodoc:all
168
+ CHUNK_SIZE=32768
169
+ def initialize(inputStream)
170
+ super()
171
+ @inputStream=inputStream
172
+ end
173
+ end
174
+
175
+ class Inflater < Decompressor #:nodoc:all
176
+ def initialize(inputStream)
177
+ super
178
+ @zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
179
+ @outputBuffer=""
180
+ @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
181
+ end
182
+
183
+ def sysread(numberOfBytes = nil, buf = nil)
184
+ readEverything = (numberOfBytes == nil)
185
+ while (readEverything || @outputBuffer.length < numberOfBytes)
186
+ break if internal_input_finished?
187
+ @outputBuffer << internal_produce_input(buf)
188
+ end
189
+ return value_when_finished if @outputBuffer.length==0 && input_finished?
190
+ endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes
191
+ return @outputBuffer.slice!(0...endIndex)
192
+ end
193
+
194
+ def produce_input
195
+ if (@outputBuffer.empty?)
196
+ return internal_produce_input
197
+ else
198
+ return @outputBuffer.slice!(0...(@outputBuffer.length))
199
+ end
200
+ end
201
+
202
+ # to be used with produce_input, not read (as read may still have more data cached)
203
+ # is data cached anywhere other than @outputBuffer? the comment above may be wrong
204
+ def input_finished?
205
+ @outputBuffer.empty? && internal_input_finished?
206
+ end
207
+ alias :eof :input_finished?
208
+ alias :eof? :input_finished?
209
+
210
+ private
211
+
212
+ def internal_produce_input(buf = nil)
213
+ retried = 0
214
+ begin
215
+ @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE, buf))
216
+ rescue Zlib::BufError
217
+ raise if (retried >= 5) # how many times should we retry?
218
+ retried += 1
219
+ retry
220
+ end
221
+ end
222
+
223
+ def internal_input_finished?
224
+ @zlibInflater.finished?
225
+ end
226
+
227
+ # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
228
+ def value_when_finished # mimic behaviour of ruby File object.
229
+ return nil if @hasReturnedEmptyString
230
+ @hasReturnedEmptyString=true
231
+ return ""
232
+ end
233
+ end
234
+
235
+ class PassThruDecompressor < Decompressor #:nodoc:all
236
+ def initialize(inputStream, charsToRead)
237
+ super inputStream
238
+ @charsToRead = charsToRead
239
+ @readSoFar = 0
240
+ @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
241
+ end
242
+
243
+ # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
244
+ def sysread(numberOfBytes = nil, buf = nil)
245
+ if input_finished?
246
+ hasReturnedEmptyStringVal=@hasReturnedEmptyString
247
+ @hasReturnedEmptyString=true
248
+ return "" unless hasReturnedEmptyStringVal
249
+ return nil
250
+ end
251
+
252
+ if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead)
253
+ numberOfBytes = @charsToRead-@readSoFar
254
+ end
255
+ @readSoFar += numberOfBytes
256
+ @inputStream.read(numberOfBytes, buf)
257
+ end
258
+
259
+ def produce_input
260
+ sysread(Decompressor::CHUNK_SIZE)
261
+ end
262
+
263
+ def input_finished?
264
+ (@readSoFar >= @charsToRead)
265
+ end
266
+ alias :eof :input_finished?
267
+ alias :eof? :input_finished?
268
+ end
269
+
270
+ class NullDecompressor #:nodoc:all
271
+ include Singleton
272
+ def sysread(numberOfBytes = nil, buf = nil)
273
+ nil
274
+ end
275
+
276
+ def produce_input
277
+ nil
278
+ end
279
+
280
+ def input_finished?
281
+ true
282
+ end
283
+
284
+ def eof
285
+ true
286
+ end
287
+ alias :eof? :eof
288
+ end
289
+
290
+ class NullInputStream < NullDecompressor #:nodoc:all
291
+ include IOExtras::AbstractInputStream
292
+ end
293
+
294
+ class ZipEntry
295
+ STORED = 0
296
+ DEFLATED = 8
297
+
298
+ FSTYPE_FAT = 0
299
+ FSTYPE_AMIGA = 1
300
+ FSTYPE_VMS = 2
301
+ FSTYPE_UNIX = 3
302
+ FSTYPE_VM_CMS = 4
303
+ FSTYPE_ATARI = 5
304
+ FSTYPE_HPFS = 6
305
+ FSTYPE_MAC = 7
306
+ FSTYPE_Z_SYSTEM = 8
307
+ FSTYPE_CPM = 9
308
+ FSTYPE_TOPS20 = 10
309
+ FSTYPE_NTFS = 11
310
+ FSTYPE_QDOS = 12
311
+ FSTYPE_ACORN = 13
312
+ FSTYPE_VFAT = 14
313
+ FSTYPE_MVS = 15
314
+ FSTYPE_BEOS = 16
315
+ FSTYPE_TANDEM = 17
316
+ FSTYPE_THEOS = 18
317
+ FSTYPE_MAC_OSX = 19
318
+ FSTYPE_ATHEOS = 30
319
+
320
+ FSTYPES = {
321
+ FSTYPE_FAT => 'FAT'.freeze,
322
+ FSTYPE_AMIGA => 'Amiga'.freeze,
323
+ FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze,
324
+ FSTYPE_UNIX => 'Unix'.freeze,
325
+ FSTYPE_VM_CMS => 'VM/CMS'.freeze,
326
+ FSTYPE_ATARI => 'Atari ST'.freeze,
327
+ FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze,
328
+ FSTYPE_MAC => 'Macintosh'.freeze,
329
+ FSTYPE_Z_SYSTEM => 'Z-System'.freeze,
330
+ FSTYPE_CPM => 'CP/M'.freeze,
331
+ FSTYPE_TOPS20 => 'TOPS-20'.freeze,
332
+ FSTYPE_NTFS => 'NTFS'.freeze,
333
+ FSTYPE_QDOS => 'SMS/QDOS'.freeze,
334
+ FSTYPE_ACORN => 'Acorn RISC OS'.freeze,
335
+ FSTYPE_VFAT => 'Win32 VFAT'.freeze,
336
+ FSTYPE_MVS => 'MVS'.freeze,
337
+ FSTYPE_BEOS => 'BeOS'.freeze,
338
+ FSTYPE_TANDEM => 'Tandem NSK'.freeze,
339
+ FSTYPE_THEOS => 'Theos'.freeze,
340
+ FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze,
341
+ FSTYPE_ATHEOS => 'AtheOS'.freeze,
342
+ }.freeze
343
+
344
+ attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
345
+ :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes, :gp_flags, :header_signature
346
+
347
+ attr_accessor :follow_symlinks
348
+ attr_accessor :restore_times, :restore_permissions, :restore_ownership
349
+ attr_accessor :unix_uid, :unix_gid, :unix_perms
350
+
351
+ attr_reader :ftype, :filepath # :nodoc:
352
+
353
+ def initialize(zipfile = "", name = "", comment = "", extra = "",
354
+ compressed_size = 0, crc = 0,
355
+ compression_method = ZipEntry::DEFLATED, size = 0,
356
+ time = Time.now)
357
+ super()
358
+ if name.starts_with("/")
359
+ raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /"
360
+ end
361
+ @localHeaderOffset = 0
362
+ @internalFileAttributes = 1
363
+ @externalFileAttributes = 0
364
+ @version = 52 # this library's version
365
+ @ftype = nil # unspecified or unknown
366
+ @filepath = nil
367
+ if Zip::RUNNING_ON_WINDOWS
368
+ @fstype = FSTYPE_FAT
369
+ else
370
+ @fstype = FSTYPE_UNIX
371
+ end
372
+ @zipfile, @comment, @compressed_size, @crc, @extra, @compression_method,
373
+ @name, @size = zipfile, comment, compressed_size, crc,
374
+ extra, compression_method, name, size
375
+ @time = time
376
+
377
+ @follow_symlinks = false
378
+
379
+ @restore_times = true
380
+ @restore_permissions = false
381
+ @restore_ownership = false
382
+
383
+ # BUG: need an extra field to support uid/gid's
384
+ @unix_uid = nil
385
+ @unix_gid = nil
386
+ @unix_perms = nil
387
+ # @posix_acl = nil
388
+ # @ntfs_acl = nil
389
+
390
+ if name_is_directory?
391
+ @ftype = :directory
392
+ else
393
+ @ftype = :file
394
+ end
395
+
396
+ unless ZipExtraField === @extra
397
+ @extra = ZipExtraField.new(@extra.to_s)
398
+ end
399
+ end
400
+
401
+ def time
402
+ if @extra["UniversalTime"]
403
+ @extra["UniversalTime"].mtime
404
+ else
405
+ # Atandard time field in central directory has local time
406
+ # under archive creator. Then, we can't get timezone.
407
+ @time
408
+ end
409
+ end
410
+ alias :mtime :time
411
+
412
+ def time=(aTime)
413
+ unless @extra.member?("UniversalTime")
414
+ @extra.create("UniversalTime")
415
+ end
416
+ @extra["UniversalTime"].mtime = aTime
417
+ @time = aTime
418
+ end
419
+
420
+ # Returns +true+ if the entry is a directory.
421
+ def directory?
422
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
423
+ @ftype == :directory
424
+ end
425
+ alias :is_directory :directory?
426
+
427
+ # Returns +true+ if the entry is a file.
428
+ def file?
429
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
430
+ @ftype == :file
431
+ end
432
+
433
+ # Returns +true+ if the entry is a symlink.
434
+ def symlink?
435
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
436
+ @ftype == :link
437
+ end
438
+
439
+ def name_is_directory? #:nodoc:all
440
+ (%r{\/$} =~ @name) != nil
441
+ end
442
+
443
+ def local_entry_offset #:nodoc:all
444
+ localHeaderOffset + local_header_size
445
+ end
446
+
447
+ def local_header_size #:nodoc:all
448
+ LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.local_size : 0)
449
+ end
450
+
451
+ def cdir_header_size #:nodoc:all
452
+ CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) +
453
+ (@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 0)
454
+ end
455
+
456
+ def next_header_offset #:nodoc:all
457
+ local_entry_offset + self.compressed_size
458
+ end
459
+
460
+ # Extracts entry to file destPath (defaults to @name).
461
+ def extract(destPath = @name, &onExistsProc)
462
+ onExistsProc ||= proc { false }
463
+
464
+ if directory?
465
+ create_directory(destPath, &onExistsProc)
466
+ elsif file?
467
+ write_file(destPath, &onExistsProc)
468
+ elsif symlink?
469
+ create_symlink(destPath, &onExistsProc)
470
+ else
471
+ raise RuntimeError, "unknown file type #{self.inspect}"
472
+ end
473
+
474
+ self
475
+ end
476
+
477
+ def to_s
478
+ @name
479
+ end
480
+
481
+ protected
482
+
483
+ def ZipEntry.read_zip_short(io) # :nodoc:
484
+ io.read(2).unpack('v')[0]
485
+ end
486
+
487
+ def ZipEntry.read_zip_long(io) # :nodoc:
488
+ io.read(4).unpack('V')[0]
489
+ end
490
+ public
491
+
492
+ LOCAL_ENTRY_SIGNATURE = 0x04034b50
493
+ LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30
494
+ LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4
495
+
496
+ def read_local_entry(io) #:nodoc:all
497
+ @localHeaderOffset = io.tell
498
+ staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH)
499
+ unless (staticSizedFieldsBuf.size==LOCAL_ENTRY_STATIC_HEADER_LENGTH)
500
+ raise ZipError, "Premature end of file. Not enough data for zip entry local header"
501
+ end
502
+
503
+ @header_signature ,
504
+ @version ,
505
+ @fstype ,
506
+ @gp_flags ,
507
+ @compression_method,
508
+ lastModTime ,
509
+ lastModDate ,
510
+ @crc ,
511
+ @compressed_size ,
512
+ @size ,
513
+ nameLength ,
514
+ extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
515
+
516
+ unless (@header_signature == LOCAL_ENTRY_SIGNATURE)
517
+ raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
518
+ end
519
+ set_time(lastModDate, lastModTime)
520
+
521
+ @name = io.read(nameLength)
522
+ extra = io.read(extraLength)
523
+
524
+ if (extra && extra.length != extraLength)
525
+ raise ZipError, "Truncated local zip entry header"
526
+ else
527
+ if ZipExtraField === @extra
528
+ @extra.merge(extra)
529
+ else
530
+ @extra = ZipExtraField.new(extra)
531
+ end
532
+ end
533
+ end
534
+
535
+ def ZipEntry.read_local_entry(io)
536
+ entry = new(io.path)
537
+ entry.read_local_entry(io)
538
+ return entry
539
+ rescue ZipError
540
+ return nil
541
+ end
542
+
543
+ def write_local_entry(io) #:nodoc:all
544
+ @localHeaderOffset = io.tell
545
+
546
+ io <<
547
+ [LOCAL_ENTRY_SIGNATURE ,
548
+ 0 ,
549
+ 0 , # @gp_flags ,
550
+ @compression_method ,
551
+ @time.to_binary_dos_time , # @lastModTime ,
552
+ @time.to_binary_dos_date , # @lastModDate ,
553
+ @crc ,
554
+ @compressed_size ,
555
+ @size ,
556
+ @name ? @name.length : 0,
557
+ @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv')
558
+ io << @name
559
+ io << (@extra ? @extra.to_local_bin : "")
560
+ end
561
+
562
+ CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
563
+ CDIR_ENTRY_STATIC_HEADER_LENGTH = 46
564
+
565
+ def read_c_dir_entry(io) #:nodoc:all
566
+ staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH)
567
+ unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH)
568
+ raise ZipError, "Premature end of file. Not enough data for zip cdir entry header"
569
+ end
570
+
571
+ @header_signature ,
572
+ @version , # version of encoding software
573
+ @fstype , # filesystem type
574
+ @versionNeededToExtract,
575
+ @gp_flags ,
576
+ @compression_method ,
577
+ lastModTime ,
578
+ lastModDate ,
579
+ @crc ,
580
+ @compressed_size ,
581
+ @size ,
582
+ nameLength ,
583
+ extraLength ,
584
+ commentLength ,
585
+ diskNumberStart ,
586
+ @internalFileAttributes,
587
+ @externalFileAttributes,
588
+ @localHeaderOffset ,
589
+ @name ,
590
+ @extra ,
591
+ @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
592
+
593
+ unless (@header_signature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
594
+ raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
595
+ end
596
+ set_time(lastModDate, lastModTime)
597
+
598
+ @name = io.read(nameLength)
599
+ if ZipExtraField === @extra
600
+ @extra.merge(io.read(extraLength))
601
+ else
602
+ @extra = ZipExtraField.new(io.read(extraLength))
603
+ end
604
+ @comment = io.read(commentLength)
605
+ unless (@comment && @comment.length == commentLength)
606
+ raise ZipError, "Truncated cdir zip entry header"
607
+ end
608
+
609
+ case @fstype
610
+ when FSTYPE_UNIX
611
+ @unix_perms = (@externalFileAttributes >> 16) & 07777
612
+
613
+ case (@externalFileAttributes >> 28)
614
+ when 04
615
+ @ftype = :directory
616
+ when 010
617
+ @ftype = :file
618
+ when 012
619
+ @ftype = :link
620
+ else
621
+ raise ZipInternalError, "unknown file type #{'0%o' % (@externalFileAttributes >> 28)}"
622
+ end
623
+ else
624
+ if name_is_directory?
625
+ @ftype = :directory
626
+ else
627
+ @ftype = :file
628
+ end
629
+ end
630
+ end
631
+
632
+ def ZipEntry.read_c_dir_entry(io) #:nodoc:all
633
+ entry = new(io.path)
634
+ entry.read_c_dir_entry(io)
635
+ return entry
636
+ rescue ZipError
637
+ return nil
638
+ end
639
+
640
+ def file_stat(path) # :nodoc:
641
+ if @follow_symlinks
642
+ return File::stat(path)
643
+ else
644
+ return File::lstat(path)
645
+ end
646
+ end
647
+
648
+ def get_extra_attributes_from_path(path) # :nodoc:
649
+ unless Zip::RUNNING_ON_WINDOWS
650
+ stat = file_stat(path)
651
+ @unix_uid = stat.uid
652
+ @unix_gid = stat.gid
653
+ @unix_perms = stat.mode & 07777
654
+ end
655
+ end
656
+
657
+ def set_extra_attributes_on_path(destPath) # :nodoc:
658
+ return unless (file? or directory?)
659
+
660
+ case @fstype
661
+ when FSTYPE_UNIX
662
+ # BUG: does not update timestamps into account
663
+ # ignore setuid/setgid bits by default. honor if @restore_ownership
664
+ unix_perms_mask = 01777
665
+ unix_perms_mask = 07777 if (@restore_ownership)
666
+ File::chmod(@unix_perms & unix_perms_mask, destPath) if (@restore_permissions && @unix_perms)
667
+ File::chown(@unix_uid, @unix_gid, destPath) if (@restore_ownership && @unix_uid && @unix_gid && Process::egid == 0)
668
+ # File::utimes()
669
+ end
670
+ end
671
+
672
+ def write_c_dir_entry(io) #:nodoc:all
673
+ case @fstype
674
+ when FSTYPE_UNIX
675
+ ft = nil
676
+ case @ftype
677
+ when :file
678
+ ft = 010
679
+ @unix_perms ||= 0644
680
+ when :directory
681
+ ft = 004
682
+ @unix_perms ||= 0755
683
+ when :symlink
684
+ ft = 012
685
+ @unix_perms ||= 0755
686
+ else
687
+ raise ZipInternalError, "unknown file type #{self.inspect}"
688
+ end
689
+
690
+ @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16
691
+ end
692
+
693
+ io <<
694
+ [CENTRAL_DIRECTORY_ENTRY_SIGNATURE,
695
+ @version , # version of encoding software
696
+ @fstype , # filesystem type
697
+ 0 , # @versionNeededToExtract ,
698
+ 0 , # @gp_flags ,
699
+ @compression_method ,
700
+ @time.to_binary_dos_time , # @lastModTime ,
701
+ @time.to_binary_dos_date , # @lastModDate ,
702
+ @crc ,
703
+ @compressed_size ,
704
+ @size ,
705
+ @name ? @name.length : 0 ,
706
+ @extra ? @extra.c_dir_length : 0 ,
707
+ @comment ? comment.length : 0 ,
708
+ 0 , # disk number start
709
+ @internalFileAttributes , # file type (binary=0, text=1)
710
+ @externalFileAttributes , # native filesystem attributes
711
+ @localHeaderOffset ,
712
+ @name ,
713
+ @extra ,
714
+ @comment ].pack('VCCvvvvvVVVvvvvvVV')
715
+
716
+ io << @name
717
+ io << (@extra ? @extra.to_c_dir_bin : "")
718
+ io << @comment
719
+ end
720
+
721
+ def == (other)
722
+ return false unless other.class == self.class
723
+ # Compares contents of local entry and exposed fields
724
+ (@compression_method == other.compression_method &&
725
+ @crc == other.crc &&
726
+ @compressed_size == other.compressed_size &&
727
+ @size == other.size &&
728
+ @name == other.name &&
729
+ @extra == other.extra &&
730
+ @filepath == other.filepath &&
731
+ self.time.dos_equals(other.time))
732
+ end
733
+
734
+ def <=> (other)
735
+ return to_s <=> other.to_s
736
+ end
737
+
738
+ # Returns an IO like object for the given ZipEntry.
739
+ # Warning: may behave weird with symlinks.
740
+ def get_input_stream(&aProc)
741
+ if @ftype == :directory
742
+ return yield(NullInputStream.instance) if block_given?
743
+ return NullInputStream.instance
744
+ elsif @filepath
745
+ case @ftype
746
+ when :file
747
+ return File.open(@filepath, "rb", &aProc)
748
+
749
+ when :symlink
750
+ linkpath = File::readlink(@filepath)
751
+ stringio = StringIO.new(linkpath)
752
+ return yield(stringio) if block_given?
753
+ return stringio
754
+ else
755
+ raise "unknown @ftype #{@ftype}"
756
+ end
757
+ else
758
+ zis = ZipInputStream.new(@zipfile, localHeaderOffset)
759
+ zis.get_next_entry
760
+ if block_given?
761
+ begin
762
+ return yield(zis)
763
+ ensure
764
+ zis.close
765
+ end
766
+ else
767
+ return zis
768
+ end
769
+ end
770
+ end
771
+
772
+ def gather_fileinfo_from_srcpath(srcPath) # :nodoc:
773
+ stat = file_stat(srcPath)
774
+ case stat.ftype
775
+ when 'file'
776
+ if name_is_directory?
777
+ raise ArgumentError,
778
+ "entry name '#{newEntry}' indicates directory entry, but "+
779
+ "'#{srcPath}' is not a directory"
780
+ end
781
+ @ftype = :file
782
+ when 'directory'
783
+ if ! name_is_directory?
784
+ @name += "/"
785
+ end
786
+ @ftype = :directory
787
+ when 'link'
788
+ if name_is_directory?
789
+ raise ArgumentError,
790
+ "entry name '#{newEntry}' indicates directory entry, but "+
791
+ "'#{srcPath}' is not a directory"
792
+ end
793
+ @ftype = :symlink
794
+ else
795
+ raise RuntimeError, "unknown file type: #{srcPath.inspect} #{stat.inspect}"
796
+ end
797
+
798
+ @filepath = srcPath
799
+ get_extra_attributes_from_path(@filepath)
800
+ end
801
+
802
+ def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all
803
+ if @ftype == :directory
804
+ aZipOutputStream.put_next_entry(self)
805
+ elsif @filepath
806
+ aZipOutputStream.put_next_entry(self)
807
+ get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
808
+ else
809
+ aZipOutputStream.copy_raw_entry(self)
810
+ end
811
+ end
812
+
813
+ def parent_as_string
814
+ entry_name = name.chomp("/")
815
+ slash_index = entry_name.rindex("/")
816
+ slash_index ? entry_name.slice(0, slash_index+1) : nil
817
+ end
818
+
819
+ def get_raw_input_stream(&aProc)
820
+ File.open(@zipfile, "rb", &aProc)
821
+ end
822
+
823
+ private
824
+
825
+ def set_time(binaryDosDate, binaryDosTime)
826
+ @time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime)
827
+ rescue ArgumentError
828
+ puts "Invalid date/time in zip entry"
829
+ end
830
+
831
+ def write_file(destPath, continueOnExistsProc = proc { false })
832
+ if File.exists?(destPath) && ! yield(self, destPath)
833
+ raise ZipDestinationFileExistsError,
834
+ "Destination '#{destPath}' already exists"
835
+ end
836
+ File.open(destPath, "wb") do |os|
837
+ get_input_stream do |is|
838
+ set_extra_attributes_on_path(destPath)
839
+
840
+ buf = ''
841
+ while buf = is.sysread(Decompressor::CHUNK_SIZE, buf)
842
+ os << buf
843
+ end
844
+ end
845
+ end
846
+ end
847
+
848
+ def create_directory(destPath)
849
+ if File.directory? destPath
850
+ return
851
+ elsif File.exists? destPath
852
+ if block_given? && yield(self, destPath)
853
+ File.rm_f destPath
854
+ else
855
+ raise ZipDestinationFileExistsError,
856
+ "Cannot create directory '#{destPath}'. "+
857
+ "A file already exists with that name"
858
+ end
859
+ end
860
+ Dir.mkdir destPath
861
+ set_extra_attributes_on_path(destPath)
862
+ end
863
+
864
+ # BUG: create_symlink() does not use &onExistsProc
865
+ def create_symlink(destPath)
866
+ stat = nil
867
+ begin
868
+ stat = File::lstat(destPath)
869
+ rescue Errno::ENOENT
870
+ end
871
+
872
+ io = get_input_stream
873
+ linkto = io.read
874
+
875
+ if stat
876
+ if stat.symlink?
877
+ if File::readlink(destPath) == linkto
878
+ return
879
+ else
880
+ raise ZipDestinationFileExistsError,
881
+ "Cannot create symlink '#{destPath}'. "+
882
+ "A symlink already exists with that name"
883
+ end
884
+ else
885
+ raise ZipDestinationFileExistsError,
886
+ "Cannot create symlink '#{destPath}'. "+
887
+ "A file already exists with that name"
888
+ end
889
+ end
890
+
891
+ File::symlink(linkto, destPath)
892
+ end
893
+ end
894
+
895
+
896
+ # ZipOutputStream is the basic class for writing zip files. It is
897
+ # possible to create a ZipOutputStream object directly, passing
898
+ # the zip file name to the constructor, but more often than not
899
+ # the ZipOutputStream will be obtained from a ZipFile (perhaps using the
900
+ # ZipFileSystem interface) object for a particular entry in the zip
901
+ # archive.
902
+ #
903
+ # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order
904
+ # to provide an IO-like interface for writing to a single zip
905
+ # entry. Beyond methods for mimicking an IO-object it contains
906
+ # the method put_next_entry that closes the current entry
907
+ # and creates a new.
908
+ #
909
+ # Please refer to ZipInputStream for example code.
910
+ #
911
+ # java.util.zip.ZipOutputStream is the original inspiration for this
912
+ # class.
913
+
914
+ class ZipOutputStream
915
+ include IOExtras::AbstractOutputStream
916
+
917
+ attr_accessor :comment
918
+
919
+ # Opens the indicated zip file. If a file with that name already
920
+ # exists it will be overwritten.
921
+ def initialize(fileName)
922
+ super()
923
+ @fileName = fileName
924
+ @outputStream = File.new(@fileName, "wb")
925
+ @entrySet = ZipEntrySet.new
926
+ @compressor = NullCompressor.instance
927
+ @closed = false
928
+ @currentEntry = nil
929
+ @comment = nil
930
+ end
931
+
932
+ # Same as #initialize but if a block is passed the opened
933
+ # stream is passed to the block and closed when the block
934
+ # returns.
935
+ def ZipOutputStream.open(fileName)
936
+ return new(fileName) unless block_given?
937
+ zos = new(fileName)
938
+ yield zos
939
+ ensure
940
+ zos.close if zos
941
+ end
942
+
943
+ # Closes the stream and writes the central directory to the zip file
944
+ def close
945
+ return if @closed
946
+ finalize_current_entry
947
+ update_local_headers
948
+ write_central_directory
949
+ @outputStream.close
950
+ @closed = true
951
+ end
952
+
953
+ # Closes the current entry and opens a new for writing.
954
+ # +entry+ can be a ZipEntry object or a string.
955
+ def put_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
956
+ raise ZipError, "zip stream is closed" if @closed
957
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@fileName, entry.to_s)
958
+ init_next_entry(newEntry, level)
959
+ @currentEntry=newEntry
960
+ end
961
+
962
+ def copy_raw_entry(entry)
963
+ entry = entry.dup
964
+ raise ZipError, "zip stream is closed" if @closed
965
+ raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry)
966
+ finalize_current_entry
967
+ @entrySet << entry
968
+ src_pos = entry.local_entry_offset
969
+ entry.write_local_entry(@outputStream)
970
+ @compressor = NullCompressor.instance
971
+ @outputStream << entry.get_raw_input_stream {
972
+ |is|
973
+ is.seek(src_pos, IO::SEEK_SET)
974
+ is.read(entry.compressed_size)
975
+ }
976
+ @compressor = NullCompressor.instance
977
+ @currentEntry = nil
978
+ end
979
+
980
+ private
981
+ def finalize_current_entry
982
+ return unless @currentEntry
983
+ finish
984
+ @currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset -
985
+ @currentEntry.local_header_size
986
+ @currentEntry.size = @compressor.size
987
+ @currentEntry.crc = @compressor.crc
988
+ @currentEntry = nil
989
+ @compressor = NullCompressor.instance
990
+ end
991
+
992
+ def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
993
+ finalize_current_entry
994
+ @entrySet << entry
995
+ entry.write_local_entry(@outputStream)
996
+ @compressor = get_compressor(entry, level)
997
+ end
998
+
999
+ def get_compressor(entry, level)
1000
+ case entry.compression_method
1001
+ when ZipEntry::DEFLATED then Deflater.new(@outputStream, level)
1002
+ when ZipEntry::STORED then PassThruCompressor.new(@outputStream)
1003
+ else raise ZipCompressionMethodError,
1004
+ "Invalid compression method: '#{entry.compression_method}'"
1005
+ end
1006
+ end
1007
+
1008
+ def update_local_headers
1009
+ pos = @outputStream.tell
1010
+ @entrySet.each {
1011
+ |entry|
1012
+ @outputStream.pos = entry.localHeaderOffset
1013
+ entry.write_local_entry(@outputStream)
1014
+ }
1015
+ @outputStream.pos = pos
1016
+ end
1017
+
1018
+ def write_central_directory
1019
+ cdir = ZipCentralDirectory.new(@entrySet, @comment)
1020
+ cdir.write_to_stream(@outputStream)
1021
+ end
1022
+
1023
+ protected
1024
+
1025
+ def finish
1026
+ @compressor.finish
1027
+ end
1028
+
1029
+ public
1030
+ # Modeled after IO.<<
1031
+ def << (data)
1032
+ @compressor << data
1033
+ end
1034
+ end
1035
+
1036
+
1037
+ class Compressor #:nodoc:all
1038
+ def finish
1039
+ end
1040
+ end
1041
+
1042
+ class PassThruCompressor < Compressor #:nodoc:all
1043
+ def initialize(outputStream)
1044
+ super()
1045
+ @outputStream = outputStream
1046
+ @crc = Zlib::crc32
1047
+ @size = 0
1048
+ end
1049
+
1050
+ def << (data)
1051
+ val = data.to_s
1052
+ @crc = Zlib::crc32(val, @crc)
1053
+ @size += val.size
1054
+ @outputStream << val
1055
+ end
1056
+
1057
+ attr_reader :size, :crc
1058
+ end
1059
+
1060
+ class NullCompressor < Compressor #:nodoc:all
1061
+ include Singleton
1062
+
1063
+ def << (data)
1064
+ raise IOError, "closed stream"
1065
+ end
1066
+
1067
+ attr_reader :size, :compressed_size
1068
+ end
1069
+
1070
+ class Deflater < Compressor #:nodoc:all
1071
+ def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION)
1072
+ super()
1073
+ @outputStream = outputStream
1074
+ @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS)
1075
+ @size = 0
1076
+ @crc = Zlib::crc32
1077
+ end
1078
+
1079
+ def << (data)
1080
+ val = data.to_s
1081
+ @crc = Zlib::crc32(val, @crc)
1082
+ @size += val.size
1083
+ @outputStream << @zlibDeflater.deflate(data)
1084
+ end
1085
+
1086
+ def finish
1087
+ until @zlibDeflater.finished?
1088
+ @outputStream << @zlibDeflater.finish
1089
+ end
1090
+ end
1091
+
1092
+ attr_reader :size, :crc
1093
+ end
1094
+
1095
+
1096
+ class ZipEntrySet #:nodoc:all
1097
+ include Enumerable
1098
+
1099
+ def initialize(anEnumerable = [])
1100
+ super()
1101
+ @entrySet = {}
1102
+ anEnumerable.each { |o| push(o) }
1103
+ end
1104
+
1105
+ def include?(entry)
1106
+ @entrySet.include?(entry.to_s)
1107
+ end
1108
+
1109
+ def <<(entry)
1110
+ @entrySet[entry.to_s] = entry
1111
+ end
1112
+ alias :push :<<
1113
+
1114
+ def size
1115
+ @entrySet.size
1116
+ end
1117
+ alias :length :size
1118
+
1119
+ def delete(entry)
1120
+ @entrySet.delete(entry.to_s) ? entry : nil
1121
+ end
1122
+
1123
+ def each(&aProc)
1124
+ @entrySet.values.each(&aProc)
1125
+ end
1126
+
1127
+ def entries
1128
+ @entrySet.values
1129
+ end
1130
+
1131
+ # deep clone
1132
+ def dup
1133
+ newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup })
1134
+ end
1135
+
1136
+ def == (other)
1137
+ return false unless other.kind_of?(ZipEntrySet)
1138
+ return @entrySet == other.entrySet
1139
+ end
1140
+
1141
+ def parent(entry)
1142
+ @entrySet[entry.parent_as_string]
1143
+ end
1144
+
1145
+ def glob(pattern, flags = File::FNM_PATHNAME|File::FNM_DOTMATCH)
1146
+ entries.select {
1147
+ |entry|
1148
+ File.fnmatch(pattern, entry.name.chomp('/'), flags)
1149
+ }
1150
+ end
1151
+
1152
+ #TODO attr_accessor :auto_create_directories
1153
+ protected
1154
+ attr_accessor :entrySet
1155
+ end
1156
+
1157
+
1158
+ class ZipCentralDirectory
1159
+ include Enumerable
1160
+
1161
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
1162
+ MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18
1163
+ STATIC_EOCD_SIZE = 22
1164
+
1165
+ attr_reader :comment
1166
+
1167
+ # Returns an Enumerable containing the entries.
1168
+ def entries
1169
+ @entrySet.entries
1170
+ end
1171
+
1172
+ def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc:
1173
+ super()
1174
+ @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries)
1175
+ @comment = comment
1176
+ end
1177
+
1178
+ def write_to_stream(io) #:nodoc:
1179
+ offset = io.tell
1180
+ @entrySet.each { |entry| entry.write_c_dir_entry(io) }
1181
+ write_e_o_c_d(io, offset)
1182
+ end
1183
+
1184
+ def write_e_o_c_d(io, offset) #:nodoc:
1185
+ io <<
1186
+ [END_OF_CENTRAL_DIRECTORY_SIGNATURE,
1187
+ 0 , # @numberOfThisDisk
1188
+ 0 , # @numberOfDiskWithStartOfCDir
1189
+ @entrySet? @entrySet.size : 0 ,
1190
+ @entrySet? @entrySet.size : 0 ,
1191
+ cdir_size ,
1192
+ offset ,
1193
+ @comment ? @comment.length : 0 ].pack('VvvvvVVv')
1194
+ io << @comment
1195
+ end
1196
+ private :write_e_o_c_d
1197
+
1198
+ def cdir_size #:nodoc:
1199
+ # does not include eocd
1200
+ @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value }
1201
+ end
1202
+ private :cdir_size
1203
+
1204
+ def read_e_o_c_d(io) #:nodoc:
1205
+ buf = get_e_o_c_d(io)
1206
+ @numberOfThisDisk = ZipEntry::read_zip_short(buf)
1207
+ @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf)
1208
+ @totalNumberOfEntriesInCDirOnThisDisk = ZipEntry::read_zip_short(buf)
1209
+ @size = ZipEntry::read_zip_short(buf)
1210
+ @sizeInBytes = ZipEntry::read_zip_long(buf)
1211
+ @cdirOffset = ZipEntry::read_zip_long(buf)
1212
+ commentLength = ZipEntry::read_zip_short(buf)
1213
+ @comment = buf.read(commentLength)
1214
+ raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
1215
+ end
1216
+
1217
+ def read_central_directory_entries(io) #:nodoc:
1218
+ begin
1219
+ io.seek(@cdirOffset, IO::SEEK_SET)
1220
+ rescue Errno::EINVAL
1221
+ raise ZipError, "Zip consistency problem while reading central directory entry"
1222
+ end
1223
+ @entrySet = ZipEntrySet.new
1224
+ @size.times {
1225
+ @entrySet << ZipEntry.read_c_dir_entry(io)
1226
+ }
1227
+ end
1228
+
1229
+ def read_from_stream(io) #:nodoc:
1230
+ read_e_o_c_d(io)
1231
+ read_central_directory_entries(io)
1232
+ end
1233
+
1234
+ def get_e_o_c_d(io) #:nodoc:
1235
+ begin
1236
+ io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
1237
+ rescue Errno::EINVAL
1238
+ io.seek(0, IO::SEEK_SET)
1239
+ rescue Errno::EFBIG # FreeBSD 4.9 raise Errno::EFBIG instead of Errno::EINVAL
1240
+ io.seek(0, IO::SEEK_SET)
1241
+ end
1242
+
1243
+ # 'buf = io.read' substituted with lump of code to work around FreeBSD 4.5 issue
1244
+ retried = false
1245
+ buf = nil
1246
+ begin
1247
+ buf = io.read
1248
+ rescue Errno::EFBIG # FreeBSD 4.5 may raise Errno::EFBIG
1249
+ raise if (retried)
1250
+ retried = true
1251
+
1252
+ io.seek(0, IO::SEEK_SET)
1253
+ retry
1254
+ end
1255
+
1256
+ sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V'))
1257
+ raise ZipError, "Zip end of central directory signature not found" unless sigIndex
1258
+ buf=buf.slice!((sigIndex+4)...(buf.size))
1259
+ def buf.read(count)
1260
+ slice!(0, count)
1261
+ end
1262
+ return buf
1263
+ end
1264
+
1265
+ # For iterating over the entries.
1266
+ def each(&proc)
1267
+ @entrySet.each(&proc)
1268
+ end
1269
+
1270
+ # Returns the number of entries in the central directory (and
1271
+ # consequently in the zip archive).
1272
+ def size
1273
+ @entrySet.size
1274
+ end
1275
+
1276
+ def ZipCentralDirectory.read_from_stream(io) #:nodoc:
1277
+ cdir = new
1278
+ cdir.read_from_stream(io)
1279
+ return cdir
1280
+ rescue ZipError
1281
+ return nil
1282
+ end
1283
+
1284
+ def == (other) #:nodoc:
1285
+ return false unless other.kind_of?(ZipCentralDirectory)
1286
+ @entrySet.entries.sort == other.entries.sort && comment == other.comment
1287
+ end
1288
+ end
1289
+
1290
+
1291
+ class ZipError < StandardError ; end
1292
+
1293
+ class ZipEntryExistsError < ZipError; end
1294
+ class ZipDestinationFileExistsError < ZipError; end
1295
+ class ZipCompressionMethodError < ZipError; end
1296
+ class ZipEntryNameError < ZipError; end
1297
+ class ZipInternalError < ZipError; end
1298
+
1299
+ # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
1300
+ # The most important methods are those inherited from
1301
+ # ZipCentralDirectory for accessing information about the entries in
1302
+ # the archive and methods such as get_input_stream and
1303
+ # get_output_stream for reading from and writing entries to the
1304
+ # archive. The class includes a few convenience methods such as
1305
+ # #extract for extracting entries to the filesystem, and #remove,
1306
+ # #replace, #rename and #mkdir for making simple modifications to
1307
+ # the archive.
1308
+ #
1309
+ # Modifications to a zip archive are not committed until #commit or
1310
+ # #close is called. The method #open accepts a block following
1311
+ # the pattern from File.open offering a simple way to
1312
+ # automatically close the archive when the block returns.
1313
+ #
1314
+ # The following example opens zip archive <code>my.zip</code>
1315
+ # (creating it if it doesn't exist) and adds an entry
1316
+ # <code>first.txt</code> and a directory entry <code>a_dir</code>
1317
+ # to it.
1318
+ #
1319
+ # require 'zip/zip'
1320
+ #
1321
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
1322
+ # |zipfile|
1323
+ # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
1324
+ # zipfile.mkdir("a_dir")
1325
+ # }
1326
+ #
1327
+ # The next example reopens <code>my.zip</code> writes the contents of
1328
+ # <code>first.txt</code> to standard out and deletes the entry from
1329
+ # the archive.
1330
+ #
1331
+ # require 'zip/zip'
1332
+ #
1333
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
1334
+ # |zipfile|
1335
+ # puts zipfile.read("first.txt")
1336
+ # zipfile.remove("first.txt")
1337
+ # }
1338
+ #
1339
+ # ZipFileSystem offers an alternative API that emulates ruby's
1340
+ # interface for accessing the filesystem, ie. the File and Dir classes.
1341
+
1342
+ class ZipFile < ZipCentralDirectory
1343
+
1344
+ CREATE = 1
1345
+
1346
+ attr_reader :name
1347
+
1348
+ # default -> false
1349
+ attr_accessor :restore_ownership
1350
+ # default -> false
1351
+ attr_accessor :restore_permissions
1352
+ # default -> true
1353
+ attr_accessor :restore_times
1354
+
1355
+ # Opens a zip archive. Pass true as the second parameter to create
1356
+ # a new archive if it doesn't exist already.
1357
+ def initialize(fileName, create = nil)
1358
+ super()
1359
+ @name = fileName
1360
+ @comment = ""
1361
+ if (File.exists?(fileName))
1362
+ File.open(name, "rb") { |f| read_from_stream(f) }
1363
+ elsif (create)
1364
+ @entrySet = ZipEntrySet.new
1365
+ else
1366
+ raise ZipError, "File #{fileName} not found"
1367
+ end
1368
+ @create = create
1369
+ @storedEntries = @entrySet.dup
1370
+
1371
+ @restore_ownership = false
1372
+ @restore_permissions = false
1373
+ @restore_times = true
1374
+ end
1375
+
1376
+ # Same as #new. If a block is passed the ZipFile object is passed
1377
+ # to the block and is automatically closed afterwards just as with
1378
+ # ruby's builtin File.open method.
1379
+ def ZipFile.open(fileName, create = nil)
1380
+ zf = ZipFile.new(fileName, create)
1381
+ if block_given?
1382
+ begin
1383
+ yield zf
1384
+ ensure
1385
+ zf.close
1386
+ end
1387
+ else
1388
+ zf
1389
+ end
1390
+ end
1391
+
1392
+ # Returns the zip files comment, if it has one
1393
+ attr_accessor :comment
1394
+
1395
+ # Iterates over the contents of the ZipFile. This is more efficient
1396
+ # than using a ZipInputStream since this methods simply iterates
1397
+ # through the entries in the central directory structure in the archive
1398
+ # whereas ZipInputStream jumps through the entire archive accessing the
1399
+ # local entry headers (which contain the same information as the
1400
+ # central directory).
1401
+ def ZipFile.foreach(aZipFileName, &block)
1402
+ ZipFile.open(aZipFileName) {
1403
+ |zipFile|
1404
+ zipFile.each(&block)
1405
+ }
1406
+ end
1407
+
1408
+ # Returns an input stream to the specified entry. If a block is passed
1409
+ # the stream object is passed to the block and the stream is automatically
1410
+ # closed afterwards just as with ruby's builtin File.open method.
1411
+ def get_input_stream(entry, &aProc)
1412
+ get_entry(entry).get_input_stream(&aProc)
1413
+ end
1414
+
1415
+ # Returns an output stream to the specified entry. If a block is passed
1416
+ # the stream object is passed to the block and the stream is automatically
1417
+ # closed afterwards just as with ruby's builtin File.open method.
1418
+ def get_output_stream(entry, &aProc)
1419
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
1420
+ if newEntry.directory?
1421
+ raise ArgumentError,
1422
+ "cannot open stream to directory entry - '#{newEntry}'"
1423
+ end
1424
+ zipStreamableEntry = ZipStreamableStream.new(newEntry)
1425
+ @entrySet << zipStreamableEntry
1426
+ zipStreamableEntry.get_output_stream(&aProc)
1427
+ end
1428
+
1429
+ # Returns the name of the zip archive
1430
+ def to_s
1431
+ @name
1432
+ end
1433
+
1434
+ # Returns a string containing the contents of the specified entry
1435
+ def read(entry)
1436
+ get_input_stream(entry) { |is| is.read }
1437
+ end
1438
+
1439
+ # Convenience method for adding the contents of a file to the archive
1440
+ def add(entry, srcPath, &continueOnExistsProc)
1441
+ continueOnExistsProc ||= proc { false }
1442
+ check_entry_exists(entry, continueOnExistsProc, "add")
1443
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
1444
+ newEntry.gather_fileinfo_from_srcpath(srcPath)
1445
+ @entrySet << newEntry
1446
+ end
1447
+
1448
+ # Removes the specified entry.
1449
+ def remove(entry)
1450
+ @entrySet.delete(get_entry(entry))
1451
+ end
1452
+
1453
+ # Renames the specified entry.
1454
+ def rename(entry, newName, &continueOnExistsProc)
1455
+ foundEntry = get_entry(entry)
1456
+ check_entry_exists(newName, continueOnExistsProc, "rename")
1457
+ foundEntry.name=newName
1458
+ end
1459
+
1460
+ # Replaces the specified entry with the contents of srcPath (from
1461
+ # the file system).
1462
+ def replace(entry, srcPath)
1463
+ check_file(srcPath)
1464
+ add(remove(entry), srcPath)
1465
+ end
1466
+
1467
+ # Extracts entry to file destPath.
1468
+ def extract(entry, destPath, &onExistsProc)
1469
+ onExistsProc ||= proc { false }
1470
+ foundEntry = get_entry(entry)
1471
+ foundEntry.extract(destPath, &onExistsProc)
1472
+ end
1473
+
1474
+ # Commits changes that has been made since the previous commit to
1475
+ # the zip archive.
1476
+ def commit
1477
+ return if ! commit_required?
1478
+ on_success_replace(name) {
1479
+ |tmpFile|
1480
+ ZipOutputStream.open(tmpFile) {
1481
+ |zos|
1482
+
1483
+ @entrySet.each { |e| e.write_to_zip_output_stream(zos) }
1484
+ zos.comment = comment
1485
+ }
1486
+ true
1487
+ }
1488
+ initialize(name)
1489
+ end
1490
+
1491
+ # Closes the zip file committing any changes that has been made.
1492
+ def close
1493
+ commit
1494
+ end
1495
+
1496
+ # Returns true if any changes has been made to this archive since
1497
+ # the previous commit
1498
+ def commit_required?
1499
+ return @entrySet != @storedEntries || @create == ZipFile::CREATE
1500
+ end
1501
+
1502
+ # Searches for entry with the specified name. Returns nil if
1503
+ # no entry is found. See also get_entry
1504
+ def find_entry(entry)
1505
+ @entrySet.detect {
1506
+ |e|
1507
+ e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
1508
+ }
1509
+ end
1510
+
1511
+ # Searches for an entry just as find_entry, but throws Errno::ENOENT
1512
+ # if no entry is found.
1513
+ def get_entry(entry)
1514
+ selectedEntry = find_entry(entry)
1515
+ unless selectedEntry
1516
+ raise Errno::ENOENT, entry
1517
+ end
1518
+ selectedEntry.restore_ownership = @restore_ownership
1519
+ selectedEntry.restore_permissions = @restore_permissions
1520
+ selectedEntry.restore_times = @restore_times
1521
+
1522
+ return selectedEntry
1523
+ end
1524
+
1525
+ # Creates a directory
1526
+ def mkdir(entryName, permissionInt = 0755)
1527
+ if find_entry(entryName)
1528
+ raise Errno::EEXIST, "File exists - #{entryName}"
1529
+ end
1530
+ @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt)
1531
+ end
1532
+
1533
+ private
1534
+
1535
+ def is_directory(newEntry, srcPath)
1536
+ srcPathIsDirectory = File.directory?(srcPath)
1537
+ if newEntry.is_directory && ! srcPathIsDirectory
1538
+ raise ArgumentError,
1539
+ "entry name '#{newEntry}' indicates directory entry, but "+
1540
+ "'#{srcPath}' is not a directory"
1541
+ elsif ! newEntry.is_directory && srcPathIsDirectory
1542
+ newEntry.name += "/"
1543
+ end
1544
+ return newEntry.is_directory && srcPathIsDirectory
1545
+ end
1546
+
1547
+ def check_entry_exists(entryName, continueOnExistsProc, procedureName)
1548
+ continueOnExistsProc ||= proc { false }
1549
+ if @entrySet.detect { |e| e.name == entryName }
1550
+ if continueOnExistsProc.call
1551
+ remove get_entry(entryName)
1552
+ else
1553
+ raise ZipEntryExistsError,
1554
+ procedureName+" failed. Entry #{entryName} already exists"
1555
+ end
1556
+ end
1557
+ end
1558
+
1559
+ def check_file(path)
1560
+ unless File.readable? path
1561
+ raise Errno::ENOENT, path
1562
+ end
1563
+ end
1564
+
1565
+ def on_success_replace(aFilename)
1566
+ tmpfile = get_tempfile
1567
+ tmpFilename = tmpfile.path
1568
+ tmpfile.close
1569
+ if yield tmpFilename
1570
+ File.move(tmpFilename, name)
1571
+ end
1572
+ end
1573
+
1574
+ def get_tempfile
1575
+ tempFile = Tempfile.new(File.basename(name), File.dirname(name))
1576
+ tempFile.binmode
1577
+ tempFile
1578
+ end
1579
+
1580
+ end
1581
+
1582
+ class ZipStreamableDirectory < ZipEntry
1583
+ def initialize(zipfile, entry, srcPath = nil, permissionInt = nil)
1584
+ super(zipfile, entry)
1585
+
1586
+ @ftype = :directory
1587
+ entry.get_extra_attributes_from_path(srcPath) if (srcPath)
1588
+ @unix_perms = permissionInt if (permissionInt)
1589
+ end
1590
+ end
1591
+
1592
+ class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all
1593
+ def initialize(entry)
1594
+ super(entry)
1595
+ @tempFile = Tempfile.new(File.basename(name), File.dirname(zipfile))
1596
+ @tempFile.binmode
1597
+ end
1598
+
1599
+ def get_output_stream
1600
+ if block_given?
1601
+ begin
1602
+ yield(@tempFile)
1603
+ ensure
1604
+ @tempFile.close
1605
+ end
1606
+ else
1607
+ @tempFile
1608
+ end
1609
+ end
1610
+
1611
+ def get_input_stream
1612
+ if ! @tempFile.closed?
1613
+ raise StandardError, "cannot open entry for reading while its open for writing - #{name}"
1614
+ end
1615
+ @tempFile.open # reopens tempfile from top
1616
+ @tempFile.binmode
1617
+ if block_given?
1618
+ begin
1619
+ yield(@tempFile)
1620
+ ensure
1621
+ @tempFile.close
1622
+ end
1623
+ else
1624
+ @tempFile
1625
+ end
1626
+ end
1627
+
1628
+ def write_to_zip_output_stream(aZipOutputStream)
1629
+ aZipOutputStream.put_next_entry(self)
1630
+ get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
1631
+ end
1632
+ end
1633
+
1634
+ class ZipExtraField < Hash
1635
+ ID_MAP = {}
1636
+
1637
+ # Meta class for extra fields
1638
+ class Generic
1639
+ def self.register_map
1640
+ if self.const_defined?(:HEADER_ID)
1641
+ ID_MAP[self.const_get(:HEADER_ID)] = self
1642
+ end
1643
+ end
1644
+
1645
+ def self.name
1646
+ self.to_s.split("::")[-1]
1647
+ end
1648
+
1649
+ # return field [size, content] or false
1650
+ def initial_parse(binstr)
1651
+ if ! binstr
1652
+ # If nil, start with empty.
1653
+ return false
1654
+ elsif binstr[0,2] != self.class.const_get(:HEADER_ID)
1655
+ $stderr.puts "Warning: weired extra feild header ID. skip parsing"
1656
+ return false
1657
+ end
1658
+ [binstr[2,2].unpack("v")[0], binstr[4..-1]]
1659
+ end
1660
+
1661
+ def ==(other)
1662
+ self.class != other.class and return false
1663
+ each { |k, v|
1664
+ v != other[k] and return false
1665
+ }
1666
+ true
1667
+ end
1668
+
1669
+ def to_local_bin
1670
+ s = pack_for_local
1671
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
1672
+ end
1673
+
1674
+ def to_c_dir_bin
1675
+ s = pack_for_c_dir
1676
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
1677
+ end
1678
+ end
1679
+
1680
+ # Info-ZIP Additional timestamp field
1681
+ class UniversalTime < Generic
1682
+ HEADER_ID = "UT"
1683
+ register_map
1684
+
1685
+ def initialize(binstr = nil)
1686
+ @ctime = nil
1687
+ @mtime = nil
1688
+ @atime = nil
1689
+ @flag = nil
1690
+ binstr and merge(binstr)
1691
+ end
1692
+ attr_accessor :atime, :ctime, :mtime, :flag
1693
+
1694
+ def merge(binstr)
1695
+ binstr == "" and return
1696
+ size, content = initial_parse(binstr)
1697
+ size or return
1698
+ @flag, mtime, atime, ctime = content.unpack("CVVV")
1699
+ mtime and @mtime ||= Time.at(mtime)
1700
+ atime and @atime ||= Time.at(atime)
1701
+ ctime and @ctime ||= Time.at(ctime)
1702
+ end
1703
+
1704
+ def ==(other)
1705
+ @mtime == other.mtime &&
1706
+ @atime == other.atime &&
1707
+ @ctime == other.ctime
1708
+ end
1709
+
1710
+ def pack_for_local
1711
+ s = [@flag].pack("C")
1712
+ @flag & 1 != 0 and s << [@mtime.to_i].pack("V")
1713
+ @flag & 2 != 0 and s << [@atime.to_i].pack("V")
1714
+ @flag & 4 != 0 and s << [@ctime.to_i].pack("V")
1715
+ s
1716
+ end
1717
+
1718
+ def pack_for_c_dir
1719
+ s = [@flag].pack("C")
1720
+ @flag & 1 == 1 and s << [@mtime.to_i].pack("V")
1721
+ s
1722
+ end
1723
+ end
1724
+
1725
+ # Info-ZIP Extra for UNIX uid/gid
1726
+ class IUnix < Generic
1727
+ HEADER_ID = "Ux"
1728
+ register_map
1729
+
1730
+ def initialize(binstr = nil)
1731
+ @uid = 0
1732
+ @gid = 0
1733
+ binstr and merge(binstr)
1734
+ end
1735
+ attr_accessor :uid, :gid
1736
+
1737
+ def merge(binstr)
1738
+ binstr == "" and return
1739
+ size, content = initial_parse(binstr)
1740
+ # size: 0 for central direcotry. 4 for local header
1741
+ return if(! size || size == 0)
1742
+ uid, gid = content.unpack("vv")
1743
+ @uid ||= uid
1744
+ @gid ||= gid
1745
+ end
1746
+
1747
+ def ==(other)
1748
+ @uid == other.uid &&
1749
+ @gid == other.gid
1750
+ end
1751
+
1752
+ def pack_for_local
1753
+ [@uid, @gid].pack("vv")
1754
+ end
1755
+
1756
+ def pack_for_c_dir
1757
+ ""
1758
+ end
1759
+ end
1760
+
1761
+ ## start main of ZipExtraField < Hash
1762
+ def initialize(binstr = nil)
1763
+ binstr and merge(binstr)
1764
+ end
1765
+
1766
+ def merge(binstr)
1767
+ binstr == "" and return
1768
+ i = 0
1769
+ while i < binstr.length
1770
+ id = binstr[i,2]
1771
+ len = binstr[i+2,2].to_s.unpack("v")[0]
1772
+ if id && ID_MAP.member?(id)
1773
+ field_name = ID_MAP[id].name
1774
+ if self.member?(field_name)
1775
+ self[field_name].mergea(binstr[i, len+4])
1776
+ else
1777
+ field_obj = ID_MAP[id].new(binstr[i, len+4])
1778
+ self[field_name] = field_obj
1779
+ end
1780
+ elsif id
1781
+ unless self["Unknown"]
1782
+ s = ""
1783
+ class << s
1784
+ alias_method :to_c_dir_bin, :to_s
1785
+ alias_method :to_local_bin, :to_s
1786
+ end
1787
+ self["Unknown"] = s
1788
+ end
1789
+ if ! len || len+4 > binstr[i..-1].length
1790
+ self["Unknown"] << binstr[i..-1]
1791
+ break;
1792
+ end
1793
+ self["Unknown"] << binstr[i, len+4]
1794
+ end
1795
+ i += len+4
1796
+ end
1797
+ end
1798
+
1799
+ def create(name)
1800
+ field_class = nil
1801
+ ID_MAP.each { |id, klass|
1802
+ if klass.name == name
1803
+ field_class = klass
1804
+ break
1805
+ end
1806
+ }
1807
+ if ! field_class
1808
+ raise ZipError, "Unknown extra field '#{name}'"
1809
+ end
1810
+ self[name] = field_class.new()
1811
+ end
1812
+
1813
+ def to_local_bin
1814
+ s = ""
1815
+ each { |k, v|
1816
+ s << v.to_local_bin
1817
+ }
1818
+ s
1819
+ end
1820
+ alias :to_s :to_local_bin
1821
+
1822
+ def to_c_dir_bin
1823
+ s = ""
1824
+ each { |k, v|
1825
+ s << v.to_c_dir_bin
1826
+ }
1827
+ s
1828
+ end
1829
+
1830
+ def c_dir_length
1831
+ to_c_dir_bin.length
1832
+ end
1833
+ def local_length
1834
+ to_local_bin.length
1835
+ end
1836
+ alias :c_dir_size :c_dir_length
1837
+ alias :local_size :local_length
1838
+ alias :length :local_length
1839
+ alias :size :local_length
1840
+ end # end ZipExtraField
1841
+
1842
+ end # Zip namespace module
1843
+
1844
+
1845
+
1846
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
1847
+ # rubyzip is free software; you can redistribute it and/or
1848
+ # modify it under the terms of the ruby license.