mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
900514c2b3
* fix(koreader): respect KOReader global HTTP proxy settings Allow KOReader's Network -> Proxy settings (stored in G_reader_settings) to be respected for both JSON API requests and binary downloads. Also support explicit proxy in BookOrbitApi options, and route Tailscale tailnet addresses (100.x.y.z and *.ts.net) through the local userspace HTTP proxy (127.0.0.1:1056) when running on e-ink devices lacking TUN. Fixes #753 * fix(koreader): address review feedback on host parsing and HTTPS proxy safety * fix(koreader): simplify getProxy to respect KOReader proxy settings without vendor fallbacks * fix: tunnel KOReader HTTPS through configured HTTP proxies Route HTTPS requests through CONNECT so proxy-only servers stay reachable without exposing BookOrbit credentials to the proxy. Restrict redirects to the original host and allow secure upgrades for API calls and downloads. Bump the plugin version so installed clients discover the fix. --------- Co-authored-by: Andrej <acikarski14@gmail.com> Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
102 lines
3.9 KiB
Lua
102 lines
3.9 KiB
Lua
local url = {
|
|
parse = function(value)
|
|
local scheme, rest = value:match("^(%w+)://(.+)$")
|
|
if not scheme then return nil end
|
|
local credentials = rest:match("^([^@]+)@")
|
|
if credentials then rest = rest:sub(#credentials + 2) end
|
|
local host, port = rest:match("^([^:]+):(%d+)$")
|
|
return {
|
|
scheme = scheme,
|
|
host = host,
|
|
port = port,
|
|
user = credentials and credentials:match("^([^:]+):"),
|
|
password = credentials and credentials:match(":(.+)$"),
|
|
}
|
|
end,
|
|
unescape = function(value) return value end,
|
|
}
|
|
local mime = { b64 = function() return "encoded-credentials" end }
|
|
|
|
local response
|
|
local raw
|
|
local tls
|
|
package.loaded["socket"] = {
|
|
tcp = function()
|
|
raw = { sent = "", position = 1 }
|
|
function raw:settimeout(value)
|
|
self.timeout = value
|
|
return 1
|
|
end
|
|
function raw:connect(host, port)
|
|
self.host, self.port = host, port
|
|
return 1
|
|
end
|
|
function raw:send(value)
|
|
self.sent = self.sent .. value
|
|
return #value
|
|
end
|
|
function raw:receive()
|
|
if self.position > #response then return nil, "closed" end
|
|
local char = response:sub(self.position, self.position)
|
|
self.position = self.position + 1
|
|
return char
|
|
end
|
|
function raw:close() return 1 end
|
|
return raw
|
|
end,
|
|
}
|
|
package.loaded["socket.url"] = url
|
|
package.loaded["mime"] = mime
|
|
package.loaded["ssl.https"] = { TIMEOUT = 10 }
|
|
package.loaded["ssl"] = {
|
|
wrap = function()
|
|
tls = setmetatable({}, { __index = {
|
|
sni = function(self, host) self.host = host end,
|
|
settimeout = function(self, value) self.timeout = value end,
|
|
dohandshake = function() return 1 end,
|
|
send = function() return 1 end,
|
|
receive = function() return "ok" end,
|
|
close = function() return 1 end,
|
|
} })
|
|
return tls
|
|
end,
|
|
}
|
|
|
|
package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path
|
|
local Proxy = require("bookorbit_proxy")
|
|
|
|
local function assertEqual(actual, expected, label)
|
|
if actual ~= expected then error(label .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual)) end
|
|
end
|
|
|
|
response = "HTTP/1.1 200 Connection Established\r\nProxy-Agent: test\r\n\r\n"
|
|
local conn = Proxy.connectSocket("http://reader:password@127.0.0.1:3128")()
|
|
conn:settimeout(10)
|
|
assertEqual(conn:connect("books.example", 443), 1, "CONNECT succeeds")
|
|
assertEqual(raw.host, "127.0.0.1", "connects to proxy host")
|
|
assertEqual(raw.port, 3128, "connects to proxy port")
|
|
assertEqual(raw.sent:match("^CONNECT books%.example:443 HTTP/1%.1"),
|
|
"CONNECT books.example:443 HTTP/1.1", "sends tunnel request")
|
|
assertEqual(raw.sent:find("Proxy-Authorization: Basic ", 1, true) ~= nil, true,
|
|
"sends proxy credentials only in tunnel request")
|
|
assertEqual(raw.sent:find("x-auth-key", 1, true), nil, "never sends BookOrbit credentials in CONNECT")
|
|
assertEqual(tls.host, "books.example", "uses target host for TLS SNI")
|
|
assertEqual(tls.timeout, 10, "sets TLS timeout")
|
|
assertEqual(conn:receive(), "ok", "forwards socket methods to TLS")
|
|
|
|
response = "HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"
|
|
conn = Proxy.connectSocket("http://127.0.0.1:3128")()
|
|
local success, err = conn:connect("books.example", 443)
|
|
assertEqual(success, nil, "rejects failed CONNECT")
|
|
assertEqual(err, "proxy_connect_failed", "reports failed CONNECT")
|
|
assertEqual(raw.sent:find("Proxy-Authorization", 1, true), nil,
|
|
"does not invent a proxy authorization header")
|
|
|
|
response = "HTTP/1.1 200 Connection Established\r\nX-Large: " .. string.rep("x", 8192)
|
|
conn = Proxy.connectSocket("http://127.0.0.1:3128")()
|
|
success, err = conn:connect("books.example", 443)
|
|
assertEqual(success, nil, "rejects oversized proxy response")
|
|
assertEqual(err, "proxy_response_too_large", "bounds proxy headers")
|
|
|
|
print("bookorbit_proxy_test.lua: ok")
|