diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ac3fb..7983227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added +* Wifi QR code type (PR [#27](https://code.antopie.org/miraty/libreqr/pulls/27))
+**Breaking**: HTTP requests for `wifi` needs to be forwarded to the `index.php` endpoint.
+To ease with webserver configuration, `index.php` is now able to serve any HTTP ressource that is used in the LibreQR interface. This means that every request can be forwarded to `index.php`. * German localization (PR [#25](https://code.antopie.org/miraty/libreqr/pulls/25)) ## 2.0.1 - 2023-07-08 diff --git a/README.md b/README.md index 6eb02c7..ced7fa8 100755 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A LibreQR instance is available at . ### Generic -Just place this source code in a Web server with PHP8.0+, extensions `gd`, `mbstring` and `iconv`, and writing rights on the `css/` directory. +Place this source code in a Web server with PHP8.0+, extensions `gd`, `mbstring` and `iconv`, and writing rights on the `css/` directory. Every request needs to be sent to `index.php`. #### Security hardening diff --git a/common.php b/common.php new file mode 100644 index 0000000..3c74266 --- /dev/null +++ b/common.php @@ -0,0 +1,52 @@ +
+
+
+ +

+ +

+
+ +
+ +
+
+ +

+ +

+
+ +
+ +
+
+ +

+ +

+
+ +
+ +
+ +
+
+ + +
+
+ + +
+
+ +
+ +
\ No newline at end of file diff --git a/index.php b/index.php index c9dee13..0dd37b3 100755 --- a/index.php +++ b/index.php @@ -28,7 +28,7 @@ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { } } -require "locales/" . $locale . ".php"; +require "locales/" . $locale . ".php"; $chosen_loc = $loc; if ($locale != DEFAULT_LOCALE) { @@ -55,115 +55,140 @@ function getIntlString( return $default_loc[$string_label]; return "" . $default_loc[$string_label] . ""; } - + if ($raw) return $template_loc[$string_label]; return "" . $template_loc[$string_label] . ""; } -$params = array( - "txt" => "", - "redundancy" => DEFAULT_REDUNDANCY, - "margin" => DEFAULT_MARGIN, - "size" => DEFAULT_SIZE, - "bgColor" => DEFAULT_BGCOLOR, - "fgColor" => DEFAULT_FGCOLOR, +require "themes/" . THEME . "/theme.php"; +$colorScheme['theme'] = THEME; + +$css_filename = Less_Cache::Get( + less_files: ['style.less' => ''], + parser_options: ['cache_dir' => 'css/', 'compress' => true], + modify_vars: $colorScheme, ); +preg_match('#.*/(?.*)$#', $_SERVER['REQUEST_URI'], $matches); +define('PAGE', match ($matches['page']) { + 'wifi' => 'wifi', + '' => 'home', + default => 'unknown', +}); + +if (PAGE === 'unknown') { + $allowed_filenames['css/' . $css_filename] = 'text/css'; + foreach ($themeDimensionsIcons as $icon_dimension) + $allowed_filenames['themes/' . THEME . '/icons/' . $icon_dimension . '.png'] = '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 = [ + "form" => $_POST['form'] ?? NULL, + "wifi" => [ + "ssid" => $_POST['wifi']['ssid'] ?? "", + "password" => $_POST['wifi']['password'] ?? "", + ], + "main" => [ + "txt" => $_POST['main']['txt'] ?? "", + "redundancy" => $_POST['main']['redundancy'] ?? DEFAULT_REDUNDANCY, + "margin" => $_POST['main']['margin'] ?? DEFAULT_MARGIN, + "size" => $_POST['main']['size'] ?? DEFAULT_SIZE, + "bgColor" => $_POST['main']['bgColor'] ?? "#" . DEFAULT_BGCOLOR, + "fgColor" => $_POST['main']['fgColor'] ?? "#" . DEFAULT_FGCOLOR, + ], +]; + +if ($_POST['wifi']['ssid'] !== "") { + if (!(strlen($_POST['wifi']['ssid']) >= 1 AND 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 ( - isset($_POST['txt']) - AND isset($_POST['redundancy']) - AND isset($_POST['margin']) - AND isset($_POST['size']) - AND isset($_POST['bgColor']) - AND isset($_POST['fgColor']) -) { - +if ($_POST['main']['txt'] !== "") { $qrCodeAvailable = true; - if (strlen($_POST['txt']) >= 1 AND strlen($_POST['txt']) <= 4096) { - $params['txt'] = $_POST['txt']; - } else { + if (!(strlen($_POST['main']['txt']) >= 1 AND strlen($_POST['main']['txt']) <= 4096)) { http_response_code(400); exit("Wrong value for txt"); } - if ($_POST['redundancy'] === "low" OR $_POST['redundancy'] === "medium" OR $_POST['redundancy'] === "quartile" OR $_POST['redundancy'] === "high") { - $params['redundancy'] = $_POST['redundancy']; - } else { + 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['margin']) AND $_POST['margin'] >= 0 AND $_POST['margin'] <= 1024) { - $params['margin'] = $_POST['margin']; - } else if (empty($_POST['margin'])) { - $params['margin'] = NULL; - } else { + if (!(is_numeric($_POST['main']['margin']) AND $_POST['main']['margin'] >= 0 AND $_POST['main']['margin'] <= 1024)) { http_response_code(400); exit("Wrong value for margin"); } - if (is_numeric($_POST['size']) AND $_POST['size'] >= 21 AND $_POST['size'] <= 4096) { - $params['size'] = $_POST['size']; - } else if (empty($_POST['size'])) { - $params['size'] = NULL; - } else { + if (!(is_numeric($_POST['main']['size']) AND $_POST['main']['size'] >= 21 AND $_POST['main']['size'] <= 4096)) { http_response_code(400); exit("Wrong value for size"); } - if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['bgColor'])) { - $params['bgColor'] = substr($_POST['bgColor'], -6); - } else { + 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['fgColor'])) { - $params['fgColor'] = substr($_POST['fgColor'], -6); - } else { + if (preg_match("/^#[abcdefABCDEF0-9]{6}$/", $_POST['main']['fgColor']) === false) { http_response_code(400); exit("Wrong value for fgColor"); } - $validFormSubmitted = true; - - $rgbBgColor = array( - 'r' => hexdec(substr($params['bgColor'],0,2)), - 'g' => hexdec(substr($params['bgColor'],2,2)), - 'b' => hexdec(substr($params['bgColor'],4,2)), - ); + $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($params['txt']); - if (!is_null($params['margin'])) - $qrCode->margin($params['margin']); - if (!is_null($params['size'])) - $qrCode->size($params['size']); - - if ($params['redundancy'] === "high") - $qrCode->errorCorrectionLevel(new ErrorCorrectionLevelHigh()); - else if ($params['redundancy'] === "quartile") - $qrCode->errorCorrectionLevel(new ErrorCorrectionLevelQuartile()); - else if ($params['redundancy'] === "medium") - $qrCode->errorCorrectionLevel(new ErrorCorrectionLevelMedium()); - else if ($params['redundancy'] === "low") - $qrCode->errorCorrectionLevel(new ErrorCorrectionLevelLow()); - - $qrCode - ->backgroundColor(new Color( - $rgbBgColor['r'], - $rgbBgColor['g'], - $rgbBgColor['b'] - )) - ->foregroundColor(new Color( - hexdec(substr($params['fgColor'],0,2)), - hexdec(substr($params['fgColor'],2,2)), - hexdec(substr($params['fgColor'],4,2)) - )); + ->data($_POST['main']['txt']) + ->margin($_POST['main']['margin']) + ->size($_POST['main']['size']) + ->errorCorrectionLevel(match ($_POST['main']['redundancy']) { + "low" => new ErrorCorrectionLevelLow(), + "medium" => new ErrorCorrectionLevelMedium(), + "quartile" => new ErrorCorrectionLevelQuartile(), + "high" => new ErrorCorrectionLevelHigh(), + }) + ->backgroundColor(new Color( + $rgbBgColor['r'], + $rgbBgColor['g'], + $rgbBgColor['b'], + )) + ->foregroundColor(new Color( + hexdec(substr($_POST['main']['fgColor'],1,2)), + hexdec(substr($_POST['main']['fgColor'],3,2)), + hexdec(substr($_POST['main']['fgColor'],5,2)), + )); try { $result = $qrCode->build(); @@ -179,21 +204,21 @@ if ( - LibreQR · <?= getIntlString('subtitle') ?> + <?= +match (PAGE) { + 'home' => 'LibreQR · ' . getIntlString('subtitle'), + 'wifi' => getIntlString('tab_wifi_title') . ' · LibreQR', + 'unknown' => getIntlString('error_404') . ' · LibreQR', +} +?> - 'css/', 'compress' => true); -$cssFileName = Less_Cache::Get(array("style.less" => ""), $options, $colorScheme); -?> - + ' . "\n"; @@ -204,97 +229,73 @@ foreach($themeDimensionsIcons as $dimFav) // Set all icons dimensions
-
+

LibreQR

-

-
+

+
-
+ -
+ + +
+ + +
+
+
+ +
+ +
+
+ +
+ + + +
+
- +
- -
- -
-
- -

- -

-
- -
- -
-
- -

- -

-
- -
- -
-
- -

- -

-
- -
- -
- -
-
- - -
-
- - -
-
- -
- -
- +
+ +

+ +

+ getDataUri(); - $qrSize = $params['size'] + 2 * $params['margin']; + $qrSize = $_POST['main']['size'] + 2 * $_POST['main']['margin']; ?>
- +
+ +

+ +

+
+ $value) { ?> + + + +
+
- + \ No newline at end of file diff --git a/locales/en.php b/locales/en.php index daa9502..274a8ec 100644 --- a/locales/en.php +++ b/locales/en.php @@ -3,6 +3,12 @@ $loc = array( 'subtitle' => "QR codes generator", 'description' => "Generate QR codes freely. Choose content, size, colors…", + 'tab_text' => "Text", + 'tab_wifi' => "Wifi", + 'tab_wifi_title' => "Wifi QR codes generation", + + 'label_wifi_ssid' => "Network name / SSID", + 'label_wifi_password' => "Password", 'label_content' => "Text to encode", 'label_redundancy' => "Redundancy rate", 'label_margin' => "Margin size", @@ -11,7 +17,10 @@ $loc = array( 'label_fgColor' => "Foreground color", 'placeholder' => "Enter the text to encode in the QR code", + 'placeholder_wifi_ssid' => "Box-A31B", + 'placeholder_wifi_password' => "correct horse battery staple", + 'help_wifi_password' => "The WPA, WPA2 or WPA3 key. Leave empty if it's an open network.", 'help_content' => "

You can encode whatever text you want.

Software decoding these QR codes could suggest to open them with dedicated software, depending on their URI scheme.

@@ -25,12 +34,16 @@ $loc = array( 'button_create' => "Generate", 'button_download' => "Save this QR code", + 'button_edit' => "Edit", 'title_showOnlyQR' => "Show this QR code only", 'alt_QR_before' => 'QR code meaning "', 'alt_QR_after' => '"', + 'wifi_raw_content_before' => "This QR code contains: ", + 'wifi_raw_content_after' => "", + 'metaText_qr' => "

What's a QR code?

A QR code is a 2 dimensional barcode in which text is written in binary. It can be decoded with a device equipped with a photo sensor and adequate software. @@ -39,4 +52,5 @@ $loc = array( 'metaText_legal' => "LibreQR " . LIBREQR_VERSION . " is free software whose source code is available under the terms of the AGPLv3+.", 'error_generation' => "An error occurred while generating the QR code. Try with different parameters.", -); + 'error_404' => "This page doesn't exist.", +); \ No newline at end of file diff --git a/locales/fr.php b/locales/fr.php index c7af543..aa76d2c 100644 --- a/locales/fr.php +++ b/locales/fr.php @@ -3,6 +3,12 @@ $loc = array( 'subtitle' => "Générer des codes QR", 'description' => "Générer des codes QR librement. Choix du contenu, de la taille, des couleurs…", + 'tab_text' => "Texte", + 'tab_wifi' => "Wifi", + 'tab_wifi_title' => "Générer des codes QR Wifi", + + 'label_wifi_ssid' => "Nom du réseau / SSID", + 'label_wifi_password' => "Mot de passe", 'label_content' => "Texte à encoder", 'label_redundancy' => "Taux de redondance", 'label_margin' => "Taille de la marge", @@ -11,7 +17,10 @@ $loc = array( 'label_fgColor' => "Couleur de premier plan", 'placeholder' => "Entrez le texte à encoder dans le code QR", + 'placeholder_wifi_ssid' => "Box-A31B", + 'placeholder_wifi_password' => "correct cheval batterie agrafe", + 'help_wifi_password' => "La clé WPA, WPA2 ou WPA3. Laisser vide si c'est un réseau ouvert.", 'help_content' => "

Vous pouvez encoder ce que vous voulez sous forme de texte.

Les logiciels qui décodent ces codes QR pourraient proposer de les ouvrir avec un logiciel dédié, en fonction de leur schéma d'URI.

@@ -25,12 +34,16 @@ $loc = array( 'button_create' => "Générer", 'button_download' => "Enregistrer ce code QR", + 'button_edit' => "Modifier", 'title_showOnlyQR' => "Afficher uniquement ce code QR", 'alt_QR_before' => "Code QR signifiant « ", 'alt_QR_after' => " »", + 'wifi_raw_content_before' => "Ce code QR contient : ", + 'wifi_raw_content_after' => "", + 'metaText_qr' => "

Qu'est-ce qu'un code QR ?

Un code QR est un code-barres en 2 dimensions dans lequel du texte est inscrit en binaire. Il peut être décodé avec un appareil muni d'un capteur photo et d'un logiciel adéquat. @@ -39,4 +52,5 @@ $loc = array( 'metaText_legal' => "LibreQR " . LIBREQR_VERSION . " est un logiciel libre dont le code source est disponible selon les termes de l'AGPLv3+.", 'error_generation' => "Une erreur a eu lieu lors de la génération du code QR. Essayez avec des paramètres différents.", -); + 'error_404' => "Cette page n'existe pas.", +); \ No newline at end of file diff --git a/locales/template.php b/locales/template.php index 7c63a2f..166c5f1 100644 --- a/locales/template.php +++ b/locales/template.php @@ -3,6 +3,12 @@ $loc = array( 'subtitle' => "subtitle", 'description' => "description", + 'tab_text' => "tab_text", + 'tab_wifi' => "tab_wifi", + 'tab_wifi_title' => "tab_wifi_title", + + 'label_wifi_ssid' => "label_wifi_ssid", + 'label_wifi_password' => "label_wifi_password", 'label_content' => "label_content", 'label_redundancy' => "label_redundancy", 'label_margin' => "label_margin", @@ -11,9 +17,10 @@ $loc = array( 'label_fgColor' => "label_fgColor", 'placeholder' => "placeholder", + 'placeholder_wifi_ssid' => "placeholder_wifi_ssid", + 'placeholder_wifi_password' => "placeholder_wifi_password", - 'value_default' => "value_default", - + 'help_wifi_password' => "help_wifi_password", 'help_content' => "help_content", 'help_redundancy' => "help_redundancy", 'help_margin' => "help_margin", @@ -21,14 +28,19 @@ $loc = array( 'button_create' => "button_create", 'button_download' => "button_download", + 'button_edit' => "button_edit", 'title_showOnlyQR' => "title_showOnlyQR", 'alt_QR_before' => "alt_QR_before", 'alt_QR_after' => "alt_QR_after", + 'wifi_raw_content_before' => "wifi_raw_content_before", + 'wifi_raw_content_after' => "wifi_raw_content_after", + 'metaText_qr' => "metaText_qr", 'metaText_legal' => "metaText_legal", 'error_generation' => "error_generation", -); + 'error_404' => "error_404", +); \ No newline at end of file diff --git a/style.less b/style.less index 163b99a..0ae5ded 100755 --- a/style.less +++ b/style.less @@ -72,8 +72,90 @@ a { } } +nav { + margin-top: 10px; + margin-bottom: 10px; + display: flex; + justify-content: center; + + & ul { + display: flex; + flex-direction: row; + list-style: none; + padding: 0; + margin: 0; + a { + text-decoration: none; + } + & li { + & div { + padding: 8px 25px 8px 25px; + } + + &.tab-selected { + border: solid; + border-width: 2px 2px 0px 2px; + border-radius: 10px 10px 0px 0px; + @media @light { + background-color: @bg-light; + } + @media @dark { + background-color: @bg-dark; + } + } + &:not(&.tab-selected) { + border-bottom: 2px solid; + @media @light { + border-top: 2px solid @bg-light; + } + @media @dark { + border-top: 2px solid @bg-dark; + } + &:first-child { + @media @light { + border-left: 2px solid @bg-light; + } + @media @dark { + border-left: 2px solid @bg-dark; + } + } + &:last-child { + @media @light { + border-right: 2px solid @bg-light; + } + @media @dark { + border-right: 2px solid @bg-dark; + } + } + } + @media @light { + border-color: @border-light; + } + @media @dark { + border-color: @border-dark; + } + } + } +} + +label[for=ssid] { + padding-left: 18px; +} + +.sr-only { /* Hide content from screen but not from screen readers */ + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + code { font-family: monospace; + user-select: all; } .helpText { @@ -163,6 +245,15 @@ summary { } } +#output form, #output input[type=submit], #output p { + display: inline; +} + +#output input[type=submit] { + font-size: 20px; + padding: 5px 10px; +} + .centered { text-align: center; } @@ -193,6 +284,10 @@ header { } } +hgroup p { + margin: 0; +} + #titles { margin-left: 2%; } @@ -312,7 +407,7 @@ small { /* Inputs */ -#redundancy, #margin, #txt, #size, input[type=color], input[type=submit], .button { +#redundancy, #margin, #ssid, #password, #txt, #size, input[type=color], input[type=submit], .button { border-width: 2px; border-style: solid; border-radius: 10px; @@ -364,6 +459,10 @@ small { } } +#password, #ssid { + height: 38px; +} + #redundancy { width: 250px; height: 44px; @@ -407,7 +506,7 @@ input[type=color] { } } -#txtParam { +.textboxParam { display: flex; flex-direction: column; } @@ -438,7 +537,7 @@ input[type=submit] { padding-right: 14px; } -#txt::placeholder { +#password::placeholder, #ssid::placeholder, #txt::placeholder { opacity: 1; font-family: system-ui, sans-serif; font-weight: normal; @@ -458,4 +557,4 @@ a[download]::before { filter: drop-shadow(-1px 1px 1px white) drop-shadow(1px -1px 1px white); -} +} \ No newline at end of file