Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cgi.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ package frankenphp
// #cgo nocallback frankenphp_register_variable_safe
// #cgo nocallback frankenphp_register_known_variable
// #cgo nocallback frankenphp_init_persistent_string
// #cgo nocallback frankenphp_add_to_sandboxed_env
// #cgo noescape frankenphp_register_server_vars
// #cgo noescape frankenphp_register_variable_safe
// #cgo noescape frankenphp_register_known_variable
// #cgo noescape frankenphp_init_persistent_string
// #cgo noescape frankenphp_add_to_sandboxed_env
// #include "frankenphp.h"
// #include <php_variables.h>
import "C"
Expand Down Expand Up @@ -180,6 +182,13 @@ func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
fc.env = nil
}

// addPreparedEnvToSandbox exposes fc.env to getenv() before any PHP code runs.
func addPreparedEnvToSandbox(fc *frankenPHPContext) {
for k, v := range fc.env {
C.frankenphp_add_to_sandboxed_env(toUnsafeChar(k), C.size_t(len(k)-1), toUnsafeChar(v), C.size_t(len(v)))
}
Comment thread
henderkes marked this conversation as resolved.
}

//export go_register_server_variables
func go_register_server_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
thread := phpThreads[threadIndex]
Expand Down Expand Up @@ -298,6 +307,8 @@ func go_update_request_info(threadIndex C.uintptr_t, info *C.sapi_request_info)
return nil
}

addPreparedEnvToSandbox(fc)

if m, ok := cStringHTTPMethods[request.Method]; ok {
info.request_method = m
} else {
Expand Down
40 changes: 38 additions & 2 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ HashTable *main_thread_env = NULL;
__thread uintptr_t thread_index;
__thread bool is_worker_thread = false;
__thread HashTable *sandboxed_env = NULL;
/* prepared_env holds entries from php(_server)'s `env KEY VAL`,
* so they can be merged into $_ENV when 'E' is in
* variables_order. Separate from putenv() so we don't leak into $_ENV */
__thread HashTable *prepared_env = NULL;

/* Published via SG(server_context) so ext-parallel children, which inherit
* the parent's SG(server_context), can route SAPI callbacks back to the
Expand Down Expand Up @@ -429,9 +433,17 @@ bool frankenphp_shutdown_dummy_request(void) {
}

void get_full_env(zval *track_vars_array) {
zend_hash_extend(Z_ARR_P(track_vars_array),
zend_hash_num_elements(main_thread_env), 0);
size_t total = zend_hash_num_elements(main_thread_env);
if (prepared_env != NULL) {
// perf: doesn't matter if we get the exact count, just >= needed
total += zend_hash_num_elements(prepared_env);
}
zend_hash_extend(Z_ARR_P(track_vars_array), total, 0);
zend_hash_copy(Z_ARR_P(track_vars_array), main_thread_env, NULL);
if (prepared_env != NULL) {
zend_hash_copy(Z_ARR_P(track_vars_array), prepared_env,
(copy_ctor_func_t)zval_add_ref);
}
}

/* Adapted from php_request_startup() */
Expand Down Expand Up @@ -1239,6 +1251,30 @@ static inline void reset_sandboxed_environment() {
zend_hash_release(sandboxed_env);
sandboxed_env = NULL;
}
if (prepared_env != NULL) {
zend_hash_release(prepared_env);
prepared_env = NULL;
}
}

/* Adds a key/value pair to the per-thread sandboxed environment so it becomes
* visible to getenv()/$_ENV. Used to expose env vars declared in the
* php(_server) directive, which would otherwise only appear in $_SERVER. */
void frankenphp_add_to_sandboxed_env(char *name, size_t name_len, char *val,
size_t val_len) {
if (sandboxed_env == NULL) {
Comment thread
AlliBalliBaba marked this conversation as resolved.
Outdated
sandboxed_env = zend_array_dup(main_thread_env);
}
zval zv = {0};
ZVAL_STRINGL(&zv, val, val_len);
zend_hash_str_update(sandboxed_env, name, name_len, &zv);

if (prepared_env == NULL) {
prepared_env = zend_new_array(8);
}
zval zv2 = {0};
ZVAL_STRINGL(&zv2, val, val_len);
zend_hash_str_update(prepared_env, name, name_len, &zv2);
}

static void *php_thread(void *arg) {
Expand Down
2 changes: 2 additions & 0 deletions frankenphp.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ void frankenphp_register_variable_safe(char *key, char *var, size_t val_len,
zval *track_vars_array);
void frankenphp_register_server_vars(zval *track_vars_array,
frankenphp_server_vars vars);
void frankenphp_add_to_sandboxed_env(char *name, size_t name_len, char *val,
size_t val_len);

zend_string *frankenphp_init_persistent_string(const char *string, size_t len);
int frankenphp_reset_opcache(void);
Expand Down
19 changes: 19 additions & 0 deletions frankenphp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,25 @@ func TestEnvIsNotResetInWorkerMode(t *testing.T) {
}, &testOptions{workerScript: "env/remember-env.php"})
}

// reproduction of https://github.com/php/frankenphp/issues/1674
func TestPreparedEnvIsVisibleToGetenv_module(t *testing.T) {
testPreparedEnvIsVisibleToGetenv(t, &testOptions{nbParallelRequests: 1})
}
func TestPreparedEnvIsVisibleToGetenv_worker(t *testing.T) {
testPreparedEnvIsVisibleToGetenv(t, &testOptions{
workerScript: "env/prepared-env-getenv.php",
})
}
func testPreparedEnvIsVisibleToGetenv(t *testing.T, opts *testOptions) {
opts.requestOpts = append(opts.requestOpts,
frankenphp.WithRequestEnv(map[string]string{"FRANKENPHP_TEST_PHP_SERVER_ENV_IN_GETENV": "hello"}),
)
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) {
body, _ := testGet("http://example.com/env/prepared-env-getenv.php", handler, t)
assert.Equal(t, "getenv='hello'\nserver='hello'\n", body)
}, opts)
}

// reproduction of https://github.com/php/frankenphp/issues/1061
func TestModificationsToEnvPersistAcrossRequests(t *testing.T) {
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
Expand Down
11 changes: 11 additions & 0 deletions testdata/env/prepared-env-getenv.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

require_once __DIR__ . '/../_executor.php';

return function () {
// Variables declared via the env subdirective in the php(_server) directive
// (or via WithRequestEnv) must be exposed to both $_SERVER and getenv().
// See https://github.com/php/frankenphp/issues/1674
echo "getenv=" . var_export(getenv('FRANKENPHP_TEST_PHP_SERVER_ENV_IN_GETENV'), true) . "\n";
echo "server=" . var_export($_SERVER['FRANKENPHP_TEST_PHP_SERVER_ENV_IN_GETENV'] ?? null, true) . "\n";
Comment thread
henderkes marked this conversation as resolved.
};
Loading