From 1a451b9184b986c58c852a7793ece723c914fb5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 5 Dec 2025 15:17:20 +0100 Subject: [PATCH 1/8] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d862e03 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Apps Dev Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 765a1754dbb58aa22a97a95e888ccaf9fdd1c45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 5 Dec 2025 15:17:59 +0100 Subject: [PATCH 2/8] Add license field to composer.json --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index fac40cf..0207ae4 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "src/" ] }, + "license": "MIT", "require": { "nette/utils": "^2.0|^3.0|^4.0" }, From 31c7dc74c77bf91d89a07a82687df3de36202216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Thu, 8 Jan 2026 18:55:45 +0100 Subject: [PATCH 3/8] Update Image.php --- src/Filters/Image.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 7a272aa..7e6f9b0 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -61,6 +61,10 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ $width = $width * $this->multiplier; $height = $height * $this->multiplier; $ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); + if ($ext === 'svg') { + return $url; + } + $newFile = $this->dir . '/' . $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '_' . $ext . '.' . self::FormatToExtensions[$format]; $this->createImage($contents, $width, $height, $mode, $format, $this->path . '/' . $newFile); From 508d7d9de3e4f369967d842e6170ef453726559e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Thu, 8 Jan 2026 19:07:35 +0100 Subject: [PATCH 4/8] Update Image.php --- src/Filters/Image.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 7e6f9b0..556a82d 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -33,13 +33,15 @@ public function __construct(string $path, string $dir = 'thumbnails', int $multi */ public function format(string $url, int $width, int $height, int $mode = \Nette\Utils\Image::OrSmaller, int $format = IMAGETYPE_WEBP): string { + $originalUrl = $url; + $isRemoteUrl = $this->isRemoteUrl($url); // original file does not exist if ($isRemoteUrl) { $contents = @file_get_contents($url); if (!$contents) { - return $url; + return $originalUrl; } list($urlWithoutExtension,) = $this->splitUrlOnLastDot($this->removeProtocol($url)); @@ -47,12 +49,12 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ } else { $url = trim($url, '/'); if (!file_exists($this->path . '/' . $url)) { - return $url; + return $originalUrl; } $contents = file_get_contents($this->path . '/' . $url); if ($this->isAnimatedGif($contents)) { - return $url; + return $originalUrl; } list($urlWithoutExtension,) = $this->splitUrlOnLastDot($url); @@ -62,7 +64,7 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ $height = $height * $this->multiplier; $ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); if ($ext === 'svg') { - return $url; + return $originalUrl; } $newFile = $this->dir . '/' . $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '_' . $ext . '.' . self::FormatToExtensions[$format]; From 23705dccc9770920d70d9106dcb05dbf753d6467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Konvi=C4=8Dka?= Date: Tue, 7 Apr 2026 12:55:32 +0200 Subject: [PATCH 5/8] add components method --- src/JsComponents.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/JsComponents.php b/src/JsComponents.php index 12b9686..2e49c73 100644 --- a/src/JsComponents.php +++ b/src/JsComponents.php @@ -2,17 +2,25 @@ namespace ADT\Utils; +use Nette\Utils\Json; + class JsComponents { protected array $components = []; public function generateConfig(): string { - return json_encode($this->components); + return Json::encode($this->components); } public function setRecaptcha(string $siteKey): string { return $this->components['recaptcha']['siteKey'] = $siteKey; } + + public function setComponents(array $components): self + { + $this->components = array_merge($this->components, $components); + return $this; + } } From e4a912023fb31231b5a434ac237e480021350c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Tue, 28 Apr 2026 07:51:28 +0200 Subject: [PATCH 6/8] Sanitize and truncate Guzzle error messages Prevent binary data and excessively long request/response bodies from being included in exception messages to ensure logs remain readable. --- src/Guzzle.php | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Guzzle.php b/src/Guzzle.php index ada18ab..f9d72c0 100644 --- a/src/Guzzle.php +++ b/src/Guzzle.php @@ -10,6 +10,8 @@ class Guzzle { + private const MAX_BODY_LENGTH = 10000; + /** * @throws Throwable */ @@ -18,13 +20,39 @@ public static function handleException(Throwable $e): ?Exception if ($e instanceof GuzzleException) { $message = ''; if ($e instanceof ConnectException || $e instanceof RequestException) { - $message = "--- REQUEST ---\n" . Message::toString($e->getRequest()) . "\n --- RESPONSE ---\n"; + $message = "--- REQUEST ---\n" . self::sanitizeMessage(Message::toString($e->getRequest())) . "\n --- RESPONSE ---\n"; } - $message .= ($e instanceof RequestException && $e->getResponse() ? Message::toString($e->getResponse()) : $e->getMessage()); + $message .= ($e instanceof RequestException && $e->getResponse() ? self::sanitizeMessage(Message::toString($e->getResponse())) : $e->getMessage()); throw new Exception($message); } throw $e; } + + private static function sanitizeMessage(string $message): string + { + // Odstraneni binarnich dat (null byty apod.) + if (preg_match('/[^\x20-\x7E\x0A\x0D\t]/u', $message)) { + // Najdeme konec hlavicek (prazdny radek) + $headerEnd = strpos($message, "\r\n\r\n"); + if ($headerEnd === false) { + $headerEnd = strpos($message, "\n\n"); + } + + if ($headerEnd !== false) { + $headers = substr($message, 0, $headerEnd); + return $headers . "\n\n[binary data removed]"; + } + + return '[binary data removed]'; + } + + // Oriznuti prilis dlouhych textovych odpovedi + if (strlen($message) > self::MAX_BODY_LENGTH) { + return substr($message, 0, self::MAX_BODY_LENGTH) . "\n\n... [truncated, total " . strlen($message) . " bytes]"; + } + + return $message; + } } From 889f13213f509806b74b792de2e0a133526f89a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxmili=C3=A1n=20Holomek?= Date: Tue, 7 Jul 2026 12:13:01 +0200 Subject: [PATCH 7/8] fix deprecated --- src/TErrorPresenter.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TErrorPresenter.php b/src/TErrorPresenter.php index 42a921c..6f1dcff 100644 --- a/src/TErrorPresenter.php +++ b/src/TErrorPresenter.php @@ -2,6 +2,7 @@ namespace ADT\Utils; +use Nette\Application\Attributes\Persistent; use Nette\Application\BadRequestException; use Nette\Application\Helpers; use Nette\Routing\Router; @@ -15,7 +16,7 @@ trait TErrorPresenter protected bool $log404 = true; protected bool $log500 = true; - /** @persistent */ + #[Persistent] public $url; public function __construct(Router $router) From eaff13dd39a0268ac5636747f157c4d401355d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 31 Jul 2026 07:59:03 +0200 Subject: [PATCH 8/8] Prevent memory exhaustion from large Guzzle message bodies The original GuzzleHttp\Psr7\Message::toString() loads entire request/response bodies into memory. For large payloads (e.g., file uploads), this can cause out-of-memory errors before the body can be sanitized or truncated for logging. This introduces a custom messageToString() method that reads only a limited portion of the message stream, effectively truncating the body proactively to prevent memory issues during error logging. --- src/Guzzle.php | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Guzzle.php b/src/Guzzle.php index f9d72c0..60ba4a1 100644 --- a/src/Guzzle.php +++ b/src/Guzzle.php @@ -5,7 +5,9 @@ use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Psr7\Message; +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use Throwable; class Guzzle @@ -20,9 +22,9 @@ public static function handleException(Throwable $e): ?Exception if ($e instanceof GuzzleException) { $message = ''; if ($e instanceof ConnectException || $e instanceof RequestException) { - $message = "--- REQUEST ---\n" . self::sanitizeMessage(Message::toString($e->getRequest())) . "\n --- RESPONSE ---\n"; + $message = "--- REQUEST ---\n" . self::sanitizeMessage(self::messageToString($e->getRequest())) . "\n --- RESPONSE ---\n"; } - $message .= ($e instanceof RequestException && $e->getResponse() ? self::sanitizeMessage(Message::toString($e->getResponse())) : $e->getMessage()); + $message .= ($e instanceof RequestException && $e->getResponse() ? self::sanitizeMessage(self::messageToString($e->getResponse())) : $e->getMessage()); throw new Exception($message); } @@ -30,6 +32,42 @@ public static function handleException(Throwable $e): ?Exception throw $e; } + /** + * Obdoba GuzzleHttp\Psr7\Message::toString(), ale tělo čte ze streamu jen do MAX_BODY_LENGTH. + * Message::toString() načítá celé tělo do paměti, což u velkých těl (např. upload souboru + * v multipart requestu) shodí proces na memory limitu ještě před ořezáním v sanitizeMessage(). + */ + private static function messageToString(MessageInterface $message): string + { + if ($message instanceof RequestInterface) { + $msg = trim($message->getMethod() . ' ' . $message->getRequestTarget()) . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' . $message->getStatusCode() . ' ' . $message->getReasonPhrase(); + } else { + $msg = ''; + } + + foreach ($message->getHeaders() as $name => $values) { + $msg .= "\r\n" . $name . ': ' . implode(', ', $values); + } + + $body = ''; + $stream = $message->getBody(); + if ($stream->isSeekable() && $stream->isReadable()) { + $size = $stream->getSize(); + $stream->seek(0); + $body = $stream->read(self::MAX_BODY_LENGTH); + if ($size === null || $size > self::MAX_BODY_LENGTH) { + $body .= "\n\n... [truncated" . ($size !== null ? ', total ' . $size . ' bytes' : '') . ']'; + } + } + + return $msg . "\r\n\r\n" . $body; + } + private static function sanitizeMessage(string $message): string { // Odstraneni binarnich dat (null byty apod.)