bcms_webdav 1.0.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GPL.txt +674 -0
- data/Gemfile +9 -0
- data/README.rdoc +18 -20
- data/app/assets/javascripts/application.js +15 -0
- data/app/assets/javascripts/bcms_webdav/application.js +15 -0
- data/app/assets/stylesheets/application.css +13 -0
- data/app/assets/stylesheets/bcms_webdav/application.css +13 -0
- data/app/controllers/bcms_webdav/application_controller.rb +4 -0
- data/app/helpers/bcms_webdav/application_helper.rb +4 -0
- data/config/routes.rb +2 -0
- data/lib/bcms_webdav.rb +5 -3
- data/lib/bcms_webdav/bcms_attachment_extensions.rb +25 -6
- data/lib/bcms_webdav/engine.rb +16 -0
- data/lib/bcms_webdav/file.rb +11 -16
- data/lib/bcms_webdav/resource.rb +67 -61
- data/lib/bcms_webdav/version.rb +3 -0
- data/lib/bcms_webdav/web_dav_middleware.rb +1 -1
- data/lib/generators/bcms_webdav/install/USAGE +3 -0
- data/lib/generators/bcms_webdav/install/install_generator.rb +20 -0
- data/lib/tasks/bcms_webdav_tasks.rake +4 -0
- metadata +77 -86
- data/Rakefile +0 -10
- data/VERSION +0 -1
- data/doc/README_FOR_APP +0 -2
- data/lib/bcms_webdav/routes.rb +0 -7
- data/lib/tasks/jeweler.rake +0 -34
- data/public/bcms/webdav/README +0 -1
- data/rails/init.rb +0 -4
- data/test/performance/browsing_test.rb +0 -9
- data/test/test_helper.rb +0 -46
- data/test/unit/attachment_test.rb +0 -17
- data/test/unit/web_dav_middleware_test.rb +0 -50
- data/test/unit/web_dav_resource_test.rb +0 -331
data/Gemfile
ADDED
data/README.rdoc
CHANGED
@@ -23,33 +23,30 @@ In order to take advantage of this module, users will need a WebDAV client. Many
|
|
23
23
|
* Transmit (Mac OS) - http://panic.com/transmit/
|
24
24
|
* CrossFTP Pro (Windows, etc) - http://www.crossftp.com/
|
25
25
|
|
26
|
-
This module has been tested with both of the above
|
26
|
+
This module has been tested with both of the above clients. In addition, operating systems support accessing WebDAV
|
27
27
|
servers via mounted drives. See: http://en.wikipedia.org/wiki/WebDAV for more discussion.
|
28
28
|
|
29
29
|
=== Installation
|
30
30
|
|
31
|
-
|
31
|
+
$ gem install bcms_webdav
|
32
|
+
$ rails g cms:install bcms_webdav
|
32
33
|
|
33
|
-
|
34
|
+
In your config/environments/development.rb, add the following:
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
# config/environment.rb
|
38
|
-
config.gem 'dav4rack'
|
39
|
-
config.gem 'bcms_webdav'
|
40
|
-
|
41
|
-
Add the following file to your project.
|
42
|
-
|
43
|
-
# config/initializers/bcms_webdav.rb
|
44
|
-
Rails.configuration.middleware.use Bcms::WebDavMiddleware, :port=>3001
|
36
|
+
config.middleware.use Bcms::WebDavMiddleware, :port=>3001
|
45
37
|
|
46
38
|
This configures the WebDAV server to listen on port 3001 for WeDAV requests in development. To access the WebDAV API,
|
47
39
|
you will need to start another rails server instance like so:
|
48
40
|
|
49
41
|
script/server --port=3001
|
50
42
|
|
51
|
-
|
52
|
-
|
43
|
+
==== For production
|
44
|
+
|
45
|
+
In your production.rb, add the following:
|
46
|
+
|
47
|
+
config.middleware.use Bcms::WebDavMiddleware
|
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. subdomain is used for the admin interface. The default subdomain is 'webdav'. Here's an example apache config file, with a new subdomain configured:
|
53
50
|
|
54
51
|
<VirtualHost *:80>
|
55
52
|
ServerName webdav.mysite.com
|
@@ -70,8 +67,7 @@ This means there should be a total of three domains in place for a site in produ
|
|
70
67
|
|
71
68
|
You can change the subdomain that this module listens for requests on via:
|
72
69
|
|
73
|
-
|
74
|
-
Rails.configuration.middleware.use Bcms::WebDavMiddleware, :port=>3001, :subdomain=>"dav"
|
70
|
+
config.middleware.use Bcms::WebDavMiddleware, :port=>3001, :subdomain=>"dav"
|
75
71
|
|
76
72
|
This would change the subdomain from 'webdav' to 'dav'. This webserver config file would also need to be changed as well.
|
77
73
|
|
@@ -84,8 +80,7 @@ make 'webdav.localhost' map to your local development environment, like so:
|
|
84
80
|
|
85
81
|
Then configure the module so its no longer listening on port 3001, like so:
|
86
82
|
|
87
|
-
|
88
|
-
Rails.configuration.middleware.use Bcms::WebDavMiddleware
|
83
|
+
config.middleware.use Bcms::WebDavMiddleware
|
89
84
|
|
90
85
|
Now you can make web requests to localhost:3000 and WebDAV requests to webdav.localhost:3000
|
91
86
|
|
@@ -102,8 +97,11 @@ This is an incomplete implementation of WebDAV, so many operations are not expli
|
|
102
97
|
* All files are uploaded as 'FileBlocks', regardless of whether they are images or not.
|
103
98
|
* All content_types are set to 'application/octet-stream' regardless of their actual type.
|
104
99
|
* Links do not appear in the list of content items returned.
|
100
|
+
* Uploading a file with a space in it will not replace the existing file. Since we convert spaces into _, they are treated as two separate resources.
|
101
|
+
* Refactoring to avoid the need for configuring the middleware is possible. Engines can self setup middleware now.
|
105
102
|
|
106
103
|
=== Performance
|
107
104
|
|
108
105
|
This module almost certainly needs performance testing when dealing with larger sites. Certain FTP clients will make more requests for
|
109
|
-
resources than others. (CrossFTP seems 'chattier' than Transmit for instance).
|
106
|
+
resources than others. (CrossFTP seems 'chattier' than Transmit for instance).
|
107
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
data/config/routes.rb
ADDED
data/lib/bcms_webdav.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require 'bcms_webdav/web_dav_middleware'
|
1
|
+
require "bcms_webdav/engine"
|
3
2
|
require 'bcms_webdav/file'
|
4
|
-
require 'bcms_webdav/bcms_attachment_extensions'
|
5
3
|
require 'bcms_webdav/resource'
|
4
|
+
require 'bcms_webdav/web_dav_middleware'
|
5
|
+
|
6
|
+
module BcmsWebdav
|
7
|
+
end
|
@@ -1,7 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
# Exist to ensure compliance with Resource API for WebDAV (like Section and Pages).
|
2
|
+
module BcmsWebdav
|
3
|
+
|
4
|
+
# Attachment#path is actually the file path, so we have to define a new method that all
|
5
|
+
# child_nodes can use.
|
6
|
+
module Extensions
|
7
|
+
module Attachments
|
8
|
+
def relative_path
|
9
|
+
url
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Pages
|
14
|
+
def relative_path
|
15
|
+
path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Sections
|
20
|
+
def relative_path
|
21
|
+
path
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
6
25
|
end
|
7
|
-
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'browsercms'
|
2
|
+
require 'dav4rack'
|
3
|
+
|
4
|
+
module BcmsWebdav
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
isolate_namespace BcmsWebdav
|
7
|
+
include Cms::Module
|
8
|
+
|
9
|
+
config.to_prepare do
|
10
|
+
require 'bcms_webdav/bcms_attachment_extensions'
|
11
|
+
Cms::Attachment.send(:include, BcmsWebdav::Extensions::Attachments)
|
12
|
+
Cms::Page.send(:include, BcmsWebdav::Extensions::Pages)
|
13
|
+
Cms::Section.send(:include, BcmsWebdav::Extensions::Sections)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/bcms_webdav/file.rb
CHANGED
@@ -5,26 +5,21 @@ module Bcms
|
|
5
5
|
# public path of CMS files is different that actual file path.
|
6
6
|
class File < Rack::File
|
7
7
|
|
8
|
-
#
|
8
|
+
# @param [String] absolute_file_path Absolute path to the file on the server.
|
9
9
|
def initialize(absolute_file_path)
|
10
|
-
@
|
10
|
+
@cms_path_to_file = absolute_file_path
|
11
|
+
|
12
|
+
# Normally, files are restricted under the root of the web application.
|
13
|
+
# Here, we set the root to blank. The cms_path_to_file is the complete path to the file.
|
14
|
+
@root = ""
|
11
15
|
end
|
12
16
|
|
13
|
-
#
|
14
|
-
def
|
15
|
-
|
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
|
17
|
+
# As of Rack 1.4.1, we override this call to have it stuff the CMS path into the PATH_INFO
|
18
|
+
def call(env)
|
19
|
+
env['PATH_INFO'] = @cms_path_to_file
|
20
|
+
dup._call(env)
|
27
21
|
end
|
22
|
+
|
28
23
|
end
|
29
24
|
end
|
30
25
|
end
|
data/lib/bcms_webdav/resource.rb
CHANGED
@@ -26,7 +26,7 @@ module Bcms
|
|
26
26
|
|
27
27
|
def authenticate(username, password)
|
28
28
|
log "Authenticating user '#{username}'"
|
29
|
-
user = User.authenticate(username, password)
|
29
|
+
user = Cms::User.authenticate(username, password)
|
30
30
|
|
31
31
|
unless user
|
32
32
|
Rails.logger.error "Failed authentication attempt by user '#{username}'"
|
@@ -52,20 +52,20 @@ module Bcms
|
|
52
52
|
# This should always be called by DAV4Rack controller before any other primary operation (get, put) on a resource.
|
53
53
|
def exist?
|
54
54
|
path_to_find = Resource.normalize_path(path)
|
55
|
-
@section = Section.with_path(path_to_find).first
|
55
|
+
@section = Cms::Section.with_path(path_to_find).first
|
56
56
|
|
57
57
|
if have_section
|
58
58
|
log_exists('section', path_to_find)
|
59
59
|
@resource = @section if have_section
|
60
60
|
end
|
61
61
|
|
62
|
-
@page = Page.with_path(path_to_find).first
|
62
|
+
@page = Cms::Page.with_path(path_to_find).first
|
63
63
|
if have_page
|
64
64
|
log_exists('page', path_to_find)
|
65
65
|
@resource = @page
|
66
66
|
end
|
67
67
|
|
68
|
-
@file = Attachment.
|
68
|
+
@file = Cms::Attachment.find_live_by_file_path(path)
|
69
69
|
if have_file
|
70
70
|
log_exists('file', path_to_find)
|
71
71
|
@resource = @file
|
@@ -73,7 +73,14 @@ module Bcms
|
|
73
73
|
|
74
74
|
have_section || have_page || have_file
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
|
+
# Find the parent resource. We cache the result here so it can be loaded from the DB properly.
|
78
|
+
def parent
|
79
|
+
return @parent if @parent
|
80
|
+
@parent = super
|
81
|
+
@parent
|
82
|
+
end
|
83
|
+
|
77
84
|
def children
|
78
85
|
if exist?
|
79
86
|
child_nodes = @section.child_nodes
|
@@ -88,12 +95,14 @@ module Bcms
|
|
88
95
|
end
|
89
96
|
end
|
90
97
|
|
98
|
+
# 1 year ago handles missing files (which is hopefully a temporary bug while trying to upgrade to CMS 3.5)
|
91
99
|
def creation_date
|
92
|
-
@resource.created_at
|
100
|
+
@resource ? @resource.created_at : 1.year.ago
|
93
101
|
end
|
94
102
|
|
95
103
|
def last_modified
|
96
|
-
@resource.created_at if exist?
|
104
|
+
return @resource.created_at if exist?
|
105
|
+
1.year.ago
|
97
106
|
end
|
98
107
|
|
99
108
|
def collection?
|
@@ -109,16 +118,16 @@ module Bcms
|
|
109
118
|
end
|
110
119
|
|
111
120
|
def content_length
|
112
|
-
have_file ? @resource.
|
121
|
+
have_file ? @resource.size : 0
|
113
122
|
end
|
114
123
|
|
115
124
|
def get(request, response)
|
116
|
-
log "GET request for #{request.path}"
|
125
|
+
# log "GET request for #{request.path}"
|
117
126
|
if have_file
|
118
|
-
|
119
|
-
log "For attachment '#{@resource}' file
|
120
|
-
file = Bcms::WebDAV::File.new(
|
121
|
-
log "Sending file '#{
|
127
|
+
path_to_file = @resource.data.path
|
128
|
+
# log "For attachment '#{@resource}' path to file is '#{path_to_file}"
|
129
|
+
file = Bcms::WebDAV::File.new(path_to_file)
|
130
|
+
log "Sending file '#{path_to_file}'"
|
122
131
|
response.body = file
|
123
132
|
end
|
124
133
|
end
|
@@ -126,82 +135,75 @@ module Bcms
|
|
126
135
|
# Handle uploading file.
|
127
136
|
def put(request, response)
|
128
137
|
temp_file = extract_tempfile(request)
|
129
|
-
|
130
|
-
add_rails_like_methods(temp_file)
|
131
|
-
|
132
138
|
section = find_section_for(path)
|
133
139
|
|
134
|
-
file_block = FileBlock.new(:name=>path, :
|
140
|
+
file_block = Cms::FileBlock.new(:name=>path, :publish_on_save=>true)
|
141
|
+
file_block.attachments.build(:data => temp_file, :attachment_name => 'file', :parent => section, :data_file_path => path)
|
142
|
+
|
143
|
+
# Ensure the file pointer is at the beginning so Paperclip can copy after the block is saved.
|
144
|
+
# Something in assigning the tempfile to the Block is not correctly rewinding the file.
|
145
|
+
# Not doing this causes an empty file to be saved in uploads directory.
|
146
|
+
temp_file.rewind
|
147
|
+
|
135
148
|
unless file_block.save
|
136
149
|
log "Couldn't save file."
|
137
150
|
file_block.errors.each do |error|
|
138
151
|
log error
|
139
152
|
end
|
140
|
-
|
153
|
+
work_around_dav4rack_bug
|
154
|
+
return InternalServerError
|
141
155
|
end
|
142
|
-
|
156
|
+
Created
|
143
157
|
end
|
144
158
|
|
159
|
+
|
160
|
+
# Ensures path is encoded. In most cases, an encoded path may occur after uploading a file (PUT) with special characters in it.
|
161
|
+
def public_path
|
162
|
+
p = super
|
163
|
+
begin
|
164
|
+
URI(p)
|
165
|
+
rescue URI::InvalidURIError
|
166
|
+
return URI.escape(p)
|
167
|
+
end
|
168
|
+
p
|
169
|
+
end
|
170
|
+
|
171
|
+
# If Created isn't returned, dav4rack controller will set the body to nil, which will cause Rack to blow up.
|
172
|
+
# i.e. response.body = response['Location'] but 'Location' is only set if Created == true
|
173
|
+
# See https://github.com/chrisroberts/dav4rack/blob/master/lib/dav4rack/controller.rb#put
|
174
|
+
def work_around_dav4rack_bug
|
175
|
+
response['Location'] = ''
|
176
|
+
end
|
177
|
+
|
145
178
|
def find_section_for(path)
|
146
|
-
log "Looking up section for path '#{path}"
|
147
179
|
path_obj = Path.new(path)
|
148
180
|
section_path = path_obj.path_without_filename
|
149
181
|
path_to_find = Resource.normalize_path(section_path)
|
150
|
-
|
151
|
-
log "Section.path = #{path_to_find}"
|
152
|
-
Section.with_path(path_to_find).first
|
182
|
+
Cms::Section.with_path(path_to_find).first
|
153
183
|
end
|
154
184
|
|
155
185
|
private
|
156
186
|
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
# Until Rails 3, which may have a consistent middleware for extracting a Tempfile, we have to do it this way.
|
187
|
+
# Save and return the tempfile. This is slightly duplicative of how Rack/Rails save a tempfile, then Paperclip automatically
|
188
|
+
# handles copying it. In our case, since WebDAV isn't like a form multipart upload, we have to explicitly save it
|
189
|
+
# rather than having Rails implicitly handle this for us.
|
162
190
|
def extract_tempfile(request)
|
163
|
-
input = request.body
|
164
|
-
if input
|
165
|
-
# Handle Mongrel
|
166
|
-
return input if input.is_a?(Tempfile)
|
167
|
-
|
168
|
-
# Handle Passenger
|
169
|
-
# This is highly brittle and terrible.
|
170
|
-
if input.respond_to?(:make_rewindable, true)
|
171
|
-
input.size # Force creation of Tempfile
|
172
|
-
return input.instance_variable_get(:@rewindable_io)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
191
|
|
192
|
+
uploaded_file = Paperclip.io_adapters.for(request.body)
|
193
|
+
uploaded_path = Path.new(path)
|
194
|
+
uploaded_file.original_filename = uploaded_path.file_name
|
195
|
+
uploaded_file.content_type = 'application/octet-stream'
|
196
|
+
uploaded_file
|
177
197
|
end
|
178
198
|
|
179
199
|
def log_exists(type, path)
|
180
200
|
log "Resource of type '#{type}' with path '#{path}' exists."
|
181
201
|
end
|
182
202
|
|
183
|
-
# Make this TempFile object act like a RailsTempFile
|
184
|
-
def add_rails_like_methods(temp_file)
|
185
|
-
# For the purposes of Rails 2, this will have to do. Rails 3 make this much easier by providing additional
|
186
|
-
# Rack processors for ActionDispatch::Http::UploadedFile which makes this unncessary.
|
187
|
-
|
188
|
-
def temp_file.content_type
|
189
|
-
'application/octet-stream'
|
190
|
-
end
|
191
|
-
|
192
|
-
def temp_file.original_filename
|
193
|
-
path
|
194
|
-
end
|
195
|
-
|
196
|
-
def temp_file.local_path
|
197
|
-
self.path
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
203
|
def child_node(section_node)
|
202
204
|
node_object = section_node.node
|
203
|
-
return nil if node_object == nil || node_object.is_a?(Link)
|
204
|
-
child_node = self.class.new(node_object.
|
205
|
+
return nil if node_object == nil || node_object.is_a?(Cms::Link)
|
206
|
+
child_node = self.class.new(node_object.relative_path, node_object.relative_path, request, response, options.merge(:user => @user))
|
205
207
|
child_node.exist? # Force lookup of info from DB.
|
206
208
|
child_node
|
207
209
|
end
|
@@ -232,6 +234,10 @@ module Bcms
|
|
232
234
|
@parts = scanned[0]
|
233
235
|
end
|
234
236
|
|
237
|
+
def sanitize_file_path(path)
|
238
|
+
Cms::Attachment.sanitize_file_path(path)
|
239
|
+
end
|
240
|
+
|
235
241
|
def parts
|
236
242
|
@parts
|
237
243
|
end
|