2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/frontend/apps/cloudstorage/webdavapi.lua
themanifold 23ebe39031
Support for Apache WebDAV module (#6510)
This change to the parser in `cloudstorage.lua` adds support for the [Apache WebDAV module][1]

It was manually tested using the [bytemark/webdav][2] docker container.

I developed this in Windows, with a docker container that hosted an extracted AppImage and a VNC server that I viewed using a VNC client.

I will write up my work flow at a later point.

Changes have not been tested against other webdav servers (what was this originally tested against?). Please could someone test against other webdav servers?

I also noticed a logic inversion error where we were looking for a slash at the end of the URL and if it exists, then we explicitly set `has_trailing_slash=false` - so I fixed it to set to `true`. I had to do this so that we weren't visiting the URL without a trailing slash - apache sends back a 301 redirect with a `location` header with a trailing slash, if you don't put a trailing slash.

As a side note, I think we should consider replacing this regexp pattern matching parser with the [XML parser in the newsreader plugin[3]

[1]: https://httpd.apache.org/docs/2.4/mod/mod_dav.html
[2]: https://github.com/BytemarkHosting/docker-webdav
[3]: https://github.com/koreader/koreader/blob/master/plugins/newsdownloader.koplugin/lib/xml.lua
2020-08-17 09:54:16 +02:00

171 lines
5.6 KiB
Lua

local DocumentRegistry = require("document/documentregistry")
local FFIUtil = require("ffi/util")
local http = require('socket.http')
local https = require('ssl.https')
local ltn12 = require('ltn12')
local mime = require('mime')
local socket = require('socket')
local url = require('socket.url')
local util = require("util")
local _ = require("gettext")
local WebDavApi = {
}
function WebDavApi:isCurrentDirectory( current_item, address, path )
local is_home, is_parent
local home_path
-- find first occurence of / after http(s)://
local start = string.find( address, "/", 9 )
if not start then
home_path = "/"
else
home_path = string.sub( address, start )
end
local item
if string.sub( current_item, -1 ) == "/" then
item = string.sub( current_item, 1, -2 )
else
item = current_item
end
if item == home_path then
is_home = true
else
local temp_path = string.sub( item, string.len(home_path) + 1 )
if temp_path == path then
is_parent = true
end
end
return is_home or is_parent
end
-- version of urlEncode that doesn't encode the /
function WebDavApi:urlEncode(url_data)
local char_to_hex = function(c)
return string.format("%%%02X", string.byte(c))
end
if url_data == nil then
return
end
url_data = url_data:gsub("([^%w%/%-%.%_%~%!%*%'%(%)])", char_to_hex)
return url_data
end
function WebDavApi:listFolder(address, user, pass, folder_path)
local path = self:urlEncode( folder_path )
local webdav_list = {}
local webdav_file = {}
local has_trailing_slash = false
local has_leading_slash = false
if string.sub( address, -1 ) == "/" then has_trailing_slash = true end
if path == nil or path == "/" then
path = ""
elseif string.sub( path, 1, 2 ) == "/" then
if has_trailing_slash then
-- too many slashes, remove one
path = string.sub( path, 1 )
end
has_leading_slash = true
end
if not has_trailing_slash and not has_leading_slash then
address = address .. "/"
end
local webdav_url = address .. path
if not has_trailing_slash then
webdav_url = webdav_url .. "/"
end
local request, sink = {}, {}
local parsed = url.parse(webdav_url)
local data = [[<?xml version="1.0"?><a:propfind xmlns:a="DAV:"><a:prop><a:resourcetype/></a:prop></a:propfind>]]
local auth = string.format("%s:%s", user, pass)
local headers = { ["Authorization"] = "Basic " .. mime.b64( auth ),
["Content-Type"] = "application/xml",
["Depth"] = "1",
["Content-Length"] = #data}
request["url"] = webdav_url
request["method"] = "PROPFIND"
request["headers"] = headers
request["source"] = ltn12.source.string(data)
request["sink"] = ltn12.sink.table(sink)
http.TIMEOUT = 5
https.TIMEOUT = 5
local httpRequest = parsed.scheme == "http" and http.request or https.request
local headers_request = socket.skip(1, httpRequest(request))
if headers_request == nil then
return nil
end
local res_data = table.concat(sink)
if res_data ~= "" then
-- iterate through the <d:response> tags, each containing an entry
for item in res_data:gmatch("<[^:]*:response[^>]*>(.-)</[^:]*:response>") do
--logger.dbg("WebDav catalog item=", item)
-- <d:href> is the path and filename of the entry.
local item_fullpath = item:match("<[^:]*:href[^>]*>(.*)</[^:]*:href>")
if string.sub( item_fullpath, -1 ) == "/" then
item_fullpath = string.sub( item_fullpath, 1, -2 )
end
local is_current_dir = self:isCurrentDirectory( item_fullpath, address, path )
local item_name = util.urlDecode( FFIUtil.basename( item_fullpath ) )
local item_path = path .. "/" .. item_name
if item:find("<[^:]*:collection/>") then
item_name = item_name .. "/"
if not is_current_dir then
table.insert(webdav_list, {
text = item_name,
url = util.urlDecode( item_path ),
type = "folder",
})
end
elseif item:find("<[^:]*:resourcetype/>") and (DocumentRegistry:hasProvider(item_name)
or G_reader_settings:isTrue("show_unsupported")) then
table.insert(webdav_file, {
text = item_name,
url = util.urlDecode( item_path ),
type = "file",
})
end
end
else
return nil
end
--sort
table.sort(webdav_list, function(v1,v2)
return v1.text < v2.text
end)
table.sort(webdav_file, function(v1,v2)
return v1.text < v2.text
end)
for _, files in ipairs(webdav_file) do
table.insert(webdav_list, {
text = files.text,
url = files.url,
type = files.type,
})
end
return webdav_list
end
function WebDavApi:downloadFile(file_url, user, pass, local_path)
local parsed = url.parse(file_url)
local auth = string.format("%s:%s", user, pass)
local headers = { ["Authorization"] = "Basic " .. mime.b64( auth ) }
http.TIMEOUT = 5
https.TIMEOUT = 5
local httpRequest = parsed.scheme == "http" and http.request or https.request
local _, code_return, _ = httpRequest{
url = file_url,
method = "GET",
headers = headers,
sink = ltn12.sink.file(io.open(local_path, "w"))
}
return code_return
end
return WebDavApi