208 lines
6.1 KiB
Ruby
208 lines
6.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative './environment.rb'
|
|
require 'rack/test'
|
|
require 'nokogiri'
|
|
require 'rack/utils'
|
|
|
|
describe 'webdav' do
|
|
include Rack::Test::Methods
|
|
|
|
def app
|
|
@app ||= begin
|
|
builder = Rack::Builder.parse_file('config.ru')
|
|
builder = builder.first if builder.is_a?(Array)
|
|
builder = builder.to_app if builder.respond_to?(:to_app)
|
|
builder
|
|
end
|
|
end
|
|
|
|
before do
|
|
@site = Fabricate(:site)
|
|
@site.update(plan_type: 'supporter')
|
|
ThumbnailWorker.jobs.clear
|
|
PurgeCacheWorker.jobs.clear
|
|
ScreenshotWorker.jobs.clear
|
|
end
|
|
|
|
def auth_put(path, body)
|
|
basic_authorize @site.username, 'abcde'
|
|
put "/webdav/#{path}", body
|
|
end
|
|
|
|
def auth_get(path)
|
|
basic_authorize @site.username, 'abcde'
|
|
get "/webdav/#{path}"
|
|
end
|
|
|
|
def auth_head(path)
|
|
basic_authorize @site.username, 'abcde'
|
|
head "/webdav/#{path}"
|
|
end
|
|
|
|
def auth_propfind(path, depth='1')
|
|
basic_authorize @site.username, 'abcde'
|
|
header 'Depth', depth
|
|
request "/webdav/#{path}", method: 'PROPFIND'
|
|
header 'Depth', nil
|
|
end
|
|
|
|
def auth_mkcol(path)
|
|
basic_authorize @site.username, 'abcde'
|
|
request "/webdav/#{path}", method: 'MKCOL'
|
|
end
|
|
|
|
def auth_delete(path)
|
|
basic_authorize @site.username, 'abcde'
|
|
delete "/webdav/#{path}"
|
|
end
|
|
|
|
def auth_move(from_path, to_path)
|
|
basic_authorize @site.username, 'abcde'
|
|
header 'Destination', "http://example.org/webdav/#{to_path}"
|
|
request "/webdav/#{from_path}", method: 'MOVE'
|
|
header 'Destination', nil
|
|
end
|
|
|
|
it 'creates files via PUT' do
|
|
auth_put 'create.txt', 'hello webdav'
|
|
_(last_response.status).must_equal 201
|
|
_(Site[@site.id].file_exists?('create.txt')).must_equal true
|
|
end
|
|
|
|
it 'decodes percent-encoded paths when storing files' do
|
|
encoded_path = 'Alien%20%281%29.jpg'
|
|
decoded_path = 'Alien (1).jpg'
|
|
|
|
auth_put encoded_path, 'image data'
|
|
_(last_response.status).must_equal 201
|
|
|
|
site = Site[@site.id]
|
|
_(site.file_exists?(decoded_path)).must_equal true
|
|
_(site.site_files_dataset.where(path: decoded_path).count).must_equal 1
|
|
_(site.site_files_dataset.where(path: encoded_path).count).must_equal 0
|
|
end
|
|
|
|
it 'reads files via GET' do
|
|
auth_put 'read.txt', 'read me'
|
|
auth_get 'read.txt'
|
|
_(last_response.status).must_equal 200
|
|
_(last_response.body).must_equal 'read me'
|
|
end
|
|
|
|
it 'provides metadata via HEAD without a body' do
|
|
auth_put 'meta.txt', 'meta'
|
|
|
|
auth_head 'meta.txt'
|
|
_(last_response.status).must_equal 200
|
|
_(last_response.body).must_equal ''
|
|
_(last_response.headers['Content-Length']).wont_be_nil
|
|
_(last_response.headers['Content-Type']).must_equal 'text/plain'
|
|
site_file = Site[@site.id].site_files_dataset.where(path: 'meta.txt').first
|
|
_(site_file).wont_be_nil
|
|
header_time = Time.httpdate(last_response.headers['Last-Modified'])
|
|
_(header_time.to_i).must_equal site_file.updated_at.to_i
|
|
end
|
|
|
|
it 'updates files via MOVE' do
|
|
auth_put 'update.txt', 'content'
|
|
auth_move 'update.txt', 'renamed.txt'
|
|
_(last_response.status).must_equal 201
|
|
site = Site[@site.id]
|
|
_(site.file_exists?('update.txt')).must_equal false
|
|
_(site.file_exists?('renamed.txt')).must_equal true
|
|
end
|
|
|
|
it 'strips leading and trailing slashes when moving files' do
|
|
auth_put 'slashy.txt', 'content'
|
|
|
|
auth_move 'slashy.txt/', '/renamed.txt/'
|
|
_(last_response.status).must_equal 201
|
|
site = Site[@site.id]
|
|
_(site.file_exists?('slashy.txt')).must_equal false
|
|
_(site.file_exists?('renamed.txt')).must_equal true
|
|
end
|
|
|
|
it 'fails MOVE when destination header is empty' do
|
|
auth_put 'move.txt', 'body'
|
|
basic_authorize @site.username, 'abcde'
|
|
header 'Destination', ''
|
|
request '/webdav/move.txt', method: 'MOVE'
|
|
header 'Destination', nil
|
|
_(last_response.status).must_equal 400
|
|
end
|
|
|
|
it 'deletes files via DELETE' do
|
|
auth_put 'delete.txt', 'gone'
|
|
auth_delete 'delete.txt'
|
|
_(last_response.status).must_equal 201
|
|
_(Site[@site.id].file_exists?('delete.txt')).must_equal false
|
|
end
|
|
|
|
it 'rejects GET requests for directories' do
|
|
auth_mkcol 'dir'
|
|
auth_get 'dir'
|
|
_(last_response.status).must_equal 403
|
|
_(last_response.body).must_include 'directory'
|
|
end
|
|
|
|
it 'rejects DELETE of index.html' do
|
|
auth_put 'index.html', 'index'
|
|
auth_delete 'index.html'
|
|
_(last_response.status).must_equal 403
|
|
_(last_response.body).must_include 'index.html'
|
|
end
|
|
|
|
it 'prevents PUT when a directory exists at the path' do
|
|
auth_mkcol 'dup'
|
|
auth_put 'dup', 'nope'
|
|
_(last_response.status).must_equal 409
|
|
_(last_response.body).must_include 'conflicts with an existing directory'
|
|
end
|
|
|
|
it 'rejects MKCOL with invalid paths' do
|
|
auth_mkcol '../bad'
|
|
_(last_response.status).must_equal 400
|
|
_(last_response.body).must_include 'Invalid path'
|
|
end
|
|
|
|
it 'returns 401 for unauthenticated requests' do
|
|
get '/webdav/test.txt'
|
|
_(last_response.status).must_equal 401
|
|
_(last_response.headers['WWW-Authenticate']).must_include 'Basic'
|
|
end
|
|
|
|
it 'requires supporter accounts' do
|
|
free_site = Fabricate(:site)
|
|
free_site.update(plan_type: 'free')
|
|
basic_authorize free_site.username, 'abcde'
|
|
get '/webdav/test.txt'
|
|
_(last_response.status).must_equal 402
|
|
_(last_response.headers['X-Upgrade-Required']).must_include 'supporter'
|
|
end
|
|
|
|
it 'lists resources with PROPFIND depth 0 on root' do
|
|
auth_propfind '', '0'
|
|
_(last_response.status).must_equal 207
|
|
doc = Nokogiri::XML(last_response.body)
|
|
hrefs = doc.xpath('//D:href', 'D' => 'DAV:').map(&:text)
|
|
_(hrefs).must_include('/webdav/')
|
|
end
|
|
|
|
it 'lists children with PROPFIND depth 1' do
|
|
auth_mkcol 'folder'
|
|
auth_put 'folder/file.txt', 'data'
|
|
|
|
auth_propfind 'folder', '1'
|
|
_(last_response.status).must_equal 207
|
|
doc = Nokogiri::XML(last_response.body)
|
|
hrefs = doc.xpath('//D:href', 'D' => 'DAV:').map(&:text)
|
|
_(hrefs).must_include('/webdav/folder/')
|
|
_(hrefs).must_include('/webdav/folder/file.txt')
|
|
end
|
|
|
|
it 'returns 404 for PROPFIND on missing paths' do
|
|
auth_propfind 'missing.txt', '0'
|
|
_(last_response.status).must_equal 404
|
|
end
|
|
end
|