Implement cookie utilities

This commit is contained in:
Sky Johnson 2025-03-26 09:39:58 -05:00
parent e37e798377
commit 727ce89da7
5 changed files with 263 additions and 35 deletions

View File

@ -156,7 +156,7 @@ func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode
ctx.Set("path", r.URL.Path)
ctx.Set("host", r.Host)
// Inline the header conversion (previously makeHeaderMap)
// Add headers to context
headerMap := make(map[string]any, len(r.Header))
for name, values := range r.Header {
if len(values) == 1 {
@ -167,11 +167,20 @@ func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode
}
ctx.Set("headers", headerMap)
// Add cookies to context
if cookies := r.Cookies(); len(cookies) > 0 {
cookieMap := make(map[string]any, len(cookies))
for _, cookie := range cookies {
cookieMap[cookie.Name] = cookie.Value
}
ctx.Set("cookies", cookieMap)
}
// Add URL parameters
if params.Count > 0 {
paramMap := make(map[string]any, params.Count)
for i := 0; i < params.Count; i++ {
paramMap[params.Keys[i]] = params.Values[i]
for i, key := range params.Keys {
paramMap[key] = params.Values[i]
}
ctx.Set("params", paramMap)
}
@ -230,6 +239,11 @@ func writeResponse(w http.ResponseWriter, result any, log *logger.Logger) {
w.Header().Set(name, value)
}
// Set cookies
for _, cookie := range httpResp.Cookies {
http.SetCookie(w, cookie)
}
// Set status code
w.WriteHeader(httpResp.Status)
@ -243,7 +257,7 @@ func writeResponse(w http.ResponseWriter, result any, log *logger.Logger) {
switch res := result.(type) {
case string:
// String result - plain text
// String result - default to plain text
setContentTypeIfMissing(w, contentTypePlain)
w.Write([]byte(res))
default:

172
core/runner/cookies.go Normal file
View File

@ -0,0 +1,172 @@
package runner
import (
"net/http"
"time"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
)
// extractCookie grabs cookies from the Lua state
func extractCookie(state *luajit.State) *http.Cookie {
cookie := &http.Cookie{
Path: "/", // Default path
}
// Get name
state.GetField(-1, "name")
if !state.IsString(-1) {
state.Pop(1)
return nil // Name is required
}
cookie.Name = state.ToString(-1)
state.Pop(1)
// Get value
state.GetField(-1, "value")
if state.IsString(-1) {
cookie.Value = state.ToString(-1)
}
state.Pop(1)
// Get path
state.GetField(-1, "path")
if state.IsString(-1) {
cookie.Path = state.ToString(-1)
}
state.Pop(1)
// Get domain
state.GetField(-1, "domain")
if state.IsString(-1) {
cookie.Domain = state.ToString(-1)
}
state.Pop(1)
// Get expires
state.GetField(-1, "expires")
if state.IsNumber(-1) {
expiry := int64(state.ToNumber(-1))
cookie.Expires = time.Unix(expiry, 0)
}
state.Pop(1)
// Get max age
state.GetField(-1, "max_age")
if state.IsNumber(-1) {
cookie.MaxAge = int(state.ToNumber(-1))
}
state.Pop(1)
// Get secure
state.GetField(-1, "secure")
if state.IsBoolean(-1) {
cookie.Secure = state.ToBoolean(-1)
}
state.Pop(1)
// Get http only
state.GetField(-1, "http_only")
if state.IsBoolean(-1) {
cookie.HttpOnly = state.ToBoolean(-1)
}
state.Pop(1)
return cookie
}
// LuaCookieModule provides cookie functionality to Lua scripts
const LuaCookieModule = `
-- Cookie module implementation
local cookie = {
-- Set a cookie
set = function(name, value, expires, path, domain, secure, http_only)
if type(name) ~= "string" then
error("cookie.set: name must be a string", 2)
end
-- Get or create response
local resp = __http_responses[1] or {}
resp.cookies = resp.cookies or {}
__http_responses[1] = resp
-- Create cookie table
local cookie = {
name = name,
value = value or "",
path = path or "/",
domain = domain
}
-- Handle expiry
if expires then
if type(expires) == "number" then
if expires > 0 then
-- Add seconds to current time
cookie.max_age = expires
local now = os.time()
cookie.expires = now + expires
elseif expires < 0 then
-- Session cookie (default)
else
-- Expire immediately
cookie.expires = 0
cookie.max_age = 0
end
end
end
-- Set flags
cookie.secure = secure or false
cookie.http_only = http_only or false
-- Store in cookies table
local n = #resp.cookies + 1
resp.cookies[n] = cookie
return true
end,
-- Get a cookie value
get = function(name)
if type(name) ~= "string" then
error("cookie.get: name must be a string", 2)
end
-- Access values directly from current environment
local env = getfenv(1)
-- Check if context exists and has cookies
if env.ctx and env.ctx.cookies and env.ctx.cookies[name] then
return tostring(env.ctx.cookies[name])
end
return nil
end,
-- Remove a cookie
remove = function(name, path, domain)
if type(name) ~= "string" then
error("cookie.remove: name must be a string", 2)
end
-- Create an expired cookie
return cookie.set(name, "", 0, path or "/", domain, false, false)
end
}
-- Install cookie module
_G.cookie = cookie
-- Make sure the cookie module is accessible in sandbox
if __env_system and __env_system.base_env then
__env_system.base_env.cookie = cookie
end
`
// CookieModuleInitFunc returns an initializer for the cookie module
func CookieModuleInitFunc() StateInitFunc {
return func(state *luajit.State) error {
return state.DoString(LuaCookieModule)
}
}

View File

@ -1,6 +1,8 @@
package runner
import (
"net/http"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
)
@ -9,6 +11,7 @@ type HTTPResponse struct {
Status int `json:"status"`
Headers map[string]string `json:"headers"`
Body any `json:"body"`
Cookies []*http.Cookie `json:"-"`
}
// NewHTTPResponse creates a default HTTP response
@ -16,6 +19,7 @@ func NewHTTPResponse() *HTTPResponse {
return &HTTPResponse{
Status: 200,
Headers: make(map[string]string),
Cookies: []*http.Cookie{},
}
}
@ -128,6 +132,26 @@ func GetHTTPResponse(state *luajit.State) (*HTTPResponse, bool) {
}
state.Pop(1)
// Get cookies
state.GetField(-1, "cookies")
if state.IsTable(-1) {
// Iterate through cookies array
length := state.GetTableLength(-1)
for i := 1; i <= length; i++ {
state.PushNumber(float64(i))
state.GetTable(-2)
if state.IsTable(-1) {
cookie := extractCookie(state)
if cookie != nil {
response.Cookies = append(response.Cookies, cookie)
}
}
state.Pop(1)
}
}
state.Pop(1)
// Clean up
state.Pop(2) // Pop response table and __http_responses

View File

@ -103,14 +103,14 @@ func NewRunner(options ...RunnerOption) (*LuaRunner, error) {
return nil, ErrInitFailed
}
// Initialize HTTP module BEFORE sandbox setup
httpInit := HTTPModuleInitFunc()
if err := httpInit(state); err != nil {
// Preload core modules
moduleInits := CombineInitFuncs(HTTPModuleInitFunc(), CookieModuleInitFunc())
if err := moduleInits(state); err != nil {
state.Close()
return nil, ErrInitFailed
}
// Set up sandbox AFTER HTTP module is initialized
// Set up sandbox after core modules are initialized
if err := runner.sandbox.Setup(state); err != nil {
state.Close()
return nil, ErrInitFailed

View File

@ -81,8 +81,8 @@ func (s *Sandbox) Setup(state *luajit.State) error {
preload = package.preload
}
-- Add HTTP module explicitly to the base environment
base.http = http
base.cookie = cookie
-- Add registered custom modules
if __sandbox_modules then
@ -127,6 +127,24 @@ func (s *Sandbox) Setup(state *luajit.State) error {
env.require = function(modname)
return __fast_require(env, modname)
end
-- Install cookie module methods directly into environment
env.cookie = {
get = function(name)
if type(name) ~= "string" then
error("cookie.get: name must be a string", 2)
end
if env.ctx and env.ctx.cookies and env.ctx.cookies[name] then
return tostring(env.ctx.cookies[name])
end
return nil
end,
set = cookie.set,
remove = cookie.remove
}
end
-- Store reference to current environment