rack-svn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-svn.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Takashi Kato
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Rack::Svn
2
+
3
+ rack application (not a middleware) serves contents in a svn repository directly ( like Rack::File)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rack-svn'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rack-svn
18
+
19
+ ## Usage
20
+
21
+ config.ru
22
+
23
+ run Rack::Svn::Directory
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,164 @@
1
+ require 'time'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+ require 'rack/svn/file'
5
+ require 'svn/core'
6
+ require 'svn/client'
7
+
8
+ module Rack
9
+ # Rack::Directory serves entries below the +root+ given, according to the
10
+ # path info of the Rack request. If a directory is found, the file's contents
11
+ # will be presented in an html based index. If a file is found, the env will
12
+ # be passed to the specified +app+.
13
+ #
14
+ # If +app+ is not specified, a Rack::File of the same +root+ will be used.
15
+
16
+ module Svn
17
+ class Directory
18
+ DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
19
+ DIR_PAGE = <<-PAGE
20
+ <html><head>
21
+ <title>%s</title>
22
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
23
+ <style type='text/css'>
24
+ table { width:100%%; }
25
+ .name { text-align:left; }
26
+ .size, .mtime { text-align:right; }
27
+ .type { width:11em; }
28
+ .mtime { width:15em; }
29
+ </style>
30
+ </head><body>
31
+ <h1>%s</h1>
32
+ <hr />
33
+ <table>
34
+ <tr>
35
+ <th class='name'>Name</th>
36
+ <th class='size'>Size</th>
37
+ <th class='type'>Type</th>
38
+ <th class='mtime'>Last Modified</th>
39
+ </tr>
40
+ %s
41
+ </table>
42
+ <hr />
43
+ </body></html>
44
+ PAGE
45
+
46
+ attr_reader :files
47
+ attr_accessor :root, :path
48
+
49
+ def initialize(root, app=nil)
50
+ @root = root
51
+ @app = app || Rack::Svn::File.new(@root)
52
+ end
53
+
54
+ def call(env)
55
+ dup._call(env)
56
+ end
57
+
58
+ F = ::File
59
+
60
+ def _call(env)
61
+ @env = env
62
+ @branch_name = env['PATH_PREFIX'] || ""
63
+ @script_name = env['SCRIPT_NAME']
64
+ @path_info = Utils.unescape(env['PATH_INFO'])
65
+
66
+ if forbidden = check_forbidden
67
+ forbidden
68
+ else
69
+ @path = F.join(@root, @branch_name, @path_info).sub(/\/$/,'')
70
+ list_path
71
+ end
72
+ end
73
+
74
+ def check_forbidden
75
+ return unless @path_info.include? ".."
76
+
77
+ body = "Forbidden\n"
78
+ size = Rack::Utils.bytesize(body)
79
+ return [403, {"Content-Type" => "text/plain",
80
+ "Content-Length" => size.to_s,
81
+ "X-Cascade" => "pass"}, [body]]
82
+ end
83
+
84
+ def list_directory
85
+ @files = [['../','Parent Directory','','','']]
86
+
87
+ context = ::Svn::Client::Context.new
88
+
89
+ context.list(@path ,"HEAD") do |name,dirent,lock,abs_path|
90
+ basename = F.basename(name)
91
+ ext = F.extname(name)
92
+
93
+ url = F.join(@script_name, @path_info, basename)
94
+ size = dirent.size
95
+ type = dirent.directory? ? 'directory' : Mime.mime_type(ext)
96
+ size = dirent.directory? ? '-' : filesize_format(size)
97
+ mtime = dirent.time2.httpdate
98
+ url << '/' if dirent.directory?
99
+ basename << '/' if dirent.directory?
100
+
101
+ @files << [ url, basename, size, type, mtime ]
102
+ end
103
+ return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
104
+ end
105
+
106
+ #def stat(node, max = 10)
107
+ # F.stat(node)
108
+ #rescue Errno::ENOENT, Errno::ELOOP
109
+ # return nil
110
+ #end
111
+
112
+ # TODO: add correct response if not readable, not sure if 404 is the best
113
+ # option
114
+ def list_path
115
+
116
+ context = ::Svn::Client::Context.new
117
+ depth_or_recurse=::Svn::Core::DEPTH_EMPTY
118
+ begin
119
+ context.list(@path, "HEAD", nil, depth_or_recurse) do |path, dirent, lock, abs_path|
120
+ @stat = dirent
121
+ return @app.call(@env) if @stat.file?
122
+ return list_directory if @stat.directory?
123
+ end
124
+ rescue ::Svn::Error::FsNotFound
125
+ raise Errno::ENOENT, 'No such file or directory'
126
+ end
127
+ rescue Errno::ENOENT, Errno::ELOOP
128
+ return entity_not_found
129
+ end
130
+
131
+ def entity_not_found
132
+ body = "Entity not found: #{@path_info}\n"
133
+ size = Rack::Utils.bytesize(body)
134
+ return [404, {"Content-Type" => "text/plain",
135
+ "Content-Length" => size.to_s,
136
+ "X-Cascade" => "pass"}, [body]]
137
+ end
138
+
139
+ def each
140
+ show_path = @path.sub(/^#{@root}/,'')
141
+ files = @files.map{|f| DIR_FILE % f }*"\n"
142
+ page = DIR_PAGE % [ show_path, show_path , files ]
143
+ page.each_line{|l| yield l }
144
+ end
145
+
146
+ # Stolen from Ramaze
147
+
148
+ FILESIZE_FORMAT = [
149
+ ['%.1fT', 1 << 40],
150
+ ['%.1fG', 1 << 30],
151
+ ['%.1fM', 1 << 20],
152
+ ['%.1fK', 1 << 10],
153
+ ]
154
+
155
+ def filesize_format(int)
156
+ FILESIZE_FORMAT.each do |format, size|
157
+ return format % (int.to_f / size) if int >= size
158
+ end
159
+
160
+ int.to_s + 'B'
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,93 @@
1
+ require 'time'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+ require 'svn/client'
5
+
6
+ module Rack
7
+ module Svn
8
+ class File
9
+ attr_accessor :root
10
+ attr_accessor :path
11
+
12
+ alias :to_path :path
13
+
14
+ def initialize(root)
15
+ @root = root
16
+ end
17
+
18
+ def call(env)
19
+ dup._call(env)
20
+ end
21
+
22
+ F = ::File
23
+
24
+ def _call(env)
25
+ @branch_name = env['PATH_PREFIX'] || ""
26
+ @path_info = Rack::Utils.unescape(env["PATH_INFO"])
27
+ return forbidden if @path_info.include? ".."
28
+
29
+ @path = F.join(@root, @branch_name, @path_info)
30
+
31
+ begin
32
+ #if F.file?(@path) && F.readable?(@path)
33
+ serving
34
+ #else
35
+ #raise Errno::EPERM
36
+ #end
37
+ rescue SystemCallError
38
+ not_found
39
+ end
40
+ end
41
+
42
+ def forbidden
43
+ body = "Forbidden\n"
44
+ [403, {"Content-Type" => "text/plain",
45
+ "Content-Length" => body.size.to_s,
46
+ "X-Cascade" => "pass"},
47
+ [body]]
48
+ end
49
+
50
+ # NOTE:
51
+ # We check via File::size? whether this file provides size info
52
+ # via stat (e.g. /proc files often don't), otherwise we have to
53
+ # figure it out by reading the whole file into memory. And while
54
+ # we're at it we also use this as body then.
55
+
56
+ def serving
57
+ context = ::Svn::Client::Context.new
58
+ body = context.cat(@path, "HEAD","HEAD")
59
+
60
+ last_modified = nil
61
+ size = nil
62
+ context.list(@path ,"HEAD","HEAD") do |name,dirent,lock,abs_path|
63
+ size = dirent.size
64
+ last_modified = dirent.time2
65
+ end
66
+
67
+ [200, {
68
+ "Last-Modified" => last_modified.httpdate,
69
+ "Content-Type" => Rack::Mime.mime_type(F.extname(@path_info), 'text/plain'),
70
+ "Content-Length" => size.to_s
71
+ }, body]
72
+ end
73
+
74
+ def not_found
75
+ body = "File not found: #{@path_info}\n"
76
+ [404, {"Content-Type" => "text/plain",
77
+ "Content-Length" => body.size.to_s,
78
+ "X-Cascade" => "pass"},
79
+ [body]]
80
+ end
81
+
82
+ def each
83
+ context = ::Svn::Client::Context.new
84
+ body = context.cat(@path, "HEAD","HEAD")
85
+ StringIO.new(body, "rb") do |file|
86
+ while part = file.read(8192)
87
+ yield part
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module Svn
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
data/rack-svn.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rack/svn/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["tohosaku"]
6
+ gem.email = ["ny@cosmichorror.org"]
7
+ gem.description = %q{rack application (not a middleware) serves contents in a svn repository directly }
8
+ gem.summary = %q{rack application (not a middleware) serves contents in a svn repository directly }
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "rack-svn"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Rack::Svn::VERSION
17
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-svn
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - tohosaku
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-08-16 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: "rack application (not a middleware) serves contents in a svn repository directly "
22
+ email:
23
+ - ny@cosmichorror.org
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - Gemfile
33
+ - LICENSE
34
+ - README.md
35
+ - Rakefile
36
+ - lib/rack/svn/directory.rb
37
+ - lib/rack/svn/file.rb
38
+ - lib/rack/svn/version.rb
39
+ - rack-svn.gemspec
40
+ homepage: ""
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ hash: 3
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.17
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: rack application (not a middleware) serves contents in a svn repository directly
73
+ test_files: []
74
+