Files
gen1recomp/tests/engine/hostshell_win_http_nonascii.lua
T
bryanthaboi 93cd84d232 follow up
2026-10-06 19:45:14 -04:00

162 lines
6.7 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
local SAVE = "C:/Users/H\xC3\xBCseyin/AppData/Roaming/LOVE/gen1recomp"
local SAVE_WIN = "C:\\Users\\H\xC3\xBCseyin\\AppData\\Roaming\\LOVE\\gen1recomp"
local MARK = "\n__gen1recomp_http__"
local URL = "https://example.com/a.zip"
local creates, closes, pipeOutput, createResult = {}, {}, "", 1
local fakeFfi = { os = "Windows" }
function fakeFfi.cdef() end
function fakeFfi.new(ct)
if ct == "PP_STARTUPINFOW" then return { cb = 0, dwFlags = 0 } end
if ct == "PP_PROCESS_INFORMATION" then return { hProcess = "proc", hThread = "thread" } end
return {}
end
function fakeFfi.sizeof(ct)
if ct == "PP_STARTUPINFOW" then return 104 end
if ct == "PP_SECURITY_ATTRIBUTES" then return 24 end
return 0
end
function fakeFfi.string(buf, n) return buf.data:sub(1, n) end
fakeFfi.C = {
MultiByteToWideChar = function(cp, _, s, _, w)
assert(cp == 65001, "CP_UTF8")
if w ~= nil then w.src = s end
return #s
end,
CreatePipe = function(rd, wr, sa)
assert(sa.bInheritHandle == 1, "inheritable write end")
rd[0], wr[0] = "rd", "wr"
return 1
end,
SetHandleInformation = function() return 1 end,
CreateProcessW = function(app, cmd, _, _, inherit, flags, _, cwd, si)
creates[#creates + 1] = {
app = app and app.src, cmd = cmd and cmd.src, cwd = cwd and cwd.src,
inherit = inherit, flags = flags, si = si,
}
return createResult
end,
ReadFile = function(_, buf, _, got)
if pipeOutput == "" then got[0] = 0 return 0 end
buf.data, got[0], pipeOutput = pipeOutput, #pipeOutput, ""
return 1
end,
WaitForSingleObject = function() return 0 end,
GetExitCodeProcess = function(_, code) code[0] = 0 return 1 end,
CloseHandle = function(h) closes[#closes + 1] = h return 1 end,
}
package.loaded.ffi = fakeFfi
local files, removed = {}, {}
local osName = "Windows"
love = {
system = { getOS = function() return osName end },
filesystem = {
getSaveDirectory = function() return SAVE end,
write = function(name, data) files[name] = data return true end,
remove = function(name) removed[#removed + 1] = name files[name] = nil return true end,
},
}
local shellCalls, lastShell = 0, nil
os.execute = function() shellCalls = shellCalls + 1 return 0 end
io.popen = function(cmd)
shellCalls, lastShell = shellCalls + 1, cmd
return {
read = function() return "body" .. MARK .. "200" end,
close = function() return true end,
}
end
local HostShell = require("src.core.HostShell")
HostShell.haveCurl = function() return true end
local function ascii(s) return not s:find("[\128-\255]") end
local cwd, rels = HostShell.winRelative({ SAVE .. "/mods/x.zip", SAVE .. "/etag/x.etag" })
eq(cwd, SAVE_WIN, "common folder of the paths becomes the cwd")
eq(rels[1], "mods\\x.zip", "first path relative to it")
eq(rels[2], "etag\\x.etag", "second path relative to it")
cwd = HostShell.winRelative({ "D:/x.zip" })
eq(cwd, "D:\\", "drive root keeps its backslash")
cwd = HostShell.winRelative({ "\\\\server\\share\\x.zip" })
eq(cwd, "\\\\server\\share", "UNC prefix kept")
cwd, rels = HostShell.winRelative({ SAVE .. "/Pok\xC3\xA9/x.zip", SAVE .. "/a.etag" })
eq(rels[1], nil, "a non-ASCII relative name is not offered")
eq(rels[2], "a.etag", "the ASCII one still is")
pipeOutput = MARK .. "200"
local ok, err = HostShell.httpDownload(URL, SAVE .. "/mods/x.zip", "ua", nil, 90,
SAVE .. "/etag/x.etag")
eq(ok, true, "download succeeds: " .. tostring(err))
eq(shellCalls, 0, "download never goes through cmd.exe or the ANSI CRT")
eq(#creates, 1, "curl spawned once")
local c = creates[1] or {}
eq(c.app, nil, "curl resolved through the search path")
eq(c.cwd, SAVE_WIN, "curl runs in the save folder, passed wide")
check(c.cmd and ascii(c.cmd), "no non-ASCII byte reaches curl's argv")
check(c.cmd and c.cmd:find(" -o mods\\x.zip ", 1, true) ~= nil, "-o is relative")
check(c.cmd and c.cmd:find("--etag-compare etag\\x.etag --etag-save etag\\x.etag", 1, true) ~= nil,
"etag paths are relative")
check(c.cmd and c.cmd:find('"User-Agent: ua"', 1, true) ~= nil, "header quoted per CommandLineToArgvW")
check(c.cmd and c.cmd:sub(-#URL) == URL, "url last")
eq(c.flags, 0x08000000, "CREATE_NO_WINDOW")
eq(c.inherit, 1, "pipe write end inherited")
eq(c.si and c.si.dwFlags, 0x100, "STARTF_USESTDHANDLES")
eq(c.si and c.si.hStdOutput, "wr", "stdout to the pipe")
eq(c.si and c.si.hStdError, "wr", "stderr merged into the pipe")
eq(table.concat(closes, ","), "wr,thread,rd,proc", "every handle closed")
closes = {}
pipeOutput = "{\"tag\":1}" .. MARK .. "200"
local body = HostShell.httpGet("https://example.com/api", "ua")
eq(body, "{\"tag\":1}", "httpGet returns the body read from the pipe")
eq(creates[2] and creates[2].cwd, nil, "a GET with no paths keeps the inherited cwd")
eq(shellCalls, 0, "httpGet never shells out")
pipeOutput = MARK .. "201"
ok, err = HostShell.httpPost("https://example.com/logs", "log body", "text/plain", "ua", 10)
eq(ok, true, "post succeeds: " .. tostring(err))
local p = creates[3] or {}
eq(p.cwd, SAVE_WIN, "post runs in the save folder")
local staged = p.cmd and p.cmd:match("%-%-data%-binary @(%S+)")
check(staged and staged:find("^gen1recomp%-post%-") ~= nil, "body is staged by a relative name")
check(p.cmd and ascii(p.cmd), "post argv is ASCII")
eq(removed[#removed], staged, "staged body removed through love.filesystem")
eq(shellCalls, 0, "post never shells out")
pipeOutput = "ok" .. MARK .. "200"
local resp, rerr, status = HostShell.httpRequest("https://example.com/sync",
{ method = "PUT", headers = { ["x-token"] = "t" }, body = "{}" })
eq(resp, "ok", "request body returned: " .. tostring(rerr))
eq(status, 200, "request status returned")
local r = creates[4] or {}
eq(r.cwd, SAVE_WIN, "request runs in the save folder")
check(r.cmd and r.cmd:find("-H @gen1recomp-req-head-", 1, true) ~= nil, "header file relative")
check(r.cmd and r.cmd:find("--data-binary @gen1recomp-req-body-", 1, true) ~= nil, "body file relative")
check(r.cmd and ascii(r.cmd), "request argv is ASCII")
eq(next(files), nil, "every staged file removed")
eq(shellCalls, 0, "request never shells out")
createResult = 0
body = HostShell.httpGet("https://example.com/api", "ua")
eq(shellCalls, 1, "a failed wide spawn falls back to the shell")
eq(body, "body", "and the fallback still answers")
createResult = 1
osName = "OS X"
shellCalls = 0
HostShell.httpGet("https://x/y", "ua")
eq(lastShell, "'curl' -sSL --proto =http,https --proto-redir =http,https --connect-timeout 10 "
.. "--max-time 40 -H 'User-Agent: ua' -w '\\n__gen1recomp_http__%{http_code}' 'https://x/y' 2>&1",
"POSIX command line unchanged")
eq(#creates, 5, "POSIX never spawns through WinApi")
T.finish("hostshell_win_http_nonascii")