mirror of
https://git.bakhai.co.in/FbIN/libreqr.git
synced 2026-04-21 01:46:17 +05:30
390 lines
No EOL
13 KiB
PHP
Executable file
390 lines
No EOL
13 KiB
PHP
Executable file
<?php declare(strict_types=1);
|
|
|
|
// This file is part of LibreQR, which is distributed under the GNU AGPLv3+ license
|
|
|
|
use Endroid\QrCode\Builder\Builder;
|
|
use Endroid\QrCode\Color\Color;
|
|
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
|
|
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
|
|
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelMedium;
|
|
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelQuartile;
|
|
|
|
require 'config.inc.php';
|
|
require 'vendor/autoload.php';
|
|
|
|
const CONTRAST_THRESHOLD = 64;
|
|
const LibreQR_VERSION = '2.0.1+dev';
|
|
|
|
// Defines the locale to be used
|
|
$locale = DEFAULT_LOCALE;
|
|
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
|
$clientLocales = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
|
|
$clientLocales = preg_replace('#[A-Z0-9]|q=|;|-|\.#', '', $clientLocales);
|
|
$clientLocales = explode(',', $clientLocales);
|
|
foreach (array_diff(scandir('locales'), ['.', '..']) as $key => $localeFile) {
|
|
$availableLocales[$key] = basename($localeFile, '.php');
|
|
}
|
|
foreach ($clientLocales as $clientLocale) {
|
|
if (in_array($clientLocale, $availableLocales, true)) {
|
|
$locale = $clientLocale;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
header('Content-Language: ' . $locale);
|
|
|
|
require 'locales/' . $locale . '.php';
|
|
$chosen_loc = $loc;
|
|
|
|
if ($locale !== DEFAULT_LOCALE) {
|
|
require 'locales/' . DEFAULT_LOCALE . '.php';
|
|
$default_loc = $loc;
|
|
} else {
|
|
$default_loc = $chosen_loc;
|
|
}
|
|
|
|
require 'locales/en.php';
|
|
$english_loc = $loc;
|
|
|
|
// Function to get a specific string from the locale file, fall back on default then english if missing
|
|
function getIntlString(
|
|
string $string_label,
|
|
bool $raw = false,
|
|
): string {
|
|
global $chosen_loc, $default_loc, $english_loc, $locale;
|
|
|
|
if (array_key_exists($string_label, $chosen_loc) && $chosen_loc[$string_label] !== '') {
|
|
return $chosen_loc[$string_label];
|
|
}
|
|
|
|
if ($locale !== DEFAULT_LOCALE && array_key_exists($string_label, $default_loc) && $default_loc[$string_label] !== '') {
|
|
if ($raw) {
|
|
return $default_loc[$string_label];
|
|
}
|
|
return '<span lang="' . DEFAULT_LOCALE . '">' . $default_loc[$string_label] . '</span>';
|
|
}
|
|
|
|
if ($raw) {
|
|
return $english_loc[$string_label];
|
|
}
|
|
return '<span lang="en">' . $english_loc[$string_label] . '</span>';
|
|
}
|
|
|
|
function getIntlStringInterpolated(
|
|
string $string_label,
|
|
array $vars = [],
|
|
bool $raw = false,
|
|
): string {
|
|
return vsprintf(getIntlString($string_label, $raw), $vars);
|
|
}
|
|
|
|
preg_match('#.*/(?<page>.*)$#', $_SERVER['REQUEST_URI'], $matches);
|
|
define('PAGE', match ($matches['page']) {
|
|
'wifi' => 'wifi',
|
|
'' => 'home',
|
|
default => 'unknown',
|
|
});
|
|
|
|
define('ICONS_DIR', 'themes/' . THEME . '/icons');
|
|
$icon_files = array_diff(scandir(ICONS_DIR), ['.', '..']);
|
|
if (PAGE === 'unknown') {
|
|
$allowed_filenames['LICENSE.html'] = 'text/html';
|
|
$allowed_filenames['synclog.txt'] = 'text/html';
|
|
$allowed_filenames['style.css'] = 'text/css';
|
|
$allowed_filenames['themes/' . THEME . '/theme.css'] = 'text/css';
|
|
$allowed_filenames['themes/' . THEME . '/logo-dark.png'] = 'image/png';
|
|
$allowed_filenames['themes/' . THEME . '/logo-light.png'] = 'image/png';
|
|
foreach ($icon_files as $icon_file) {
|
|
$allowed_filenames[ICONS_DIR . '/' . $icon_file] = 'image/png';
|
|
}
|
|
foreach ($allowed_filenames as $filename => $type) {
|
|
if (str_ends_with($_SERVER['REQUEST_URI'], $filename)) {
|
|
header('Content-Type: ' . $type);
|
|
echo file_get_contents($filename);
|
|
exit;
|
|
}
|
|
}
|
|
http_response_code(404);
|
|
}
|
|
|
|
$_POST = [
|
|
'wifi' => [
|
|
'ssid' => (string) ($_POST['wifi']['ssid'] ?? ''),
|
|
'password' => (string) ($_POST['wifi']['password'] ?? ''),
|
|
],
|
|
'main' => [
|
|
'txt' => (string) ($_POST['main']['txt'] ?? ''),
|
|
'redundancy' => $_POST['main']['redundancy'] ?? (string) DEFAULT_REDUNDANCY,
|
|
'margin' => $_POST['main']['margin'] ?? (string) DEFAULT_MARGIN,
|
|
'size' => $_POST['main']['size'] ?? (string) DEFAULT_SIZE,
|
|
'bgColor' => $_POST['main']['bgColor'] ?? '#' . (string) DEFAULT_BGCOLOR,
|
|
'fgColor' => $_POST['main']['fgColor'] ?? '#' . (string) DEFAULT_FGCOLOR,
|
|
],
|
|
];
|
|
|
|
if ($_POST['wifi']['ssid'] !== '') {
|
|
if (!(strlen($_POST['wifi']['ssid']) >= 1 && strlen($_POST['wifi']['ssid']) <= 4096)) {
|
|
http_response_code(400);
|
|
exit('Wrong value for ssid');
|
|
}
|
|
$escaped_ssid = preg_replace('/([\\\;\,"\:])/', '\\\$1', $_POST['wifi']['ssid']);
|
|
|
|
if ($_POST['wifi']['password'] === '') {
|
|
$_POST['main']['txt'] = "WIFI:T:nopass;S:{$escaped_ssid};;";
|
|
} else {
|
|
if (strlen($_POST['wifi']['password']) > 4096) {
|
|
http_response_code(400);
|
|
exit('Wrong value for password');
|
|
}
|
|
|
|
$escaped_password = preg_replace('/([\\\;\,"\:])/', '\\\$1', $_POST['wifi']['password']);
|
|
|
|
$_POST['main']['txt'] = "WIFI:T:WPA;S:{$escaped_ssid};P:{$escaped_password};;";
|
|
}
|
|
}
|
|
|
|
$qrCodeAvailable = null;
|
|
|
|
if ($_POST['main']['txt'] !== '') {
|
|
$qrCodeAvailable = true;
|
|
|
|
if (!(strlen($_POST['main']['txt']) >= 1 && strlen($_POST['main']['txt']) <= 4096)) {
|
|
http_response_code(400);
|
|
exit('Wrong value for txt');
|
|
}
|
|
|
|
if (!in_array($_POST['main']['redundancy'], ['low', 'medium', 'quartile', 'high'], strict: true)) {
|
|
http_response_code(400);
|
|
exit('Wrong value for redundancy');
|
|
}
|
|
|
|
if (!(is_numeric($_POST['main']['margin']) && $_POST['main']['margin'] >= 0 && $_POST['main']['margin'] <= 1024)) {
|
|
http_response_code(400);
|
|
exit('Wrong value for margin');
|
|
}
|
|
|
|
if (!(is_numeric($_POST['main']['size']) && $_POST['main']['size'] >= 21 && $_POST['main']['size'] <= 4096)) {
|
|
http_response_code(400);
|
|
exit('Wrong value for size');
|
|
}
|
|
|
|
if (preg_match('/^#[abcdefABCDEF0-9]{6}$/', $_POST['main']['bgColor']) === false) {
|
|
http_response_code(400);
|
|
exit('Wrong value for bgColor');
|
|
}
|
|
|
|
if (preg_match('/^#[abcdefABCDEF0-9]{6}$/', $_POST['main']['fgColor']) === false) {
|
|
http_response_code(400);
|
|
exit('Wrong value for fgColor');
|
|
}
|
|
|
|
$rgbBgColor = [
|
|
'r' => hexdec(substr($_POST['main']['bgColor'], 1, 2)),
|
|
'g' => hexdec(substr($_POST['main']['bgColor'], 3, 2)),
|
|
'b' => hexdec(substr($_POST['main']['bgColor'], 5, 2)),
|
|
];
|
|
|
|
$qrCode = Builder::create()
|
|
->data($_POST['main']['txt'])
|
|
->margin((int) $_POST['main']['margin'])
|
|
->size((int) $_POST['main']['size'])
|
|
->errorCorrectionLevel(match ($_POST['main']['redundancy']) {
|
|
'low' => new ErrorCorrectionLevelLow(),
|
|
'medium' => new ErrorCorrectionLevelMedium(),
|
|
'quartile' => new ErrorCorrectionLevelQuartile(),
|
|
'high' => new ErrorCorrectionLevelHigh(),
|
|
})
|
|
->backgroundColor(new Color(
|
|
(int) $rgbBgColor['r'],
|
|
(int) $rgbBgColor['g'],
|
|
(int) $rgbBgColor['b'],
|
|
))
|
|
->foregroundColor(new Color(
|
|
(int) hexdec(substr($_POST['main']['fgColor'], 1, 2)),
|
|
(int) hexdec(substr($_POST['main']['fgColor'], 3, 2)),
|
|
(int) hexdec(substr($_POST['main']['fgColor'], 5, 2)),
|
|
))
|
|
;
|
|
|
|
try {
|
|
$result = $qrCode->build();
|
|
} catch (Exception $ex) {
|
|
http_response_code(500);
|
|
$qrCodeAvailable = false;
|
|
error_log('FbIN QR encountered an error while generating a QR code: ' . $ex);
|
|
}
|
|
}
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="<?= $locale ?>">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title><?= match (PAGE) {
|
|
'home' => 'FbIN QR · ' . getIntlString('subtitle'),
|
|
'wifi' => getIntlString('tab_wifi_title') . ' · FbIN QR',
|
|
'unknown' => getIntlString('error_404') . ' · FbIN QR',
|
|
} ?></title>
|
|
<meta name="description" content="<?= getIntlString('description', raw: true) ?>">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="color-scheme" content="dark light">
|
|
<meta name="application-name" content="Libre QR">
|
|
<meta name="referrer" content="no-referrer">
|
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data:; style-src 'self'; form-action 'self';">
|
|
|
|
<!-- Avoids leaking the "prefers-color-scheme" media query to the server -->
|
|
<link rel="preload" as="image" href="themes/<?= THEME ?>/logo-dark.png">
|
|
<link rel="preload" as="image" href="themes/<?= THEME ?>/logo-light.png">
|
|
|
|
<link rel="stylesheet" media="screen" href="style.css">
|
|
<link rel="stylesheet" media="screen" href="themes/<?= THEME ?>/theme.css">
|
|
<?php
|
|
natsort($icon_files);
|
|
foreach ($icon_files as $icon_file) {
|
|
if (ctype_digit($icon_size = substr($icon_file, 0, -4))) {
|
|
echo ' <link rel="icon" type="image/png" href="' . ICONS_DIR . '/' . $icon_size . '.png" sizes="' . $icon_size . 'x' . $icon_size . '">' . "\n";
|
|
}
|
|
}
|
|
?>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<header>
|
|
<a id="linkTitles" href="./">
|
|
<hgroup id="titles">
|
|
<h1>FbIN QR</h1>
|
|
<p><?= getIntlString('subtitle') ?></p>
|
|
</hgroup>
|
|
</a>
|
|
</header>
|
|
|
|
<nav>
|
|
<h2 class="sr-only">Type de code QR</h2>
|
|
<ul>
|
|
<li<?php if (PAGE === 'home') {
|
|
echo ' class="tab-selected"';
|
|
} ?>><a href="./"><div><?= getIntlString('tab_text') ?></div></a></li>
|
|
<li<?php if (PAGE === 'wifi') {
|
|
echo ' class="tab-selected"';
|
|
} ?>><a href="./wifi"><div><?= getIntlString('tab_wifi') ?></div></a></li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<?php if (PAGE === 'wifi') { ?>
|
|
<form method="post" action="./wifi#output" id="form">
|
|
<div class="param textboxParam">
|
|
<label for="ssid"><?= getIntlString('label_wifi_ssid') ?></label>
|
|
<input type="text" id="ssid" placeholder="<?= getIntlString('placeholder_wifi_ssid', raw: true) ?>" name="wifi[ssid]" required maxlength="4096" value="<?= htmlspecialchars($_POST['wifi']['ssid']) ?>">
|
|
</div>
|
|
<div class="param textboxParam">
|
|
<details>
|
|
<summary><label for="password"><?= getIntlString('label_wifi_password') ?></label></summary>
|
|
<div class="helpText">
|
|
<?= getIntlString('help_wifi_password') ?>
|
|
</div>
|
|
</details>
|
|
<input type="text" id="password" placeholder="<?= getIntlString('placeholder_wifi_password', raw: true) ?>" name="wifi[password]" maxlength="4096" value="<?= htmlspecialchars($_POST['wifi']['password']) ?>">
|
|
</div>
|
|
<?php require 'common.php' ?>
|
|
</form>
|
|
<?php } elseif (PAGE === 'home') { ?>
|
|
<form method="post" action="./#output" id="form">
|
|
<div class="param textboxParam" id="txtParam">
|
|
<details>
|
|
<summary><label for="txt"><?= getIntlString('label_content') ?></label></summary>
|
|
<div class="helpText">
|
|
<?= getIntlString('help_content') ?>
|
|
</div>
|
|
</details>
|
|
<textarea rows="3" id="txt" placeholder="<?= getIntlString('placeholder', raw: true) ?>" name="main[txt]"><?= htmlspecialchars($_POST['main']['txt']) ?></textarea>
|
|
</div>
|
|
<?php require 'common.php' ?>
|
|
</form>
|
|
<?php } else { ?>
|
|
<p>
|
|
<?= getIntlString('error_404') ?>
|
|
</p>
|
|
<?php } ?>
|
|
|
|
<?php
|
|
|
|
if ($qrCodeAvailable) {
|
|
$dataUri = $result->getDataUri();
|
|
|
|
$qrSize = (int) ($_POST['main']['size']) + 2 * (int) ($_POST['main']['margin']);
|
|
|
|
?>
|
|
|
|
<section id="output">
|
|
<div class="centered" id="downloadQR">
|
|
<output form="form"><a href="<?= $dataUri ?>" class="button" download="<?= htmlspecialchars($_POST['main']['txt']); ?>.png"><?= getIntlString('button_download') ?></a></output>
|
|
</div>
|
|
|
|
<div class="centered" id="showOnlyQR">
|
|
<output form="form"><a title="<?= getIntlString('title_showOnlyQR', raw: true) ?>" href="<?= $dataUri ?>"><img width="<?= $qrSize ?>" height="<?= $qrSize ?>" alt='<?= getIntlStringInterpolated('alt_QR', [htmlspecialchars($_POST['main']['txt'])], raw: true) ?>' id="qrCode"<?php
|
|
|
|
// Compute the difference between the QR code and theme background colors to determine whether a CSS corner is needed to let the user see the margin of the QR code
|
|
preg_match(
|
|
'/prefers-color-scheme: light.+--bg: (?<bg_light>#[A-Fa-f0-9]{6});.+prefers-color-scheme: dark.+--bg: (?<bg_dark>#[A-Fa-f0-9]{6});.+/s',
|
|
file_get_contents('themes/' . THEME . '/theme.css'),
|
|
$css,
|
|
);
|
|
if (
|
|
abs($rgbBgColor['r'] - hexdec(substr($css['bg_light'], -6, 2)))
|
|
+ abs($rgbBgColor['g'] - hexdec(substr($css['bg_light'], -4, 2)))
|
|
+ abs($rgbBgColor['b'] - hexdec(substr($css['bg_light'], -2, 2)))
|
|
< CONTRAST_THRESHOLD
|
|
) {
|
|
echo " class='needLightContrast'";
|
|
}
|
|
if (
|
|
abs($rgbBgColor['r'] - hexdec(substr($css['bg_dark'], -6, 2)))
|
|
+ abs($rgbBgColor['g'] - hexdec(substr($css['bg_dark'], -4, 2)))
|
|
+ abs($rgbBgColor['b'] - hexdec(substr($css['bg_dark'], -2, 2)))
|
|
< CONTRAST_THRESHOLD
|
|
) {
|
|
echo " class='needDarkContrast'";
|
|
}
|
|
|
|
?> src="<?= $dataUri ?>"></a></output>
|
|
</div>
|
|
<?php if (PAGE === 'wifi') { ?>
|
|
<p>
|
|
<?= getIntlStringInterpolated('wifi_raw_content', ['<code>' . htmlspecialchars($_POST['main']['txt']) . '</code>']) ?>
|
|
</p>
|
|
<form method="POST" action="./">
|
|
<?php foreach ($_POST['main'] as $name => $value) { ?>
|
|
<input type="hidden" name="main[<?= htmlspecialchars($name) ?>]" value="<?= htmlspecialchars($value) ?>">
|
|
<?php } ?>
|
|
<input type="submit" class="button" value="<?= getIntlString('button_edit', raw: true) ?>">
|
|
</form>
|
|
<?php } ?>
|
|
</section>
|
|
|
|
<?php
|
|
} elseif ($qrCodeAvailable === false) {
|
|
echo ' <p><strong>' . getIntlString('error_generation') . '</strong></p></body></html>';
|
|
}
|
|
?>
|
|
|
|
<footer>
|
|
|
|
<section id="info" class="metaText">
|
|
<?= getIntlString('metaText_qr') ?>
|
|
</section>
|
|
|
|
<?php if (CUSTOM_TEXT_ENABLED) { ?>
|
|
<section class="metaText">
|
|
<?= CUSTOM_TEXT ?>
|
|
</section>
|
|
<?php } ?>
|
|
|
|
<section class="metaText">
|
|
<small><?= getIntlStringInterpolated('metaText_legal', [LibreQR_VERSION]) ?></small>
|
|
</section>
|
|
|
|
</footer>
|
|
|
|
</body>
|
|
</html>
|