Skip to content

Commit

Permalink
A-frame architecture
Browse files Browse the repository at this point in the history
* Inline Plugin
* CAPTCHA verification is a responsibility of CaptchaController
* Inline Plugin::renderCaptcha()
* The current language is not a dependency of CaptchaController
* Move action dispatch into CaptchaController
* Test CaptchaController
* Wrap random_bytes()
* Fetch current URL from Request
* CaptchaController returns Response
* VisualCaptcha::create(ErrorImage)() returns string
* Rename Url to Infra\Url
* Rename SystemChecker to Infra\SystemChecker
* Rename CodeStore to Infra\CodeStore
* Rename CodeGenerator to Infra\CodeGenerator
* Rename VisualCaptcha to Infra\VisualCaptcha
* Rename AudioCaptcha to Infra\AudioCaptcha
* Rename View to Infra\View
* Rename Char to Value\Char
* Rename HtmlString to Value\Html
  • Loading branch information
cmb69 committed Mar 23, 2023
1 parent ae3fbe6 commit 3244e74
Show file tree
Hide file tree
Showing 34 changed files with 706 additions and 292 deletions.
40 changes: 40 additions & 0 deletions admin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/**
* Copyright 2011-2021 Christoph M. Becker
*
* This file is part of Cryptographp_XH.
*
* Cryptographp_XH is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cryptographp_XH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cryptographp_XH. If not, see <http://www.gnu.org/licenses/>.
*/

use Cryptographp\Dic;

/**
* @var string $admin
* @var string $o
*/

XH_registerStandardPluginMenuItems(false);

if (XH_wantsPluginAdministration("cryptographp")) {
$o .= print_plugin_admin("off");
switch ($admin) {
case "":
$o .= Dic::makeInfoController()();
break;
default:
$o .= plugin_admin_common();
}
}
1 change: 1 addition & 0 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<exec executable="phpcs" passthru="true" checkreturn="true">
<arg value="--standard=PSR2"/>
<arg line="--runtime-set ignore_warnings_on_exit true"/>
<arg file="admin.php"/>
<arg file="captcha.php"/>
<arg file="index.php"/>
<arg file="classes"/>
Expand Down
8 changes: 5 additions & 3 deletions captcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@
* along with Cryptographp_XH. If not, see <http://www.gnu.org/licenses/>.
*/

use Cryptographp\Plugin;
use Cryptographp\Dic;
use Cryptographp\Infra\Request;
use Cryptographp\Infra\Responder;

function cryptographp_captcha_display(): string
{
return Plugin::renderCaptcha();
return Responder::respond(Dic::makeCaptchaController()(Request::current()));
}

function cryptographp_captcha_check(): bool
{
return Plugin::checkCAPTCHA();
return Dic::makeCaptchaController()->verifyCaptcha();
}
111 changes: 66 additions & 45 deletions classes/CaptchaController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@

namespace Cryptographp;

use GdImage;
use Cryptographp\Infra\AudioCaptcha;
use Cryptographp\Infra\CodeGenerator;
use Cryptographp\Infra\CodeStore;
use Cryptographp\Infra\Request;
use Cryptographp\Infra\View;
use Cryptographp\Infra\VisualCaptcha;
use Cryptographp\Value\Response;

