diff --git a/.docker/release/Dockerfile b/.docker/release/Dockerfile index d6675462e..0e6473191 100644 --- a/.docker/release/Dockerfile +++ b/.docker/release/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 -FROM alpine:3.18.5 AS builder -RUN apk add --no-cache php82 php82-json php-phar php-zip +FROM alpine:3.24.3 AS builder +RUN apk add --no-cache php85 php85-json php-phar php-zip RUN apk add --no-cache npm RUN npm install -g gulp yarn WORKDIR /source @@ -23,7 +23,7 @@ RUN set -eux; \ rm -v /snappymail/README.md /snappymail/_include.php # Inspired by the original Rainloop dockerfile from youtous on GitLab -FROM php:8.2-fpm-alpine AS final +FROM php:8.5-fpm-alpine AS final LABEL org.label-schema.description="SnappyMail webmail client image using nginx, php-fpm on Alpine" @@ -99,9 +99,6 @@ RUN set -eux; \ # mysql RUN docker-php-ext-install pdo_mysql -# opcache -RUN docker-php-ext-install opcache - # postgres RUN set -eux; \ apk add --no-cache postgresql-libs; \ @@ -122,6 +119,13 @@ RUN set -eux; \ apk del .build-dependencies; \ apk del .deps +# sqlite +RUN set -eux; \ + apk add --no-cache sqlite-libs; \ + apk add --no-cache --virtual .deps sqlite-dev; \ + docker-php-ext-install pdo_sqlite; \ + apk del .deps + # tidy RUN set -eux; \ apk add --no-cache tidyhtml; \ diff --git a/.docker/release/files/entrypoint.sh b/.docker/release/files/entrypoint.sh index 75e82d729..c0ed7df0c 100755 --- a/.docker/release/files/entrypoint.sh +++ b/.docker/release/files/entrypoint.sh @@ -9,6 +9,12 @@ UPLOAD_MAX_SIZE=${UPLOAD_MAX_SIZE:-25M} MEMORY_LIMIT=${MEMORY_LIMIT:-128M} SECURE_COOKIES=${SECURE_COOKIES:-true} +# Disable listening on ipv6 for ipv4-only environments, or nginx will fail to start +if ! ip a | grep 'inet6 ' > /dev/null; then + echo "[INFO] ipv6 is disabled. Configuring nginx to not listen on ipv6" + sed -i '/listen \[::\]:/d' /etc/nginx/nginx.conf +fi + # Set attachment size limit sed -i "s//$UPLOAD_MAX_SIZE/g" /usr/local/etc/php-fpm.d/php-fpm.conf /etc/nginx/nginx.conf sed -i "s//$MEMORY_LIMIT/g" /usr/local/etc/php-fpm.d/php-fpm.conf diff --git a/SECURITY.md b/SECURITY.md index faaba8d68..11d1ddada 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -36,5 +36,5 @@ YMXQVq4SJt7UpWABAORudfJxsBqCRKtPlZMgGTJLjcOkyFJ9C2Fx7DeN0J4I ## Publishing and Credits I will analyze and fix the reported issue as fast as possible. -Together with the reporter I plan the disclosure of the found and fixed vulnerability. +Together with the reporter, I plan the disclosure of the found and fixed vulnerability. Credits to the reporter are granted and can be included in all public communication if desired. diff --git a/cli/compile.php b/cli/compile.php new file mode 100755 index 000000000..936643cc3 --- /dev/null +++ b/cli/compile.php @@ -0,0 +1,247 @@ +#!/usr/bin/php +{'use strict';var aa="function"==typeof Object.defineProperties?Object.defineProperty:function(C,F,E){if(C==Array.prototype||C==Object.prototype)return C;C[F]=E.value;return C};function ba(C){C=["object"==typeof globalThis&&globalThis,C,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var F=0;FE&&(E=Math.max(E+S,0));E'); + // ECMASCRIPT_202 + $data = substr($data, 0, $p1) . substr($data, $p1 + 6, -6) . "\n"; + // ECMASCRIPT_2018 +// $data = substr($data, 0, $p1) . substr($data, $p1 + 19, -6) . "\n"; + // ECMASCRIPT6 +// $data = substr($data, 0, $p1) . substr($data, strpos($data, 'return!0}return!1}});') + 22, -6) . "\n"; + + file_put_contents($file, $data); + exit; +} + +if (isset($options['openpgp'])) { + chdir(ROOT_DIR . '/vendors/openpgp-5'); + execCmd('git pull', $return_var); + execCmd('./build.sh', $return_var); + exit; +} + +if (isset($options['squire'])) { + chdir(ROOT_DIR . '/vendors/squire2'); +// execCmd('git pull', $return_var); +// execCmd('git checkout snappymail', $return_var); + execCmd('./build.sh', $return_var); + exit; +} + +if (isset($options['source'])) { + #cd ROOT_DIR . '/vendors/openpgp-2.6.2/'; + #rollup -c + #npm cache clean --force + + if ($gulp = trim(`which gulp`)) { + execCmd($gulp, $return_var); + } else { + exit('gulp not installed, run as root: npm install --global gulp-cli'); + } + + #rollup -c + + $cmddir = escapeshellcmd(ROOT_DIR) . '/snappymail/v/0.0.0/static'; + + if ($gzip = trim(`which gzip`)) { + echo "\x1b[33;1m === Gzip *.js and *.css === \x1b[0m\n"; +// execCmd("{$gzip} -k --best {$cmddir}/js/*.js"); + execCmd("{$gzip} -k --best {$cmddir}/js/min/*.js"); + execCmd("{$gzip} -k --best {$cmddir}/css/admin*.css"); + execCmd("{$gzip} -k --best {$cmddir}/css/app*.css"); +// unlink($cmddir . '/js/boot.js.gz'); +// unlink($cmddir . '/js/min/boot.min.js.gz'); + } + + if ($brotli = trim(`which brotli`)) { + echo "\x1b[33;1m === Brotli *.js and *.css === \x1b[0m\n"; +// execCmd("{$brotli} -k --best {$cmddir}/js/*.js"); + execCmd("{$brotli} -k --best {$cmddir}/js/min/*.js"); + execCmd("{$brotli} -k --best {$cmddir}/css/admin*.css"); + execCmd("{$brotli} -k --best {$cmddir}/css/app*.css"); +// unlink($cmddir . '/js/boot.js.br'); +// unlink($cmddir . '/js/min/boot.min.js.br'); + } +} + +if (isset($options['readme'])) { + + $basedir = escapeshellcmd(ROOT_DIR) . '/snappymail/v/0.0.0'; + + $readme = file_get_contents(ROOT_DIR . '/README.md'); + + $total = filesize("{$basedir}/static/js/serviceworker.js"); + foreach (['admin','app','boot','libs','sieve'] as $name) { + $size = filesize("{$basedir}/static/js/{$name}.js"); + $total += $size; + $readme = preg_replace("/(\\|{$name}\\.js\\s+\\|[\\s0-9.]+\\|)[\\s0-9.]+\\|/Ds", + '$1' . str_pad(number_format($size, 0, '', '.'), 9, ' ', STR_PAD_LEFT) . ' |', + $readme + ); + } + $readme = preg_replace("/(\\|TOTAL\\s+\\|8[\\s0-9.]+\\|)[\\s0-9.]+\\|/Ds", + '$1' . str_pad(number_format($total, 0, '', '.'), 9, ' ', STR_PAD_LEFT) . ' |', + $readme + ); + + $total = []; + $total_br = []; + $total_gz = []; + foreach (['admin','app','boot','libs','sieve'] as $name) { + $size = filesize("{$basedir}/static/js/min/{$name}.min.js"); + $size_br = filesize("{$basedir}/static/js/min/{$name}.min.js.br"); + $size_gz = filesize("{$basedir}/static/js/min/{$name}.min.js.gz"); + $total[$name] = $size; + $total_br[$name] = $size_br; + $total_gz[$name] = $size_gz; + $readme = preg_replace( + "/(\\|{$name}\\.min\\.js\\s+\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+\\|/Ds", + '$1|' + . str_pad(number_format($size, 0, '', '.'), 9, ' ', STR_PAD_LEFT) . ' ' + . '$2|' + . str_pad(number_format($size_gz, 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '$3|' + . str_pad(number_format($size_br, 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '|' + , + $readme + ); + } + $readme = preg_replace( + "/(TOTAL user\+sieve\\s+\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+\\|/Ds", + '$1|' + . str_pad(number_format(array_sum($total) - $total['admin'], 0, '', '.'), 9, ' ', STR_PAD_LEFT) . ' ' + . '$2|' + . str_pad(number_format(array_sum($total_gz) - $total_gz['admin'], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '$3|' + . str_pad(number_format(array_sum($total_br) - $total_br['admin'], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '|' + , + $readme + ); + + //$smaller = 1217635 / (array_sum($total) - $total['admin']); + $smaller = 358761 / (array_sum($total_gz) - $total_gz['admin']); + //$smaller = 299485 / (array_sum($total_br) - $total_br['admin']); + $readme = preg_replace( + '/(For a user it is around) [0-9]+/', + '$1 ' . round(100 - 100 / $smaller), + $readme + ); + + unset($total['sieve']); + unset($total_br['sieve']); + unset($total_gz['sieve']); + $readme = preg_replace( + "/(TOTAL user\\s+\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+\\|/Ds", + '$1|' + . str_pad(number_format(array_sum($total) - $total['admin'], 0, '', '.'), 9, ' ', STR_PAD_LEFT) . ' ' + . '$2|' + . str_pad(number_format(array_sum($total_gz) - $total_gz['admin'], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '$3|' + . str_pad(number_format(array_sum($total_br) - $total_br['admin'], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '|' + , + $readme + ); + $readme = preg_replace( + "/(TOTAL admin\\s+\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+\\|/Ds", + '$1|' + . str_pad(number_format(array_sum($total) - $total['app'], 0, '', '.'), 9, ' ', STR_PAD_LEFT) . ' ' + . '$2|' + . str_pad(number_format(array_sum($total_gz) - $total_gz['app'], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '$3|' + . str_pad(number_format(array_sum($total_br) - $total_br['app'], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '|' + , + $readme + ); + + foreach (['admin','app'] as $name) { + $readme = preg_replace( + "/(\\|{$name}\\.css\\s+\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+\\|[\\s0-9.]+\\|/Ds", + '$1|' + . str_pad(number_format(filesize("{$basedir}/static/css/{$name}.css"), 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '$2|' + . str_pad(number_format(filesize("{$basedir}/static/css/{$name}.css.gz"), 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' |' + . str_pad(number_format(filesize("{$basedir}/static/css/{$name}.css.br"), 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '|' + , + $readme + ); + $readme = preg_replace( + "/(\\|{$name}\\.min\\.css\\s+\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+\\|[\\s0-9.]+\\|/Ds", + '$1|' + . str_pad(number_format(filesize("{$basedir}/static/css/{$name}.min.css"), 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '$2|' + . str_pad(number_format(filesize("{$basedir}/static/css/{$name}.min.css.gz"), 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' |' + . str_pad(number_format(filesize("{$basedir}/static/css/{$name}.min.css.br"), 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '|' + , + $readme + ); + } + + + $sizes = [ + filesize("{$basedir}/static/js/min/openpgp.min.js"), + filesize("{$basedir}/static/js/min/openpgp.min.js.gz"), + filesize("{$basedir}/static/js/min/openpgp.min.js.br") + ]; + //|openpgp.min.js | 330.742 | 540.542 |102.388 | 167.397 | 84.241 | 137.502 | + $readme = preg_replace( + "/(openpgp\.min\.js\\s+\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+(\\|[\\s0-9.]+)\\|[\\s0-9.]+\\|/Ds", + '$1|' + . str_pad(number_format($sizes[0], 0, '', '.'), 9, ' ', STR_PAD_LEFT) . ' ' + . '$2| ' + . str_pad(number_format($sizes[1], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '$3| ' + . str_pad(number_format($sizes[2], 0, '', '.'), 7, ' ', STR_PAD_LEFT) . ' ' + . '|' + , + $readme + ); + + //file:// + + file_put_contents(ROOT_DIR . '/README.md', $readme); + + $data = file_put_contents( + "{$basedir}/app/templates/Views/Admin/AdminSettingsAbout.html", + preg_replace( + '/20[0-9][0-9] ©/', + date('Y').' ©', + file_get_contents("{$basedir}/app/templates/Views/Admin/AdminSettingsAbout.html") + ) + ); +} diff --git a/cli/release.php b/cli/release.php index aac915b02..fe406f7f3 100755 --- a/cli/release.php +++ b/cli/release.php @@ -9,7 +9,7 @@ if (isset($options['plugins'])) { require(ROOT_DIR . '/build/plugins.php'); } -$gulp = trim(`which gulp`); +$gulp = trim(shell_exec('which gulp')); if (!$gulp) { exit('gulp not installed, run as root: npm install --global gulp-cli'); } @@ -34,7 +34,7 @@ $options['aur'] = isset($options['aur']); // Docker build $options['docker'] = isset($options['docker']); -if ($options['docker'] && !($docker = trim(`which docker`))) { +if ($options['docker'] && !($docker = trim(shell_exec('which docker')))) { exit('docker not found'); } if ($options['docker'] && $options['aur']) { @@ -60,7 +60,7 @@ if (!isset($options['skip-gulp'])) { $cmddir = escapeshellcmd(ROOT_DIR) . '/snappymail/v/0.0.0/static'; - if ($gzip = trim(`which gzip`)) { + if ($gzip = trim(shell_exec('which gzip'))) { echo "\x1b[33;1m === Gzip *.js and *.css === \x1b[0m\n"; passthru("{$gzip} -k --best {$cmddir}/js/*.js"); passthru("{$gzip} -k --best {$cmddir}/js/min/*.js"); @@ -70,7 +70,7 @@ if (!isset($options['skip-gulp'])) { unlink(ROOT_DIR . '/snappymail/v/0.0.0/static/js/min/boot.min.js.gz'); } - if ($brotli = trim(`which brotli`)) { + if ($brotli = trim(shell_exec('which brotli'))) { echo "\x1b[33;1m === Brotli *.js and *.css === \x1b[0m\n"; passthru("{$brotli} -k --best {$cmddir}/js/*.js"); passthru("{$brotli} -k --best {$cmddir}/js/min/*.js"); @@ -188,7 +188,7 @@ file_put_contents("{$destPath}core.json", '{ // Arch User Repository if ($options['aur']) { // extension_loaded('blake2') - if (!function_exists('b2sum') && $b2sum = trim(`which b2sum`)) { + if (!function_exists('b2sum') && $b2sum = trim(shell_exec('which b2sum'))) { function b2sum($file) { $file = escapeshellarg($file); exec("b2sum --binary {$file} 2>&1", $output, $exitcode); diff --git a/dev/App/User.js b/dev/App/User.js index aafec7da3..5e28164ac 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -1,6 +1,7 @@ import 'External/User/ko'; import { SMAudio } from 'Common/Audio'; +import { arrayLength } from 'Common/Utils'; import { mailToHelper, setLayoutResizer, dropdownsDetectVisibility, loadAccountsAndIdentities } from 'Common/UtilsUser'; import { @@ -240,7 +241,15 @@ export class AppUser extends AbstractApp { showMessageComposer(params = []) { - showScreenPopup(ComposePopupView, params); + let msg = params[1]; + if (1 == arrayLength(msg)) { + msg = msg[0]; + } + if (msg) { + msg.decrypt().then((/*success*/)=>showScreenPopup(ComposePopupView, params)); + } else { + showScreenPopup(ComposePopupView, params); + } } } @@ -251,7 +260,8 @@ AskPopupView.password = function(sAskDesc, btnText, ask) { view => resolve({ password:view.passphrase(), username:/*ask & 2 ? */view.username(), - remember:/*ask & 4 ? */view.remember() + remember:/*ask & 4 ? */view.remember(), + rememberPermanent: view.rememberPermanent(), }), () => resolve(null), true, diff --git a/dev/Common/Html.js b/dev/Common/Html.js index 0be300ec9..d495a8c31 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -232,6 +232,7 @@ export const detectHiddenImages = true, // !!SettingsGet('try_to_detect_hidden_images'), bqLevel = parseInt(SettingsUserStore.maxBlockquotesLevel()), + minFontSize = parseInt(SettingsUserStore.minFontSize()), result = { hasExternals: false, @@ -257,6 +258,15 @@ export const rightmargin: (value, node) => node.style.marginRight = pInt(value) + 'px' }, allowedAttributes = [ + // SnappyMail +// 'data-x-href-broken', +// 'data-x-href-tracking', + 'data-x-src', +// 'data-x-src-hidden', +// 'data-x-src-tracking', +// 'data-x-src-broken', +// 'data-x-style-url', +// 'data-x-style-broken-urls', // defaults 'name', 'dir', 'lang', 'style', 'title', @@ -299,8 +309,11 @@ export const // Not supported by