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/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+ gem "jquery-rails"
5
+ gem 'sqlite3'
6
+
7
+ group :test do
8
+ gem 'mocha'
9
+ end
@@ -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 client. In addition, operating systems support accessing WebDAV
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
- 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.
31
+ $ gem install bcms_webdav
32
+ $ rails g cms:install bcms_webdav
32
33
 
33
- gem install bcms_webdav
34
+ In your config/environments/development.rb, add the following:
34
35
 
35
- Add the gem to your project.
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
- 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.
52
- subdomain is used for the admin interface. The default subdomain is 'webdav'. Here's an example apache config file, with a new subdomain configured:
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
- # config/initializers/bcms_webdav.rb
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
- # config/initializers/bcms_webdav.rb
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
+ */
@@ -0,0 +1,4 @@
1
+ module BcmsWebdav
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module BcmsWebdav
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ BcmsWebdav::Engine.routes.draw do
2
+ end
@@ -1,5 +1,7 @@
1
- require 'bcms_webdav/routes'
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
- 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
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
@@ -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
- # This should be an absolute file path
8
+ # @param [String] absolute_file_path Absolute path to the file on the server.
9
9
  def initialize(absolute_file_path)
10
- @path = absolute_file_path
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
- # 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
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
@@ -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.find_by_file_path(path)
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.file_size : 0
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
- file_location = @resource.full_file_location
119
- log "For attachment '#{@resource}' file location is '#{file_location}"
120
- file = Bcms::WebDAV::File.new(file_location)
121
- log "Sending file '#{file.path}'"
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, :attachment_file=>temp_file, :attachment_section => section, :attachment_file_path=>path, :publish_on_save=>true)
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
- return
153
+ work_around_dav4rack_bug
154
+ return InternalServerError
141
155
  end
142
- OK
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
- # Different webservers have slightly different behavior for uploaded files.
158
- # 1. Webrick/mongrel - body is a TempFile
159
- # 2. Passenger - body is a PhusionPassenger::Utils::RewindableInput
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.path, node_object.path, request, response, options.merge(:user => @user))
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