From 9e67c5a614121cf6e787f47eca4905f442d5915e Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 4 May 2018 17:06:58 +0200 Subject: [PATCH] CloudStorage: Allow use reserved characters in FTP username and FTP password (#3924) Depends on RFC 3986 compliant util.urlEncode() and adds unit tests for the new functions. --- frontend/apps/cloudstorage/ftp.lua | 6 +++--- frontend/apps/cloudstorage/ftpapi.lua | 20 ++++++++--------- frontend/util.lua | 31 +++++++++++++++++++++++++++ plugins/send2ebook.koplugin/main.lua | 2 +- spec/unit/util_spec.lua | 27 +++++++++++++++++++++++ 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/frontend/apps/cloudstorage/ftp.lua b/frontend/apps/cloudstorage/ftp.lua index fe66c9210..5408ebe34 100644 --- a/frontend/apps/cloudstorage/ftp.lua +++ b/frontend/apps/cloudstorage/ftp.lua @@ -13,14 +13,14 @@ local T = require("ffi/util").template local Ftp = {} function Ftp:run(address, user, pass, path) - local url = FtpApi:generateUrl(address, user, pass) .. path + local url = FtpApi:generateUrl(address, util.urlEncode(user), util.urlEncode(pass)) .. path return FtpApi:listFolder(url, path) end function Ftp:downloadFile(item, address, user, pass, path, close) - local url = FtpApi:generateUrl(address, user, pass) .. item.url + local url = FtpApi:generateUrl(address, util.urlEncode(user), util.urlEncode(pass)) .. item.url logger.dbg("downloadFile url", url) - local response = FtpApi:downloadFile(url) + local response = FtpApi:ftpGet(url, "retr") if response ~= nil then path = util.fixUtf8(path, "_") local file = io.open(path, "w") diff --git a/frontend/apps/cloudstorage/ftpapi.lua b/frontend/apps/cloudstorage/ftpapi.lua index 74aa6579e..5a4eac8fb 100644 --- a/frontend/apps/cloudstorage/ftpapi.lua +++ b/frontend/apps/cloudstorage/ftpapi.lua @@ -1,6 +1,7 @@ local DocumentRegistry = require("document/documentregistry") local ftp = require("socket.ftp") local ltn12 = require("ltn12") +local util = require("util") local url = require("socket.url") local FtpApi = { @@ -15,27 +16,28 @@ function FtpApi:generateUrl(address, user, pass) if pass ~= "" then colon_sign = ":" end - local replace = "://" .. user .. colon_sign .. pass .. at_sign - local generated_url = string.gsub(address, "://", replace) + local generated_url = "ftp://" .. user .. colon_sign .. pass .. at_sign .. address:gsub("ftp://", "") return generated_url end -function FtpApi:nlst(u) +function FtpApi:ftpGet(u, command) local t = {} local p = url.parse(u) - p.command = "nlst" + p.user = util.urlDecode(p.user) + p.password = util.urlDecode(p.password) + p.command = command p.sink = ltn12.sink.table(t) local r, e = ftp.get(p) return r and table.concat(t), e end -function FtpApi:listFolder(address_path,folder_path) +function FtpApi:listFolder(address_path, folder_path) local ftp_list = {} local ftp_file = {} local type local extension local file_name - local ls_ftp = self:nlst(address_path) + local ls_ftp = self:ftpGet(address_path, "nlst") if ls_ftp == nil then return false end if folder_path == "/" then folder_path = "" @@ -79,12 +81,10 @@ function FtpApi:listFolder(address_path,folder_path) return ftp_list end -function FtpApi:downloadFile(file_path) - return ftp.get(file_path ..";type=i") -end - function FtpApi:delete(file_path) local p = url.parse(file_path) + p.user = util.urlDecode(p.user) + p.password = util.urlDecode(p.password) p.argument = string.gsub(p.path, "^/", "") p.command = "dele" p.check = 250 diff --git a/frontend/util.lua b/frontend/util.lua index 9e7348c77..dd9c1bd64 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -646,4 +646,35 @@ function util.clearTable(t) for i = 0, c do t[i] = nil end end +--- Encode URL also known as percent-encoding see https://en.wikipedia.org/wiki/Percent-encoding +--- @string text the string to encode +--- @treturn encode string +--- Taken from https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99 +function util.urlEncode(url) + local char_to_hex = function(c) + return string.format("%%%02X", string.byte(c)) + end + if url == nil then + return + end + url = url:gsub("\n", "\r\n") + url = url:gsub("([^%w%-%.%_%~%!%*%'%(%)])", char_to_hex) + return url +end + +--- Decode URL (reverse process to util.urlEncode()) +--- @string text the string to decode +--- @treturn decode string +--- Taken from https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99 +function util.urlDecode(url) + local hex_to_char = function(x) + return string.char(tonumber(x, 16)) + end + if url == nil then + return + end + url = url:gsub("%%(%x%x)", hex_to_char) + return url +end + return util diff --git a/plugins/send2ebook.koplugin/main.lua b/plugins/send2ebook.koplugin/main.lua index 84cf6dbf2..a9880650d 100644 --- a/plugins/send2ebook.koplugin/main.lua +++ b/plugins/send2ebook.koplugin/main.lua @@ -130,7 +130,7 @@ function Send2Ebook:process() local count = 1 local ftp_config = send2ebook_settings:readSetting("ftp_config") or {address="Please setup ftp in settings", username="", password="", folder=""} - local connection_url = FtpApi:generateUrl(ftp_config.address, ftp_config.username, ftp_config.password) + local connection_url = FtpApi:generateUrl(ftp_config.address, util.urlEncode(ftp_config.username), util.urlEncode(ftp_config.password)) local ftp_files_table = FtpApi:listFolder(connection_url .. ftp_config.folder, ftp_config.folder) --args looks strange but otherwise resonse with invalid paths diff --git a/spec/unit/util_spec.lua b/spec/unit/util_spec.lua index a2362900e..2bed26411 100644 --- a/spec/unit/util_spec.lua +++ b/spec/unit/util_spec.lua @@ -385,4 +385,31 @@ describe("util module", function() util.secondsToClock(120)) end) end) + + describe("urlEncode() and urlDecode", function() + it("should encode string", function() + assert.is_equal("Secret_Password123", util.urlEncode("Secret_Password123")) + assert.is_equal("Secret%20Password123", util.urlEncode("Secret Password123")) + assert.is_equal("S*cret%3DP%40%24%24word*!%23%3F", util.urlEncode("S*cret=P@$$word*!#?")) + assert.is_equal("~%5E-_%5C%25!*'()%3B%3A%40%26%3D%2B%24%2C%2F%3F%23%5B%5D", + util.urlEncode("~^-_\\%!*'();:@&=+$,/?#[]")) + end) + it("should decode string", function() + assert.is_equal("Secret_Password123", util.urlDecode("Secret_Password123")) + assert.is_equal("Secret Password123", util.urlDecode("Secret%20Password123")) + assert.is_equal("S*cret=P@$$word*!#?", util.urlDecode("S*cret%3DP%40%24%24word*!%23%3F")) + assert.is_equal("~^-_\\%!*'();:@&=+$,/?#[]", + util.urlDecode("~%5E-_%5C%25!*'()%3B%3A%40%26%3D%2B%24%2C%2F%3F%23%5B%5D")) + end) + it("should encode and back decode string", function() + assert.is_equal("Secret_Password123", + util.urlDecode(util.urlEncode("Secret_Password123"))) + assert.is_equal("Secret Password123", + util.urlDecode(util.urlEncode("Secret Password123"))) + assert.is_equal("S*cret=P@$$word*!#?", + util.urlDecode(util.urlEncode("S*cret=P@$$word*!#?"))) + assert.is_equal("~^-_%!*'();:@&=+$,/?#[]", + util.urlDecode(util.urlEncode("~^-_%!*'();:@&=+$,/?#[]"))) + end) + end) end)