Initial commit - Typo3 11.5.41

This commit is contained in:
Matteo Gallo
2026-07-03 17:53:31 +02:00
commit 5ca4743197
6811 changed files with 568848 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/**
* Utility dealing with audio content
*/
class AudioContentUtility
{
/**
* Joins multiple audio files
*
* @param array $files: the array of audio files
* @param string $format: the audio format
* @return string the contents of joined audio file
*/
public static function joinAudioFiles($files, $format = 'wav') {
switch ($format) {
case 'mp3':
return self::joinMp3Files($files);
break;
case 'wav':
default:
return self::joinWavFiles($files);
break;
}
}
/**
* Joins multiple wav files
*
* All wave files need to have the same format and need to be uncompressed.
* The headers of the last file will be used (with recalculated datasize
* of course)
*
* @link http://ccrma.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/
* @link http://www.thescripts.com/forum/thread3770.html
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr <gohr@cosmocode.de>
*
* @param array $wavs: the array of wav files
* @return string the contents of joined wav file
*/
protected static function joinWavFiles($wavs)
{
$fields = join('/', array(
'H8Format',
'H8Subchunk1ID',
'VSubchunk1Size',
'vAudioFormat',
'vNumChannels',
'VSampleRate',
'VByteRate',
'vBlockAlign',
'vBitsPerSample'
));
$data = '';
foreach ($wavs as $wav){
$fp = fopen($wav, 'rb');
// Read ChunkID
$headerPart1 = fread($fp, 4);
// Read ChunkSize
$headerPart2 = fread($fp, 4);
// Read following fields
$headerPart3 = fread($fp, 28);
$info = unpack($fields, $headerPart3);
// Read optional extra stuff
// We will not use this since AudioFormat of all our sound files is PCM
if ($info['Subchunk1Size'] > 16) {
$headerPart3 .= fread($fp, ($info['Subchunk1Size']-16));
}
// Read SubChunk2ID
$headerPart3 .= fread($fp, 4);
// Read Subchunk2Size
$size = unpack('VSubChunk2Size', fread($fp, 4));
$size = $size['SubChunk2Size'];
// Read data
$data .= fread($fp, $size);
fclose($fp);
}
return $headerPart1 . pack('V', 36 + strlen($data)) . $headerPart3 . pack('V', strlen($data)) . $data;
}
/**
* Joins multiple mp3 files
*
* All mp3 files need to have the same format and need to be uncompressed.
* The headers of the last file will be used (with recalculated datasize
* of course)
*
* @link http://www.theblog.ca/merge-mp3s-php
* @param array $files: the array of mp3 files
* @return string the contents of joined mp3 file
*/
protected static function joinMp3Files($files)
{
$data = '';
foreach ($files as $file) {
$mp3 = new Mp3ContentUtility($file);
$data .= $mp3->striptags();
}
return $data;
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
class EncryptionUtility
{
/**
* Salt
*/
const SALT = 'cH!swe!retReGu7W6bEDRup7usuDUh9THeD2CHeGE*ewr4n39=E@rAsp7c-Ph@pH';
/**
* Encrypts a string
*
* @param array $string: the string to be encrypted
* @return array an array with the string as the first element and the initialization vector as the second element
*/
public static function encrypt($string)
{
if (in_array('openssl', get_loaded_extensions())) {
$encryptionAlgorithm = strtolower($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['encryptionAlgorithm'] ?? 'aes-256-cbc');
$availableAlgorithms = openssl_get_cipher_methods(true);
if (in_array($encryptionAlgorithm, $availableAlgorithms)) {
$key = md5($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] ?? 0, true);
$iv_size = openssl_cipher_iv_length($encryptionAlgorithm);
$salt = (isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) && trim($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt'])) ? trim($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) : self::SALT;
$hash = hash('sha256', $salt . $key . $salt);
$iv = substr($hash, strlen($hash) - $iv_size);
$key = substr($hash, 0, 32);
$string = openssl_encrypt($string, $encryptionAlgorithm, $key, OPENSSL_RAW_DATA, $iv);
$cypher = [base64_encode($string), base64_encode($iv)];
} else {
$cypher = [base64_encode($string)];
}
} else {
$cypher = [base64_encode($string)];
}
return $cypher;
}
/**
* Decrypts a string
*
* @param array $cypher: an array as returned by encrypt()
* @return string the decrypted string
*/
public static function decrypt($cypher)
{
if (in_array('openssl', get_loaded_extensions())) {
$encryptionAlgorithm = strtolower($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['encryptionAlgorithm'] ?? 'aes-256-cbc');
$availableAlgorithms = openssl_get_cipher_methods(true);
if (in_array($encryptionAlgorithm, $availableAlgorithms)) {
$key = md5($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] ?? 0, true);
$salt = isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) ? trim($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sr_freecap']['salt']) : self::SALT;
$hash = hash('sha256', $salt . $key . $salt);
$key = substr($hash, 0, 32);
$string = trim(openssl_decrypt(base64_decode($cypher[0]), $encryptionAlgorithm, $key, OPENSSL_RAW_DATA, base64_decode($cypher[1])));
} else {
$string = base64_decode($cypher[0]);
}
} else {
$string = base64_decode($cypher[0]);
}
return $string;
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Utility for making GD fonts
*/
class FontMakingUtility
{
/**
* Returns an image displaying a list of characters with specified font file and character size
*
* @param string $characters: text to display on the image
* @param string $font: specified True Type font file name
* @param integer $width: width of each character
* @param string $height: height of the image
* @return array image file info array
*/
public static function makeFontImage($characters, $font, $width = 34, $height = 50)
{
$size = intval($height * .8);
$vertOffset = intval($height * .7);
$color = '#000000';
$bgColor = 'white';
$align = 'left';
$charactersArray = explode(',', $characters);
$charactersCount = count($charactersArray);
$gifObjArray = array();
$gifObjArray['backColor'] = $bgColor;
$gifObjArray['transparentBackground'] = 0;
$gifObjArray['reduceColors'] = '';
$gifObjArray['maxWidth'] = ($charactersCount * $width) + 1;
$gifObjArray['XY'] = ($charactersCount * $width) . ',' . $height;
for ($ic = 1; $ic < $charactersCount+1; $ic++) {
$gifObjArray[$ic . '0'] = 'TEXT';
$gifObjArray[$ic . '0.']['text'] = $charactersArray[$ic-1];
$bbox = imagettfbbox($size, 0, GeneralUtility::getFileAbsFileName($font), $charactersArray[$ic-1]);
$hOffset = intval(($width - ($bbox[4] - $bbox[6]))/2);
$vOffset = intval(($width - ($bbox[7] - $bbox[1]))/2);
$gifObjArray[$ic . '0.']['niceText'] = 0;
$gifObjArray[$ic . '0.']['antiAlias'] = 1;
$gifObjArray[$ic . '0.']['align'] = $align;
$gifObjArray[$ic . '0.']['fontSize'] = $size;
$gifObjArray[$ic . '0.']['fontFile'] = '../' . $font;
$gifObjArray[$ic . '0.']['fontColor'] = $color;
$gifObjArray[$ic . '0.']['maxWidth'] = $width;
$gifObjArray[$ic . '0.']['offset'] = (($ic-1) * $width + $hOffset) . ',' . $vertOffset;
}
$gifCreator = GeneralUtility::makeInstance(GifBuilderUtility::class);
if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'] ?? false) {
$gifCreator->start($gifObjArray, []);
return $gifCreator->gifBuild();
} else {
return false;
}
}
/************************************************************\
*
* GD Fontmaker Copyright 2005 Howard Yeend
* www.puremango.co.uk
*
* This file is part of GD Fontmaker.
*
* GD Fontmaker 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 2 of the License, or
* (at your option) any later version.
*
* GD Fontmaker 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 GD Fontmaker; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
\************************************************************/
public static function makeFont($image, $numchars, $startchar, $pixelwidth, $pixelheight, $endianness = 0)
{
$startchar = ord($startchar);
// encode this at start of font
if ($endianness) {
// big-endian
$fontdata = chr(0).chr(0).chr(0).chr($numchars).chr(0).chr(0).chr(0).chr($startchar).chr(0).chr(0).chr(0).chr($pixelwidth).chr(0).chr(0).chr(0).chr($pixelheight);
} else {
// little-endian
$fontdata = chr($numchars).chr(0).chr(0).chr(0).chr($startchar).chr(0).chr(0).chr(0).chr($pixelwidth).chr(0).chr(0).chr(0).chr($pixelheight).chr(0).chr(0).chr(0);
}
// loop through each pixel of each character of the PNG
// (we know the dimensions of the characters because the user told us what they were)
$y = 0;
$x = 0;
$start_x = 0;
for ($c = 0; $c < $numchars*$pixelwidth; $c += $pixelwidth) {
for ($y = 0; $y < $pixelheight; $y++) {
for ($x = $c; $x < $c+$pixelwidth; $x++) {
// get colour of this pixel
$rgb = ImageColorAt($image, $x, $y);
if ($rgb == 0) {
// it's black; font data
$fontdata .= chr(255);
} else {
// it's not black; background
$fontdata .= chr(0);
}
$i++;
}
}
}
return $fontdata;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2018 Stanislas Rolland <typo3(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
/**
* Utility extending Gif builder
*/
class GifBuilderUtility extends GifBuilder
{
/**
* Returns the reference to a "resource" in TypoScript.
*
* @param string The resource value.
* @return string Returns the relative filepath
*/
public function checkFile($file)
{
$file = GeneralUtility::getFileAbsFileName(Environment::getPublicPath() . '/' . $file);
$file = PathUtility::stripPathSitePrefix($file);
return $file;
}
/**
* Writes the input GDlib image pointer to file
*
* @param resource The GDlib image resource pointer
* @param string The filename to write to
* @param int $quality The image quality (for JPEGs)
* @return mixed The output of either imageGif, imagePng or imageJpeg based on the filename to write
* @see maskImageOntoImage(), scale(), output()
*/
public function ImageWrite($destImg, $theImage, $quality = 0)
{
return parent::ImageWrite($destImg, Environment::getPublicPath() . '/' . $theImage, $quality);
}
}

View File

@@ -0,0 +1,446 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2022 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/************************************************************\
*
* freeCap v1.4.1 Copyright 2005 Howard Yeend
* www.puremango.co.uk
*
* This file is part of freeCap.
*
* freeCap 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 2 of the License, or
* (at your option) any later version.
*
* freeCap 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 freeCap; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
\************************************************************/
use SJBR\SrFreecap\Utility\RandomContentUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Utility dealing with image content
*/
class ImageContentUtility {
const BACKGROUND_TYPE_TRANSPARENT = 'Transparent';
const BACKGROUND_TYPE_WHITE_WITH_GRID = 'White with grid';
const BACKGROUND_TYPE_WHITE_WITH_SQUIGGLES = 'White with squiggles';
const BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS = 'Morphed image blocks';
const MERGE_BACKGROUND_OVER_CAPTCHA = 0;
const MERGE_CAPTCHA_OVER_BACKGROUND = 1;
const TEXT_COLOR_UNIFORM = 0;
const TEXT_COLOR_ONE_PER_CHARACTER = 1;
const SITE_TAG_POSITION_TOP = 0;
const SITE_TAG_POSITION_BOTTOM = 1;
const SITE_TAG_POSITION_BOTH = 2;
/**
* Write word on image
*
* @param int $width: width of the image in pixels
* @param int $height: width of the image in pixels
* @param string $word: the captcha word
* @param int $textColorType (see constants)
* @param array $textPosition: 'horizontal' => horizontal starting position of text, 'vertical' => vertical starting position of text
* @param int $colorMaximum: 'darkness' => maximum color darkness along any dimension, 'lightness' => maximum color lightness along any dimension
* @param string $backgroundType (see constants)
* @param array $fontLocations: array of font files locations
* @param array $fontWidths: array of font widths
* @param int $morphfactor: text morphing factor
* @return string GD image identifier of noisy background
*/
public static function writeWordOnImage($width, $height, $word, $textColorType, $textPosition, $colorMaximum, $backgroundType, $fontLocations, $fontWidths, $morphFactor = 1)
{
$image = ImageCreate($width, $height);
$image2 = ImageCreate($width, $height);
// Set background colour (can change to any colour not in possible $text_col range)
// it doesn't matter as it'll be transparent or coloured over.
// if you're using background type 'Morphed image blocks', you might want to try to ensure that the color chosen
// below doesn't appear too much in any of your background images.
$background = ImageColorAllocate($image, 254, 254, 254);
$background2 = ImageColorAllocate($image2, 254, 254, 254);
// Set transparencies
ImageColorTransparent($image, $background);
// $image2 transparent to allow characters to overlap slightly while morphing
ImageColorTransparent($image2, $background2);
// Fill backgrounds
ImageFill($image, 0, 0, $background);
ImageFill($image2, 0, 0, $background2);
// Write word in random starting X position
$word_start_x = RandomContentUtility::getRandomNumberInRange(5, $textPosition['horizontal']);
// Y positions jiggled about later
$word_start_y = $textPosition['vertical'];
// Get uniform color
if ($textColorType == self::TEXT_COLOR_UNIFORM) {
$textColor = RandomContentUtility::getRandomColor($colorMaximum['darkness'], $colorMaximum['lightness'], $backgroundType == self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS);
$textColor2 = ImageColorAllocate($image2, $textColor[0], $textColor[1], $textColor[2]);
}
// Write each character in different font
$textFontWidths = array();
$x_pos = $word_start_x;
for ($i = 0; $i < strlen($word); $i++) {
// Get changing color
if ($textColorType == self::TEXT_COLOR_ONE_PER_CHARACTER) {
$textColor = RandomContentUtility::getRandomColor($colorMaximum['darkness'], $colorMaximum['lightness'], $backgroundType == self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS);
$textColor2 = ImageColorAllocate($image2, $textColor[0], $textColor[1], $textColor[2]);
}
$fontIndex = RandomContentUtility::getRandomNumberInRange(0, sizeof($fontLocations)-1);
$font = ImageLoadFont($fontLocations[$fontIndex]);
ImageString($image2, $font, $x_pos, $word_start_y, $word[$i], $textColor2);
$textFontWidths[$i] = $fontWidths[$fontIndex];
$x_pos += $textFontWidths[$i];
}
// Morph Image
// Firstly move each character up or down a bit:
$x_pos = $word_start_x;
$y_pos = 0;
for ($i = 0; $i < strlen($word); $i++) {
// Move on Y axis
// Deviate at least 4 pixels between each letter
$prev_y = $y_pos;
do {
$y_pos = RandomContentUtility::getRandomNumberInRange(-5, 5);
} while ($y_pos < $prev_y + 2 && $y_pos > $prev_y - 2);
ImageCopy($image, $image2, $x_pos, $y_pos, $x_pos, 0, $textFontWidths[$i], $height);
$x_pos += $textFontWidths[$i];
}
ImageFilledRectangle($image2, 0, 0, $width, $height, $background2);
// Randomly morph each character individually on x-axis
// This is where the main distortion happens
$y_chunk = 1;
$morph_x = 0;
$orig_x = $word_start_x - $textFontWidths[0];
for ($j = 0; $j < strlen($word); $j++) {
$orig_x += $textFontWidths[$j];
$y_pos = 0;
for ($i = 0; $i <= $height; $i += $y_chunk) {
// morph x += so that instead of deviating from orig x each time, we deviate from where we last deviated to
// get it? instead of a zig zag, we get more of a sine wave.
// I wish we could deviate more but it looks crap if we do.
$morph_x += RandomContentUtility::getRandomNumberInRange(-$morphFactor, $morphFactor);
// had to change this to ImageCopyMerge when starting using ImageCreateTrueColor
// according to the manual; "when (pct is) 100 this function behaves identically to imagecopy()"
// but this is NOT true when dealing with transparencies...
ImageCopyMerge($image2, $image, $orig_x + $morph_x, $i + $y_pos, $orig_x, $i, $textFontWidths[$j], $y_chunk, 100);
}
}
ImageFilledRectangle($image, 0, 0, $width, $height, $background);
// Now do the same on the y-axis
// (much easier because we can just do it across the whole image, don't have to do it char-by-char)
$y_pos = 0;
$x_chunk = 1;
for ($i = 0; $i <= $width ; $i += $x_chunk) {
// Can result in image going too far off on Y-axis;
// not much I can do about that, apart from make image bigger
// again, I wish I could do 1.5 pixels
$y_pos += RandomContentUtility::getRandomNumberInRange(-1, 1);
ImageCopy($image, $image2, $i, $y_pos, $i, 0, $x_chunk, $height);
}
// Cleanup
ImageDestroy($image2);
return $image;
}
/**
* Generate noisy background
*
* @param int $width: width of the image in pixels
* @param int $height: width of the image in pixels
* @param string $word: the captcha word
* @param string $backgroundType (see constants)
* @param array $backgroundImages: array of background image file names
* @param boolean $morphBackground: if TRUE, the background will be morphed
* @param boolean $blurBackground: if TRUE, the background will be blurred
* @return string GD image identifier of noisy background
*/
public static function generateNoisyBackground($width, $height, $word, $backgroundType, $backgroundImages = array(), $morphBackground = true, $blurBackground = true)
{
$image = ImageCreateTrueColor($width, $height);
if ($backgroundType != self::BACKGROUND_TYPE_TRANSPARENT) {
// Generate noisy background, to be merged with CAPTCHA later
// any suggestions on how best to do this much appreciated
// sample code would be even better!
// I'm not an OCR expert (hell, I'm not even an image expert; puremango.co.uk was designed in MsPaint)
// so the noise models are based around my -guesswork- as to what would make it hard for an OCR prog
// ideally, the character obfuscation would be strong enough not to need additional background noise
// in any case, I hope at least one of the options given here provide some extra security!
$tempBackground = ImageCreateTrueColor($width*1.5, $height*1.5);
$background = ImageColorAllocate($image, 255, 255, 255);
ImageFill($image, 0, 0, $background);
$tempBackgroundColor = ImageColorAllocate($tempBackground, 255, 255, 255);
ImageFill($tempBackground, 0, 0, $tempBackgroundColor);
// We draw all noise onto tempBackground
// then if we're morphing, merge from tempBackground to image
// or if not, just copy a $width x $height portion of $tempBackground to $image
// tempBackground is much larger so that when morphing, the edges retain the noise.
switch ($backgroundType) {
case self::BACKGROUND_TYPE_WHITE_WITH_GRID:
// Draw grid on x
for ($i = RandomContentUtility::getRandomNumberInRange(6, 20); $i < $width*2; $i += RandomContentUtility::getRandomNumberInRange(10, 25)) {
ImageSetThickness($tempBackground, RandomContentUtility::getRandomNumberInRange(2, 6));
$textColor = RandomContentUtility::getRandomColor(100, 150);
$textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]);
ImageLine($tempBackground, $i, 0, $i, $height*2, $textColor2);
}
// Draw grid on y
for ($i = RandomContentUtility::getRandomNumberInRange(6, 20); $i < $height*2 ; $i += RandomContentUtility::getRandomNumberInRange(10, 25)) {
ImageSetThickness($tempBackground, RandomContentUtility::getRandomNumberInRange(2, 6));
$textColor = RandomContentUtility::getRandomColor(100, 150);
$textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]);
ImageLine($tempBackground, 0, $i, $width*2, $i , $textColor2);
}
break;
case self::BACKGROUND_TYPE_WHITE_WITH_SQUIGGLES:
ImageSetThickness($tempBackground, 4);
for ($i = 0; $i < strlen($word)+1; $i++) {
$textColor = RandomContentUtility::getRandomColor(100, 150);
$textColor2 = ImageColorAllocate($tempBackground, $textColor[0], $textColor[1], $textColor[2]);
$points = Array();
// Draw random squiggle for each character
// the longer the loop, the more complex the squiggle
// keep random so OCR can't say "if found shape has 10 points, ignore it"
// each squiggle will, however, be a closed shape, so OCR could try to find
// line terminations and start from there. (I don't think they're that advanced yet..)
for ($j = 1; $j < RandomContentUtility::getRandomNumberInRange(5, 10); $j++) {
$points[] = RandomContentUtility::getRandomNumberInRange(1*(20*($i+1)), 1*(50*($i+1)));
$points[] = RandomContentUtility::getRandomNumberInRange(30, $height+30);
}
ImagePolygon($tempBackground, $points, intval(sizeof($points)/2), $textColor2);
}
break;
case self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS:
// Take random chunks of $backgroundImages and paste them onto the background
for ($i = 0; $i < sizeof($backgroundImages); $i++) {
// Read each image and its size
$tempImages[$i] = ImageCreateFromJPEG(GeneralUtility::getFileAbsFileName($backgroundImages[$i]));
$tempWidths[$i] = imagesx($tempImages[$i]);
$tempHeights[$i] = imagesy($tempImages[$i]);
}
$blocksize = RandomContentUtility::getRandomNumberInRange(20, 60);
for ($i = 0; $i < $width*2; $i += $blocksize) {
// Could randomise blocksize here... hardly matters
for ($j = 0; $j < $height*2; $j += $blocksize) {
$imageIndex = RandomContentUtility::getRandomNumberInRange(0, sizeof($tempImages)-1);
$cut_x = RandomContentUtility::getRandomNumberInRange(0, $tempWidths[$imageIndex]-$blocksize);
$cut_y = RandomContentUtility::getRandomNumberInRange(0, $tempHeights[$imageIndex]-$blocksize);
ImageCopy($tempBackground, $tempImages[$imageIndex], $i, $j, $cut_x, $cut_y, $blocksize, $blocksize);
}
}
// Cleanup
for ($i = 0; $i < sizeof($tempImages); $i++) {
ImageDestroy($tempImages[$i]);
}
break;
}
if ($morphBackground) {
// Morph background
// We do this separately to the main text morph because:
// a) the main text morph is done char-by-char, this is done across whole image
// b) if an attacker could un-morph the background, it would un-morph the CAPTCHA
// hence background is morphed differently to text
// why do we morph it at all? it might make it harder for an attacker to remove the background
// morph_chunk 1 looks better but takes longer
// this is a different and less perfect morph than the one we do on the CAPTCHA
// occasonally you get some dark background showing through around the edges
// it doesn't need to be perfect as it's only the background.
$morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5);
$morph_y = 0;
for ($x = 0; $x < $width; $x += $morph_chunk) {
$morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5);
$morph_y += RandomContentUtility::getRandomNumberInRange(-1, 1);
ImageCopy($image, $tempBackground, $x, 0, $x+30, 30+$morph_y, $morph_chunk, $height*2);
}
ImageCopy($tempBackground, $image, 0, 0, 0, 0, $width, $height);
$morph_x = 0;
for ($y = 0; $y <= $height; $y += $morph_chunk) {
$morph_chunk = RandomContentUtility::getRandomNumberInRange(1, 5);
$morph_x += RandomContentUtility::getRandomNumberInRange(-1, 1);
ImageCopy($image, $tempBackground, $morph_x, $y, 0, $y, $width, $morph_chunk);
}
} else {
// Just copy tempBackground onto $image
ImageCopy($image, $tempBackground, 0, 0, 30, 30, $width, $height);
}
// Cleanup
ImageDestroy($tempBackground);
if ($blurBackground) {
$image = self::blurImage($image);
}
}
return $image;
}
/**
* Merge captcha image with background
*
* @param int $width: width of the image in pixels
* @param int $height: width of the image in pixels
* @param string $captchaImage: a GD image identifier
* @param string $backgroundImage: a GD image identifier
* @param string $backgroundType (see constants)
* @param int $mergeType: 0 - Background over captcha, 1 - Captcha over background (see constants)
* @param int $backgroundFadePercentage: fading factor for background
* @return string GD image identifier of merged image
*/
public static function mergeCaptchaWithBackground($width, $height, $captchaImage, $backgroundImage, $backgroundType, $mergeType)
{
if ($backgroundType != self::BACKGROUND_TYPE_TRANSPARENT) {
// How faded should the background be? (100=totally gone, 0=bright as the day)
// to test how much protection the bg noise gives, take a screenshot of the freeCap image
// and take it into a photo editor. play with contrast and brightness.
// If you can remove most of the background, then it's not a good enough percentage
switch ($backgroundType) {
case self::BACKGROUND_TYPE_WHITE_WITH_GRID:
case self::BACKGROUND_TYPE_WHITE_WITH_SQUIGGLES:
$backgroundFadePercentage = 65;
break;
case self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS:
$backgroundFadePercentage = 50;
break;
}
// Slightly randomize the background fade
$backgroundFadePercentage += RandomContentUtility::getRandomNumberInRange(-2, 2);
// Fade background
if ($backgroundType != self::BACKGROUND_TYPE_MORPHED_IMAGE_BLOCKS) {
$tempImage = ImageCreateTrueColor($width, $height);
$white = ImageColorAllocate($tempImage, 255, 255, 255);
ImageFill($tempImage, 0, 0, $white);
ImageCopyMerge($backgroundImage, $tempImage, 0, 0, 0, 0, $width, $height, $backgroundFadePercentage);
ImageDestroy($tempImage);
$colorFadePercentage = 50;
} else {
$colorFadePercentage = $backgroundFadePercentage;
}
// Merge background image with CAPTCHA image to create smooth background
if ($mergeType == self::MERGE_CAPTCHA_OVER_BACKGROUND) {
// Might want to not blur if using this method, otherwise leaves white-ish border around each letter
ImageCopyMerge($backgroundImage, $captchaImage, 0, 0, 0, 0, $width, $height, 100);
ImageCopy($captchaImage, $backgroundImage, 0, 0, 0, 0, $width, $height);
} else {
// Background over captcha
ImageCopyMerge($captchaImage, $backgroundImage, 0, 0, 0, 0, $width, $height, $colorFadePercentage);
}
}
return $captchaImage;
}
/**
* Blurs an image
*
* @param string $image: a GD image identifier
* @return string GD image identifier of blurred image
*/
public static function blurImage($image)
{
// w00t. my very own blur function
// in GD2, there's a gaussian blur function. bunch of bloody show-offs... :-)
$width = imagesx($image);
$height = imagesy($image);
$temp_im = ImageCreateTrueColor($width, $height);
$bg = ImageColorAllocate($temp_im, 150, 150, 150);
// preserves transparency if in orig image
ImageColorTransparent($temp_im, $bg);
// fill bg
ImageFill($temp_im, 0, 0, $bg);
// anything higher than 3 makes it totally unreadable
// might be useful in a 'real' blur function, though (ie blurring pictures not text)
$distance = 1;
// use $distance=30 to have multiple copies of the word. not sure if this is useful.
// blur by merging with itself at different x/y offsets:
ImageCopyMerge($temp_im, $image, 0, 0, 0, $distance, $width, $height-$distance, 70);
ImageCopyMerge($image, $temp_im, 0, 0, $distance, 0, $width-$distance, $height, 70);
ImageCopyMerge($temp_im, $image, 0, $distance, 0, 0, $width, $height, 70);
ImageCopyMerge($image, $temp_im, $distance, 0, 0, 0, $width, $height, 70);
// remove temp image
ImageDestroy($temp_im);
return $image;
}
/**
* Outputs the image with appropriate headers
*
* @param string $image: a GD image identifier
* @param string $imageType: type of image (jpg, gif or png)
* @return void
*/
public static function sendImage($image, $imageType)
{
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Pragma: no-cache');
header('Cache-Control: no-cache, no-store, must-revalidate');
// Setting privacy policy header for IE in popup window
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
switch ($imageType) {
case 'jpg':
header('Content-Type: image/jpeg');
ImageJPEG($image);
break;
case 'gif':
header('Content-Type: image/gif');
ImageGIF($image);
break;
case 'png':
default:
header('Content-Type: image/png');
ImagePNG($image);
break;
}
}
}

