| 1 |
-------------------------------------------------------------------------------- |
|---|
| 2 |
-- Title: WikiDAV.lua |
|---|
| 3 |
-- Description: Like a square peg in a round hole |
|---|
| 4 |
-- Author: Raphaël Szwarc http://alt.textdrive.com/lua/ |
|---|
| 5 |
-- Creation Date: January 30, 2007 |
|---|
| 6 |
-- Legal: Copyright (C) 2007 Raphaël Szwarc |
|---|
| 7 |
-- Under the terms of the MIT License |
|---|
| 8 |
-- http://www.opensource.org/licenses/mit-license.html |
|---|
| 9 |
-------------------------------------------------------------------------------- |
|---|
| 10 |
|
|---|
| 11 |
-- import dependencies |
|---|
| 12 |
local HTTP = require( 'HTTP' ) |
|---|
| 13 |
local HTTPExtra = require( 'HTTPExtra' ) |
|---|
| 14 |
local HTTPResponse = require( 'HTTPResponse' ) |
|---|
| 15 |
local URL = require( 'URL' ) |
|---|
| 16 |
local URLPath = require( 'URLPath' ) |
|---|
| 17 |
local WikiService = require( 'WikiService' ) |
|---|
| 18 |
|
|---|
| 19 |
local Encode = WikiService.Encode |
|---|
| 20 |
|
|---|
| 21 |
local os = require( 'os' ) |
|---|
| 22 |
|
|---|
| 23 |
local assert = assert |
|---|
| 24 |
local getmetatable = getmetatable |
|---|
| 25 |
local module = module |
|---|
| 26 |
local pairs = pairs |
|---|
| 27 |
local require = require |
|---|
| 28 |
local setmetatable = setmetatable |
|---|
| 29 |
local tonumber = tonumber |
|---|
| 30 |
local tostring = tostring |
|---|
| 31 |
|
|---|
| 32 |
-------------------------------------------------------------------------------- |
|---|
| 33 |
-- HTTPResponse (Extra) |
|---|
| 34 |
-------------------------------------------------------------------------------- |
|---|
| 35 |
|
|---|
| 36 |
module( 'HTTPResponse' ) |
|---|
| 37 |
|
|---|
| 38 |
local self = _M |
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 |
function WebDAVFilter( aRequest, aResponse ) |
|---|
| 42 |
aResponse.header[ 'dav' ] = '1, 2' |
|---|
| 43 |
aResponse.header[ 'ms-author-via' ] = 'DAV' |
|---|
| 44 |
end |
|---|
| 45 |
|
|---|
| 46 |
self.filter[ #self.filter + 1 ] = WebDAVFilter |
|---|
| 47 |
|
|---|
| 48 |
-------------------------------------------------------------------------------- |
|---|
| 49 |
-- WikiDAV |
|---|
| 50 |
-------------------------------------------------------------------------------- |
|---|
| 51 |
|
|---|
| 52 |
module( 'WikiDAV' ) |
|---|
| 53 |
_VERSION = '1.0' |
|---|
| 54 |
|
|---|
| 55 |
local self = setmetatable( _M, {} ) |
|---|
| 56 |
local meta = getmetatable( self ) |
|---|
| 57 |
|
|---|
| 58 |
-------------------------------------------------------------------------------- |
|---|
| 59 |
-- Utitities |
|---|
| 60 |
-------------------------------------------------------------------------------- |
|---|
| 61 |
|
|---|
| 62 |
local function Status( aCode, aDescription ) |
|---|
| 63 |
HTTP.response.status.code = aCode |
|---|
| 64 |
HTTP.response.status.description = aDescription |
|---|
| 65 |
HTTP.response.header[ 'content-type' ] = 'text/plain' |
|---|
| 66 |
|
|---|
| 67 |
return ( '%d %s' ):format( aCode, aDescription ) |
|---|
| 68 |
end |
|---|
| 69 |
|
|---|
| 70 |
local function NamedResource( aResource ) |
|---|
| 71 |
if not aResource.name then |
|---|
| 72 |
local aPath = HTTP.request.url.path |
|---|
| 73 |
|
|---|
| 74 |
aResource = aPath[ #aPath ] |
|---|
| 75 |
end |
|---|
| 76 |
|
|---|
| 77 |
return aResource |
|---|
| 78 |
end |
|---|
| 79 |
|
|---|
| 80 |
local function Href( aResource, isFirst ) |
|---|
| 81 |
local anURL = HTTP.request.url |
|---|
| 82 |
local aName = aResource.name or anURL.path[ #anURL.path ] or '/' |
|---|
| 83 |
local aPath = anURL.path( aResource.name ) |
|---|
| 84 |
|
|---|
| 85 |
if isFirst then |
|---|
| 86 |
aPath = anURL.path |
|---|
| 87 |
end |
|---|
| 88 |
|
|---|
| 89 |
aPath = URLPath( aPath ) |
|---|
| 90 |
aPath.directory = aResource.mode == 'directory' |
|---|
| 91 |
aResource.name = aName |
|---|
| 92 |
|
|---|
| 93 |
return tostring( aPath ) |
|---|
| 94 |
end |
|---|
| 95 |
|
|---|
| 96 |
local function ContentType( aResource ) |
|---|
| 97 |
if aResource.mode == 'file' then |
|---|
| 98 |
local MIME = require( 'MIME' ) |
|---|
| 99 |
local MIMEType = require( 'MIMEType' ) |
|---|
| 100 |
local aName = aResource.name |
|---|
| 101 |
local anExtension = ( aName:match( '^.+%.(%w+)$' ) or '' ):lower() |
|---|
| 102 |
|
|---|
| 103 |
return MIMEType[ anExtension ] or 'application/octet-stream' |
|---|
| 104 |
end |
|---|
| 105 |
|
|---|
| 106 |
return 'text/html' |
|---|
| 107 |
end |
|---|
| 108 |
|
|---|
| 109 |
local function ETag( aResource, aLocation ) |
|---|
| 110 |
local crypto = require( 'crypto' ) |
|---|
| 111 |
local aTag = ( 'DAV:%s:%s:%d:%d' ):format( aLocation, aResource.name, aResource.modification, aResource.size ) |
|---|
| 112 |
|
|---|
| 113 |
return crypto.sha1( aTag ) |
|---|
| 114 |
end |
|---|
| 115 |
|
|---|
| 116 |
local function ResourceType( aResource ) |
|---|
| 117 |
if aResource.mode == 'directory' then |
|---|
| 118 |
return '<collection/>' |
|---|
| 119 |
end |
|---|
| 120 |
end |
|---|
| 121 |
|
|---|
| 122 |
local function ResourceIterator( aResource ) |
|---|
| 123 |
local anIterator = aResource.iterator |
|---|
| 124 |
local isFirst = true |
|---|
| 125 |
|
|---|
| 126 |
return function() |
|---|
| 127 |
if isFirst then |
|---|
| 128 |
isFirst = false |
|---|
| 129 |
|
|---|
| 130 |
return aResource, true |
|---|
| 131 |
end |
|---|
| 132 |
|
|---|
| 133 |
if anIterator then |
|---|
| 134 |
return anIterator(), false |
|---|
| 135 |
end |
|---|
| 136 |
end |
|---|
| 137 |
end |
|---|
| 138 |
|
|---|
| 139 |
-------------------------------------------------------------------------------- |
|---|
| 140 |
-- Service methods |
|---|
| 141 |
-------------------------------------------------------------------------------- |
|---|
| 142 |
|
|---|
| 143 |
function self:propfind() |
|---|
| 144 |
local aDepth = tonumber( HTTP.request.header[ 'depth' ] ) |
|---|
| 145 |
|
|---|
| 146 |
if aDepth and aDepth >= 0 and aDepth <= 1 then |
|---|
| 147 |
local crypto = require( 'crypto' ) |
|---|
| 148 |
local Template = require( 'Template' ) |
|---|
| 149 |
local aTemplate = Template[ 'WikiDAV.txt' ] |
|---|
| 150 |
local anEtag = 'DAV' |
|---|
| 151 |
local aModification = 0 |
|---|
| 152 |
|
|---|
| 153 |
for aResource, isFirst in ResourceIterator( self.resource ) do |
|---|
| 154 |
local aResourceTemplate = aTemplate[ 'resources' ] |
|---|
| 155 |
local aResourceHref = Href( aResource, isFirst ) |
|---|
| 156 |
local aResourceEtag = ETag( aResource, aResourceHref ) |
|---|
| 157 |
|
|---|
| 158 |
aResourceTemplate[ 'href' ] = Encode( aResourceHref ) |
|---|
| 159 |
aResourceTemplate[ 'displayName' ] = Encode( aResource.name ) |
|---|
| 160 |
aResourceTemplate[ 'contentLength' ] = aResource.size |
|---|
| 161 |
aResourceTemplate[ 'contentType' ] = ContentType( aResource ) |
|---|
| 162 |
aResourceTemplate[ 'etag' ] = aResourceEtag |
|---|
| 163 |
aResourceTemplate[ 'creationDate' ] = os.date( '!%a, %d %b %Y %H:%M:%S GMT', aResource.creation or aResource.modification ) |
|---|
| 164 |
aResourceTemplate[ 'lastModified' ] = os.date( '!%a, %d %b %Y %H:%M:%S GMT', aResource.modification ) |
|---|
| 165 |
aResourceTemplate[ 'lockToken' ] = Encode( crypto.sha1( ( 'DAV:LOCK:%s' ):format( tostring( aResourceHref ) ) ) ) |
|---|
| 166 |
aResourceTemplate[ 'resourceType' ] = ResourceType( aResource ) |
|---|
| 167 |
|
|---|
| 168 |
aTemplate[ 'resources' ] = aResourceTemplate |
|---|
| 169 |
|
|---|
| 170 |
anEtag = crypto.sha1( ( '%s:%s' ):format( anEtag, aResourceEtag ) ) |
|---|
| 171 |
|
|---|
| 172 |
if aResource.modification > aModification then |
|---|
| 173 |
aModification = aResource.modification |
|---|
| 174 |
end |
|---|
| 175 |
|
|---|
| 176 |
if aDepth == 0 then |
|---|
| 177 |
break |
|---|
| 178 |
end |
|---|
| 179 |
end |
|---|
| 180 |
|
|---|
| 181 |
HTTP.response.status.code = 207 |
|---|
| 182 |
HTTP.response.status.description = 'Multi-Status' |
|---|
| 183 |
HTTP.response.header[ 'content-type' ] = 'application/xml; charset=utf-8' |
|---|
| 184 |
HTTP.response.header[ 'etag' ] = anEtag |
|---|
| 185 |
HTTP.response.header[ 'last-modified' ] = os.date( '!%a, %d %b %Y %H:%M:%S GMT', aModification ) |
|---|
| 186 |
|
|---|
| 187 |
return tostring( aTemplate ) |
|---|
| 188 |
end |
|---|
| 189 |
|
|---|
| 190 |
return Status( 403, 'Forbidden' ) |
|---|
| 191 |
end |
|---|
| 192 |
|
|---|
| 193 |
-------------------------------------------------------------------------------- |
|---|
| 194 |
-- Metamethods |
|---|
| 195 |
-------------------------------------------------------------------------------- |
|---|
| 196 |
|
|---|
| 197 |
function meta:__call( aResource ) |
|---|
| 198 |
local aService = { resource = assert( aResource ) } |
|---|
| 199 |
|
|---|
| 200 |
setmetatable( aService, self ) |
|---|
| 201 |
self.__index = self |
|---|
| 202 |
|
|---|
| 203 |
return aService |
|---|
| 204 |
end |
|---|
| 205 |
|
|---|
| 206 |
function meta:__concat( aValue ) |
|---|
| 207 |
return tostring( self ) .. tostring( aValue ) |
|---|
| 208 |
end |
|---|
| 209 |
|
|---|
| 210 |
function meta:__tostring() |
|---|
| 211 |
return ( '%s/%s' ):format( self._NAME, self._VERSION ) |
|---|
| 212 |
end |
|---|
| 213 |
|
|---|