rack-archive-zip-extract 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 72114a73a80de1f5a42ba4439d0de4d8bd490be9
4
- data.tar.gz: 1b0f2ca04e99752201464ac5f7e5e113df7c441d
3
+ metadata.gz: 8f335e345e79786674c599d49801cfab192613ec
4
+ data.tar.gz: 05268821dfdf5582e97e7580accbcf2cbe9a187b
5
5
  SHA512:
6
- metadata.gz: 7256c9a7a0cc5fde2f603e04ad495a46ad20af2858afa32707e7afd1576a7faf9dd1ec4c6050869d87827535e0484ee2bbeb4cd5ab79fe77c9d60f1dbb28b503
7
- data.tar.gz: c49eebd140a6258aad2987ed476af758dbd634515fb7f823c2bee7999eb0faab722625f1554b1bffd84ec48e8fd0f2d61cc81df6ec700c1de1dd9fee6e5bc62a
6
+ metadata.gz: 0d32ed10d8c653fbc13463163c94198af348cfdfcd43b9e916cf92762def4eafe2419a47e40e1a2a618ffcfdd41d9f078be5a98202d649e3b3ce3a56511e7f96
7
+ data.tar.gz: 61a4b7dc23f899ce983976f0d334b8bca21459813a6aab5e43e3b69c7cf41789aefa9bda5f139640b7c455aa48174c7e343b05eb54d0ee016195dc7f99c75ae0
@@ -0,0 +1,25 @@
1
+ CHANGELOG
2
+ =========
3
+
4
+ 0.0.4
5
+ -----
6
+
7
+ * Change method name `extract_content` -> `extract_file`
8
+ * [BUG FIX]Close body when content not modified
9
+ * Make buffer size for reading file in zip archive configuable
10
+
11
+ 0.0.3
12
+ -----
13
+
14
+ * Chunk content body and use buffer rather than read body at a time
15
+
16
+ 0.0.2
17
+ -----
18
+
19
+ * Use mtime of file in archive for Last-Modified header
20
+ * Don't read file body from zip archive when file is older than If-Modified-Since header
21
+
22
+ 0.0.1
23
+ -----
24
+
25
+ * Initial release
data/MIT-LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2013, 2014 KITAITIMAKOTO <KitaitiMakoto@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,61 @@
1
+ Rack::Archive::Zip::Extract
2
+ ===========================
3
+
4
+ Rack::Archive::Zip::Extract is a Rack application which serves files in ZIP archives.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ $ gem install rack-archive-zip-extract
10
+
11
+ Or add this line to your application's Gemfile:
12
+
13
+ $ gem 'rack-archive-zip-extract', :require => 'rack/archive/zip/extract'
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Usage
20
+ -----
21
+
22
+ Write below in your config.ru:
23
+
24
+ require 'rack/archive/zip/extract'
25
+
26
+ run Rack::Archive::Zip::Extract.new('path/to/docroot')
27
+
28
+ `'path/to/docroot'` above should have some ZIP files like this:
29
+
30
+ $ tree path/to/docroot
31
+ some.zip
32
+ another.zip
33
+ andmore.zip
34
+
35
+ Then, run server:
36
+
37
+ $ rackup
38
+
39
+ Now you can see files in zip archives. For example, visit http://localhost:9292/some/inner-file.txt and then you can see the text in "inner-file.txt" file in "some.zip" archive file.
40
+
41
+ *Note that Rack::Archive::Zip::Extract doesn't serve ZIP file itself.*
42
+
43
+ ### File extensions
44
+
45
+ By default, files with extension ".zip" is recognized as ZIP files.
46
+ You can tell the app extensions by option argument:
47
+
48
+ run Rack::Archive::Zip::Extract.new('path/to/docroot', extensions: ['.epub', '.zip'])
49
+
50
+ ### Buffer size
51
+
52
+ Buffer size for reading file in zip archive is set to {Rack::Archive::Zip::Extract::ExtractedFile::BUFFER_SIZE 8192} bytes by default.
53
+
54
+ You can change it by passing `buffer_size` named argument when initailizig the app:
55
+
56
+ run Rack::Archive::Zip::Extract.new('path/to/docroot', buffer_size: 1024 * 1024)
57
+
58
+ License
59
+ -------
60
+
61
+ This program is distribuetd under the term of the MIT License. See MIT-LICENSE file for more info.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
+ require 'rubygems/tasks'
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |test|
8
+ test.options = '--no-show-detail-immediately --verbose'
9
+ end
10
+
11
+ namespace :test do
12
+ desc 'Build test fixtures'
13
+ task :build do
14
+ sh 'cd test/fixtures/sample-zip && zip -r ../../sample.zip .'
15
+ sh 'cd test/fixtures/sample-ext && zip -r ../../sample.ext .'
16
+ end
17
+ end
18
+
19
+ task :test => [:clean, 'test:build']
20
+
21
+ Gem::Tasks.new
22
+ Gem::Tasks::Sign::Checksum.new
@@ -19,8 +19,7 @@ module Rack::Archive
19
19
  SEPS = Rack::File::SEPS
