From b0df6d53e98c2778561f27921e775f254047e7b8 Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 15 Sep 2015 00:59:00 +0800 Subject: [PATCH] add Basic Authentication for OPDS catalog use coroutine to support ad-hoc callback in login button so that when the username/password is incorrect it will retry automatically currently tested with COPS on apache This should fix #1001. --- frontend/ui/widget/menu.lua | 6 +- frontend/ui/widget/opdsbrowser.lua | 95 ++++++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua index 182683321..ea9fc87d2 100644 --- a/frontend/ui/widget/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -291,7 +291,11 @@ function MenuItem:onTapSelect(arg, ges) UIManager:scheduleIn(0.1, function() self[1].invert = false UIManager:setDirty(self.show_parent, refreshfunc) - self.menu:onMenuSelect(self.table, pos) + DEBUG("creating coroutine for menu select") + local co = coroutine.create(function() + self.menu:onMenuSelect(self.table, pos) + end) + coroutine.resume(co) end) return true end diff --git a/frontend/ui/widget/opdsbrowser.lua b/frontend/ui/widget/opdsbrowser.lua index 26e985d33..806826174 100644 --- a/frontend/ui/widget/opdsbrowser.lua +++ b/frontend/ui/widget/opdsbrowser.lua @@ -1,6 +1,7 @@ local MultiInputDialog = require("ui/widget/multiinputdialog") local ButtonDialog = require("ui/widget/buttondialog") local InfoMessage = require("ui/widget/infomessage") +local LoginDialog = require("ui/widget/logindialog") local lfs = require("libs/libkoreader-lfs") local OPDSParser = require("ui/opdsparser") local NetworkMgr = require("ui/networkmgr") @@ -20,6 +21,7 @@ local socket = require('socket') local http = require('socket.http') local https = require('ssl.https') local ltn12 = require('ltn12') +local mime = require('mime') local CatalogCacheItem = CacheItem:new{ size = 1024, -- fixed size for catalog item @@ -214,12 +216,37 @@ function OPDSBrowser:genItemTableFromRoot() return item_table end +function OPDSBrowser:getBasicAuthentication(host) + local authentications = G_reader_settings:readSetting("www-auth") or {} + return authentications[host] +end + +function OPDSBrowser:setBasicAuthentication(host, username, password) + local authentications = G_reader_settings:readSetting("www-auth") or {} + authentications[host] = { + username = username, + password = password, + } + G_reader_settings:saveSetting("www-auth", authentications) +end + +function OPDSBrowser:getAuthorizationHeader(host) + local auth = self:getBasicAuthentication(host) + if auth then + local authorization = auth.username .. ':' .. auth.password + return { + Authorization = "Basic " .. mime.b64(authorization), + } + end +end + function OPDSBrowser:fetchFeed(feed_url) local headers, request, sink = {}, {}, {} local parsed = url.parse(feed_url) request['url'] = feed_url request['method'] = 'GET' request['sink'] = ltn12.sink.table(sink) + request['headers'] = self:getAuthorizationHeader(parsed.host) DEBUG("request", request) http.TIMEOUT, https.TIMEOUT = 10, 10 local httpRequest = parsed.scheme == 'http' and http.request or https.request @@ -230,13 +257,72 @@ function OPDSBrowser:fetchFeed(feed_url) error(code) end - local xml = table.concat(sink) - if xml ~= "" then - --DEBUG("xml", xml) - return xml + --DEBUG("response", code, headers, status) + if code == 401 and status and status:find("Unauthorized") then + self._coroutine = coroutine.running() or self._coroutine + self:fetchWithLogin(parsed.host, function() + return self:fetchFeed(feed_url) + end) + if coroutine.running() then + local result = coroutine.yield() + return result + end + else + local xml = table.concat(sink) + if xml ~= "" then + --DEBUG("xml", xml) + return xml + end end end +function OPDSBrowser:fetchWithLogin(host, callback) + self.login_dialog = LoginDialog:new{ + title = _("Login to OPDS server"), + username = "", + buttons = { + { + { + text = _("Cancel"), + enabled = true, + callback = function() + self:closeDialog() + end, + }, + { + text = _("Login"), + enabled = true, + callback = function() + local username, password = self:getCredential() + self:setBasicAuthentication(host, username, password) + self:closeDialog() + UIManager:scheduleIn(0.5, function() + local res = callback() + if res then + coroutine.resume(self._coroutine, res) + end + end) + end, + }, + }, + }, + width = Screen:getWidth() * 0.8, + height = Screen:getHeight() * 0.4, + } + + self.login_dialog:onShowKeyboard() + UIManager:show(self.login_dialog) +end + +function OPDSBrowser:closeDialog() + self.login_dialog:onClose() + UIManager:close(self.login_dialog) +end + +function OPDSBrowser:getCredential() + return self.login_dialog:getCredential() +end + function OPDSBrowser:parseFeed(feed_url) local feed = nil local hash = "opds|catalog|" .. feed_url @@ -388,6 +474,7 @@ function OPDSBrowser:downloadFile(title, format, remote_url) local httpRequest = parsed.scheme == 'http' and http.request or https.request local r, c, h = httpRequest{ url = remote_url, + headers = self:getAuthorizationHeader(parsed.host), sink = ltn12.sink.file(io.open(local_path, "w")), }