View File

@@ -0,0 +1,159 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2009 Sebastian Kurfürst <sebastian@typo3.org>
* (c) 2013-2021 Stanislas Rolland <typo3AAAA(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Localization\Locales;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Localization helper which should be used to fetch appropriate words list or voice rendering language
*
*/
class LocalizationUtility
{
/**
* Key of the extension to which this class belongs
*
* @var string
*/
protected static $extensionKey = 'sr_freecap';
/**
* Gets the location of the words list based on configured language
*
* @param string $defaultWordsList: location of the default words list
* @return string the location of the words list to be used
*/
public static function getWordsListLocation($defaultWordsList = '')
{
$languageKeys = static::getLanguageKeys();
$initialWordsList = $defaultWordsList;
if (!trim($initialWordsList)) {
$initialWordsList = 'EXT:' . self::$extensionKey . '/Resources/Private/Captcha/Words/default_freecap_words';
}
$path = dirname(GeneralUtility::getFileAbsFileName($initialWordsList)) . '/';
$wordsListLocation = $path . $languageKeys['languageKey'] . '_freecap_words';
if (!is_file($wordsListLocation)) {
foreach ($languageKeys['alternativeLanguageKeys'] as $language) {
$wordsListLocation = $path . $language . '_freecap_words';
if (is_file($wordsListLocation)) {
break;
}
}
}
if (!is_file($wordsListLocation)) {
$wordsListLocation = $path . 'default_freecap_words';
if (!is_file($wordsListLocation)) {
$wordsListLocation = '';
}
}
return $wordsListLocation;
}
/**
* Gets the directory of wav files based on configured language
*
* @return string name of the directory containing the wav files to be used
*/
public static function getVoicesDirectory()
{
$languageKeys = static::getLanguageKeys();
$path = ExtensionManagementUtility::extPath(self::$extensionKey) . 'Resources/Private/Captcha/Voices/';
$voicesDirectory = $path . $languageKeys['languageKey'] . '/';
if (!is_dir($voicesDirectory)) {
foreach ($languageKeys['alternativeLanguageKeys'] as $language) {
$voicesDirectory = $path . $language . '/';
if (is_dir($voicesDirectory)) {
break;
}
}
}
if (!is_dir($voicesDirectory)) {
$voicesDirectory = $path . 'default/';
}
return $voicesDirectory;
}
/**
* Sets the currently active language/language_alt keys.
* Default values are "default" for language key and an empty array for language_alt key.
*
* @return array
*/
protected static function getLanguageKeys(): array
{
$languageKeys = [
'languageKey' => 'default',
'alternativeLanguageKeys' => [],
];
if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
) {
$tsfe = static::getTypoScriptFrontendController();
$pageId = $tsfe->id;
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
$currentSite = $siteFinder->getSiteByPageId($pageId);
$languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
// Current language
$currentSiteLanguageId = $languageAspect->getId();
$currentSiteLanguage = $currentSite->getLanguageById((int)$currentSiteLanguageId);
$languageKeys['languageKey'] = $currentSiteLanguage->getTypo3Language();
// Alternative languages
$alternativeLanguageIds = $languageAspect->getFallbackChain();
foreach ($alternativeLanguageIds as $alternativeLanguageId) {
$alternativeLanguage = $currentSite->getLanguageById((int)$alternativeLanguageId);
$languageKeys['alternativeLanguageKeys'][] = $alternativeLanguage->getTypo3Language();
}
if (empty($languageKeys['alternativeLanguageKeys'])) {
$locales = GeneralUtility::makeInstance(Locales::class);
if (in_array($languageKeys['languageKey'], $locales->getLocales())) {
foreach ($locales->getLocaleDependencies($languageKeys['languageKey']) as $language) {
$languageKeys['alternativeLanguageKeys'][] = $language;
}
}
}
} else {
$languageKeys['languageKey'] = $GLOBALS['BE_USER']->uc['lang'];
}
return $languageKeys;
}
/**
* @return TypoScriptFrontendController
*/
protected static function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'];
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/**
* Utility dealing with mp3 audio content
* See http://www.theblog.ca/merge-mp3s-php
*/
class Mp3ContentUtility
{
var $mp3Content;
// Create a new mp3
public function __construct($file = '')
{
if ($file != '') {
$this->mp3Content = file_get_contents($file);
}
}
// Get the mp3 content
public function getContent()
{
return $this->mp3Content;
}
// Calculate where's the beginning of the sound file
protected function getStart()
{
$strlen = strlen($this->mp3Content);
for ($i=0; $i < $strlen; $i++) {
$v = substr($this->mp3Content, $i, 1);
$value = ord($v);
if ($value == 255) {
return $i;
}
}
}
// Calculate where's the end of the sound file
protected function getIdvEnd()
{
$strlen = strlen($this->mp3Content);
$str = substr($this->mp3Content, ($strlen - 128));
$str1 = substr($str, 0, 3);
if (strtolower($str1) == strtolower('TAG')) {
return $str;
} else {
return false;
}
}
// Remove the ID3 tags
public function striptags()
{
// Remove start stuff...
$newStr = '';
$s = $start = $this->getStart();
if ($s === false) {
return false;
} else {
$this->mp3Content = substr($this->mp3Content, $start);
}
//Remove end tag stuff
$end = $this->getIdvEnd();
if ($end !== false) {
$this->mp3Content = substr($this->mp3Content, 0, (strlen($this->mp3Content)-129));
}
return $this->mp3Content;
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace SJBR\SrFreecap\Utility;
/*
* Copyright notice
*
* (c) 2012-2020 Stanislas Rolland <typo32020(arobas)sjbr.ca>
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
*/
/**
* Random content utility
*/
class RandomContentUtility
{
/**
* Returns a random number within the given range
*
* @param int $min: the lower boundary of the range
* @param int $max: the upper boundary of the range
* @return int the generated number
*/
public static function getRandomNumberInRange($min, $max)
{
if ($min > $max) {
$newMin = $max;
$newMax = $min;
} else {
$newMin = $min;
$newMax = $max;
}
return mt_rand($newMin, $newMax);
}
/**
* Returns a random color in a the form of an array of 3 numbers
*
* @param int $colorMaximumDarkness: maximum darkness along any dimension
* @param int $colorMaximumLightness: maximum lightness along any dimension
* @param boolean $darker: if true, produce a possibly darker image by default
* @return array the random color
*/
public static function getRandomColor ($colorMaximumDarkness, $colorMaximumLightness, $darker = false)
{
$color = [];
if ($darker) {
// Needs darker colour..
$minimum = isset($colorMaximumDarkness) ? $colorMaximumDarkness : 10;
$maximum = isset($colorMaximumLightness) ? $colorMaximumLightness : 100;
} else {
$minimum = isset($colorMaximumDarkness) ? $colorMaximumDarkness : 30;
$maximum = isset($colorMaximumLightness) ? $colorMaximumLightness : 140;
}
for ($i = 0; $i < 3 ; $i++) {
$color[] = self::getRandomNumberInRange($minimum, $maximum);
}
return $color;
}
/**
* Returns a random word
*
* @param boolean $useWordsList: if true, a word dictionary is used
* @param string $wordsList: dictionary file location
* @param boolean $numbersOnly: if true, use only numbers
*
* @return string random word
*/
public static function getRandomWord($useWordsList = false, $wordsList = '', $numbersOnly = false, $maxWordLength = 6)
{
if ($useWordsList && is_file($wordsList)) {
// Load dictionary and choose random word
// Keep dictionary in non-web accessible folder, or htaccess it
// or modify so word comes from a database; SELECT word FROM words ORDER BY rand() LIMIT 1
$words = @file($wordsList);
$word = strtolower($words[self::getRandomNumberInRange(0, sizeof($words)-1)]);
// Cut off line breaks
$word = preg_replace('/['.preg_quote(chr(10).chr(13)).']+/', '', $word);
unset($words);
} else {
// Based on code originally by breakzero at hotmail dot com
// (http://uk.php.net/manual/en/function.rand.php)
// generate pseudo-random string
// doesn't use ijtf as are easily mistaken
// I'm not using numbers because the custom fonts I've created don't support anything other than
// lowercase or space (but you can download new fonts or create your own using my GD fontmaker script)
if ($numbersOnly) {
$consonants = '123456789';
$vowels = '123456789';
} else {
$consonants = 'bcdghklmnpqrsvwxyz';
$vowels = 'aeuo';
}
$word = '';
$wordlen = self::getRandomNumberInRange(5, $maxWordLength);
for ($i = 0; $i < $wordlen; $i++) {
// Don't allow to start with 'vowel'
if (self::getRandomNumberInRange(0, 20) >= 10 && $i != 0) {
$word .= $vowels[self::getRandomNumberInRange(0, strlen($vowels)-1)];
} else {
$word .= $consonants[self::getRandomNumberInRange(0, strlen($consonants)-1)];
}
}
}
return $word;
}
}