Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions extension/include/config/dynamic_admin_api/default.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@
*/
void kg_config_dynamic_admin_api_defaults_load(void);

/**
* @brief Releases persistent config strings owned by dynamic admin callbacks.
*/
void kg_config_dynamic_admin_api_defaults_release(void);

#endif /* KING_CONFIG_DYNAMIC_ADMIN_API_DEFAULT_H */
3 changes: 0 additions & 3 deletions extension/include/php_king.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
#endif

#include <php.h>
#include <zend_object_handlers.h>
#include <Zend/zend_execute.h>
#include <zend_exceptions.h>
#include <stdint.h>
#include <string.h>
#include <stdatomic.h>
Expand Down
23 changes: 23 additions & 0 deletions extension/src/config/dynamic_admin_api/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
#include "config/dynamic_admin_api/default.h"
#include "config/dynamic_admin_api/base_layer.h"

static void dynamic_admin_api_release_owned_string(char **value)
{
if (*value != NULL) {
pefree(*value, 1);
*value = NULL;
}
}

void kg_config_dynamic_admin_api_defaults_load(void)
{
king_dynamic_admin_api_config.bind_host = NULL;
Expand All @@ -23,3 +31,18 @@ void kg_config_dynamic_admin_api_defaults_load(void)
king_dynamic_admin_api_config.cert_file = NULL;
king_dynamic_admin_api_config.key_file = NULL;
}

void kg_config_dynamic_admin_api_defaults_release(void)
{
/*
* bind_host is managed by the engine's STD_PHP_INI_ENTRY storage.
* The remaining string fields are persistent copies created by the
* dynamic-admin-api INI callbacks in this config family.
*/
king_dynamic_admin_api_config.bind_host = NULL;
king_dynamic_admin_api_config.port = 2019;
dynamic_admin_api_release_owned_string(&king_dynamic_admin_api_config.auth_mode);
dynamic_admin_api_release_owned_string(&king_dynamic_admin_api_config.ca_file);
dynamic_admin_api_release_owned_string(&king_dynamic_admin_api_config.cert_file);
dynamic_admin_api_release_owned_string(&king_dynamic_admin_api_config.key_file);
}
1 change: 1 addition & 0 deletions extension/src/config/dynamic_admin_api/index.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ void kg_config_dynamic_admin_api_init(void)
void kg_config_dynamic_admin_api_shutdown(void)
{
kg_config_dynamic_admin_api_ini_unregister();
kg_config_dynamic_admin_api_defaults_release();
}
69 changes: 53 additions & 16 deletions extension/tests/server_websocket_wire_helper.inc
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

/* Test helper for starting the on-wire HTTP/1/WebSocket one-shot listener harness. */

function king_server_websocket_wire_is_decimal_digits(string $value): bool
{
return $value !== '' && strspn($value, '0123456789') === strlen($value);
}