20
20
  DOT = '.'.freeze
21
21
  DOUBLE_DOT = '..'.freeze
22
- COMMA = ','.freeze
23
- ALLOWED_VERBS = Rack::File::ALLOWED_VERBS
22
+ ALLOWED_METHODS = Rack::File::ALLOWED_VERBS.join(', ').freeze
24
23
  ALLOW = 'Allow'.freeze
25
24
  CONTENT_TYPE = 'Content-Type'.freeze
26
25
  CONTENT_LENGTH = 'Content-Length'.freeze
@@ -29,33 +28,31 @@ module Rack::Archive
29
28
  REQUEST_METHOD = 'REQUEST_METHOD'.freeze
30
29
  PATH_INFO = 'PATH_INFO'.freeze
31
30
 
32
- attr_reader :root
33
-
34
31
  # @param root [Pathname, #to_path, String] path to document root
35
32
  # @param extensions [Array<String>] extensions which is recognized as a zip file
36
33
  # @raise [ArgumentError] if +root+ is not a directory
37
- def initialize(root, extensions: %w[.zip])
34
+ def initialize(root, extensions: %w[.zip], buffer_size: ExtractedFile::BUFFER_SIZE)
38
35
  @root = root.kind_of?(Pathname) ? root : Pathname(root)
39
36
  @root = @root.expand_path
40
- @extensions = extensions
37
+ @extensions = extensions.each {|extention| extention.freeze}.lazy
38
+ @buffer_size = buffer_size
41
39
  raise ArgumentError, "Not a directory: #{@root}" unless @root.directory?
42
40
  end
43
41
 
44
42
  def call(env)
45
- return [status_code(:method_not_allowd), {ALLOW => ALLOWED_VERBS.join(COMMA)}, []] unless ALLOWED_VERBS.include? env[REQUEST_METHOD]
43
+ return [status_code(:method_not_allowd), {ALLOW => ALLOWED_METHODS}, []] unless Rack::File::ALLOWED_VERBS.include? env[REQUEST_METHOD]
46
44
 
47
45
  path_info = unescape(env[PATH_INFO])
48
- file = nil
49
- @extensions.each do |ext|
46
+ file = @extensions.map {|ext|
50
47
  zip_file, inner_path = find_zip_file_and_inner_path(path_info, ext)
51
- file = extract_content(zip_file, inner_path)
52
- break if file
53
- end
48
+ extract_file(zip_file, inner_path)
49
+ }.select {|file| file}.first
54
50
  return [status_code(:not_found), {}, []] if file.nil?
55
51
 
56
52
  if_modified_since = env[IF_MODIFIED_SINCE]
57
53
  if_modified_since = Time.parse(if_modified_since) if if_modified_since
58
54
  if if_modified_since and file.mtime <= if_modified_since
55
+ file.close
59
56
  [status_code(:not_modified), {}, []]
60
57
  else