class CaptchaController
{
Expand All @@ -32,9 +38,6 @@ class CaptchaController
/** @var string */
private $pluginFolder;

/** @var string */
private $currentLang;

/** @var array<string,string> */
private $lang;

Expand All @@ -56,7 +59,6 @@ class CaptchaController
/** @param array<string,string> $lang */
public function __construct(
string $pluginFolder,
string $currentLang,
array $lang,
CodeStore $codeStore,
CodeGenerator $codeGenerator,
Expand All @@ -65,7 +67,6 @@ public function __construct(
View $view
) {
$this->pluginFolder = $pluginFolder;
$this->currentLang = $currentLang;
$this->lang = $lang;
$this->codeStore = $codeStore;
$this->codeGenerator = $codeGenerator;
Expand All @@ -74,23 +75,34 @@ public function __construct(
$this->view = $view;
}

/** @return void */
public function defaultAction()
public function __invoke(Request $request): Response
{
switch ($request->action()) {
default:
return $this->defaultAction($request);
case "video":
return $this->videoAction();
case "audio":
return $this->audioAction();
}
}

private function defaultAction(Request $request): Response
{
$code = $this->codeGenerator->createCode();
$key = random_bytes(15);
$key = $this->codeGenerator->randomKey();
$this->codeStore->put($key, $code);
$this->emitJavaScript();
$url = Url::current();
$nonce = rtrim(base64_encode($key), "=");
echo $this->view->render('captcha', [
$url = $request->url();
$nonce = rtrim(str_replace(["+", "/"], ["-", "_"], base64_encode($key)), "=");
return Response::create($this->view->render('captcha', [
'imageUrl' => $url->with('cryptographp_action', 'video')->with('cryptographp_nonce', $nonce),
'audioUrl' => $url->with('cryptographp_action', 'audio')->with('cryptographp_nonce', $nonce)
->with('cryptographp_lang', $this->currentLang)->with('cryptographp_download', 'yes'),
->with('cryptographp_lang', $this->lang())->with('cryptographp_download', 'yes'),
'audioImage' => "{$this->pluginFolder}images/audio.png",
'reloadImage' => "{$this->pluginFolder}images/reload.png",
'nonce' => $nonce,
]);
]));
}

/** @return void */
Expand All @@ -107,52 +119,61 @@ private function emitJavaScript()
}
}

/** @return void */
public function videoAction()
private function videoAction(): Response
{
if (!isset($_GET['cryptographp_nonce'])) {
$this->deliverImage($this->visualCaptcha->createErrorImage($this->lang['error_video']));
return $this->deliverImage($this->visualCaptcha->createErrorImage($this->lang['error_video']));
}
$code = $this->codeStore->find(base64_decode($_GET['cryptographp_nonce']));
$code = $this->codeStore->find(base64_decode(str_replace(["-", "_"], ["+", "/"], $_GET['cryptographp_nonce'])));
$image = $this->visualCaptcha->createImage($code);
$this->deliverImage($image);
return $this->deliverImage($image);
}

/**
* @param resource|GdImage $image
* @return never
*/
private function deliverImage($image)
private function deliverImage(string $image): Response
{
while (ob_get_level()) {
ob_end_clean();
}
header('Content-type: image/png');
imagepng($image);
exit;
return Response::create($image)->withContentType("image/png");
}

/** @return never */
public function audioAction()
private function audioAction(): Response
{
if (!isset($_GET['cryptographp_nonce'])) {
header('HTTP/1.0 403 Forbidden');
exit;
return Response::forbid();
}
$code = $this->codeStore->find(base64_decode($_GET['cryptographp_nonce']));
$wav = $this->audioCaptcha->createWav($code);
$code = $this->codeStore->find(base64_decode(str_replace(["-", "_"], ["+", "/"], $_GET['cryptographp_nonce'])));
$wav = $this->audioCaptcha->createWav($this->lang(), $code);
if (!isset($wav)) {
exit($this->lang['error_audio']);
}
while (ob_get_level()) {
ob_end_clean();
return Response::forbid($this->lang['error_audio']);
}
header('Content-Type: audio/x-wav');
$response = Response::create($wav)
->withContentType("audio/x-wav")
->withLength(strlen($wav));
if (isset($_GET['cryptographp_download'])) {
header('Content-Disposition: attachment; filename="captcha.wav"');
$response = $response->withAttachment("captcha.wav");
}
return $response;
}

private function lang(): string
{
$lang = basename($_GET['cryptographp_lang'] ?? "en");
if (!is_dir($this->pluginFolder . "languages/$lang")) {
$lang = 'en';
}
return $lang;
}

public function verifyCaptcha(): bool
{
$code = $_POST['cryptographp-captcha'] ?? "";
if (!isset($_POST['cryptographp_nonce'])) {
return false;
}
$nonce = base64_decode(str_replace(["-", "_"], ["+", "/"], $_POST['cryptographp_nonce']));
$storedCode = $this->codeStore->find($nonce);
if ($code !== $storedCode) {
return false;
}
header('Content-Length: ' . strlen($wav));
echo $wav;
exit;
$this->codeStore->invalidate($nonce);
return true;
}
}
16 changes: 11 additions & 5 deletions classes/Dic.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,23 @@

namespace Cryptographp;

use Cryptographp\Infra\AudioCaptcha;
use Cryptographp\Infra\CodeGenerator;
use Cryptographp\Infra\CodeStore;
use Cryptographp\Infra\SystemChecker;
use Cryptographp\Infra\View;
use Cryptographp\Infra\VisualCaptcha;

class Dic
{
public static function makeCaptchaController(string $lang): CaptchaController
public static function makeCaptchaController(): CaptchaController
{
global $pth, $sl, $plugin_cf, $plugin_tx;
global $pth, $plugin_cf, $plugin_tx;
static $instance;

if (!isset($instance)) {
$instance = new CaptchaController(
"{$pth['folder']['plugins']}cryptographp/",
$sl,
$plugin_tx['cryptographp'],
self::makeCodeStore(),
new CodeGenerator($plugin_cf['cryptographp']),
Expand All @@ -40,7 +46,7 @@ public static function makeCaptchaController(string $lang): CaptchaController
"{$pth['folder']['plugins']}cryptographp/fonts",
$plugin_cf['cryptographp']
),
new AudioCaptcha("{$pth['folder']['plugins']}cryptographp/languages/$lang/"),
new AudioCaptcha("{$pth['folder']['plugins']}cryptographp/languages/"),
new View("{$pth['folder']['plugins']}cryptographp/views", $plugin_tx["cryptographp"])
);
}
Expand All @@ -58,7 +64,7 @@ public static function makeInfoController(): InfoController
);
}

public static function makeCodeStore(): CodeStore
private static function makeCodeStore(): CodeStore
{
global $pth, $plugin_cf;

Expand Down
5 changes: 4 additions & 1 deletion classes/InfoController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

namespace Cryptographp;

use Cryptographp\Infra\SystemChecker;
use Cryptographp\Infra\View;

class InfoController
{
/** @var string */
Expand All @@ -45,7 +48,7 @@ public function __invoke(): string
{
$view = new View("{$this->pluginFolder}views", $this->lang);
return $view->render('info', [
'version' => Plugin::VERSION,
'version' => CRYPTOGRAPHP_VERSION,
'checks' => $this->getChecks(),
]);
}
Expand Down

0 comments on commit 3244e74

Please sign in to comment.