function king_server_websocket_wire_start_server(string $mode, int $iterations = 1, ?int $fixedPort = null, array $env = []): array
{
$maxAttempts = $fixedPort !== null ? 1 : 10;

for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
if ($fixedPort !== null) {
if ($fixedPort < 1 || $fixedPort > 65535) {
throw new RuntimeException('fixed on-wire HTTP/1 test port is outside the valid TCP port range');
}
$port = $fixedPort;
} else {
$errno = 0;
Expand All @@ -19,38 +27,54 @@ function king_server_websocket_wire_start_server(string $mode, int $iterations =

$serverName = stream_socket_get_name($probe, false);
fclose($probe);
[, $port] = explode(':', $serverName, 2);
$port = (int) $port;
if (!is_string($serverName)) {
throw new RuntimeException('failed to read reserved on-wire HTTP/1 test port name');
}

$colon = strrpos($serverName, ':');
if ($colon === false) {
throw new RuntimeException('reserved on-wire HTTP/1 test port name did not contain a port');
}

$portText = substr($serverName, $colon + 1);
if (!king_server_websocket_wire_is_decimal_digits($portText)) {
throw new RuntimeException('reserved on-wire HTTP/1 test port was not numeric');
}

$port = (int) $portText;
if ($port < 1 || $port > 65535) {
throw new RuntimeException('reserved on-wire HTTP/1 test port is outside the valid TCP port range');
}
}

$capture = tempnam(sys_get_temp_dir(), 'king-server-websocket-wire-');
if ($capture === false) {
throw new RuntimeException('failed to create on-wire HTTP/1 websocket capture file');
}

$extensionPath = dirname(__DIR__) . '/modules/king.so';
$commandParts = [
escapeshellarg(PHP_BINARY),
$command = [
PHP_BINARY,
'-n',
'-d',
escapeshellarg('extension=' . $extensionPath),
'extension=' . $extensionPath,
'-d',
escapeshellarg('king.security_allow_config_override=1'),
'king.security_allow_config_override=1',
];

if ($mode === 'cors-allowlist') {
$commandParts[] = '-d';
$commandParts[] = escapeshellarg(
'king.security_cors_allowed_origins=https://app.king.test, https://admin.king.test'
);
$command[] = '-d';
$command[] = 'king.security_cors_allowed_origins=https://app.king.test, https://admin.king.test';
}

$commandParts = array_merge($commandParts, [
escapeshellarg(__DIR__ . '/server_websocket_wire_server.inc'),
escapeshellarg($capture),
$command = array_merge($command, [
__DIR__ . '/server_websocket_wire_server.inc',
$capture,
(string) (int) $port,
escapeshellarg($mode),
$mode,
(string) max(1, $iterations),
]);

$command = implode(' ', $commandParts);

$processEnv = null;
if ($env !== []) {
$baseEnv = getenv();
Expand Down Expand Up @@ -95,6 +119,17 @@ function king_server_websocket_wire_start_server(string $mode, int $iterations =
throw new RuntimeException('failed to launch on-wire HTTP/1 websocket server');
}

function king_server_websocket_wire_assert_request_target(string $path): void
{
if ($path === '' || $path[0] !== '/') {
throw new RuntimeException('raw websocket request target must be an absolute path');
}

if (str_contains($path, "\r") || str_contains($path, "\n")) {
throw new RuntimeException('raw websocket request target must not contain CRLF bytes');
}
}

function king_server_websocket_wire_stop_server(array $server): array
{
$stdout = isset($server['pipes'][1]) ? stream_get_contents($server['pipes'][1]) : '';
Expand Down Expand Up @@ -284,6 +319,8 @@ function king_server_websocket_wire_raw_client_connect(int $port, string $path)
$socket = false;
$message = 'connection failed';

king_server_websocket_wire_assert_request_target($path);

if (!function_exists('socket_create')) {
throw new RuntimeException('The sockets extension is required for raw websocket client fixtures. Please enable it in php.ini.');
}
Expand Down
77 changes: 56 additions & 21 deletions infra/scripts/install-ubuntu-php-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@
set -euo pipefail

PHP_VERSION="${1:-}"
ONDREJ_PHP_KEY_FINGERPRINT="B8DC7E53946656EFBCE4C1DD71DAEAAB4AD4CAB6"
APT_RETRY_OPTIONS=(
-o Acquire::Retries=5
-o Acquire::http::Timeout=30
-o Acquire::https::Timeout=30
-o Acquire::ForceIPv4=true
)

if [[ -z "${PHP_VERSION}" ]]; then
echo "Usage: install-ubuntu-php-runtime.sh <php-version>" >&2
exit 1
fi

if [[ ! "${PHP_VERSION}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
echo "Invalid PHP version: ${PHP_VERSION}" >&2
exit 1
fi

normalize_ubuntu_sources_to_scheme() {
local scheme="$1"
local source_file=""
Expand All @@ -23,14 +35,44 @@ normalize_ubuntu_sources_to_scheme() {
done
}

install_ondrej_php_keyring() {
local key_tmp=""
local keyring_tmp=""
local fingerprint=""

key_tmp="$(mktemp)"
keyring_tmp="$(mktemp)"

if ! curl --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors -fsSL \
'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xB8DC7E53946656EFBCE4C1DD71DAEAAB4AD4CAB6' \
-o "${key_tmp}"; then
rm -f "${key_tmp}" "${keyring_tmp}"
return 1
fi

fingerprint="$(
gpg --show-keys --with-colons "${key_tmp}" \
| awk -F: '$1 == "fpr" { print $10; exit }'
)"

if [[ "${fingerprint}" != "${ONDREJ_PHP_KEY_FINGERPRINT}" ]]; then
echo "Unexpected Ondrej PHP PPA key fingerprint: ${fingerprint:-<empty>}" >&2
rm -f "${key_tmp}" "${keyring_tmp}"
return 1
fi

if ! gpg --batch --yes --dearmor -o "${keyring_tmp}" "${key_tmp}"; then
rm -f "${key_tmp}" "${keyring_tmp}"
return 1
fi

install -m 0644 "${keyring_tmp}" /usr/share/keyrings/ondrej-php.gpg
rm -f "${key_tmp}" "${keyring_tmp}"
}

normalize_ubuntu_sources_to_scheme http

apt-get \
-o Acquire::Retries=5 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
-o Acquire::ForceIPv4=true \
update
apt-get "${APT_RETRY_OPTIONS[@]}" update

apt-get install -y --no-install-recommends \
ca-certificates \
Expand All @@ -41,28 +83,21 @@ apt-get install -y --no-install-recommends \

normalize_ubuntu_sources_to_scheme https

apt-get \
-o Acquire::Retries=5 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
-o Acquire::ForceIPv4=true \
update
apt-get "${APT_RETRY_OPTIONS[@]}" update

mkdir -p /usr/share/keyrings
curl --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors -fsSL \
'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xB8DC7E53946656EFBCE4C1DD71DAEAAB4AD4CAB6' \
| gpg --dearmor -o /usr/share/keyrings/ondrej-php.gpg
install_ondrej_php_keyring

. /etc/os-release
if [[ -z "${VERSION_CODENAME:-}" || ! "${VERSION_CODENAME}" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "Ubuntu VERSION_CODENAME is missing or invalid." >&2
exit 1
fi

printf 'deb [signed-by=/usr/share/keyrings/ondrej-php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu %s main\n' "${VERSION_CODENAME}" \
> /etc/apt/sources.list.d/ondrej-php.list

apt-get \
-o Acquire::Retries=5 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
-o Acquire::ForceIPv4=true \
update
apt-get "${APT_RETRY_OPTIONS[@]}" update

apt-get install -y --no-install-recommends \
"php${PHP_VERSION}-cli" \
Expand Down
35 changes: 32 additions & 3 deletions infra/scripts/resolve-docker-buildx-cache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ if [[ ! "${SCOPE}" =~ ^[A-Za-z0-9._-]+$ ]]; then
exit 1
fi

if [[ "${SCOPE}" == "." || "${SCOPE}" == ".." ]]; then
echo "--scope must not be a relative path segment." >&2
exit 1
fi

write_output() {
local key="$1"
local value="$2"
Expand All @@ -77,15 +82,39 @@ write_scalar_output() {
cache_from="type=gha,scope=${SCOPE}"
cache_to="type=gha,mode=max,scope=${SCOPE},ignore-error=true"
local_root="${KING_CI_LOCAL_DOCKER_BUILDX_CACHE_DIR:-}"
local_root_real=""
use_local="false"
local_dir=""
local_next_dir=""

if [[ -n "${local_root}" && -d "${local_root}" && -w "${local_root}" ]]; then
local_dir="${local_root%/}/${SCOPE}"
local_next_dir="${local_root%/}/${SCOPE}.next"
local_root_real="$(realpath -m "${local_root}")"
if [[ "${local_root_real}" == "/" ]]; then
echo "Refusing to use filesystem root as Docker Buildx local cache root." >&2
exit 1
fi

local_dir="${local_root_real}/${SCOPE}"
local_next_dir="${local_root_real}/${SCOPE}.next"

case "${local_dir}" in
"${local_root_real}/"*) ;;
*)
echo "Resolved local cache directory escaped the configured cache root." >&2
exit 1
;;
esac

case "${local_next_dir}" in
"${local_root_real}/"*) ;;
*)
echo "Resolved local cache next directory escaped the configured cache root." >&2
exit 1
;;
esac

mkdir -p "${local_dir}"
rm -rf "${local_next_dir}"
rm -rf -- "${local_next_dir}"
mkdir -p "${local_next_dir}"

cache_from=$(printf '%s\n%s' "type=local,src=${local_dir}" "${cache_from}")
Expand Down
Loading