Skip to content

Instantly share code, notes, and snippets.

@krobertson
Created January 1, 2009 19:12
Show Gist options
  • Save krobertson/42329 to your computer and use it in GitHub Desktop.
Save krobertson/42329 to your computer and use it in GitHub Desktop.
rackdav
#
# rackdav (prototype) by Ken Robertson
#
# rackdav is a work in progress of a Rack middleware implementation of WebDAV
#
# This code is barely functional and ugly as hell, but meant to just work on figuring out compliance.
# It is built by working through the test suite provided by litmus (http://www.webdav.org/neon/litmus)
# Once it has compliance on the commands, it will be greatly cleaned up and refactored into an actual
# library instead of a gist.
#
# TODO: Mostly property handling, custom property storage, and locking (in memory for dev, memcached perhaps for production)
#
require 'rubygems'
require 'cgi'
require 'rack'
require 'hpricot'
require 'tempfile'
require 'libxml'
require 'activesupport'
class Dav
def call(env)
path = File.join('dav', CGI.unescape(env['REQUEST_PATH']))
path = path.chop if path =~ /\/$/
dest = File.join('dav', CGI.unescape(env['HTTP_DESTINATION'].gsub('http://localhost:5000/', ''))) if env.has_key?('HTTP_DESTINATION')
dest = dest.chop if dest =~ /\/$/
puts "==================== #{env['HTTP_X_LITMUS']}: #{env['REQUEST_METHOD']} #{path}"
puts "#{Time.now}: #{env.inspect}"
puts
case env['REQUEST_METHOD'].downcase
when 'options'
[200, {'DAV' => '1'}, '']
when 'get'
unless File.exist?(path)
[404, {}, 'Not found']
else
f = File.new(path)
[200, {"Content-Type"=>"text/plain", 'Content-Length' => File.size(path).to_s}, f]
end
when 'put'
f = File.new(path, 'w')
b = env['rack.input'].read(env['rack.input'].size)
f.write b
f.close
[201, {"Content-Type"=>"text/plain"}, ['Hello']]
when 'post'
f = File.new(path, 'w')
b = env['rack.input'].read(env['rack.input'].size)
f.write b
f.close
[201, {"Content-Type"=>"text/plain"}, ['Hello']]
when 'delete'
if File.exist?(path)
FileUtils.rm_r(path)
[200, {}, '']
else
[404, {}, '']
end
when 'mkcol'
return [415, {}, ''] if env['rack.input'].size > 0
parent = path.split('/')
parent = parent[0,parent.size-1].join('/')
return [409, {}, ''] unless File.directory?(parent)
unless File.exist?(path)
FileUtils.mkdir(path)
[200, {"Content-Type"=>"text/plain"}, ["Hello world!"]]
else
[405, {"Content-Type"=>"text/plain"}, ["Resource already exists"]]
end
when 'copy'
overwrite = env['HTTP_OVERWRITE'] == 'T'
exists = File.exist?(dest)
return [412, {}, ''] if exists and !overwrite
parent = dest.split('/')
parent = parent[0,parent.size-1].join('/')
return [409, {}, ''] unless File.directory?(parent)
FileUtils.cp_r(path, dest)
[exists && overwrite ? 204 : 201, {}, '']
when 'move'
overwrite = env['HTTP_OVERWRITE'] == 'T'
exists = File.exist?(dest)
return [412, {}, ''] if exists and !overwrite
parent = dest.split('/')
parent = parent[0,parent.size-1].join('/')
return [409, {}, ''] unless File.directory?(parent)
FileUtils.rm_r(dest) if exists && overwrite
FileUtils.move(path, dest)
[exists && overwrite ? 204 : 201, {}, '']
when 'propfind'
lines = env['rack.input'].readlines.join("\n")
puts "......................"
puts lines
puts "......................"
query = xmlize(lines)
return [400, {}, ''] unless query.root.name == 'propfind'
[550, {"Content-Type"=>"text/plain"}, ["Hello world!"]]
when 'proppatch'
lines = env['rack.input'].readlines.join("\n")
puts "......................"
puts lines
puts "......................"
query = xmlize(lines)
return [400, {}, ''] unless query.root.name == 'propertyupdate'
properties = []
query.find("//D:prop").each do |prop|
puts "------------------ .#{prop.children.first.name}."
properties << prop.children.first.name
end
x = Builder::XmlMarkup.new(:indent => 2)
x.instruct!
response = x.D(:multistatus, {'xmlns:D' => "DAV:"}) do
x.D(:response) do
x.D(:href, 'http://localhost:5000' + env['REQUEST_URI'])
properties.each do |p|
x.D(:propstat) do
x.D(:prop) { x.D(:"#{p}") }
x.D(:status, 'HTTP/1.1 424 Failed Dependency')
end
end
end
end
[207, {"Content-Type"=>"text/xml", 'Content-Length' => response.size.to_s}, [response]]
else
[550, {"Content-Type"=>"text/plain"}, ["Hello world!"]]
end
end
def xmlize(env)
lines = env
# lines = env['rack.input'].readlines.join("\n") unless env.is_a?(String)
temp = Tempfile.new('a')
temp.write lines
temp.close
LibXML::XML::Document.file(temp.path)
end
end
Rack::Handler::Mongrel.run(Dav.new, {:Host => "0.0.0.0", :Port => 5000})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment