Merge pull request #1381 from ShaneMcGovernIE/fix/postlog-staging-temp-env

HostShell: stage postLog bodies via OS temp env, not tmpnam
This commit is contained in:
bryanthaboi
2026-08-15 23:10:59 -04:00
committed by GitHub
2 changed files with 29 additions and 9 deletions
+16 -2
View File
@@ -429,8 +429,22 @@ function HostShell.httpPost(url, body, contentType, userAgent, maxTime)
userAgent = userAgent or "gen1recomp" userAgent = userAgent or "gen1recomp"
if HostShell.haveCurl() then if HostShell.haveCurl() then
-- io.popen is one-way on Lua/LuaJIT: its mode is "r" or "w", never -- io.popen is one-way on Lua/LuaJIT: its mode is "r" or "w", never
-- "rw". Stage the request body so the response can stay on a read pipe. -- "rw". Stage the request body so the response can stay on a read
local bodyPath = os.tmpname() -- pipe. The staging directory comes from the OS temp contract, never
-- tmpnam(): the CRT's tmpnam() can return a name relative to the process
-- working directory, and a game installed under Program Files has no
-- writable CWD -- io.open would fail before curl ever runs and postLog
-- would silently drop the send. TEMP/TMP are per-user writable on
-- Windows; TMPDIR (with /tmp fallback) covers POSIX. No love.filesystem:
-- the sandbox-era transport stays on plain io/os.
local function stagingPath()
local dir = os.getenv("TEMP") or os.getenv("TMP")
if not dir or dir == "" then dir = os.getenv("TMPDIR") or "/tmp" end
local sep = dir:find("\\") and "\\" or "/"
return dir .. sep .. ("gen1recomp-post-%d.tmp"):format(
(os.time() % 1000000) * 100 + math.random(0, 99))
end
local bodyPath = stagingPath()
local bodyFile, bodyOpenErr = io.open(bodyPath, "wb") local bodyFile, bodyOpenErr = io.open(bodyPath, "wb")
if not bodyFile then if not bodyFile then
pcall(os.remove, bodyPath) pcall(os.remove, bodyPath)
+13 -7
View File
@@ -12,13 +12,13 @@ local check, eq = T.check, T.eq
local HostShell = require("src.core.HostShell") local HostShell = require("src.core.HostShell")
local MARK = "\n__gen1recomp_http__" local MARK = "\n__gen1recomp_http__"
local BODY_PATH = "/tmp/gen1recomp-postlog-body-test" local STAGE_DIR = "/tmp/gen1recomp-postlog-stage"
local URL = "https://logs.example.com/logs" local URL = "https://logs.example.com/logs"
local BODY = "debug log body\n" local BODY = "debug log body\n"
local realOpen = io.open local realOpen = io.open
local realPopen = io.popen local realPopen = io.popen
local realTmpname = os.tmpname local realGetenv = os.getenv
local realRemove = os.remove local realRemove = os.remove
local realHaveCurl = HostShell.haveCurl local realHaveCurl = HostShell.haveCurl
@@ -26,7 +26,12 @@ local openedPath, openedMode, writtenBody
local popenCommand, popenMode, removedPath local popenCommand, popenMode, removedPath
HostShell.haveCurl = function() return true end HostShell.haveCurl = function() return true end
os.tmpname = function() return BODY_PATH end os.getenv = function(name)
if name == "TEMP" or name == "TMP" or name == "TMPDIR" then
return STAGE_DIR
end
return realGetenv(name)
end
os.remove = function(path) os.remove = function(path)
removedPath = path removedPath = path
return true return true
@@ -55,21 +60,22 @@ local ok, err = HostShell.httpPost(URL, BODY, "text/plain", "gen1recomp-mod/test
io.open = realOpen io.open = realOpen
io.popen = realPopen io.popen = realPopen
os.tmpname = realTmpname os.getenv = realGetenv
os.remove = realRemove os.remove = realRemove
HostShell.haveCurl = realHaveCurl HostShell.haveCurl = realHaveCurl
eq(ok, true, "a desktop POST succeeds through the read-only response pipe: " .. tostring(err)) eq(ok, true, "a desktop POST succeeds through the read-only response pipe: " .. tostring(err))
eq(openedPath, BODY_PATH, "the request body is written to a temporary file") check(type(openedPath) == "string" and openedPath:find(STAGE_DIR .. "/gen1recomp-post-", 1, true) == 1, "the request body is staged under the OS temp dir")
check(openedPath and openedPath:sub(-4) == ".tmp", "the staged body carries a .tmp name")
eq(openedMode, "wb", "the temporary request body is opened for binary writing") eq(openedMode, "wb", "the temporary request body is opened for binary writing")
eq(writtenBody, BODY, "the complete log body is staged") eq(writtenBody, BODY, "the complete log body is staged")
eq(popenMode, "r", "curl is opened in the supported read-only mode") eq(popenMode, "r", "curl is opened in the supported read-only mode")
check(popenCommand:find("--data-binary", 1, true) ~= nil, check(popenCommand:find("--data-binary", 1, true) ~= nil,
"curl reads the staged body with --data-binary") "curl reads the staged body with --data-binary")
check(popenCommand:find(BODY_PATH, 1, true) ~= nil, check(openedPath and popenCommand:find(openedPath, 1, true) ~= nil,
"curl receives the temporary body path") "curl receives the temporary body path")
check(popenCommand:find(BODY, 1, true) == nil, check(popenCommand:find(BODY, 1, true) == nil,
"the log body is not placed directly in the command line") "the log body is not placed directly in the command line")
eq(removedPath, BODY_PATH, "the temporary request body is removed") eq(removedPath, openedPath, "the staged request body is removed")
T.finish("host shell postlog") T.finish("host shell postlog")