mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 22:01:33 +00:00
Drop support for 7z archives
This commit is contained in:
@@ -184,7 +184,6 @@ TEMP_UNZIP_PATH='/var/www/nntmux/resources/tmp/unzip/'
|
||||
# Binary paths - adjust these to match your system
|
||||
UNRAR_PATH=/usr/bin/unrar
|
||||
UNZIP_PATH=/usr/bin/unzip
|
||||
S7ZIP_PATH=/usr/bin/7z
|
||||
FFMPEG_PATH=/usr/bin/ffmpeg
|
||||
LAME_PATH=/usr/bin/lame
|
||||
MEDIAINFO_PATH=/usr/bin/mediainfo
|
||||
|
||||
+1
-14
@@ -4,7 +4,6 @@ FROM dunglas/frankenphp:1-php8.3
|
||||
LABEL maintainer="PyRowMan"
|
||||
ENV SERVER_NAME=:${APP_PORT:-80}
|
||||
ARG MYSQL_CLIENT="mariadb-client"
|
||||
ARG SEVENZIP_VERSION=2407
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -14,7 +13,7 @@ COPY --from=composer-base --link /usr/bin/composer /usr/bin/composer
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y --no-install-recommends \
|
||||
unrar-free 7zip lame libcap2-bin python3 gettext-base \
|
||||
unrar-free lame libcap2-bin python3 gettext-base \
|
||||
curl zip unzip git nano bash-completion sudo wget tmux time fonts-powerline \
|
||||
gnupg libpng-dev dnsutils jq htop iputils-ping net-tools ffmpeg \
|
||||
jpegoptim webp optipng pngquant libavif-bin watch iproute2 nmon \
|
||||
@@ -36,18 +35,6 @@ RUN docker-php-ext-install \
|
||||
&& docker-php-ext-enable redis \
|
||||
&& apt clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
# Determine ARCH and download and extract the appropriate version of 7-Zip
|
||||
RUN ARCH="$(dpkg --print-architecture)" && \
|
||||
if [ "$ARCH" = "amd64" ]; then \
|
||||
SZIP_URL="https://www.7-zip.org/a/7z$SEVENZIP_VERSION-linux-x64.tar.xz"; \
|
||||
fi && \
|
||||
if [ "$ARCH" = "arm64" ]; then \
|
||||
SZIP_URL="https://www.7-zip.org/a/7z$SEVENZIP_VERSION-linux-arm64.tar.xz"; \
|
||||
fi && \
|
||||
wget "$SZIP_URL" -O /tmp/7z.tar.xz && \
|
||||
tar -xf /tmp/7z.tar.xz -C /tmp/ && \
|
||||
mv /tmp/7zz /usr/bin/7zz && \
|
||||
rm -f /tmp/7z.tar.xz && rm -f /tmp/7zzs
|
||||
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
COPY ./docker/8.3/php.ini "$PHP_INI_DIR/conf.d/custom-conf.ini"
|
||||
|
||||
@@ -500,7 +500,7 @@ class AdditionalProcessingOrchestrator
|
||||
|
||||
$title = $nzbFile['title'] ?? '';
|
||||
if (! preg_match(
|
||||
'/(\\.(part\\d+|[rz]\\d+|rar|0+|0*10?|zipr\\d{2,3}|zipx?|7z(?:\\.\\d{3})?|(?:tar\\.)?(?:gz|bz2|xz))(\\s*\\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\\.[1-9]\\d{1,2}".*\\(\\d+\\/\\d{2,}\\)$)/i',
|
||||
'/(\\.(part\\d+|[rz]\\d+|rar|0+|0*10?|zipr\\d{2,3}|zipx?)(\\s*\\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\\.[1-9]\\d{1,2}".*\\(\\d+\\/\\d{2,}\\)$)/i',
|
||||
$title
|
||||
)) {
|
||||
continue;
|
||||
@@ -737,7 +737,7 @@ class AdditionalProcessingOrchestrator
|
||||
|
||||
$title = $nzbFile['title'] ?? '';
|
||||
if (! preg_match(
|
||||
'/(\\.(part0*1|rar|zip|7z))(\\s*\\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\\.[1-9]\\d{1,2}".*\\(\\d+\\/\\d{2,}\\)$/i',
|
||||
'/(\\.(part0*1|rar|zip))(\\s*\\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\\.[1-9]\\d{1,2}".*\\(\\d+\\/\\d{2,}\\)$/i',
|
||||
$title
|
||||
)) {
|
||||
continue;
|
||||
@@ -816,7 +816,7 @@ class AdditionalProcessingOrchestrator
|
||||
}
|
||||
|
||||
$foundCompressed = false;
|
||||
$pattern = '/.*\.([rz]\d{2,}|rar|zipx?|0{0,2}1|7z(?:\.\d{3})?|(?:tar\.)?(?:gz|bz2|xz))($|[^a-z0-9])/i';
|
||||
$pattern = '/.*\.([rz]\d{2,}|rar|zipx?|0{0,2}1)($|[^a-z0-9])/i';
|
||||
|
||||
try {
|
||||
$files = $this->tempWorkspace->listFiles($context->tmpPath, $pattern);
|
||||
|
||||
@@ -11,7 +11,7 @@ use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Service for extracting and processing archive files (RAR, ZIP, 7z, gzip, bzip2, xz).
|
||||
* Service for extracting and processing archive files (RAR, ZIP).
|
||||
* Handles password detection, file listing, and content extraction.
|
||||
*/
|
||||
class ArchiveExtractionService
|
||||
@@ -51,25 +51,6 @@ class ArchiveExtractionService
|
||||
|
||||
$context->compressedFilesChecked++;
|
||||
|
||||
// Detect archive type early
|
||||
$archiveType = $this->detectArchiveType($compressedData);
|
||||
|
||||
// Handle 7z, gzip, bzip2, xz with external 7zip binary
|
||||
if (in_array($archiveType, ['7z', 'gzip', 'bzip2', 'xz'], true)) {
|
||||
if ($archiveType === '7z') {
|
||||
$sevenZipResult = $this->processSevenZipArchive($compressedData, $context, $tmpPath);
|
||||
if ($sevenZipResult['success'] || $sevenZipResult['hasPassword']) {
|
||||
return $sevenZipResult;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->config->sevenZipPath) {
|
||||
$extractResult = $this->extractViaSevenZip($compressedData, $archiveType, $tmpPath);
|
||||
if ($extractResult['success']) { // @phpstan-ignore offsetAccess.notFound
|
||||
return $extractResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try ArchiveInfo for RAR/ZIP
|
||||
if (! $this->archiveInfo->setData($compressedData, true)) {
|
||||
@@ -145,425 +126,6 @@ class ArchiveExtractionService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the archive type from binary signature.
|
||||
*/
|
||||
public function detectArchiveType(string $data): ?string
|
||||
{
|
||||
$head6 = substr($data, 0, 6);
|
||||
$head4 = substr($data, 0, 4);
|
||||
|
||||
// 7z signature
|
||||
if ($head6 === "\x37\x7A\xBC\xAF\x27\x1C" && $this->isLikely7z($data)) {
|
||||
return '7z';
|
||||
}
|
||||
// GZIP
|
||||
if (strncmp($head4, "\x1F\x8B\x08", 3) === 0) {
|
||||
return 'gzip';
|
||||
}
|
||||
// BZip2
|
||||
if (strncmp($head4, 'BZh', 3) === 0) {
|
||||
return 'bzip2';
|
||||
}
|
||||
// XZ
|
||||
if ($head6 === "\xFD7zXZ\x00") {
|
||||
return 'xz';
|
||||
}
|
||||
// PDF (skip)
|
||||
if ($head4 === '%PDF') {
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Heuristic validation for 7z signature.
|
||||
*/
|
||||
private function isLikely7z(string $data): bool
|
||||
{
|
||||
if (strlen($data) < 32) {
|
||||
return false;
|
||||
}
|
||||
$verMajor = ord($data[6]);
|
||||
$verMinor = ord($data[7]);
|
||||
if ($verMajor !== 0x00 || $verMinor < 0x02 || $verMinor > 0x09) {
|
||||
return false;
|
||||
}
|
||||
$crc = substr($data, 8, 4);
|
||||
if ($crc === "\x00\x00\x00\x00" || $crc === "\xFF\xFF\xFF\xFF") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a 7z archive using external binary and internal header parsing.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function processSevenZipArchive(
|
||||
string $compressedData,
|
||||
ReleaseProcessingContext $context,
|
||||
string $tmpPath
|
||||
): array {
|
||||
$result = [
|
||||
'success' => false,
|
||||
'files' => [],
|
||||
'hasPassword' => false,
|
||||
'passwordStatus' => ReleaseBrowseService::PASSWD_NONE,
|
||||
];
|
||||
|
||||
if (! $this->config->sevenZipPath) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Try listing with external 7z binary
|
||||
$listed = $this->listSevenZipEntries($compressedData, $tmpPath);
|
||||
if (! empty($listed)) {
|
||||
if (! empty($listed[0]['__any_encrypted__'])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'files' => [],
|
||||
'hasPassword' => true,
|
||||
'passwordStatus' => ReleaseBrowseService::PASSWD_RAR,
|
||||
];
|
||||
}
|
||||
|
||||
$files = $this->filterSevenZipFiles($listed);
|
||||
if (! empty($files)) {
|
||||
return [
|
||||
'success' => true,
|
||||
'files' => $files,
|
||||
'hasPassword' => false,
|
||||
'passwordStatus' => ReleaseBrowseService::PASSWD_NONE,
|
||||
'archiveMarker' => '7z',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: scan for filenames in raw data
|
||||
$scannedNames = $this->scanSevenZipFilenames($compressedData);
|
||||
if (! empty($scannedNames)) {
|
||||
$files = array_map(fn ($name) => [
|
||||
'name' => $name,
|
||||
'size' => 0,
|
||||
'date' => time(),
|
||||
'pass' => 0,
|
||||
'crc32' => '',
|
||||
'source' => '7z-scan',
|
||||
], $scannedNames);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'files' => $files,
|
||||
'hasPassword' => false,
|
||||
'passwordStatus' => ReleaseBrowseService::PASSWD_NONE,
|
||||
'archiveMarker' => '7z',
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* List entries of a 7z archive using external 7z binary.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function listSevenZipEntries(string $compressedData, string $tmpPath): array
|
||||
{
|
||||
if (! $this->config->sevenZipPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$tmpFile = $tmpPath.uniqid('7zlist_', true).'.7z';
|
||||
if (File::put($tmpFile, $compressedData) === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cmd = [$this->config->sevenZipPath, 'l', '-slt', '-ba', '-bd', $tmpFile];
|
||||
$exitCode = 0;
|
||||
$stdout = null;
|
||||
$stderr = null;
|
||||
$ok = $this->execCommand($cmd, $exitCode, $stdout, $stderr); // @phpstan-ignore argument.type
|
||||
|
||||
if (! $ok || $exitCode !== 0 || empty($stdout)) {
|
||||
// Try plain listing fallback
|
||||
$plainResult = $this->listSevenZipPlain($tmpFile);
|
||||
File::delete($tmpFile);
|
||||
|
||||
return $plainResult;
|
||||
}
|
||||
|
||||
File::delete($tmpFile);
|
||||
|
||||
return $this->parseSevenZipStructuredOutput($stdout);
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->config->debugMode) {
|
||||
Log::debug('Exception listing 7z: '.$e->getMessage());
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain 7z listing fallback.
|
||||
*
|
||||
* @return list<array<string, int|string|false>>
|
||||
*/
|
||||
private function listSevenZipPlain(string $tmpFile): array
|
||||
{
|
||||
$cmd = [$this->config->sevenZipPath, 'l', '-ba', '-bd', $tmpFile];
|
||||
$exitCode = 0;
|
||||
$stdout = null;
|
||||
$stderr = null;
|
||||
$ok = $this->execCommand($cmd, $exitCode, $stdout, $stderr); // @phpstan-ignore argument.type
|
||||
|
||||
if (! $ok || $exitCode !== 0 || empty($stdout)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = [];
|
||||
$lines = preg_split('/\r?\n/', trim($stdout));
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || str_starts_with($line, '-----')
|
||||
|| str_contains($line, ' Date ')
|
||||
|| str_starts_with($line, 'Scanning ')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = null;
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\s+\S+\s+\d+\s+\d+\s+(\S.*)$/', $line, $m)) {
|
||||
$name = $m[1];
|
||||
} elseif (preg_match('/([A-Za-z0-9_#@()\[\]\-+&., ]+\.[A-Za-z0-9]{2,8})$/', $line, $m2)) {
|
||||
$name = trim($m2[1]);
|
||||
}
|
||||
|
||||
if ($name && strlen($name) <= 300) {
|
||||
$files[] = ['name' => trim($name), 'size' => 0, 'encrypted' => false];
|
||||
if (count($files) >= 200) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse structured 7z output.
|
||||
*
|
||||
* @return list<array<string, int|string|false>>
|
||||
*/
|
||||
private function parseSevenZipStructuredOutput(string $output): array
|
||||
{
|
||||
$blocks = preg_split('/\n\n+/u', trim($output));
|
||||
$files = [];
|
||||
$anyEncrypted = false;
|
||||
|
||||
foreach ($blocks as $block) {
|
||||
$lines = preg_split('/\r?\n/', trim($block));
|
||||
$row = [];
|
||||
foreach ($lines as $line) {
|
||||
$kv = explode(' = ', $line, 2);
|
||||
if (count($kv) === 2) {
|
||||
$row[$kv[0]] = $kv[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($row['Path'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attr = $row['Attributes'] ?? '';
|
||||
if (str_contains($attr, 'D')) {
|
||||
continue; // directory
|
||||
}
|
||||
|
||||
$encrypted = ($row['Encrypted'] ?? '') === '+';
|
||||
if ($encrypted) {
|
||||
$anyEncrypted = true;
|
||||
}
|
||||
|
||||
$size = isset($row['Size']) && ctype_digit($row['Size']) ? (int) $row['Size'] : 0;
|
||||
$files[] = ['name' => $row['Path'], 'size' => $size, 'encrypted' => $encrypted];
|
||||
|
||||
if (count($files) >= 200) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($anyEncrypted && isset($files[0])) {
|
||||
$files[0]['__any_encrypted__'] = true;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter 7z files using extension whitelist.
|
||||
*
|
||||
* @param array<string, mixed> $files
|
||||
* @return list<array{name: non-falsy-string, size: int, encrypted: bool, __any_encrypted__?: true}>
|
||||
*/
|
||||
private function filterSevenZipFiles(array $files): array
|
||||
{
|
||||
$allowedExtensions = $this->getAllowedExtensions();
|
||||
$filtered = [];
|
||||
|
||||
foreach ($files as $entry) {
|
||||
if (! empty($entry['__any_encrypted__'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $entry['name'] ?? '';
|
||||
if ($name === '' || strlen($name) > 300) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
|
||||
if (! in_array($ext, $allowedExtensions, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$base = pathinfo($name, PATHINFO_FILENAME);
|
||||
$letterCount = preg_match_all('/[a-z]/i', $base);
|
||||
if ($letterCount <= 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered[] = [
|
||||
'name' => $name,
|
||||
'size' => $entry['size'] ?? 0,
|
||||
'date' => time(),
|
||||
'pass' => 0,
|
||||
'crc32' => '',
|
||||
'source' => '7z-list',
|
||||
];
|
||||
|
||||
if (count($filtered) >= 50) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for filenames in 7z raw data.
|
||||
*
|
||||
* @return list<array{name: mixed, size: mixed, date: int<1, max>, pass: 0, crc32: '', source: '7z-list'}>
|
||||
*/
|
||||
private function scanSevenZipFilenames(string $data): array
|
||||
{
|
||||
$slice = substr($data, 0, 8 * 1024 * 1024);
|
||||
$converted = preg_replace('/([\x20-\x7E])\x00/', '$1', $slice) ?? $slice;
|
||||
$converted = str_replace("\x00", ' ', $converted);
|
||||
|
||||
$exts = '7z|rar|zip|gz|bz2|xz|tar|tgz|mp4|mkv|avi|mpg|mpeg|mov|ts|wmv|flv|m4v|mp3|flac|ogg|wav|aac|aiff|ape|mka|nfo|txt|diz|pdf|epub|mobi|jpg|jpeg|png|gif|sfv|par2|exe|dll|srt|sub|idx|iso|bin|cue|mds|mdf';
|
||||
$regex = '~(?:[A-Za-z0-9 _.+&@#,()!-]{0,120}[\\/])?([A-Za-z0-9 _.+&@#,()!-]{2,160}\.(?:'.$exts.'))~i';
|
||||
preg_match_all($regex, $converted, $matches, PREG_SET_ORDER);
|
||||
|
||||
if (empty($matches)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$names = [];
|
||||
foreach ($matches as $match) {
|
||||
$candidate = preg_replace('/ {2,}/', ' ', $match[1]);
|
||||
if ($candidate === '' || strlen($candidate) < 5 || substr_count($candidate, '.') > 10) {
|
||||
continue;
|
||||
}
|
||||
$candidate = trim($candidate, " .-\t\n\r");
|
||||
$lower = strtolower($candidate);
|
||||
if (! isset($names[$lower])) {
|
||||
$names[$lower] = $candidate;
|
||||
if (count($names) >= 80) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract using 7zip binary.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function extractViaSevenZip(string $compressedData, string $type, string $tmpPath): array
|
||||
{
|
||||
$result = [
|
||||
'success' => false,
|
||||
'files' => [],
|
||||
'hasPassword' => false,
|
||||
'passwordStatus' => ReleaseBrowseService::PASSWD_NONE,
|
||||
];
|
||||
|
||||
if ($this->config->extractUsingRarInfo || ! $this->config->sevenZipPath) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
try {
|
||||
$extMap = ['7z' => '7z', 'gzip' => 'gz', 'bzip2' => 'bz2', 'xz' => 'xz'];
|
||||
$markerMap = ['7z' => '7z', 'gzip' => 'g', 'bzip2' => 'b', 'xz' => 'x'];
|
||||
$ext = $extMap[$type] ?? 'dat';
|
||||
$marker = $markerMap[$type] ?? $type;
|
||||
|
||||
$extractDir = $tmpPath.'un7z/'.uniqid('', true).'/';
|
||||
if (! File::isDirectory($extractDir)) {
|
||||
File::makeDirectory($extractDir, 0777, true, true);
|
||||
}
|
||||
|
||||
$fileName = $tmpPath.uniqid('', true).'.'.$ext;
|
||||
File::put($fileName, $compressedData);
|
||||
|
||||
$cmd = [$this->config->sevenZipPath, 'e', '-y', '-bd', '-o'.$extractDir, $fileName];
|
||||
$exitCode = 0;
|
||||
$stdout = null;
|
||||
$stderr = null;
|
||||
$this->execCommand($cmd, $exitCode, $stdout, $stderr); // @phpstan-ignore argument.type
|
||||
|
||||
$files = [];
|
||||
if (File::isDirectory($extractDir)) {
|
||||
foreach (File::allFiles($extractDir) as $f) {
|
||||
$files[] = [
|
||||
'name' => $f->getFilename(),
|
||||
'size' => $f->getSize(),
|
||||
'date' => time(),
|
||||
'pass' => 0,
|
||||
'crc32' => '',
|
||||
'source' => $type,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
File::delete($fileName);
|
||||
|
||||
if (! empty($files)) {
|
||||
return [ // @phpstan-ignore return.type
|
||||
'success' => true,
|
||||
'files' => $this->filterExtractedFiles($files), // @phpstan-ignore argument.type
|
||||
'hasPassword' => false,
|
||||
'passwordStatus' => ReleaseBrowseService::PASSWD_NONE,
|
||||
'archiveMarker' => $marker,
|
||||
];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->config->debugMode) {
|
||||
Log::warning(strtoupper($type).' extraction exception: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter extracted files by allowed extensions.
|
||||
@@ -832,11 +394,6 @@ class ArchiveExtractionService
|
||||
}
|
||||
|
||||
// Fallback: use external tools to extract to temp directory
|
||||
$archiveType = $this->detectArchiveType($compressedData);
|
||||
|
||||
if ($archiveType === '7z' && $this->config->sevenZipPath) {
|
||||
return $this->extractFileVia7zip($compressedData, $filename, $tmpPath);
|
||||
}
|
||||
|
||||
// Try using unrar for RAR files
|
||||
if ($this->config->unrarPath) {
|
||||
@@ -857,58 +414,6 @@ class ArchiveExtractionService
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a specific file using 7zip.
|
||||
*/
|
||||
private function extractFileVia7zip(string $compressedData, string $filename, string $tmpPath): ?string
|
||||
{
|
||||
try {
|
||||
$extractDir = $tmpPath.'extract_'.uniqid('', true).'/';
|
||||
if (! File::isDirectory($extractDir)) {
|
||||
File::makeDirectory($extractDir, 0777, true, true);
|
||||
}
|
||||
|
||||
$archiveFile = $tmpPath.'archive_'.uniqid('', true).'.7z';
|
||||
File::put($archiveFile, $compressedData);
|
||||
|
||||
// Extract specific file
|
||||
$cmd = [$this->config->sevenZipPath, 'e', '-y', '-bd', '-o'.$extractDir, $archiveFile, $filename];
|
||||
$exitCode = 0;
|
||||
$stdout = null;
|
||||
$stderr = null;
|
||||
$this->execCommand($cmd, $exitCode, $stdout, $stderr); // @phpstan-ignore argument.type
|
||||
|
||||
File::delete($archiveFile);
|
||||
|
||||
// Look for extracted file
|
||||
$extractedPath = $extractDir.basename($filename);
|
||||
if (File::isFile($extractedPath)) {
|
||||
$content = File::get($extractedPath);
|
||||
File::deleteDirectory($extractDir);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Try to find it with glob (in case path differs)
|
||||
$files = File::allFiles($extractDir);
|
||||
foreach ($files as $file) {
|
||||
if (strtolower($file->getFilename()) === strtolower(basename($filename))) {
|
||||
$content = File::get($file->getPathname());
|
||||
File::deleteDirectory($extractDir);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
File::deleteDirectory($extractDir);
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->config->debugMode) {
|
||||
Log::debug('7zip extraction failed: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a specific file using unrar.
|
||||
@@ -1009,35 +514,4 @@ class ArchiveExtractionService
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command with output capture.
|
||||
*
|
||||
* @param-out int $exitCode
|
||||
* @param-out string|null $stdout
|
||||
* @param-out string|null $stderr
|
||||
*
|
||||
* @param array<string, mixed> $cmd
|
||||
*/
|
||||
private function execCommand(array $cmd, ?int &$exitCode, ?string &$stdout, ?string &$stderr): bool
|
||||
{
|
||||
$descriptorSpec = [
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
|
||||
$process = @proc_open($cmd, $descriptorSpec, $pipes, null, null, ['bypass_shell' => true]); // @phpstan-ignore argument.type
|
||||
if (! is_resource($process)) {
|
||||
$exitCode = -1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$stdout = stream_get_contents($pipes[1]) ?: null;
|
||||
fclose($pipes[1]);
|
||||
$stderr = stream_get_contents($pipes[2]) ?: null;
|
||||
fclose($pipes[2]);
|
||||
$exitCode = proc_close($process);
|
||||
|
||||
return $exitCode === 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ final readonly class ProcessingConfiguration
|
||||
|
||||
public string|false $unzipPath;
|
||||
|
||||
public string|false $sevenZipPath;
|
||||
|
||||
public string|false $timeoutPath;
|
||||
|
||||
@@ -106,7 +105,6 @@ final readonly class ProcessingConfiguration
|
||||
$this->fetchLastFiles = (bool) config('nntmux_settings.fetch_last_file');
|
||||
$this->unrarPath = config('nntmux_settings.unrar_path') ?: false;
|
||||
$this->unzipPath = config('nntmux_settings.unzip_path') ?: false;
|
||||
$this->sevenZipPath = config('nntmux_settings.7zip_path') ?: false;
|
||||
$this->timeoutPath = config('nntmux_settings.timeout_path') ?: false;
|
||||
$this->timeoutSeconds = (int) Settings::settingValue('timeoutseconds');
|
||||
$this->queryLimit = (int) (Settings::settingValue('maxaddprocessed') ?: 25);
|
||||
|
||||
@@ -38,11 +38,11 @@ class ConsoleOutputService
|
||||
PHP_EOL.
|
||||
'Failures: fC#=Compressed(part #), fS=Sample, fM=Media(video), fA=Audio, fJ=JPEG, G=Missing group, T=Timeout skip'.
|
||||
PHP_EOL.
|
||||
'Processing: r=RAR, z=ZIP, 7z=7zip (names/entries), g=GZIP, b=BZIP2, x=XZ, (vRAW)=Inline video detected'.
|
||||
'Processing: r=RAR, z=ZIP, (vRAW)=Inline video detected'.
|
||||
PHP_EOL.
|
||||
'Added: s=Sample image, j=JPEG image, A=Audio sample, a=Audio MediaInfo, v=Video sample'.
|
||||
PHP_EOL.
|
||||
'Added: m=Video MediaInfo, n=NFO, ^=Inner file details (RAR/ZIP/7z/etc)'.
|
||||
'Added: m=Video MediaInfo, n=NFO, ^=Inner file details (RAR/ZIP)'.
|
||||
'',
|
||||
'header'
|
||||
);
|
||||
|
||||
@@ -190,7 +190,7 @@ class NzbContentParser
|
||||
|
||||
// Compressed file detection
|
||||
if (! $result['hasCompressedFile'] && preg_match(
|
||||
'/(\\.(part\\d+|[rz]\\d+|rar|0+|0*10?|zipr\\d{2,3}|zipx?|7z(?:\\.\\d{3})?|(?:tar\\.)?(?:gz|bz2|xz))("|\\s*\\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\\.[1-9]\\d{1,2}".*\\(\\d+\\/\\d{2,}\\)$)/i',
|
||||
'/(\\.(part\\d+|[rz]\\d+|rar|0+|0*10?|zipr\\d{2,3}|zipx?)("|\\s*\\.rar)*($|[ ")]|-])|"[a-f0-9]{32}\\.[1-9]\\d{1,2}".*\\(\\d+\\/\\d{2,}\\)$)/i',
|
||||
$title
|
||||
)) {
|
||||
$result['hasCompressedFile'] = true;
|
||||
|
||||
@@ -78,7 +78,7 @@ class ReleaseFileManager
|
||||
|
||||
// Skip support files
|
||||
if (preg_match(
|
||||
'/(?:'.$supportFileRegex.'|part\d+|[rz]\d{1,3}|zipr\d{2,3}|\d{2,3}|zipx?|zip|rar|7z|gz|bz2|xz)(\s*\.rar)?$/i',
|
||||
'/(?:'.$supportFileRegex.'|part\d+|[rz]\d{1,3}|zipr\d{2,3}|\d{2,3}|zipx?|zip|rar)(\s*\.rar)?$/i',
|
||||
$file['name']
|
||||
)) {
|
||||
return false;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
||||
return [
|
||||
'unrar_path' => env('UNRAR_PATH', '/usr/bin/unrar'),
|
||||
'unzip_path' => env('UNZIP_PATH', '/usr/bin/unzip'),
|
||||
'7zip_path' => env('S7ZIP_PATH', '/usr/bin/7z'),
|
||||
'check_passworded_rars' => env('CHECK_PASSWORDED_RARS', false),
|
||||
'delete_passworded_releases' => env('DELETE_PASSWORDED_RELEASES', false),
|
||||
'delete_possible_passworded_releases' => env('DELETE_POSSIBLE_PASSWORDED_RELEASES', false),
|
||||
|
||||
+1
-14
@@ -5,7 +5,6 @@ LABEL maintainer="Fossil01"
|
||||
ARG WWWGROUP
|
||||
ARG NODE_VERSION=22
|
||||
ARG POSTGRES_VERSION=17
|
||||
ARG SEVENZIP_VERSION=2409
|
||||
ARG MYSQL_CLIENT="mariadb-client"
|
||||
|
||||
WORKDIR /var/www/html
|
||||
@@ -13,7 +12,7 @@ WORKDIR /var/www/html
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip unrar 7zip lame git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils \
|
||||
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip unrar lame git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils \
|
||||
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/nginx/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_nginx.list \
|
||||
@@ -45,18 +44,6 @@ RUN apt-get update \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Determine ARCH and download and extract the appropriate version of 7-Zip
|
||||
RUN ARCH="$(dpkg --print-architecture)" && \
|
||||
if [ "$ARCH" = "amd64" ]; then \
|
||||
SZIP_URL="https://www.7-zip.org/a/7z$SEVENZIP_VERSION-linux-x64.tar.xz"; \
|
||||
fi && \
|
||||
if [ "$ARCH" = "arm64" ]; then \
|
||||
SZIP_URL="https://www.7-zip.org/a/7z$SEVENZIP_VERSION-linux-arm64.tar.xz"; \
|
||||
fi && \
|
||||
wget "$SZIP_URL" -O /tmp/7z.tar.xz && \
|
||||
tar -xf /tmp/7z.tar.xz -C /tmp/ && \
|
||||
mv /tmp/7zz /usr/bin/7zz && \
|
||||
rm -f /tmp/7z.tar.xz && rm -f /tmp/7zzs
|
||||
|
||||
RUN groupadd --force -g $WWWGROUP sail
|
||||
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
|
||||
|
||||
+1
-14
@@ -5,7 +5,6 @@ LABEL maintainer="Fossil01"
|
||||
ARG WWWGROUP
|
||||
ARG NODE_VERSION=22
|
||||
ARG POSTGRES_VERSION=17
|
||||
ARG SEVENZIP_VERSION=2409
|
||||
ARG MYSQL_CLIENT="mariadb-client"
|
||||
|
||||
WORKDIR /var/www/html
|
||||
@@ -14,7 +13,7 @@ ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
# Install base packages and add repositories
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip unrar 7zip lame git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils \
|
||||
&& apt-get install -y gnupg gosu curl ca-certificates zip unzip unrar lame git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils \
|
||||
&& curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
|
||||
&& echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/nginx/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_nginx.list \
|
||||
@@ -67,18 +66,6 @@ RUN apt-get install -y yarn $MYSQL_CLIENT postgresql-client-$POSTGRES_VERSION \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Determine ARCH and download and extract the appropriate version of 7-Zip
|
||||
RUN ARCH="$(dpkg --print-architecture)" && \
|
||||
if [ "$ARCH" = "amd64" ]; then \
|
||||
SZIP_URL="https://www.7-zip.org/a/7z$SEVENZIP_VERSION-linux-x64.tar.xz"; \
|
||||
fi && \
|
||||
if [ "$ARCH" = "arm64" ]; then \
|
||||
SZIP_URL="https://www.7-zip.org/a/7z$SEVENZIP_VERSION-linux-arm64.tar.xz"; \
|
||||
fi && \
|
||||
wget "$SZIP_URL" -O /tmp/7z.tar.xz && \
|
||||
tar -xf /tmp/7z.tar.xz -C /tmp/ && \
|
||||
mv /tmp/7zz /usr/bin/7zz && \
|
||||
rm -f /tmp/7z.tar.xz && rm -f /tmp/7zzs
|
||||
|
||||
RUN groupadd --force -g $WWWGROUP sail
|
||||
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail
|
||||
|
||||
Reference in New Issue
Block a user