lk.DavServer

:MKCOL (request)

local lib = {type='lk.DavServer'} lib.__index = lib lk.DavServer = lib

-- the DavServer is an extension of the WebServer for k, v in pairs(lk.WebServer) do if k ~= 'type' and k ~= '__index' then lib[k] = v end end

setmetatable(lib, { -- new method __call = function(lib, port, root) -- make sure we run with 'C' locale (because of HTTP date parsing) os.setlocale('C') local self = { should_run = true, clients = {}, server = lk.Socket(), on_ready = {}, }

self.server:bind('*', port or 0) self.host, self.port = self.server:localHost(), self.server:localPort() self.href_base = 'http://' .. self.host .. ':' .. self.port printf("Started DavServer: %s", self.href_base) setmetatable(self, lib) if root then self:setRoot(root) else -- if no root path is provided, all the functions (find, findChildren, -- update, create, delete and move) should be implemented. end self:listen() return self end})

if type(root) == 'string' then self.root = lk.FileResource(root) else self.root = root end -- FIXME: how long do we keep these around ? self.deleted = {} end

--=============================================== Functions to sub-class local rez = self.root.cache[url] if not rez then -- this only happens on special files because we list children -- (and cache result) before we are asked for a path. rez = lk.FileResource(url, self.root) end return rez end

return resource:children() end

if resource:update(content) then return nil, {status = '200'} else return nil, {status = '400'} end end

-- create resource local url = parent.url .. '/' .. name local resource = self.deleted[url] if resource then if content and not resource.is_dir then if resource:update(content) then self.root.cache[url] = resource self.deleted[url] = nil return nil, {status = '201'} end else self.deleted[url] = nil end end if parent:createChild(name, content) then return nil, {status = '201'} end return nil, {status = '400'} end

-- we use this in case the remote uses a delete -- and create to update self.deleted[resource.url] = resource if parent:deleteChild(resource) then -- clear cache collectgarbage() return nil, {status = '204'} end return nil, {status = '400'} end

if parent:moveChild(resource, dest_parent, dest_name) then return nil, {status = '201'} else return nil, {status = '400'} end end

--=============================================== PRIVATE

local DATE_FORMAT_HTTP_DATE = '!%a, %d %b %Y %H:%M:%S GMT' lib.DATE_FORMAT_HTTP_DATE = DATE_FORMAT_HTTP_DATE

local DATE_FORMAT_ISO_8601 = '!%Y-%m-%dT%H:%M:%SZ' lib.DATE_FORMAT_ISO_8601 = DATE_FORMAT_ISO_8601

-- More status codes for WebDav lib.STATUS_CODES['207'] = 'Multi-Status'

return nil, { DAV = "1, 2" } end

local function etag(...) -- TODO: sha1 return lk.crypto.sha1(string.format(...)) end

local function getResourceType(is_dir) if is_dir then return {xml = 'collection'} end end

local function getContentLength(is_dir) if is_dir then return 2 else return 0 end end

local function isDirectory(resource) local rtype = resource:davProperty('resourcetype') if rtype then return rtype.xml == 'collection' end end

local function httpDateNoLocale(time) DATE_FORMAT_HTTP_DATE = '!%a, %d %b %Y %H:%M:%S GMT' end

local function makeResponse(self, resource, prop_list) local prop = {xml='prop'} for _, k in ipairs(prop_list) do k = k.xml local v = resource:davProperty(k) if v then if k == 'creationdate' then v = os.date(DATE_FORMAT_ISO_8601, v) elseif k == 'getlastmodified' then -- This format is a stupid: it renders differently depending on the -- locale: this is why we need setlocale('C') v = os.date(DATE_FORMAT_HTTP_DATE, v) end end table.insert(prop, {xml = k, v or ''}) end return { xml = 'response', {xml = 'href', self.href_base .. resource:davProperty('url')}, {xml = 'propstat', prop, {xml = 'status', "HTTP/1.1 200 OK"}, } } end

local function getPropList(body) local data = xml.parse(body or '') if not data then return end xml.removeNamespace(data, 'DAV:') return data:find('prop') end

local resource = self:find(request.path) local prop_list = getPropList(request.body) if not resource or not prop_list then return nil, {status = '404'} end

if params then

end

local headers = request.headers local depth = tonumber(headers['Depth'])

local res = { xml = 'multistatus', xmlns = 'DAV:' } -- use oldest change to build etag local last_mod = nil

table.insert(res, makeResponse(self, resource, prop_list)) if isDirectory(resource) and depth == 1 then local children = self:findChildren(resource) for _, child in ipairs(children) do table.insert(res, makeResponse(self, child, prop_list)) end end

local body = "\n" .. xml.dump(res)

headers = { status = 207, Etag = etag('%s', body), ['Content-Type'] = 'text/xml; charset="utf-8"', --['Last-Modified'] = os.date(DATE_FORMAT, last_mod) }

return body, headers end

local resource = self:find(request.path) if not resource then return nil, {status = '404'} end

return resource:body(), {status=200, ['Content-type']=resource:davProperty('contenttype') or 'text/plain' } end

local function getParent(self, path) local base, name = lk.pathDir(path) return self:find(base), name end

-- forget about ._ (resource fork on Mac OS X) if string.match(request.path, '^._') then return nil, {status = '400'} end local resource = self:find(request.path) if resource then return self:update(resource, request.body) else local parent, name = getParent(self, request.path) if parent then return self:create(parent, name, request.body or '') else return nil, {status = '400'} end end end

local resource = self:find(request.path) if not resource then return nil, {status = '204'} end local parent = getParent(self, request.path) if parent then return self:delete(parent, resource) else return nil, {status = '400'} end end

local resource = self:find(request.path) local destination = request.headers['Destination'] or '' destination = string.match(destination, 'http://[^/]+(/.+)$') if destination then local parent = getParent(self, request.path) local dest_parent, dest_name = getParent(self, destination) return self:move(parent, resource, dest_parent, dest_name) end return nil, {status = '400'} end

return nil end