Pith - miter
<?php
declare(strict_types=1);
session_start();

define('MITER_ASYNC', ($_SERVER['HTTP_X_MITER_ASYNC'] ?? '') === '1');

function respond(int $code, string $error = '', string $value = ''): void {
if (MITER_ASYNC) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($error === ''
? ['ok' => true, 'value' => $value]
: ['ok' => false, 'error' => $error], JSON_UNESCAPED_UNICODE);
} elseif ($error === '') {
header('Location: ../../index.php?user=settings');
} else {
http_response_code($code);
echo 'Error: ' . htmlspecialchars($error, ENT_QUOTES, 'UTF-8');
}
exit;
}

function clean_text(string $value): string {
return trim((string)preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value));
}

function store_avatar($upload, string $image_dir): string {
if (!is_array($upload) || ($upload['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
respond(422, 'Upload Failure');
}

$ext = strtolower(pathinfo((string)$upload['name'], PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif'], true)) {
respond(422, 'Invalid Extension');
}

$mime = (new finfo(FILEINFO_MIME_TYPE))->file((string)$upload['tmp_name']);
if (!in_array($mime, ['image/jpeg', 'image/png', 'image/gif'], true)) {
respond(422, 'Invalid Image');
}

$name = (string)preg_replace('/[^a-zA-Z0-9_\.-]/', '', (string)$upload['name']);
if ($name === '' || $name === '.' || $name === '..') {
respond(422, 'Invalid Filename');
}

if (!move_uploaded_file((string)$upload['tmp_name'], $image_dir . $name)) {
respond(500, 'Move Failure');
}

return $name;
}

if (empty($_SESSION['miter'])) {
if (MITER_ASYNC) {
respond(401, 'Session Expired');
}
header('Location: ../../index.php');
exit;
}
session_write_close();

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../../index.php?user=settings');
exit;
}

if (empty($_POST) && (int)($_SERVER['CONTENT_LENGTH'] ?? 0) > 0) {
respond(413, 'Upload Too Large');
}

$user_file = __DIR__ . '/../../user/user.xml';
$image_dir = __DIR__ . '/../../images/';

$fields = [
'name'       => 'name',
'bio'        => 'text',
'avatar'     => 'image',
'bg'         => 'hex',
'border'     => 'hex',
'faded'      => 'hex',
'header'     => 'hex',
'headertext' => 'hex',
'link'       => 'hex',
'mainbg'     => 'hex',
'maintext'   => 'hex',
'fontsize'   => 'int',
'radius'     => 'int',
'widthmiter' => 'int',
'widthpanel' => 'int',
'side'       => ['left', 'right'],
'underline'  => ['none', 'underline'],
];

$field = $_POST['miter_field'] ?? '';

if (!is_string($field) || !isset($fields[$field])) {
respond(400, 'Unknown Field');
}

$rule = $fields[$field];

if ($rule === 'image') {
$clean = store_avatar($_FILES['miter_value'] ?? null, $image_dir);
} else {
$value = $_POST['miter_value'] ?? null;

if (!is_string($value)) {
respond(400, 'Missing Value');
}

$value = trim($value);

if (is_array($rule)) {
if (!in_array($value, $rule, true)) {
respond(422, 'Invalid Option');
}
$clean = $value;
} elseif ($rule === 'hex') {
if (!preg_match('/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/', $value)) {
respond(422, 'Invalid Hex');
}
$clean = $value;
} elseif ($rule === 'int') {
if (!preg_match('/^[0-9]{1,5}$/', $value)) {
respond(422, 'Invalid Number');
}
$clean = (string)(int)$value;
} elseif ($rule === 'name') {
$clean = (string)preg_replace('/[\s@]+/u', '', clean_text($value));
if (!preg_match('/^.{1,47}$/u', $clean)) {
respond(422, 'Name Length 1-47');
}
$clean = '@' . $clean;
} else {
$clean = clean_text($value);
if ($clean === '') {
respond(422, 'Text Required');
}
}
}

if (!is_file($user_file) || !is_writable($user_file)) {
respond(500, 'File Exist Failure');
}

$handle = fopen($user_file, 'r+');

if ($handle === false || !flock($handle, LOCK_EX)) {
respond(500, 'File Lock Failure');
}

$internal_errors = libxml_use_internal_errors(true);
$style = simplexml_load_string((string)stream_get_contents($handle));
libxml_clear_errors();
libxml_use_internal_errors($internal_errors);

if ($style === false) {
respond(500, 'File Load Failure');
}

$nodes = $style->xpath("user[@id='probe']");

if (empty($nodes)) {
respond(500, 'Node Missing');
}

foreach ($nodes as $data) {
$data->{$field} = htmlspecialchars($clean, ENT_XML1 | ENT_SUBSTITUTE, 'UTF-8');
}

$xml_out = $style->asXML();

if (!is_string($xml_out)
|| !ftruncate($handle, 0)
|| !rewind($handle)
|| fwrite($handle, $xml_out) !== strlen($xml_out)) {
respond(500, 'Save Failure');
}

fflush($handle);
flock($handle, LOCK_UN);
fclose($handle);

respond(200, '', $field === 'name' ? substr($clean, 1) : $clean);