bcms_webdav 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ All BrowserCMS code is Copyright (C) 1998-2010 by BrowserMedia, LLC.
2
+
3
+ This program is free software: you can redistribute it and/or modify
4
+ it under the terms of the GNU Lesser General Public License as published by
5
+ the Free Software Foundation, either version 3 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU Lesser General Public License for more details.
12
+
13
+ You should have received a copy of the GNU Lesser General Public License
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ BrowserCMS is a registered trademark of BrowserMedia, LLC.
17
+
18
+ BrowserCMS includes works under other copyright notices and distributed
19
+ according to the terms of the GNU Lesser Public License or a compatible
20
+ license, including:
21
+
22
+ - jQuery - Copyright (C) 2009 John Resig
23
+ - CKEditor - Copyright (C) 2003-2009 Frederico Caldeira Knabben
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
@@ -0,0 +1,107 @@
1
+ == WebDAV
2
+
3
+ Allow access to a BrowserCMS site via WebDAV. The primary intent of this module currently is to allow bulk upload of files using an FTP client.
4
+ Clients which support WebDAV in addition to FTP may be used to review and upload/download Files. This module is implemented as
5
+ as Rack Middleware.
6
+
7
+ For more information on WebDAV see: http://en.wikipedia.org/wiki/WebDAV
8
+
9
+ === Features
10
+
11
+ * Authorized users can list pages, sections and files that exist in the content library.
12
+ * Authorized users can download files via WebDAV.
13
+ * Authorized users can upload files via WebDAV.
14
+ * Users must have 'Administer CMS' permissions in order to access resources via WebDAV.
15
+ * Users can use their normal cms username and password.
16
+
17
+ === Clients
18
+
19
+ In order to take advantage of this module, users will need a WebDAV client. Many FTP clients will support this, including:
20
+
21
+ * Transmit (Mac OS) - http://panic.com/transmit/
22
+ * CrossFTP Pro (Windows, etc) - http://www.crossftp.com/
23
+
24
+ This module has been tested with both of the above client. In addition, operating systems support accessing WebDAV
25
+ servers via mounted drives. See: http://en.wikipedia.org/wiki/WebDAV for more discussion.
26
+
27
+ === Installation
28
+
29
+ Due to the dependancy on rack 1.1.0 or later, this gem only works with a Rails 2.3.11 project, so you may need to upgrade older rails projects before using.
30
+
31
+ gem install bcms_webdav
32
+
33
+ Add the gem to your project.
34
+
35
+ # config/environment.rb
36
+ config.gem 'dav4rack'
37
+ config.gem 'bcms_webdav'
38
+
39
+ Add the following file to your project.
40
+
41
+ # config/initializers/bcms_webdav.rb
42
+ Rails.configuration.middleware.use Bcms::WebDavMiddleware, :port=>3001
43
+
44
+ This configures the WebDAV server to listen on port 3001 for WeDAV requests in development. To access the WebDAV API,
45
+ you will need to start another rails server instance like so:
46
+
47
+ script/server --port=3001
48
+
49
+ In production, a subdomain should be used rather than a port. You will need to configure another subdomain on your webserver, much like the cms.
50
+ subdomain is used for the admin interface. The default subdomain is 'webdav'. Here's an example apache config file, with a new subdomain configured:
51
+
52
+ <VirtualHost *:80>
53
+ ServerName webdav.mysite.com
54
+ DocumentRoot "/var/sites/mysite/public"
55
+ RailsEnv production
56
+ <directory "/var/sites/mysite/public">
57
+ Order allow,deny
58
+ Allow from all
59
+ </directory>
60
+ </VirtualHost>
61
+
62
+ This means there should be a total of three domains in place for a site in production:
63
+ * www - Main site accessed by the public
64
+ * cms - Site access for administrators
65
+ * webdav - Site access via WebDAV
66
+
67
+ ==== Alternate Subdomains
68
+
69
+ You can change the subdomain that this module listens for requests on via:
70
+
71
+ # config/initializers/bcms_webdav.rb
72
+ Rails.configuration.middleware.use Bcms::WebDavMiddleware, :port=>3001, :subdomain=>"dav"
73
+
74
+ This would change the subdomain from 'webdav' to 'dav'. This webserver config file would also need to be changed as well.
75
+
76
+ ==== Subdomains in Development
77
+
78
+ Rather than run two server instances locally, you can take advantage of subdomains. Start by modifying your 'hosts' file to
79
+ make 'webdav.localhost' map to your local development environment, like so:
80
+
81
+ 127.0.0.1 webdav.localhost
82
+
83
+ Then configure the module so its no longer listening on port 3001, like so:
84
+
85
+ # config/initializers/bcms_webdav.rb
86
+ Rails.configuration.middleware.use Bcms::WebDavMiddleware
87
+
88
+ Now you can make web requests to localhost:3000 and WebDAV requests to webdav.localhost:3000
89
+
90
+ == Notes
91
+
92
+ This is an incomplete implementation of WebDAV, so many operations are not explicitly supported, such as:
93
+
94
+ * Moving - Users cannot move files or pages.
95
+ * Get/Put Pages/sections - Users can't download or upload pages or sections.
96
+ * Edit/Create Pages/section - Users can't edit/rename or create new sections.
97
+
98
+ == Known Issues
99
+
100
+ * All files are uploaded as 'FileBlocks', regardless of whether they are images or not.
101
+ * All content_types are set to 'application/octet-stream' regardless of their actual type.
102
+ * This m
103
+
104
+ === Performance
105
+
106
+ This module almost certainly needs performance testing when dealing with larger sites. Certain FTP clients will make more requests for
107
+ resources than others. (CrossFTP seems 'chattier' than Transmit for instance).
@@ -0,0 +1,10 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require(File.join(File.dirname(__FILE__), 'config', 'boot'))
5
+
6
+ require 'rake'
7
+ require 'rake/testtask'
8
+ require 'rake/rdoctask'
9
+
10
+ require 'tasks/rails'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,2 @@
1
+ Use this README file to introduce your application and point to useful places in the API for learning more.
2
+ Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
@@ -0,0 +1,5 @@
1
+ require 'bcms_webdav/routes'
2
+ require 'bcms_webdav/web_dav_middleware'
3
+ require 'bcms_webdav/file'
4
+ require 'bcms_webdav/bcms_attachment_extensions'
5
+ require 'bcms_webdav/resource'
@@ -0,0 +1,7 @@
1
+ Attachment.class_eval do
2
+
3
+ # Exist to ensure compliance with Resource API for WebDAV (like Section and Pages).
4
+ def path
5
+ file_path
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ module Bcms
2
+ module WebDAV
3
+
4
+ # Inherits from Rack File in order to change where paths are looked up from, since the
5
+ # public path of CMS files is different that actual file path.
6
+ class File < Rack::File
7
+
8
+ # This should be an absolute file path
9
+ def initialize(absolute_file_path)
10
+ @path = absolute_file_path
11
+ end
12
+
13
+ # Don't look up from PATH, look up from passed in variable
14
+ def _call(env)
15
+ Rails.logger.debug "Starting to serve file @ path #{@path}"
16
+
17
+ # From here down is a copy&paste of Rack::File#_call
18
+ begin
19
+ if F.file?(@path) && F.readable?(@path)
20
+ serving
21
+ else
22
+ raise Errno::EPERM
23
+ end
24
+ rescue SystemCallError
25
+ not_found
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,235 @@
1
+ module Bcms
2
+ module WebDAV
3
+
4
+ # A virtual resource representing a CMS
5
+ # * Section
6
+ # * File
7
+ # * Image
8
+ class Resource < DAV4Rack::Resource
9
+
10
+ include DAV4Rack::HTTPStatus
11
+
12
+ # Converts WebDAV paths into CMS paths. Both have slightly different rules.
13
+ def self.normalize_path(webdav_path)
14
+ path = webdav_path
15
+ if path.end_with?("/")
16
+ path.gsub!(/\/$/, '')
17
+ end
18
+ unless path.start_with?("/")
19
+ path = path.insert(0, "/")
20
+ end
21
+ path = "/" if path == ''
22
+ path
23
+
24
+ end
25
+
26
+
27
+ def authenticate(username, password)
28
+ log "Authenticating user '#{username}'"
29
+ user = User.authenticate(username, password)
30
+
31
+ unless user
32
+ Rails.logger.error "Failed authentication attempt by user '#{username}'"
33
+ return false
34
+ end
35
+ user.able_to?(:administrate)
36
+ end
37
+
38
+ def have_section
39
+ @section != nil
40
+ end
41
+
42
+ def have_page
43
+ @page != nil
44
+ end
45
+
46
+ def have_file
47
+ @file != nil
48
+ end
49
+
50
+
51
+
52
+ # This should always be called by DAV4Rack controller before any other primary operation (get, put) on a resource.
53
+ def exist?
54
+ path_to_find = Resource.normalize_path(path)
55
+ @section = Section.with_path(path_to_find).first
56
+
57
+ if have_section
58
+ log_exists('section', path_to_find)
59
+ @resource = @section if have_section
60
+ end
61
+
62
+ @page = Page.with_path(path_to_find).first
63
+ if have_page
64
+ log_exists('page', path_to_find)
65
+ @resource = @page
66
+ end
67
+
68
+ @file = Attachment.find_by_file_path(path)
69
+ if have_file
70
+ log_exists('file', path_to_find)
71
+ @resource = @file
72
+ end
73
+
74
+ have_section || have_page || have_file
75
+ end
76
+
77
+ def children
78
+ if exist?
79
+ child_nodes = @section.child_nodes
80
+ child_resources = []
81
+ child_nodes.each do |node|
82
+ child_resources << child_node(node)
83
+ end
84
+ return child_resources
85
+ else
86
+ []
87
+ end
88
+ end
89
+
90
+ def creation_date
91
+ @resource.created_at
92
+ end
93
+
94
+ def last_modified
95
+ @resource.created_at if exist?
96
+ end
97
+
98
+ def collection?
99
+ have_section ? true : false
100
+ end
101
+
102
+ def etag
103
+ sprintf('%x-%x-%x', @resource.id, creation_date.to_i, last_modified.to_i) if exist?
104
+ end
105
+
106
+ def content_type
107
+ have_file ? @resource.file_type : "text/html"
108
+ end
109
+
110
+ def content_length
111
+ have_file ? @resource.file_size : 0
112
+ end
113
+
114
+ def get(request, response)
115
+ log "GET request for #{request.path}"
116
+ if have_file
117
+ file_location = @resource.full_file_location
118
+ log "For attachment '#{@resource}' file location is '#{file_location}"
119
+ file = Bcms::WebDAV::File.new(file_location)
120
+ log "Sending file '#{file.path}'"
121
+ response.body = file
122
+ end
123
+ end
124
+
125
+ # Handle uploading file.
126
+ def put(request, response)
127
+
128
+ temp_file = request.body
129
+ add_rails_like_methods(temp_file)
130
+
131
+ section = find_section_for(path)
132
+
133
+ file_block = FileBlock.new(:name=>path, :attachment_file=>request.body, :attachment_section => section, :attachment_file_path=>path, :publish_on_save=>true)
134
+ unless file_block.save
135
+ log "Couldn't save file."
136
+ file_block.errors.each do |error|
137
+ log error
138
+ end
139
+ return
140
+ end
141
+ OK
142
+ end
143
+
144
+ def find_section_for(path)
145
+ log "Looking up section for path '#{path}"
146
+ path_obj = Path.new(path)
147
+ section_path = path_obj.path_without_filename
148
+ path_to_find = Resource.normalize_path(section_path)
149
+
150
+ log "Section.path = #{path_to_find}"
151
+ Section.with_path(path_to_find).first
152
+ end
153
+
154
+ private
155
+
156
+ def log_exists(type, path)
157
+ log "Resource of type '#{type}' with path '#{path}' exists."
158
+ end
159
+
160
+ # Make this TempFile object act like a RailsTempFile
161
+ def add_rails_like_methods(temp_file)
162
+ # For the purposes of Rails 2, this will have to do. Rails 3 make this much easier by providing additional
163
+ # Rack processors for ActionDispatch::Http::UploadedFile which makes this unncessary.
164
+
165
+ def temp_file.content_type
166
+ 'application/octet-stream'
167
+ end
168
+
169
+ def temp_file.original_filename
170
+ path
171
+ end
172
+
173
+ def temp_file.local_path
174
+ self.path
175
+ end
176
+ end
177
+
178
+ def child_node(section_node)
179
+ child_node = self.class.new(section_node.node.path, section_node.node.path, request, response, options.merge(:user => @user))
180
+ child_node.exist? # Force lookup of info from DB.
181
+ child_node
182
+ end
183
+
184
+ def self.log(m)
185
+ Rails.logger.warn m
186
+ end
187
+
188
+ def log(m)
189
+ Rails.logger.warn m
190
+ end
191
+ end
192
+
193
+
194
+ class Path
195
+ include Cms::Behaviors::Attaching::InstanceMethods
196
+
197
+ # Based on http://stackoverflow.com/questions/27745/getting-parts-of-a-url-regex
198
+ # Tested in http://rubular.com/
199
+ #
200
+ # This will also convert paths with spaces, etc, into CMS style sanatized paths.
201
+ def initialize(path_as_string)
202
+
203
+ @string_path = sanitize_file_path(CGI::unescape(path_as_string))
204
+ Rails.logger.warn "Sanitized path is: " + @string_path
205
+ @regex = /^((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?$/
206
+ scanned = @string_path.scan(@regex)
207
+ @parts = scanned[0]
208
+ end
209
+
210
+ def parts
211
+ @parts
212
+ end
213
+
214
+ def file_name
215
+ return @parts[5] if @parts # Most longer URLs
216
+
217
+ # i.e. /somefilename.jpg
218
+ if @parts == nil
219
+ if @string_path.starts_with?("/") && @string_path.count("/") == 1
220
+ return @string_path.gsub("/", '')
221
+ end
222
+
223
+ # i.e. somefilename.jpg
224
+ return @string_path
225
+ end
226
+ end
227
+
228
+ def path_without_filename
229
+ @string_path.gsub(file_name, '')
230
+ end
231
+
232
+ end
233
+ end
234
+ end
235
+
@@ -0,0 +1,7 @@
1
+ module Cms::Routes
2
+ def routes_for_bcms_webdav
3
+ namespace(:cms) do |cms|
4
+ #cms.content_blocks :webdavs
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ require 'dav4rack'
2
+ require 'dav4rack/file_resource'
3
+
4
+ module Bcms
5
+ class WebDavMiddleware
6
+
7
+ def initialize(app, options={})
8
+ @app = app
9
+ @dav4rack = DAV4Rack::Handler.new(:root => Rails.root.to_s, :root_uri_path => '/', :log_to => [STDERR, Logger::DEBUG], :resource_class=>Bcms::WebDAV::Resource)
10
+ @options = options
11
+
12
+ unless @options[:subdomain]
13
+ @options[:subdomain] = 'webdav'
14
+ end
15
+ end
16
+
17
+ def call(env)
18
+ request = Rack::Request.new(env)
19
+ if is_webdav?(request)
20
+ return @dav4rack.call(env)
21
+ else
22
+ @app.call(env)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # A request is WebDAV if it matches either the port or subdomain (exactly).
29
+ def is_webdav?(request)
30
+ return true if @options[:port] && request.port == @options[:port]
31
+ return true if request.host.starts_with?("#{@options[:subdomain]}.")
32
+ false
33
+ end
34
+
35
+
36
+ def log(message)
37
+ Rails.logger.warn message
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "bcms_webdav"
5
+ gemspec.rubyforge_project = "browsercms"
6
+ gemspec.summary = "A BrowserCMS module for WebDAV"
7
+ gemspec.email = "github@browsermedia.com"
8
+ gemspec.homepage = "https://github.com/browsermedia/bcms_webdav"
9
+ gemspec.description = "Turns a BrowserCMS site into a WebDAV server, allowing access for bulk uploading files."
10
+ gemspec.authors = ["BrowserMedia"]
11
+ gemspec.files = []
12
+ gemspec.files += Dir["app/**/*"]
13
+ gemspec.files -= Dir["app/views/layouts/templates/*"]
14
+ gemspec.files -= Dir["app/controllers/application_controller.rb"]
15
+ gemspec.files -= Dir["app/helpers/application_helper.rb"]
16
+ gemspec.files += Dir["doc/**/*"]
17
+ gemspec.files += Dir["db/migrate/[0-9]*.rb"].reject {|f| f =~ /_browsercms|_load_seed/ }
18
+ gemspec.files += Dir["lib/**/*"]
19
+ gemspec.files -= Dir["lib/task/jeweler.rake"]
20
+ gemspec.files += Dir["rails/init.rb"]
21
+ gemspec.files += Dir["public/bcms/webdav/**/*"]
22
+ gemspec.files += Dir["README.rdoc"]
23
+ gemspec.files += Dir["Rakefile"]
24
+ gemspec.files += Dir["LICENSE.txt"]
25
+ gemspec.files += Dir["COPYRIGHT.txt"]
26
+ gemspec.files += Dir["VERSION"]
27
+ gemspec.add_dependency('browsercms', '>=3.1')
28
+ gemspec.add_dependency('dav4rack', '>=0.2.1')
29
+ end
30
+ rescue LoadError
31
+ puts "Jeweler not available. Install it with: gem install jeweler"
32
+ end
33
+
34
+ Jeweler::GemcutterTasks.new
@@ -0,0 +1 @@
1
+ Use this directory to add public files that should copied from the gem into the project.
@@ -0,0 +1,4 @@
1
+ gem_root = File.expand_path(File.join(File.dirname(__FILE__), ".."))
2
+ Cms.add_to_rails_paths gem_root
3
+ Cms.add_generator_paths gem_root, "db/migrate/[0-9]*_*.rb"
4
+ Cms.add_generator_paths gem_root, "public/bcms/webdav/**/*"
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'performance_test_help'
3
+
4
+ # Profiling results for each test method are written to tmp/performance.
5
+ class BrowsingTest < ActionController::PerformanceTest
6
+ def test_homepage
7
+ get '/'
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3
+ require 'test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Transactional fixtures accelerate your tests by wrapping each test method
7
+ # in a transaction that's rolled back on completion. This ensures that the
8
+ # test database remains unchanged so your fixtures don't have to be reloaded
9
+ # between every test method. Fewer database queries means faster tests.
10
+ #
11
+ # Read Mike Clark's excellent walkthrough at
12
+ # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
13
+ #
14
+ # Every Active Record database supports transactions except MyISAM tables
15
+ # in MySQL. Turn off transactional fixtures in this case; however, if you
16
+ # don't care one way or the other, switching from MyISAM to InnoDB tables
17
+ # is recommended.
18
+ #
19
+ # The only drawback to using transactional fixtures is when you actually
20
+ # need to test transactions. Since your test is bracketed by a transaction,
21
+ # any transactions started in your code will be automatically rolled back.
22
+ self.use_transactional_fixtures = true
23
+
24
+ # Instantiated fixtures are slow, but give you @david where otherwise you
25
+ # would need people(:david). If you don't want to migrate your existing
26
+ # test cases which use the @david style and don't mind the speed hit (each
27
+ # instantiated fixtures translates to a database query per test method),
28
+ # then set this back to true.
29
+ self.use_instantiated_fixtures = false
30
+
31
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
32
+ #
33
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
34
+ # -- they do not yet inherit this setting
35
+ fixtures :all
36
+
37
+ # Add more helper methods to be used by all tests here...
38
+ end
39
+
40
+ def file_upload_object(options)
41
+ file = ActionController::UploadedTempfile.new(options[:original_filename])
42
+ open(file.path, 'w') { |f| f << options[:read] }
43
+ file.original_path = options[:original_filename]
44
+ file.content_type = options[:content_type]
45
+ file
46
+ end
@@ -0,0 +1,17 @@
1
+ require "test_helper"
2
+
3
+ class AttachmentTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+
7
+ end
8
+
9
+ def teardown
10
+
11
+ end
12
+
13
+ test "Attachment should respond to 'path' just like pages and sections." do
14
+ a = Attachment.new(:file_path=>"/test.jpg")
15
+ assert_equal "/test.jpg", a.path
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ require "test_helper"
2
+ require 'mocha'
3
+
4
+ class WebDavMiddlewareTest < ActiveSupport::TestCase
5
+
6
+
7
+ def setup
8
+ @webdav = Bcms::WebDavMiddleware.new(nil)
9
+ @request = mock()
10
+ @path = '/public'
11
+ end
12
+
13
+ def teardown
14
+
15
+ end
16
+
17
+ test "subdomain is_webdav too" do
18
+ @request.expects(:host).returns("webdav.example.com")
19
+ assert_equal true, @webdav.send(:is_webdav?, @request)
20
+
21
+ @request.expects(:host).returns("webdav.localhost")
22
+ assert_equal true, @webdav.send(:is_webdav?, @request)
23
+
24
+ end
25
+
26
+ test "non-subdomain is_webdav too" do
27
+ @request.expects(:host).returns("localhost")
28
+ assert_equal false, @webdav.send(:is_webdav?, @request)
29
+
30
+ @request.expects(:host).returns("www.webdav.example.com")
31
+ assert_equal false, @webdav.send(:is_webdav?, @request)
32
+
33
+ end
34
+
35
+ test "can configure different subdomain for webdav" do
36
+ webdav = Bcms::WebDavMiddleware.new(nil, {:subdomain=>"dav"})
37
+
38
+ @request.expects(:host).returns("dav.example.com")
39
+ assert_equal true, webdav.send(:is_webdav?, @request)
40
+
41
+ @request.expects(:host).returns("webdav.example.com")
42
+ assert_equal false, webdav.send(:is_webdav?, @request)
43
+ end
44
+
45
+ test "Setting up to use a port for detection for webdav requests" do
46
+ webdav = Bcms::WebDavMiddleware.new(nil, {:port=>3000})
47
+ @request.expects(:port).returns(3000)
48
+ assert_equal true, webdav.send(:is_webdav?, @request)
49
+ end
50
+ end
@@ -0,0 +1,283 @@
1
+ require "test_helper"
2
+ require 'mocha'
3
+
4
+ class WebDavSectionResourceTest < ActiveSupport::TestCase
5
+
6
+ def setup
7
+ @request = stub()
8
+ @request.expects(:ip).returns('').at_least_once
9
+ @about_us = Section.create!(:name=>"About Us", :path=>"/about-us", :parent=>Section.root.first)
10
+ @resource = resource_for("/about-us")
11
+ @resource.exist?
12
+ end
13
+
14
+ def teardown
15
+
16
+ end
17
+
18
+ test 'users with administrate permissions can access resources' do
19
+ mock_user = mock()
20
+ User.expects(:authenticate).with("abc", "123").returns(mock_user)
21
+ mock_user.expects(:able_to?).with(:administrate).returns(true)
22
+
23
+ assert_equal true, @resource.authenticate("abc", "123")
24
+ end
25
+
26
+ test "users without administrate can't access resources" do
27
+ mock_user = mock()
28
+ User.expects(:authenticate).with("abc", "123").returns(mock_user)
29
+ mock_user.expects(:able_to?).with(:administrate).returns(false)
30
+
31
+ assert_equal false, @resource.authenticate("abc", "123")
32
+
33
+ end
34
+
35
+ test "need a user account to access resource" do
36
+ mock_user = mock()
37
+ User.expects(:authenticate).with("abc", "123").returns(nil)
38
+
39
+ assert_equal false, @resource.authenticate("abc", "123")
40
+ end
41
+
42
+ test "convert webdav paths to cms paths" do
43
+ assert_equal "/", Bcms::WebDAV::Resource.normalize_path("/")
44
+ assert_equal "/", Bcms::WebDAV::Resource.normalize_path("")
45
+ assert_equal "/about-us", Bcms::WebDAV::Resource.normalize_path("/about-us/"), "Remove trailing slashes, which CMS paths don't have"
46
+ assert_equal "/about-us", Bcms::WebDAV::Resource.normalize_path("about-us/"), "Add starting slashes, which all CMS paths do have."
47
+ end
48
+
49
+ test "root path exists" do
50
+ root_section = Section.create!(:name=>"My Site", :path=>"/", :root=>true)
51
+
52
+ assert_not_nil Section.root.first, "Ensure the root section exists in the database"
53
+ assert_equal true, resource_for("/").exist?
54
+ assert_equal true, resource_for("").exist?, "WebDAV treats paths of '/' and '' as the root"
55
+
56
+ end
57
+
58
+ test "find subsection" do
59
+ contact_us = Section.create!(:name=>"Contact us", :path=>"/about-us/contact-us", :parent=>@about_us)
60
+ cu = resource_for("/about-us/contact-us")
61
+ assert_equal true, cu.exist?
62
+ end
63
+
64
+ test "find section with no leading slash" do
65
+ assert_equal true, resource_for("about-us/").exist?, "WebDAV seems to request subpaths (to collections) with no leading slash, plus a trailing slash"
66
+ end
67
+
68
+ test "exist?" do
69
+ resource = resource_for("/about-us")
70
+ assert_equal true, resource.exist?
71
+ end
72
+
73
+ test "exist? fails if section is missing" do
74
+ resource = resource_for("/not-about-us")
75
+ assert_equal false, resource.exist?
76
+ end
77
+
78
+ test "children returns all sections and pages" do
79
+ child_section = Section.create!(:name=>"Child 1", :path=>"/about-us/child1", :parent=>@about_us)
80
+ child_page = Page.create!(:name=>"Child 2", :path=>"/about-us/child2", :section=>@about_us)
81
+ resource = resource_for("/about-us")
82
+ children = resource.children
83
+ assert_equal 2, children.size
84
+ assert_equal "/about-us/child1", children[0].public_path
85
+ assert_equal "/about-us/child2", children[1].public_path
86
+ end
87
+
88
+
89
+ test "propfind ensures all resources are initialized when getting properties" do
90
+ child_section = Section.create!(:name=>"Child 1", :path=>"/about-us/child1", :parent=>@about_us)
91
+ child_page = Page.create!(:name=>"Child 2", :path=>"/about-us/child2", :section=>@about_us)
92
+ resource = resource_for("/about-us")
93
+ children = resource.children
94
+
95
+ assert_about_same_time child_section.created_at, children[0].creation_date
96
+ assert_about_same_time child_page.created_at, children[1].creation_date
97
+ end
98
+
99
+ def assert_about_same_time(expected, actual)
100
+ assert expected - actual <= 100, "Ensure the times are close"
101
+ end
102
+ test "creation_date" do
103
+ resource = resource_for("/about-us")
104
+ resource.exist?
105
+ assert @about_us.created_at - resource.creation_date <= 100, "Ensure the times are close"
106
+ end
107
+
108
+ test "last_modified" do
109
+ resource = resource_for("/about-us")
110
+ assert @about_us.updated_at - resource.last_modified <= 100, "Ensure the times are close"
111
+ end
112
+
113
+
114
+ test "etag is implemented in some vaguely terrible way" do
115
+
116
+ # Note: There is no particular logic to this etag, just trying to make it unique
117
+ assert_not_nil @resource.etag
118
+ end
119
+
120
+ test "sections are collections" do
121
+ assert(@resource.collection?)
122
+ end
123
+
124
+ test "sections are text/html" do
125
+ assert_equal "text/html", @resource.content_type
126
+ end
127
+
128
+ test "sections have 0 content_length" do
129
+ assert_equal 0, @resource.content_length
130
+ end
131
+
132
+
133
+ private
134
+
135
+ def resource_for(path)
136
+ Bcms::WebDAV::Resource.new(path, path, @request, Rack::MockResponse.new(200, {}, []), {})
137
+ end
138
+
139
+
140
+ end
141
+
142
+ class PageResourceTest < ActiveSupport::TestCase
143
+ def setup
144
+ @request = stub()
145
+ @request.expects(:ip).returns('').at_least_once
146
+ @about_us = Section.create!(:name=>"About Us", :path=>"/about-us", :parent=>Section.root.first)
147
+ @contact_us = Page.create!(:name=>"Contact Us", :path=>"/about-us/contact_us", :section=>@about_us)
148
+ @resource = resource_for("/about-us/contact_us")
149
+ @resource.exist?
150
+
151
+ end
152
+
153
+ test "exists" do
154
+ assert_equal true, @resource.exist?
155
+ end
156
+
157
+ test "creation_date" do
158
+ assert @contact_us.created_at - @resource.creation_date <= 100, "Ensure the times are close"
159
+ end
160
+
161
+ test "last_modified" do
162
+ assert @contact_us.updated_at - @resource.last_modified <= 100, "Ensure the times are close"
163
+ end
164
+
165
+ test "pages are not collections" do
166
+ assert_equal false, @resource.collection?
167
+ end
168
+
169
+
170
+ private
171
+ def resource_for(path)
172
+ Bcms::WebDAV::Resource.new(path, path, @request, Rack::MockResponse.new(200, {}, []), {})
173
+ end
174
+ end
175
+
176
+ class FileResourceTest < ActiveSupport::TestCase
177
+ def setup
178
+ @request = stub()
179
+ @request.expects(:ip).returns('').at_least_once
180
+
181
+ @response = Rack::MockResponse.new(200, {}, [])
182
+ @about_us = Section.create!(:name=>"About Us", :path=>"/about-us", :parent=>Section.root.first)
183
+
184
+ @file = file_upload_object(:original_filename => "test.jpg",
185
+ :content_type => "image/jpeg", :rewind => true,
186
+ :size => "99", :read => "01010010101010101")
187
+ @file_block = FileBlock.create!(:name=>"Testing", :attachment_file => @file, :attachment_section => @about_us, :attachment_file_path => "/about-us/test.jpg", :publish_on_save => true)
188
+
189
+ @resource = resource_for("/about-us/test.jpg")
190
+ @resource.exist?
191
+
192
+ end
193
+
194
+ test "exists" do
195
+ assert_equal true, @resource.exist?
196
+ end
197
+
198
+ test "creation_date" do
199
+ assert @file_block.created_at - @resource.creation_date <= 100, "Ensure the times are close"
200
+ end
201
+
202
+ test "last_modified" do
203
+ assert @file_block.updated_at - @resource.last_modified <= 100, "Ensure the times are close"
204
+ end
205
+
206
+ test "pages are not collections" do
207
+ assert_equal false, @resource.collection?
208
+ end
209
+
210
+ test "file size is correct" do
211
+ assert_equal @file.size, @resource.content_length
212
+ end
213
+
214
+ test "content_type matches underlying file type" do
215
+ assert_equal @file.content_type, @resource.content_type
216
+ end
217
+
218
+ test "Getting a file" do
219
+ mock_rack_file = mock()
220
+ mock_rack_file.expects(:path).returns("").at_least_once
221
+ Bcms::WebDAV::File.expects(:new).with(@file_block.attachment.full_file_location).returns(mock_rack_file)
222
+
223
+ @request.expects(:path).returns("/about-us/test.jpg").at_least_once
224
+ @response.expects(:body=).with(mock_rack_file)
225
+
226
+ @resource.get(@request, @response)
227
+
228
+ end
229
+
230
+ test "Finding a section includes child files as resources" do
231
+ @section = resource_for("/about-us")
232
+ assert_equal 1, @section.children.size
233
+ assert_equal "/about-us/test.jpg", @section.children.first.path
234
+ end
235
+
236
+
237
+ test "determine target_section" do
238
+ path = Bcms::WebDAV::Path.new('/about-us/test.jpg')
239
+ assert_equal "test.jpg", path.file_name
240
+ assert_equal '/about-us/', path.path_without_filename
241
+
242
+ assert_equal @about_us, @resource.find_section_for("/about-us/test.jpg")
243
+ end
244
+
245
+ test "Uploading files with spaces or special characters" do
246
+ path = Bcms::WebDAV::Path.new('/about-us/test with spaces.jpg')
247
+ assert_equal "test_with_spaces.jpg", path.file_name
248
+ assert_equal '/about-us/', path.path_without_filename
249
+
250
+ assert_equal @about_us, @resource.find_section_for("/about-us/test with spaces.jpg")
251
+ end
252
+ test "Uploading files with encoded spaces" do
253
+ path = Bcms::WebDAV::Path.new('/about-us/test%20with%20spaces.jpg')
254
+ assert_equal "test_with_spaces.jpg", path.file_name
255
+ assert_equal '/about-us/', path.path_without_filename
256
+
257
+ assert_equal @about_us, @resource.find_section_for("/about-us/test with spaces.jpg")
258
+ end
259
+
260
+ test "uploading files to root section" do
261
+ assert_equal Section.root.first, @resource.find_section_for("/test.jpg")
262
+ end
263
+
264
+ test "parse empty section" do
265
+ path = Bcms::WebDAV::Path.new('/test.jpg')
266
+ assert_equal "test.jpg", path.file_name
267
+
268
+
269
+ end
270
+
271
+ test "parse missing slash section" do
272
+ path = Bcms::WebDAV::Path.new('test.jpg')
273
+ assert_equal "test.jpg", path.file_name
274
+
275
+
276
+ end
277
+ private
278
+ def resource_for(path)
279
+ Bcms::WebDAV::Resource.new(path, path, @request, @response, {})
280
+ end
281
+ end
282
+
283
+
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bcms_webdav
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - BrowserMedia
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-01 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: browsercms
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 5
30
+ segments:
31
+ - 3
32
+ - 1
33
+ version: "3.1"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: dav4rack
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 21
45
+ segments:
46
+ - 0
47
+ - 2
48
+ - 1
49
+ version: 0.2.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Turns a BrowserCMS site into a WebDAV server, allowing access for bulk uploading files.
53
+ email: github@browsermedia.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - LICENSE.txt
60
+ - README.rdoc
61
+ files:
62
+ - COPYRIGHT.txt
63
+ - LICENSE.txt
64
+ - README.rdoc
65
+ - Rakefile
66
+ - VERSION
67
+ - doc/README_FOR_APP
68
+ - lib/bcms_webdav.rb
69
+ - lib/bcms_webdav/bcms_attachment_extensions.rb
70
+ - lib/bcms_webdav/file.rb
71
+ - lib/bcms_webdav/resource.rb
72
+ - lib/bcms_webdav/routes.rb
73
+ - lib/bcms_webdav/web_dav_middleware.rb
74
+ - lib/tasks/jeweler.rake
75
+ - public/bcms/webdav/README
76
+ - rails/init.rb
77
+ - test/performance/browsing_test.rb
78
+ - test/test_helper.rb
79
+ - test/unit/attachment_test.rb
80
+ - test/unit/web_dav_middleware_test.rb
81
+ - test/unit/web_dav_resource_test.rb
82
+ has_rdoc: true
83
+ homepage: https://github.com/browsermedia/bcms_webdav
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options: []
88
+
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirements: []
110
+
111
+ rubyforge_project: browsercms
112
+ rubygems_version: 1.3.7
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: A BrowserCMS module for WebDAV
116
+ test_files:
117
+ - test/performance/browsing_test.rb
118
+ - test/test_helper.rb
119
+ - test/unit/attachment_test.rb
120
+ - test/unit/web_dav_middleware_test.rb
121
+ - test/unit/web_dav_resource_test.rb