61
58
  [
@@ -89,14 +86,14 @@ module Rack::Archive
89
86
  # @return [Zip::File]
90
87
  # @return [nil] if +zip_file_path+ is nil or +inner_path+ is empty
91
88
  # @return [nil] if +inner_path+ doesn't exist in zip archive
92
- def extract_content(zip_file_path, inner_path)
89
+ def extract_file(zip_file_path, inner_path)
93
90
  return if zip_file_path.nil? or inner_path.empty?
94
91
  archive = ::Zip::Archive.open(zip_file_path.to_path)
95
92
  if archive.locate_name(inner_path) < 0
96
93
  archive.close
97
94
  nil
98
95
  else
99
- ExtractedFile.new(archive, archive.fopen(inner_path))
96
+ ExtractedFile.new(archive, inner_path, @buffer_size)
100
97
  end
101
98
  end
102
99
 
@@ -114,14 +111,20 @@ module Rack::Archive
114
111
  end
115
112
 
116
113
  class ExtractedFile
114
+ BUFFER_SIZE = 8192
115
+
117
116
  # @param archive [Zip::Archive]
118
- # @param file [Zip::File]
119
- def initialize(archive, file)
120
- @archive, @file = archive, file
117
+ # @param path [String]
118
+ # @raise ArgumentError when +archive+ already closed
119
+ def initialize(archive, path, buffer_size=BUFFER_SIZE)
120
+ raise ArgumentError, 'archive already closed' unless archive.open?
121
+ @archive = archive
122
+ @file = @archive.fopen(path)
123
+ @buffer_size = buffer_size
121
124
  end
122
125
 
123
126
  def each
124
- @file.read do |chunk|
127
+ while chunk = @file.read(@buffer_size)
125
128
  yield chunk
126
129
  end
127
130
  end
@@ -0,0 +1 @@
1
+ This is a plain text file in sample.ext.
@@ -0,0 +1,3 @@
1
+ <!DOCTYPE html>
2
+ <title>HTML</title>
3
+ <p>This is a HTML file.</p>
@@ -0,0 +1 @@
1
+ This is a plain text file.
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0">
2
+ <root><content>This is an XML file.</content></root>
@@ -0,0 +1 @@
1
+ This is a plain text file in subdirectory.
@@ -0,0 +1,126 @@
1
+ require 'test/unit'
2
+ require 'test/unit/notify'
3
+ require 'rack/lint'
4
+ require 'rack/mock'
5
+ require 'rack/archive/zip/extract'
6
+
7
+ class TestRackArchiveZipExtract < Test::Unit::TestCase
8
+ def setup
9
+ @zip = Rack::Archive::Zip::Extract.new(__dir__)
10
+ end
11
+
12
+ def request(path, app=@zip, opts={})
13
+ Rack::MockRequest.new(app).get(path, opts)
14
+ end
15
+
16
+ def test_request_to_file_in_zip_returns_content
17
+ response = request('/sample/sample.txt')
18
+
19
+ assert_equal "This is a plain text file.\n", response.body
20
+ end
21
+
22
+ def test_request_to_file_in_zip_returns_last_modified
23
+ response = request('/sample/sample.txt')
24
+ expected = File.mtime(File.join(__dir__, 'fixtures', 'sample-zip', 'sample.txt'))
25
+ actual = Time.parse(response['Last-Modified'])
26
+
27
+ assert_in_delta expected, actual, 2
28
+ end
29
+
30
+ def test_request_to_old_file_returns_not_modified
31
+ mtime = File.mtime(File.join(__dir__, 'fixtures', 'sample-zip', 'sample.txt'))
32
+ if_modified_since = mtime + 12
33
+ response = request('/sample/sample.txt', @zip, {'HTTP_IF_MODIFIED_SINCE' => if_modified_since.httpdate})
34
+
35
+ assert_equal 304, response.status
36
+ end
37
+
38
+ def test_request_to_old_file_returns_no_content
39
+ mtime = File.mtime(File.join(__dir__, 'fixtures', 'sample-zip', 'sample.txt'))
40
+ if_modified_since = mtime + 12
41
+ response = request('/sample/sample.txt', @zip, {'HTTP_IF_MODIFIED_SINCE' => if_modified_since.httpdate})
42
+
43
+ assert_empty response.body
44
+ end
45
+
46
+ class TestStatusCode < self
47
+ data(
48
+ 'file in zip' => [200, '/sample/sample.txt'],
49
+ 'zip file itself' => [404, '/sample.zip'],
50
+ 'non-existent file in zip' => [404, '/sample/non-existent'],
51
+ 'file in subdirectory' => [200, '/sample/subdir/sample.txt']
52
+ )
53
+
54
+ def test_status_code(data)
55
+ status_code, path_info = data
56
+
57
+ assert_equal status_code, request(path_info).status
58
+ end
59
+ end
60
+
61
+ class TestContentType < self
62
+ data(
63
+ 'text/plain' => %w[.txt text/plain],
64
+ 'text/html' => %w[.html text/html],
65
+ 'application/xml' => %w[.xml application/xml],
66
+ 'unknown extension' => %w[.unknownextension application/octet-stream],
67
+ 'no extension' => ['', 'application/octet-stream'],
68
+ )
69
+ def test_content_type(data)
70
+ extension, content_type = data
71
+ response = request("/sample/sample#{extension}")
72
+
73
+ assert_equal content_type, response['Content-Type']
74
+ end
75
+ end
76
+
77
+ def test_extension_of_file_can_be_changed
78
+ ext = Rack::Archive::Zip::Extract.new(__dir__, extensions: %w[.ext])
79
+ response = request('/sample/sample.txt', ext)
80
+
81
+ assert_equal 200, response.status
82
+ assert_equal "This is a plain text file in sample.ext.\n", response.body
83
+ end
84
+
85
+ class TestMultipleExtensions < self
86
+ def setup
87
+ super
88
+ @multi = Rack::Archive::Zip::Extract.new(__dir__, extensions: %w[.ext .zip])
89
+ end
90
+
91
+ def test_multiple_extensions_for_zip_file_can_be_specified
92
+ response = request('/sample/sample.txt', @multi)
93
+
94
+ assert_equal 200, response.status
95
+ assert_equal "This is a plain text file in sample.ext.\n", response.body
96
+ end
97
+
98
+ def test_fallback_to_next_extension_when_file_not_exit_in_zip_archive
99
+ response = request('/sample/sample.html', @multi)
100
+
101
+ assert_equal 200, response.status
102
+ end
103
+ end
104
+
105
+ class TestExtractedFile < self
106
+ def setup
107
+ @zip_path = 'test/sample.zip'
108
+ @inner_path = 'sample.txt'
109
+ @file = Rack::Archive::Zip::Extract::ExtractedFile.new(Zip::Archive.open(@zip_path), @inner_path)
110
+ end
111
+
112
+ # If body of Rack application responds to method `to_path`,
113
+ # it is used to specify file path on file system when
114
+ # Rack::Senfile or so is stacked on Rack builder.
115
+ def test_not_respond_to_to_path
116
+ assert_false @file.respond_to? :to_path
117
+ end
118
+
119
+ def test_cannot_initialize_with_closed_archive
120
+ archive = Zip::Archive.open(@zip_path)
121
+ archive.close
122
+ assert_raise ArgumentError do Rack::Archive::Zip::Extract::ExtractedFile.new archive, @inner_path
123
+ end
124
+ end
125
+ end
126
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-archive-zip-extract
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - KITAITI Makoto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-06 00:00:00.000000000 Z
11
+ date: 2014-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -98,9 +98,22 @@ description: Rack::Archive::Zip::Extract serves files in zip archives.
98
98
  email: KitaitiMakoto@gmail.com
99
99
  executables: []
100
100
  extensions: []
101
- extra_rdoc_files: []
101
+ extra_rdoc_files:
102
+ - README.markdown
103
+ - MIT-LICENSE
104
+ - CHANGELOG.markdown
102
105
  files:
106
+ - CHANGELOG.markdown
107
+ - MIT-LICENSE
108
+ - README.markdown
109
+ - Rakefile
103
110
  - lib/rack/archive/zip/extract.rb
111
+ - test/fixtures/sample-ext/sample.txt
112
+ - test/fixtures/sample-zip/sample.html
113
+ - test/fixtures/sample-zip/sample.txt
114
+ - test/fixtures/sample-zip/sample.xml
115
+ - test/fixtures/sample-zip/subdir/sample.txt
116
+ - test/test_rack-archive-zip-extract.rb
104
117
  homepage: https://github.com/KitaitiMakoto/rack-archive-zip-extract
105
118
  licenses:
106
119
  - MIT
@@ -125,4 +138,10 @@ rubygems_version: 2.2.0
125
138
  signing_key:
126
139
  specification_version: 4
127
140
  summary: Zip file server
128
- test_files: []
141
+ test_files:
142
+ - test/test_rack-archive-zip-extract.rb
143
+ - test/fixtures/sample-ext/sample.txt
144
+ - test/fixtures/sample-zip/sample.html
145
+ - test/fixtures/sample-zip/sample.txt
146
+ - test/fixtures/sample-zip/sample.xml
147
+ - test/fixtures/sample-zip/subdir/sample.txt