On Mon, 25 Sep 2023 at 15:15:57 +0200, Guilhem Moulin wrote: > [ Other info ] > > In addition to the debdiff.gz between 1.6.1+dfsg-1 (bookworm) and 1.6.3+dfsg-1~deb12u1, > I attach a patch-applied diff excluding upstream's tests/**, program/localization/**, > and plugins/*/localization/**, which should more accurately show what > this p-u is about. Now with the attachement :-) > If you think that 1.6.3+dfsg-1~deb12u1 is beyond the scope of bookworm-pu then > I'll prepare another upload, this time backporting the aforementioned > regressions and security issue instead of following the upstream stable > branch. -- Guilhem.
diff --git a/.github/run.sh b/.github/run.sh index 6a16b2b53..e15fbe47e 100755 --- a/.github/run.sh +++ b/.github/run.sh @@ -3,8 +3,8 @@ # The script is intended for use on Travis with Trusty distribution # It installs in-browser tests dependencies and prepares Roundcube instance -GMV=1.5.11 -CHROMEVERSION=$(google-chrome-stable --version | tr -cd [:digit:]. | cut -d . -f 1) +GMV=1.6.13 +CHROMEVERSION=$(google-chrome-stable --version | tr -cd [:digit:].) GMARGS="-Dgreenmail.setup.all -Dgreenmail.users=test:test -Dgreenmail.startup.timeout=3000" # Make temp and logs writeable diff --git a/.github/workflows/browser_tests.yml b/.github/workflows/browser_tests.yml index 8c1308f7f..5798ad8f5 100644 --- a/.github/workflows/browser_tests.yml +++ b/.github/workflows/browser_tests.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -41,10 +41,7 @@ jobs: - name: Setup composer run: | cp composer.json-dist composer.json - composer require "laravel/dusk:~6.9.0" --no-update - - - name: Fix PHPUnit for PHP8 - run: composer config platform.php 7.4 + composer require "laravel/dusk:^7.9" --no-update - name: Install dependencies run: composer install --prefer-dist --no-interaction --no-progress diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a9b13e330..af41d3b21 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,13 +12,13 @@ jobs: strategy: fail-fast: true matrix: - php: ["7.3", "7.4", "8.0", "8.1"] + php: ["7.3", "7.4", "8.0", "8.1", "8.2", "8.3"] name: PHP ${{ matrix.php }}/Linux steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -39,9 +39,9 @@ jobs: cp composer.json-dist composer.json composer require "kolab/net_ldap3:~1.1.1" --no-update - - name: Fix PHPUnit for PHP 8.1 - run: composer config platform.php 8.0 - if: matrix.php >= 8.1 + - name: Fix PHPUnit for PHP 8.2 + run: composer config platform.php 8.1 + if: matrix.php >= 8.2 - name: Install dependencies run: composer install --prefer-dist --no-interaction --no-progress diff --git a/.tx/config b/.tx/config index b6e5674a2..e86eabe63 100644 --- a/.tx/config +++ b/.tx/config @@ -1,99 +1,99 @@ [main] -host = https://www.transifex.com -lang_map = be: be_BE, sr: sr_CS, fa: fa_AF, lb: lb_LU, fr: fr_FR -type = PHP_ALT_ARRAY +host = https://www.transifex.com +lang_map = sr: sr_CS, fa: fa_AF, lb: lb_LU, fr: fr_FR, be: be_BE -[roundcube-webmail.labels] +[o:roundcube:p:roundcube-webmail:r:labels] file_filter = program/localization/<lang>/labels.inc source_file = program/localization/en_US/labels.inc source_lang = en_US -[roundcube-webmail.messages] +[o:roundcube:p:roundcube-webmail:r:messages] file_filter = program/localization/<lang>/messages.inc source_file = program/localization/en_US/messages.inc source_lang = en_US -[roundcube-webmail.timezones] -file_filter = program/localization/<lang>/timezones.inc -source_file = program/localization/en_US/timezones.inc -source_lang = en_US - -[roundcube-webmail.plugin-acl] +[o:roundcube:p:roundcube-webmail:r:plugin-acl] file_filter = plugins/acl/localization/<lang>.inc source_file = plugins/acl/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-archive] +[o:roundcube:p:roundcube-webmail:r:plugin-archive] file_filter = plugins/archive/localization/<lang>.inc source_file = plugins/archive/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-attachment_reminder] +[o:roundcube:p:roundcube-webmail:r:plugin-attachment_reminder] file_filter = plugins/attachment_reminder/localization/<lang>.inc source_file = plugins/attachment_reminder/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-emoticons] +[o:roundcube:p:roundcube-webmail:r:plugin-emoticons] file_filter = plugins/emoticons/localization/<lang>.inc source_file = plugins/emoticons/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-enigma] +[o:roundcube:p:roundcube-webmail:r:plugin-enigma] file_filter = plugins/enigma/localization/<lang>.inc source_file = plugins/enigma/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-help] +[o:roundcube:p:roundcube-webmail:r:plugin-help] file_filter = plugins/help/localization/<lang>.inc source_file = plugins/help/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-hide_blockquote] +[o:roundcube:p:roundcube-webmail:r:plugin-hide_blockquote] file_filter = plugins/hide_blockquote/localization/<lang>.inc source_file = plugins/hide_blockquote/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-managesieve] +[o:roundcube:p:roundcube-webmail:r:plugin-managesieve] file_filter = plugins/managesieve/localization/<lang>.inc source_file = plugins/managesieve/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-markasjunk] +[o:roundcube:p:roundcube-webmail:r:plugin-markasjunk] file_filter = plugins/markasjunk/localization/<lang>.inc source_file = plugins/markasjunk/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-new_user_dialog] +[o:roundcube:p:roundcube-webmail:r:plugin-new_user_dialog] file_filter = plugins/new_user_dialog/localization/<lang>.inc source_file = plugins/new_user_dialog/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-newmail_notifier] +[o:roundcube:p:roundcube-webmail:r:plugin-newmail_notifier] file_filter = plugins/newmail_notifier/localization/<lang>.inc source_file = plugins/newmail_notifier/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-password] +[o:roundcube:p:roundcube-webmail:r:plugin-password] file_filter = plugins/password/localization/<lang>.inc source_file = plugins/password/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-subscriptions_option] +[o:roundcube:p:roundcube-webmail:r:plugin-subscriptions_option] file_filter = plugins/subscriptions_option/localization/<lang>.inc source_file = plugins/subscriptions_option/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-userinfo] +[o:roundcube:p:roundcube-webmail:r:plugin-userinfo] file_filter = plugins/userinfo/localization/<lang>.inc source_file = plugins/userinfo/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-vcard_attachments] +[o:roundcube:p:roundcube-webmail:r:plugin-vcard_attachments] file_filter = plugins/vcard_attachments/localization/<lang>.inc source_file = plugins/vcard_attachments/localization/en_US.inc source_lang = en_US -[roundcube-webmail.plugin-zipdownload] +[o:roundcube:p:roundcube-webmail:r:plugin-zipdownload] file_filter = plugins/zipdownload/localization/<lang>.inc source_file = plugins/zipdownload/localization/en_US.inc source_lang = en_US + +[o:roundcube:p:roundcube-webmail:r:timezones] +file_filter = program/localization/<lang>/timezones.inc +source_file = program/localization/en_US/timezones.inc +source_lang = en_US + diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8635951..f6dc05b10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog Roundcube Webmail +## Unreleased + +- Fix bug where installto.sh/update.sh scripts were removing some essential options from the config file (#9051) +- Update jQuery-UI to version 1.13.2 (#9041) +- Fix regression that broke use_secure_urls feature (#9052) +- Fix potential PHP fatal error when opening a message with message/rfc822 part (#8953) +- Fix bug where a duplicate `<title>` tag in HTML email could cause some parts being cut off (#9029) +- Fix bug where a list of folders could have been sorted incorrectly (#9057) +- Fix regression where LDAP addressbook 'filter' option was ignored (#9061) +- Fix wrong order of a multi-folder search result when sorting by size (#9065) +- Fix so install/update scripts do not require PEAR (#9037) +- Fix regression where some mail parts could have been decoded incorrectly, or not at all (#9096) +- Fix handling of an error case in Cyrus IMAP BINARY FETCH, fallback to non-binary FETCH (#9097) +- Fix PHP8 deprecation warning in the reconnect plugin (#9083) +- Fix "Show source" on mobile with x_frame_options = deny (#9084) +- Fix various PHP warnings (#9098) +- Fix deprecated use of ldap_connect() in password's ldap_simple driver (#9060) +- Fix cross-site scripting (XSS) vulnerability in handling of linkrefs in plain text messages + +## Release 1.6.2 + +- Add Uyghur localization +- Fix regression in OAuth request URI caused by use of REQUEST_URI instead of SCRIPT_NAME as a default (#8878) +- Fix bug where false attachment reminder was displayed on HTML mail with inline images (#8885) +- Fix bug where a non-ASCII character in app.js could cause error in javascript engine (#8894) +- Fix JWT decoding with url safe base64 schema (#8890) +- Fix bug where .wav instead of .mp3 file was used for the new mail notification in Firefox (#8895) +- Fix PHP8 warning (#8891) +- Fix support for Windows-31J charset (#8869) +- Fix so LDAP VLV option is disabled by default as documented (#8833) +- Fix so an email address with name is supported as input to the managesieve notify :from parameter (#8918) +- Fix Help plugin menu (#8898) +- Fix invalid onclick handler on the logo image when using non-array skin_logo setting (#8933) +- Fix duplicate recipients in "To" and "Cc" on reply (#8912) +- Fix bug where it wasn't possible to scroll lists by clicking middle mouse button (#8942) +- Fix bug where label text in a single-input dialog could be partially invisible in some locales (#8905) +- Fix bug where LDAP (fulltext) search didn't work without 'search_fields' in config (#8874) +- Fix extra leading newlines in plain text converted from HTML (#8973) +- Fix so recipients with a domain ending with .s are allowed (#8854) +- Fix so vCard output does not contain non-standard/redundant TYPE=OTHER and TYPE=INTERNET (#8838) +- Fix QR code images for contacts with non-ASCII characters (#9001) +- Fix PHP8 warnings when using list_flags and list_cols properties by plugins (#8998) +- Fix bug where subfolders could loose subscription on parent folder rename (#8892) +- Fix connecting to LDAP using an URI with ldapi:// scheme (#8990) +- Fix insecure shell command params handling in cmd_learn driver of markasjunk plugin (#9005) +- Fix bug where some mail headers didn't work in cmd_learn driver of markasjunk plugin (#9005) +- Fix PHP fatal error when importing vcf file using PHP 8.2 (#9025) +- Fix so output of log_date_format with microseconds contains time in server time zone, not UTC + ## Release 1.6.1 - Kill session if refreshing oauth token fails (#8734) diff --git a/Makefile b/Makefile index 0c0288123..36a8ac610 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,12 @@ # GITREMOTE=https://github.com/roundcube/roundcubemail.git -GITBRANCH=master +GITBRANCH=release-1.6 GPGKEY=devs@roundcube.net VERSION=1.6-git SEDI=sed -i WHICH=which +PHP_VERSION=7.3 UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) @@ -19,8 +20,13 @@ all: clean complete dependent framework complete: roundcubemail-git cp -RH roundcubemail-git roundcubemail-$(VERSION) - (cd roundcubemail-$(VERSION); jq '.require += {"kolab/net_ldap3": "~1.1.1"} | del(.suggest."kolab/net_ldap3")' --indent 4 composer.json-dist > composer.json) - (cd roundcubemail-$(VERSION); php /tmp/composer.phar install --prefer-dist --no-dev --ignore-platform-reqs --no-interaction) + (cd roundcubemail-$(VERSION); cp composer.json-dist composer.json) + (cd roundcubemail-$(VERSION); php /tmp/composer.phar config platform.php $(PHP_VERSION)) + (cd roundcubemail-$(VERSION); php /tmp/composer.phar require "kolab/net_ldap3:~1.1.1" --no-update --no-install) + (cd roundcubemail-$(VERSION); php /tmp/composer.phar config --unset suggest.kolab/net_ldap3) + (cd roundcubemail-$(VERSION); php /tmp/composer.phar config --unset require-dev) + (cd roundcubemail-$(VERSION); php /tmp/composer.phar install --prefer-dist --no-dev --no-interaction) + (cd roundcubemail-$(VERSION); php /tmp/composer.phar config --unset platform) (cd roundcubemail-$(VERSION); bin/install-jsdeps.sh --force) (cd roundcubemail-$(VERSION); bin/jsshrink.sh program/js/publickey.js; bin/jsshrink.sh plugins/managesieve/codemirror/lib/codemirror.js) (cd roundcubemail-$(VERSION); rm -f jsdeps.json bin/install-jsdeps.sh *.orig; rm -rf temp/js_cache) @@ -73,7 +79,6 @@ buildtools: /tmp/composer.phar npm install lessc npm install less-plugin-clean-css npm install csso-cli - @$(WHICH) jq || echo "!!!!!! Please install jq (https://stedolan.github.io/jq/) !!!!!!" /tmp/composer.phar: curl -sS https://getcomposer.org/installer | php -- --install-dir=/tmp/ diff --git a/SQL/mysql/2016112200.sql b/SQL/mysql/2016112200.sql index 62c974946..1bfc9ef64 100644 --- a/SQL/mysql/2016112200.sql +++ b/SQL/mysql/2016112200.sql @@ -1,4 +1,5 @@ -ALTER TABLE `dictionary` ADD COLUMN `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST; -- redundant, for compat. with Galera Cluster +-- redundant column, for compat. with Galera Cluster +ALTER TABLE `dictionary` ADD COLUMN `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST; DROP TABLE `cache`; DROP TABLE `cache_shared`; diff --git a/bin/transifexpull.sh b/bin/transifexpull.sh index a8f150288..ad78fbc2d 100755 --- a/bin/transifexpull.sh +++ b/bin/transifexpull.sh @@ -1,6 +1,9 @@ #!/bin/sh TX=`which tx` +PWD=`dirname "$0"` + +cd $PWD/.. # In 'translator' mode files will contain empty translated texts # where translation is not available, we'll remove these later @@ -8,9 +11,7 @@ TX=`which tx` # Note: there's a bug in txclib, so if the command below doesn't # work see https://github.com/transifex/transifex-client/commit/a80320735973dd608b48520bf3b89ad53e2b088b -$TX --debug pull -a -f --mode translator - -PWD=`dirname "$0"` +$TX pull -a -f --mode translator do_clean() { @@ -32,15 +33,15 @@ do_clean() } # clean up translation files -for file in $PWD/../program/localization/*/*.inc; do +for file in program/localization/*/*.inc; do do_clean $file done -for file in $PWD/../plugins/*/localization/*.inc; do +for file in plugins/*/localization/*.inc; do do_clean $file done # remove empty localization files -for file in $PWD/../program/localization/*/labels.inc; do grep -q -E '\$labels' $file || rm $file; done -for file in $PWD/../program/localization/*/timezones.inc; do grep -q -E '\$labels' $file || rm $file; done -for file in $PWD/../program/localization/*/messages.inc; do grep -q -E '\$messages' $file || rm $file; done -for file in $PWD/../plugins/*/localization/*.inc; do grep -q -E '\$(labels|messages)' $file || rm $file; done +for file in program/localization/*/labels.inc; do grep -q -E '\$labels' $file || rm $file; done +for file in program/localization/*/timezones.inc; do grep -q -E '\$labels' $file || rm $file; done +for file in program/localization/*/messages.inc; do grep -q -E '\$messages' $file || rm $file; done +for file in plugins/*/localization/*.inc; do grep -q -E '\$(labels|messages)' $file || rm $file; done diff --git a/bin/update.sh b/bin/update.sh index a35dce347..de6c10cc2 100755 --- a/bin/update.sh +++ b/bin/update.sh @@ -90,7 +90,7 @@ if ($RCI->configured) { $error = $written = false; if (!DEBIAN_PKG) { - echo ". backing up the current config file(s)...\n"; + echo "- backing up the current config file(s)...\n"; foreach (['config', 'main', 'db'] as $file) { if (file_exists(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php')) { @@ -103,7 +103,7 @@ if ($RCI->configured) { if (!$error) { $RCI->merge_config(); - echo ". writing " . RCMAIL_CONFIG_DIR . "/config.inc.php".(DEBIAN_PKG ? ".dpkg-new" : "")."...\n"; + echo "- writing " . RCMAIL_CONFIG_DIR . "/config.inc.php".(DEBIAN_PKG ? ".dpkg-new" : "")."...\n"; $written = $RCI->save_configfile($RCI->create_config(false)); } diff --git a/config/defaults.inc.php b/config/defaults.inc.php index b615f7d78..4338ec927 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -311,11 +311,11 @@ $config['smtp_timeout'] = 0; // The example below enables server certificate validation, and // requires 'smtp_timeout' to be non zero. // $config['smtp_conn_options'] = [ -// 'ssl' => [ -// 'verify_peer' => true, -// 'verify_depth' => 3, -// 'cafile' => '/etc/openssl/certs/ca.crt', -// ], +// 'ssl' => [ +// 'verify_peer' => true, +// 'verify_depth' => 3, +// 'cafile' => '/etc/openssl/certs/ca.crt', +// ], // ]; // Note: These can be also specified as an array of options indexed by hostname $config['smtp_conn_options'] = null; @@ -799,6 +799,7 @@ $config['no_save_sent_messages'] = false; // Warning: This requires http server configuration. Sample: // RewriteRule ^/roundcubemail/[a-zA-Z0-9]{16}/(.*) /roundcubemail/$1 [PT] // Alias /roundcubemail /var/www/roundcubemail/ +// Warning: This feature does NOT work with request_path = 'SCRIPT_NAME' // Note: Use assets_path to not prevent the browser from caching assets $config['use_secure_urls'] = false; @@ -808,7 +809,7 @@ $config['use_secure_urls'] = false; // The reverse proxy config can specify a custom header (e.g. X-Forwarded-Path) containing // the path under which Roundcube is exposed to the outside world (e.g. /rcube/). // This header value is then available in PHP with $_SERVER['HTTP_X_FORWARDED_PATH']. -// By default the path comes from 'REQUEST_URI', 'REDIRECT_SCRIPT_URL' or 'SCRIPT_NAME', +// By default the path comes from 'REDIRECT_SCRIPT_URL', 'SCRIPT_NAME' or 'REQUEST_URI', // whichever is set (in this order). $config['request_path'] = null; @@ -1098,10 +1099,12 @@ $config['ldap_public']['Verisign'] = [ // to be one of the search_fields, the base of base_dn is appended // to the RDN to insert into the LDAP directory. 'LDAP_rdn' => 'cn', - // The required fields needed to build a new contact as required by + // The required attributes needed to build a new contact as required by // the object classes (can include additional fields not required by the object classes). 'required_fields' => ['cn', 'sn', 'mail'], - 'search_fields' => ['mail', 'cn'], // fields to search in + // The attributes used when searching with "All fields" option + // If empty, attributes for name, surname, firstname and email fields will be used + 'search_fields' => ['mail', 'cn'], // mapping of contact fields to directory attributes // 1. for every attribute one can specify the number of values (limit) allowed. // default is 1, a wildcard * means unlimited diff --git a/debian/README.source b/debian/README.source index 215fd8447..fc72e3815 100644 --- a/debian/README.source +++ b/debian/README.source @@ -22,9 +22,7 @@ following: 1. Merge the newest upstream release tag into the 'debian/latest' branch of your packaging repository: - $ UPSTREAM_VERSION=1.6.0 - $ gbp import-orig --uscan --upstream-version=$UPSTREAM_VERSION+dfsg \ - --upstream-vcs-tag=$UPSTREAM_VERSION + $ gbp import-orig --uscan That commands does all the magic, namely - updating the `upstream` remote, @@ -34,12 +32,10 @@ following: as additional parent, and - merging 'upstream/$VERSION' into 'debian/latest' - We use --upstream-version and --upstream-vcs-tag since it doesn't - trim the +dfsg repack suffix, see https://bugs.debian.org/1009163 . - 2. Optionally, move upstream tarballs and detached signatures to the build area: + $ UPSTREAM_VERSION=1.6.2 $ mv -vt "$XDG_CACHE_HOME/build-area" ../roundcube[-_]"$UPSTREAM_VERSION"*.tar.* This step is needed when gbp.conf(5) sets a non-default export-dir, see diff --git a/debian/changelog b/debian/changelog index 8bdf27139..9110c1f06 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,46 @@ +roundcube (1.6.3+dfsg-1~deb12u1) bookworm; urgency=medium + + * Rebuild for bookworm. + * Salsa CI: Set RELEASE=bookworm. + * d/gbp.conf: Set --debian-branch=debian/bookworm. + + -- Guilhem Moulin <guilhem@debian.org> Mon, 25 Sep 2023 14:22:10 +0200 + +roundcube (1.6.3+dfsg-1) unstable; urgency=medium + + * New upstream security and bugfix release: + + Fix CVE-2023-43770: cross-site scripting (XSS) vulnerability in handling + of linkrefs in plain text messages. (Closes: #1052059) + + Fix regression that broke use_secure_urls feature hence OAuth2 + authentication. (Closes: #1050317) + + Fix regression where LDAP addressbook 'filter' option was ignored. + + Fix regression in decoding mail parts FETCHed from IMAP. + + Fix PHP8 warnings. + * roundcube-core.cron: Trigger gc twice every hour. (Closes: #1043395) + * Fix GuzzleHttp autoload location. (Closes: #1040705) + * d/p/fix-autoload-location.patch: Set ‘Forwarded: not-needed’ DEP-3 header. + * Refresh d/patches. + + -- Guilhem Moulin <guilhem@debian.org> Mon, 18 Sep 2023 14:18:17 +0200 + +roundcube (1.6.2+dfsg-1) unstable; urgency=medium + + [ Amin Bandali ] + * Test suite: Adjust short date test to make it work with all ICUs. + (Closes: #1030161) + + [ Remus-Gabriel Chelu ] + * Add Romanian debconf templates translation. (Closes: #1033468) + + [ Guilhem Moulin ] + * New upstream bugfix release. + * d/gbp.conf, d/README.source: Remove obsolete comment. + * d/sql/mysql/1.3.0-1: Move inline comment. + * d/p/fix-short-date-test-icu72.patch: Remove patch applied upstream. + * Refresh patches. + + -- Guilhem Moulin <guilhem@debian.org> Sun, 02 Jul 2023 11:54:33 +0200 + roundcube (1.6.1+dfsg-1) unstable; urgency=medium * New upstream bugfix release. diff --git a/debian/gbp.conf b/debian/gbp.conf index 771f71bc5..8ed295e5d 100644 --- a/debian/gbp.conf +++ b/debian/gbp.conf @@ -1,12 +1,10 @@ [DEFAULT] -debian-branch = debian/latest +debian-branch = debian/bookworm upstream-branch = upstream/release-1.6 pristine-tar = True components = ["tinymce", "tinymce-langs"] [import-orig] -# XXX the +dfsg suffix isn't stripped so for now we have to manually use -# `v=1.5.1; gbp import-orig --uscan --upstream-version=$v+dfsg --upstream-vcs-tag=$v` upstream-vcs-tag = %(version)s [pq] diff --git a/debian/patches/default-charset-utf8.patch b/debian/patches/default-charset-utf8.patch index e454e82cd..0894ad216 100644 --- a/debian/patches/default-charset-utf8.patch +++ b/debian/patches/default-charset-utf8.patch @@ -8,10 +8,10 @@ Forwarded: not-needed 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/defaults.inc.php b/config/defaults.inc.php -index 77e843f..b615f7d 100644 +index 00f1a8d..4338ec9 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php -@@ -1256,7 +1256,7 @@ $config['collected_senders'] = true; +@@ -1259,7 +1259,7 @@ $config['collected_senders'] = true; // ---------------------------------- // Use this charset as fallback for message decoding diff --git a/debian/patches/dont-force-set-session.gc_probability=1.patch b/debian/patches/dont-force-set-session.gc_probability=1.patch index 824ca39a0..b9c90513e 100644 --- a/debian/patches/dont-force-set-session.gc_probability=1.patch +++ b/debian/patches/dont-force-set-session.gc_probability=1.patch @@ -16,7 +16,7 @@ This reverts upstream commit 32a0ad6778cde495e30f3447e5220136f0528cee. 1 file changed, 7 deletions(-) diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php -index 31fba2e..1f75526 100644 +index 83ef67a..57e4d0f 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -450,7 +450,6 @@ class rcube diff --git a/debian/patches/fix-BaconQrCode-autoload-location.patch b/debian/patches/fix-BaconQrCode-autoload-location.patch deleted file mode 100644 index 9c45fdb6a..000000000 --- a/debian/patches/fix-BaconQrCode-autoload-location.patch +++ /dev/null @@ -1,23 +0,0 @@ -From: Guilhem Moulin <guilhem@debian.org> -Date: Sun, 13 Mar 2022 00:08:19 +0100 -Subject: Fix BaconQrCode's autoload location - -Snippet generated with `phpabtpl --suggest bacon/bacon-qr-code`. ---- - program/actions/contacts/qrcode.php | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/program/actions/contacts/qrcode.php b/program/actions/contacts/qrcode.php -index 3511c79..8ab71e8 100644 ---- a/program/actions/contacts/qrcode.php -+++ b/program/actions/contacts/qrcode.php -@@ -17,6 +17,9 @@ - +-----------------------------------------------------------------------+ - */ - -+if (stream_resolve_include_path('Bacon/BaconQrCode/autoload.php')) -+ include_once('Bacon/BaconQrCode/autoload.php'); -+ - class rcmail_action_contacts_qrcode extends rcmail_action_contacts_index - { - protected static $mode = self::MODE_HTTP; diff --git a/debian/patches/fix-autoload-locations.patch b/debian/patches/fix-autoload-locations.patch new file mode 100644 index 000000000..adb4b30de --- /dev/null +++ b/debian/patches/fix-autoload-locations.patch @@ -0,0 +1,42 @@ +From: Guilhem Moulin <guilhem@debian.org> +Date: Sun, 13 Mar 2022 00:08:19 +0100 +Subject: Fix autoload locations + +Snippets generated with `phpabtpl --suggest bacon/bacon-qr-code` and +`phpabtpl --suggest GuzzleHttp`. + +Forwarded: not-needed +Bug-Debian: https://bugs.debian.org/1040705 +--- + program/actions/contacts/qrcode.php | 3 +++ + program/include/rcmail_oauth.php | 3 +++ + 2 files changed, 6 insertions(+) + +diff --git a/program/actions/contacts/qrcode.php b/program/actions/contacts/qrcode.php +index 0ece569..87c31da 100644 +--- a/program/actions/contacts/qrcode.php ++++ b/program/actions/contacts/qrcode.php +@@ -17,6 +17,9 @@ + +-----------------------------------------------------------------------+ + */ + ++if (stream_resolve_include_path('Bacon/BaconQrCode/autoload.php')) ++ include_once('Bacon/BaconQrCode/autoload.php'); ++ + class rcmail_action_contacts_qrcode extends rcmail_action_contacts_index + { + protected static $mode = self::MODE_HTTP; +diff --git a/program/include/rcmail_oauth.php b/program/include/rcmail_oauth.php +index b3f88fb..fc592ee 100644 +--- a/program/include/rcmail_oauth.php ++++ b/program/include/rcmail_oauth.php +@@ -17,6 +17,9 @@ + +-----------------------------------------------------------------------+ + */ + ++if (stream_resolve_include_path('GuzzleHttp/autoload.php')) ++ include_once 'GuzzleHttp/autoload.php'; ++ + use GuzzleHttp\Client; + use GuzzleHttp\MessageFormatter; + use GuzzleHttp\Exception\RequestException; diff --git a/debian/patches/fix-install-path.patch b/debian/patches/fix-install-path.patch index 0598f0d69..5bb357d47 100644 --- a/debian/patches/fix-install-path.patch +++ b/debian/patches/fix-install-path.patch @@ -166,7 +166,7 @@ index e7dd5cb..3c07a81 100755 require_once INSTALL_PATH.'program/include/clisetup.php'; diff --git a/bin/update.sh b/bin/update.sh -index a031304..f468f57 100755 +index 13f0846..c123385 100755 --- a/bin/update.sh +++ b/bin/update.sh @@ -18,7 +18,7 @@ @@ -218,10 +218,10 @@ index 1145e25..eb4a0ee 100644 require INSTALL_PATH . 'program/include/iniset.php'; diff --git a/program/include/iniset.php b/program/include/iniset.php -index 9faecad..1778953 100644 +index 6f9946e..c4ab39f 100644 --- a/program/include/iniset.php +++ b/program/include/iniset.php -@@ -28,7 +28,7 @@ define('RCMAIL_VERSION', '1.6.1'); +@@ -28,7 +28,7 @@ define('RCMAIL_VERSION', '1.6-git'); define('RCMAIL_START', microtime(true)); if (!defined('INSTALL_PATH')) { diff --git a/debian/patches/fix-upstream-test-suite.patch b/debian/patches/fix-upstream-test-suite.patch index d1fd9966b..c4bec6e37 100644 --- a/debian/patches/fix-upstream-test-suite.patch +++ b/debian/patches/fix-upstream-test-suite.patch @@ -2,21 +2,16 @@ From: Guilhem Moulin <guilhem@debian.org> Date: Tue, 20 Dec 2022 17:44:16 +0100 Subject: Fix upstream's test suite -In `$rcmail->format_date($date, 'x')`'s current output the 12-hour clock -time is followed by a narrow non-breaking space (U+202F) not a normal -ASCII space (0x20). - Also, in our environment phpunit(1) resides in /usr/bin not vendor/bin. --- tests/Rcmail/OutputHtml.php | 4 ++-- - tests/Rcmail/Rcmail.php | 2 +- - 2 files changed, 3 insertions(+), 3 deletions(-) + 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Rcmail/OutputHtml.php b/tests/Rcmail/OutputHtml.php -index 9c89183..d97f5e5 100644 +index 88b61c5..b38fbfe 100644 --- a/tests/Rcmail/OutputHtml.php +++ b/tests/Rcmail/OutputHtml.php -@@ -381,7 +381,7 @@ class Rcmail_RcmailOutputHtml extends PHPUnit\Framework\TestCase +@@ -383,7 +383,7 @@ class Rcmail_RcmailOutputHtml extends PHPUnit\Framework\TestCase $rcmail = rcube::get_instance(); $output = new rcmail_output_html(); @@ -25,7 +20,7 @@ index 9c89183..d97f5e5 100644 } /** -@@ -404,7 +404,7 @@ class Rcmail_RcmailOutputHtml extends PHPUnit\Framework\TestCase +@@ -406,7 +406,7 @@ class Rcmail_RcmailOutputHtml extends PHPUnit\Framework\TestCase $output = new rcmail_output_html(); $expected = '<form name="rcmqsearchform" onsubmit="rcmail.command(\'search\'); return false"' @@ -34,16 +29,3 @@ index 9c89183..d97f5e5 100644 . '<input name="_q" class="no-bs" id="rcmqsearchbox" placeholder="Search..." type="text"></form>'; $this->assertSame($expected, $output->search_form([])); -diff --git a/tests/Rcmail/Rcmail.php b/tests/Rcmail/Rcmail.php -index 8feb8a7..1466f74 100644 ---- a/tests/Rcmail/Rcmail.php -+++ b/tests/Rcmail/Rcmail.php -@@ -279,7 +279,7 @@ class Rcmail_Rcmail extends ActionTestCase - $this->assertSame(' Mon', $rcmail->format_date($date, ' D')); - $this->assertSame('D Monday', $rcmail->format_date($date, '\\D l')); - $this->assertSame('Jun June', $rcmail->format_date($date, 'M F')); -- $this->assertSame('6/1/20, 12:20 PM', $rcmail->format_date($date, 'x')); -+ $this->assertSame('6/1/20, 12:20 PM', $rcmail->format_date($date, 'x')); - $this->assertSame('1591014030', $rcmail->format_date($date, 'U')); - $this->assertSame('2020-06-01T12:20:30+00:00', $rcmail->format_date($date, 'c')); - } diff --git a/debian/patches/map-sqlite3-to-sqlite.patch b/debian/patches/map-sqlite3-to-sqlite.patch index 967afbdc7..6a14735c4 100644 --- a/debian/patches/map-sqlite3-to-sqlite.patch +++ b/debian/patches/map-sqlite3-to-sqlite.patch @@ -9,7 +9,7 @@ Forwarded: not-needed 1 file changed, 1 insertion(+) diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php -index 41ee42f..4b64482 100644 +index 394a59b..9c0b5d9 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -81,6 +81,7 @@ class rcube_db diff --git a/debian/patches/mark-flaky-tests-as-such.patch b/debian/patches/mark-flaky-tests-as-such.patch index ca048da09..6491e949e 100644 --- a/debian/patches/mark-flaky-tests-as-such.patch +++ b/debian/patches/mark-flaky-tests-as-such.patch @@ -11,7 +11,7 @@ tests. 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Actions/Contacts/Qrcode.php b/tests/Actions/Contacts/Qrcode.php -index 7ac4444..a9c7b0a 100644 +index 2b367bb..bfb71e8 100644 --- a/tests/Actions/Contacts/Qrcode.php +++ b/tests/Actions/Contacts/Qrcode.php @@ -9,6 +9,7 @@ class Actions_Contacts_Qrcode extends ActionTestCase diff --git a/debian/patches/series b/debian/patches/series index 1ed102756..55fd5ddf4 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -12,7 +12,7 @@ update-jsdeps.patch use-system-JQueryUI.patch rename-python-to-python3.patch adjust-test-environment-for-dep8.patch -fix-BaconQrCode-autoload-location.patch +fix-autoload-locations.patch mark-flaky-tests-as-such.patch dont-force-set-session.gc_probability=1.patch fix-upstream-test-suite.patch diff --git a/debian/patches/update-script.patch b/debian/patches/update-script.patch index 1f68be407..65c49e4ac 100644 --- a/debian/patches/update-script.patch +++ b/debian/patches/update-script.patch @@ -9,7 +9,7 @@ Forwarded: not-needed 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/bin/update.sh b/bin/update.sh -index f468f57..a35dce3 100755 +index c123385..de6c10c 100755 --- a/bin/update.sh +++ b/bin/update.sh @@ -19,6 +19,7 @@ @@ -24,9 +24,9 @@ index f468f57..a35dce3 100755 if (!empty($opts['accept']) || strtolower($input) == 'y') { $error = $written = false; -- echo ". backing up the current config file(s)...\n"; +- echo "- backing up the current config file(s)...\n"; + if (!DEBIAN_PKG) { -+ echo ". backing up the current config file(s)...\n"; ++ echo "- backing up the current config file(s)...\n"; - foreach (['config', 'main', 'db'] as $file) { - if (file_exists(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php')) { @@ -43,8 +43,8 @@ index f468f57..a35dce3 100755 if (!$error) { $RCI->merge_config(); -- echo ". writing " . RCMAIL_CONFIG_DIR . "/config.inc.php...\n"; -+ echo ". writing " . RCMAIL_CONFIG_DIR . "/config.inc.php".(DEBIAN_PKG ? ".dpkg-new" : "")."...\n"; +- echo "- writing " . RCMAIL_CONFIG_DIR . "/config.inc.php...\n"; ++ echo "- writing " . RCMAIL_CONFIG_DIR . "/config.inc.php".(DEBIAN_PKG ? ".dpkg-new" : "")."...\n"; $written = $RCI->save_configfile($RCI->create_config(false)); } @@ -67,7 +67,7 @@ index f468f57..a35dce3 100755 $success = rcmail_utils::db_update(INSTALL_PATH . 'SQL', 'roundcube', $opts['version'], ['errors' => true]); } diff --git a/program/include/rcmail_install.php b/program/include/rcmail_install.php -index 95cef13..dc42877 100644 +index 13fd40e..e8278f8 100644 --- a/program/include/rcmail_install.php +++ b/program/include/rcmail_install.php @@ -18,6 +18,9 @@ diff --git a/debian/patches/use-enchant.patch b/debian/patches/use-enchant.patch index 96bcf65a0..b4b8f658a 100644 --- a/debian/patches/use-enchant.patch +++ b/debian/patches/use-enchant.patch @@ -10,10 +10,10 @@ Forwarded: not-needed 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/defaults.inc.php b/config/defaults.inc.php -index 12fecca..77e843f 100644 +index 85481d2..00f1a8d 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php -@@ -938,7 +938,8 @@ $config['spellcheck_dictionary'] = false; +@@ -939,7 +939,8 @@ $config['spellcheck_dictionary'] = false; // Since Google shut down their public spell checking service, the default settings // connect to http://spell.roundcube.net which is a hosted service provided by Roundcube. // You can connect to any other googie-compliant service by setting 'spellcheck_uri' accordingly. diff --git a/debian/patches/use-system-JQueryUI.patch b/debian/patches/use-system-JQueryUI.patch index ee4cd3a52..27277db9d 100644 --- a/debian/patches/use-system-JQueryUI.patch +++ b/debian/patches/use-system-JQueryUI.patch @@ -15,7 +15,7 @@ Forwarded: not-needed 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/jqueryui/jqueryui.php b/plugins/jqueryui/jqueryui.php -index a0bfda3..a98afba 100644 +index 7628539..2f23761 100644 --- a/plugins/jqueryui/jqueryui.php +++ b/plugins/jqueryui/jqueryui.php @@ -40,6 +40,7 @@ class jqueryui extends rcube_plugin diff --git a/debian/po/ro.po b/debian/po/ro.po new file mode 100644 index 000000000..e60a1b084 --- /dev/null +++ b/debian/po/ro.po @@ -0,0 +1,143 @@ +# Mesajele în limba română pentru pachetul roundcube. +# Romanian translation of roundcube. +# Copyright © 2023 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the roundcube package. +# +# Remus-Gabriel Chelu <remusgabriel.chelu@disroot.org>, 2023. +# +# Cronologia traducerii fișierului „roundcube”: +# Traducerea inițială, făcută de R-GC, pentru versiunea roundcube 1.6.1+dfsg-1(2009-02-15). +# Actualizare a traducerii pentru versiunea Y, făcută de X, Y(anul). +# +msgid "" +msgstr "" +"Project-Id-Version: roundcube 1.6.1+dfsg-1\n" +"Report-Msgid-Bugs-To: roundcube@packages.debian.org\n" +"POT-Creation-Date: 2009-02-15 17:05+0100\n" +"PO-Revision-Date: 2023-03-19 12:55+0100\n" +"Last-Translator: Remus-Gabriel Chelu <remusgabriel.chelu@disroot.org>\n" +"Language-Team: Romanian <debian-l10n-romanian@lists.debian.org>\n" +"Language: ro\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==0 || (n!=1 && n%100>=1 && " +"n%100<=19) ? 1 : 2);\n" +"X-Bugs: Report translation errors to the Language-Team address.\n" +"X-Generator: Poedit 3.2.2\n" + +#. Type: multiselect +#. Choices +#: ../templates:2001 +msgid "apache2" +msgstr "apache2" + +#. Type: multiselect +#. Choices +#: ../templates:2001 +msgid "lighttpd" +msgstr "lighttpd" + +#. Type: multiselect +#. Description +#: ../templates:2002 +msgid "Web server(s) to configure automatically:" +msgstr "Servere web de reconfigurat automat:" + +#. Type: multiselect +#. Description +#: ../templates:2002 +msgid "" +"RoundCube supports any web server supported by PHP, however only Apache 2 and " +"lighttpd can be configured automatically." +msgstr "" +"RoundCube acceptă orice server web acceptat de PHP, însă numai Apache 2 și " +"lighttpd pot fi configurate automat." + +#. Type: multiselect +#. Description +#: ../templates:2002 +msgid "" +"Please select the web server(s) that should be configured automatically for " +"RoundCube." +msgstr "" +"Selectați serverul(ele) web care ar trebui să fie configurat(e) automat pentru " +"RoundCube." + +#. Type: boolean +#. Description +#: ../templates:3001 +msgid "Should the webserver(s) be restarted now?" +msgstr "Doriți să fie repornit(e) serverul(ele) web acum?" + +#. Type: boolean +#. Description +#: ../templates:3001 +msgid "" +"In order to activate the new configuration, the reconfigured web server(s) have " +"to be restarted." +msgstr "" +"Pentru a activa noua configurație, serverul(ele) web reconfigurat(e) trebuie să " +"fie repornit(e)." + +#. Type: string +#. Description +#: ../templates:4001 +msgid "IMAP server(s) used with RoundCube:" +msgstr "Server(e) IMAP folosit(e) cu RoundCube:" + +#. Type: string +#. Description +#: ../templates:4001 +msgid "Please select the IMAP server(s) that should be used with RoundCube." +msgstr "" +"Selectați serverul(ele) IMAP care ar trebui să fie folosit(e) cu RoundCube." + +#. Type: string +#. Description +#: ../templates:4001 +msgid "" +"If this is left blank, a text box will be displayed at login. Entering a space-" +"separated list of hosts will display a pull-down menu. Entering a single host " +"will enforce using this host." +msgstr "" +"Dacă acesta este lăsat necompletat, va fi afișată o casetă de text la " +"conectare. Introducerea unei liste de gazde separate prin spații va afișa un " +"meniu derulant. Introducerea unei singure gazde va impune utilizarea acestei " +"gazde." + +#. Type: string +#. Description +#: ../templates:4001 +msgid "To use SSL connections, please enter host names as 'ssl://hostname:993'." +msgstr "" +"Pentru a utiliza conexiunile SSL, introduceți numele gazdei ca „ssl://nume-" +"gazdă:993”." + +#. Type: select +#. Description +#: ../templates:5001 +msgid "Default language:" +msgstr "Limba implicită:" + +#. Type: select +#. Description +#: ../templates:5001 +msgid "Please choose the default language for RoundCube." +msgstr "Alegeți limba implicită pentru RoundCube." + +#. Type: select +#. Description +#: ../templates:5001 +msgid "This choice can be overridden by individual users in their preferences." +msgstr "Această alegere poate fi înlocuită de utilizatori în preferințele lor." + +#. Type: select +#. Description +#: ../templates:5001 +msgid "" +"However, the default language will be used for the login screen and the first " +"connection of users." +msgstr "" +"Cu toate acestea, limba implicită va fi folosită pentru ecranul de conectare și " +"prima conexiune a utilizatorilor." diff --git a/debian/roundcube-core.cron.d b/debian/roundcube-core.cron.d index e8003a53d..a280a05d3 100644 --- a/debian/roundcube-core.cron.d +++ b/debian/roundcube-core.cron.d @@ -4,4 +4,4 @@ 0 5 * * * www-data test -d /run/systemd/system || /usr/share/roundcube/bin/cleandb.sh >/dev/null # Purge expired sessions, caches and tempfiles -* 5 * * * www-data test -d /run/systemd/system || /usr/share/roundcube/bin/gc.sh +5,35 * * * * www-data test -d /run/systemd/system || /usr/share/roundcube/bin/gc.sh diff --git a/debian/salsa-ci.yml b/debian/salsa-ci.yml index 70954ecc9..6ce371128 100644 --- a/debian/salsa-ci.yml +++ b/debian/salsa-ci.yml @@ -3,5 +3,6 @@ include: - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml variables: + RELEASE: 'bookworm' # install suitable RDBMS before running piuparts (workaround for #1015732) SALSA_CI_PIUPARTS_PRE_INSTALL_SCRIPT: 'debian/salsa-ci/pre_install_database-server' diff --git a/debian/sql/mysql/1.3.0-1 b/debian/sql/mysql/1.3.0-1 index 51f587132..dd3998932 100644 --- a/debian/sql/mysql/1.3.0-1 +++ b/debian/sql/mysql/1.3.0-1 @@ -11,7 +11,8 @@ ALTER TABLE `session` MODIFY `ip` varchar(40) NOT NULL; UPDATE `system` SET `value` = '2016100900' WHERE `name` = 'roundcube-version'; /* 2016112200 */ -ALTER TABLE `dictionary` ADD COLUMN `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST; -- redundant, for compat. with Galera Cluster +-- redundant, for compat. with Galera Cluster +ALTER TABLE `dictionary` ADD COLUMN `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST; DROP TABLE `cache`; DROP TABLE `cache_shared`; diff --git a/index.php b/index.php index 87de8900e..0e51d3523 100644 --- a/index.php +++ b/index.php @@ -2,7 +2,7 @@ /** +-------------------------------------------------------------------------+ | Roundcube Webmail IMAP Client | - | Version 1.6.1 | + | Version 1.6-git | | | | Copyright (C) The Roundcube Dev Team | | | diff --git a/plugins/attachment_reminder/attachment_reminder.js b/plugins/attachment_reminder/attachment_reminder.js index 722b7e75a..633922a51 100644 --- a/plugins/attachment_reminder/attachment_reminder.js +++ b/plugins/attachment_reminder/attachment_reminder.js @@ -17,14 +17,16 @@ function rcmail_get_compose_message() { - var msg; + var msg = rcmail.editor.get_content({ nosig: true }); - if (window.tinyMCE && (ed = tinyMCE.get(rcmail.env.composebody))) { - msg = ed.getContent(); - msg = msg.replace(/<blockquote[^>]*>(.|[\r\n])*<\/blockquote>/gmi, ''); + if (rcmail.editor.is_html()) { + // Remove quoted content, all HTML tags, and some entities + msg = msg.replace(/<blockquote[^>]*>(.|[\r\n])*<\/blockquote>/gmi, '') + .replace(/<[^>]+>/gm, ' ') + .replace(/ /g, ' '); } else { - msg = $('#' + rcmail.env.composebody).val(); + // Remove quoted content msg = msg.replace(/^>.*$/gmi, ''); } diff --git a/plugins/help/config.inc.php.dist b/plugins/help/config.inc.php.dist index d246cfc5f..4ac5864b5 100644 --- a/plugins/help/config.inc.php.dist +++ b/plugins/help/config.inc.php.dist @@ -3,7 +3,7 @@ // Help content iframe source // %l will be replaced by the language code resolved using the 'help_language_map' option // If you are serving roundcube via https, then change this URL to https also. -$config['help_source'] = 'http://docs.roundcube.net/doc/help/1.1/%l/'; +$config['help_source'] = 'https://docs.roundcube.net/doc/help/1.1/%l/'; // Map task/action combinations to deep-links // Use '<task>/<action>' or only '<task>' strings as keys diff --git a/plugins/help/help.js b/plugins/help/help.js index 7928b2017..6792d2006 100644 --- a/plugins/help/help.js +++ b/plugins/help/help.js @@ -55,7 +55,7 @@ if (window.rcmail) { show_help_content(rcmail.env.action); } } - catch (e) { /* ignore */} + catch (e) { /* ignore */ } } }); } diff --git a/plugins/jqueryui/composer.json b/plugins/jqueryui/composer.json index 10cf660e8..97ed99898 100644 --- a/plugins/jqueryui/composer.json +++ b/plugins/jqueryui/composer.json @@ -3,7 +3,7 @@ "type": "roundcube-plugin", "description": "Plugin adds the complete jQuery-UI library including the smoothness theme to Roundcube. This allows other plugins to use jQuery-UI without having to load their own version. The benefit of using one central jQuery-UI is that we wont run into problems of conflicting jQuery libraries being loaded. All plugins that want to use jQuery-UI should use this plugin as a requirement.", "license": "GPL-3.0-or-later", - "version": "1.13.1", + "version": "1.13.2", "authors": [ { "name": "Thomas Bruederli", diff --git a/plugins/jqueryui/jqueryui.php b/plugins/jqueryui/jqueryui.php index a98afba62..2f23761dd 100644 --- a/plugins/jqueryui/jqueryui.php +++ b/plugins/jqueryui/jqueryui.php @@ -5,7 +5,7 @@ * * Provide the jQuery UI library with according themes. * - * @version 1.13.1 + * @version 1.13.2 * @author Cor Bosman <roundcube@wa.ter.net> * @author Thomas Bruederli <roundcube@gmail.com> * @author Aleksander Machniak <alec@alec.pl> @@ -14,7 +14,7 @@ class jqueryui extends rcube_plugin { public $noajax = true; - public $version = '1.13.1'; + public $version = '1.13.2'; private static $features = []; private static $ui_theme; diff --git a/plugins/jqueryui/themes/elastic/jquery-ui.css b/plugins/jqueryui/themes/elastic/jquery-ui.css index 819a49299..c3b98cc44 100644 --- a/plugins/jqueryui/themes/elastic/jquery-ui.css +++ b/plugins/jqueryui/themes/elastic/jquery-ui.css @@ -1,4 +1,4 @@ -/*! jQuery UI - v1.13.1 - 2022-01-20 +/*! jQuery UI - v1.13.2 - 2022-07-14 * http://jqueryui.com * Includes: core.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, draggable.css, resizable.css, progressbar.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css * To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6 diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php index 549fbf699..9a16d1f61 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php @@ -676,7 +676,7 @@ class rcube_sieve_engine $notifytargets = rcube_utils::get_input_value('_action_notifytarget', rcube_utils::INPUT_POST, true); $notifyoptions = rcube_utils::get_input_value('_action_notifyoption', rcube_utils::INPUT_POST, true); $notifymessages = rcube_utils::get_input_value('_action_notifymessage', rcube_utils::INPUT_POST, true); - $notifyfrom = rcube_utils::get_input_value('_action_notifyfrom', rcube_utils::INPUT_POST); + $notifyfrom = rcube_utils::get_input_value('_action_notifyfrom', rcube_utils::INPUT_POST, true); $notifyimp = rcube_utils::get_input_value('_action_notifyimportance', rcube_utils::INPUT_POST); $addheader_name = rcube_utils::get_input_value('_action_addheader_name', rcube_utils::INPUT_POST); $addheader_value = rcube_utils::get_input_value('_action_addheader_value', rcube_utils::INPUT_POST, true); @@ -1174,28 +1174,7 @@ class rcube_sieve_engine if (!empty($this->form['actions'][$i]['from'])) { // According to RFC5230 the :from string must specify a valid [RFC2822] mailbox-list - // we'll try to extract addresses and validate them separately - $from = rcube_mime::decode_address_list($this->form['actions'][$i]['from'], null, true, RCUBE_CHARSET); - foreach ((array) $from as $idx => $addr) { - if (empty($addr['mailto']) || !rcube_utils::check_email($addr['mailto'])) { - $this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning'); - break; - } - else { - $from[$idx] = format_email_recipient($addr['mailto'], $addr['name']); - } - } - - // Only one address is allowed (at least on cyrus imap) - if (is_array($from) && count($from) > 1) { - $this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning'); - } - - // Then we convert it back to RFC2822 format - if (empty($this->errors['actions'][$i]['from']) && !empty($from)) { - $this->form['actions'][$i]['from'] = Mail_mimePart::encodeHeader( - 'From', implode(', ', $from), RCUBE_CHARSET, 'base64', ''); - } + $this->action_email_input($i, 'from'); } if ($this->form['actions'][$i]['reason'] == '') { @@ -1232,9 +1211,6 @@ class rcube_sieve_engine if (empty($notifytargets[$idx])) { $this->errors['actions'][$i]['target'] = $this->plugin->gettext('cannotbeempty'); } - if (!empty($notifyfrom[$idx]) && !rcube_utils::check_email($notifyfrom[$idx])) { - $this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning'); - } // skip empty options foreach ((array)$notifyoptions[$idx] as $opt_idx => $opt) { @@ -1248,6 +1224,12 @@ class rcube_sieve_engine $this->form['actions'][$i]['message'] = $notifymessages[$idx]; $this->form['actions'][$i]['from'] = $notifyfrom[$idx]; $this->form['actions'][$i]['importance'] = $notifyimp[$idx]; + + if (!empty($notifyfrom[$idx]) && stripos($this->form['actions'][$i]['method'], 'mailto:') === 0) { + // For mailto method :from string must specify a valid [RFC2822] mailbox-list + $this->action_email_input($i, 'from'); + } + break; } @@ -2970,13 +2952,15 @@ class rcube_sieve_engine $this->sieve->load($user_script); - foreach ($this->sieve->script->as_array() as $rules) { - foreach ($rules['actions'] as $action) { - if ($action['type'] == 'include' && empty($action['global'])) { - $name = preg_replace($filename_regex, '', $action['target']); - // make sure the script exist - if (in_array($name, $this->list)) { - $this->active[] = $name; + if (!empty($this->sieve->script)) { + foreach ($this->sieve->script->as_array() as $rules) { + foreach ($rules['actions'] as $action) { + if ($action['type'] == 'include' && empty($action['global'])) { + $name = preg_replace($filename_regex, '', $action['target']); + // make sure the script exist + if (in_array($name, $this->list)) { + $this->active[] = $name; + } } } } @@ -3494,4 +3478,38 @@ class rcube_sieve_engine return $script_name; } + + /** + * Read email address input, parse it and check validity + */ + protected function action_email_input($i, $field) + { + // According to RFC5230 the :from string must specify a valid [RFC2822] mailbox-list + // we'll try to extract addresses and validate them separately + $from = rcube_mime::decode_address_list($this->form['actions'][$i][$field], null, true, RCUBE_CHARSET); + foreach ((array) $from as $idx => $addr) { + if (empty($addr['mailto']) || !rcube_utils::check_email($addr['mailto'])) { + $this->errors['actions'][$i][$field] = $this->plugin->gettext('noemailwarning'); + break; + } + else { + $from[$idx] = format_email_recipient($addr['mailto'], $addr['name']); + } + } + + // Only one address is allowed (at least on cyrus imap) + if (is_array($from) && count($from) > 1) { + $this->errors['actions'][$i][$field] = $this->plugin->gettext('noemailwarning'); + } + + // Then we convert it back to RFC2822 format + if (empty($this->errors['actions'][$i][$field]) && !empty($from)) { + $this->form['actions'][$i][$field] = Mail_mimePart::encodeHeader( + 'From', implode(', ', $from), RCUBE_CHARSET, 'base64', ''); + + return true; + } + + return false; + } } diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php index e39b260a6..f69229113 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php @@ -701,21 +701,23 @@ class rcube_sieve_vacation extends rcube_sieve_engine if ($date_extension) { $date_value = []; - foreach ((array) $this->vacation['tests'] as $test) { - if ($test['test'] == 'currentdate') { - $idx = $test['type'] == 'value-ge' ? 'start' : 'end'; + if (!empty($this->vacation['tests'])) { + foreach ((array) $this->vacation['tests'] as $test) { + if ($test['test'] == 'currentdate') { + $idx = $test['type'] == 'value-ge' ? 'start' : 'end'; - if ($test['part'] == 'date') { - $date_value[$idx]['date'] = $test['arg']; - } - else if ($test['part'] == 'iso8601') { - $date_value[$idx]['datetime'] = $test['arg']; + if ($test['part'] == 'date') { + $date_value[$idx]['date'] = $test['arg']; + } + else if ($test['part'] == 'iso8601') { + $date_value[$idx]['datetime'] = $test['arg']; + } } } } foreach ($date_value as $idx => $value) { - $$idx = new DateTime($value['datetime'] ?: $value['date'], $timezone); + ${$idx} = new DateTime(!empty($value['datetime']) ? $value['datetime'] : $value['date'], $timezone); } } else if ($regex_extension) { @@ -738,13 +740,13 @@ class rcube_sieve_vacation extends rcube_sieve_engine 'interval' => $interval, 'start' => $start, 'end' => $end, - 'enabled' => $this->vacation['reason'] && empty($this->vacation['disabled']), - 'message' => $this->vacation['reason'], - 'subject' => $this->vacation['subject'], - 'action' => $this->vacation['action'], - 'target' => $this->vacation['target'], - 'addresses' => $this->vacation['addresses'], - 'from' => $this->vacation['from'], + 'enabled' => !empty($this->vacation['reason']) && empty($this->vacation['disabled']), + 'message' => isset($this->vacation['reason']) ? $this->vacation['reason'] : null, + 'subject' => isset($this->vacation['subject']) ? $this->vacation['subject'] : null, + 'action' => isset($this->vacation['action']) ? $this->vacation['action'] : null, + 'target' => isset($this->vacation['target']) ? $this->vacation['target'] : null, + 'addresses' => isset($this->vacation['addresses']) ? $this->vacation['addresses'] : null, + 'from' => isset($this->vacation['from']) ? $this->vacation['from'] : null, ]; return $vacation; diff --git a/plugins/markasjunk/drivers/cmd_learn.php b/plugins/markasjunk/drivers/cmd_learn.php index 83ee0abae..c3ebf9f79 100644 --- a/plugins/markasjunk/drivers/cmd_learn.php +++ b/plugins/markasjunk/drivers/cmd_learn.php @@ -3,7 +3,7 @@ /** * Command line learn driver * - * @version 3.0 + * @version 3.1 * * @author Philip Weir * Patched by Julien Vehent to support DSPAM @@ -49,14 +49,19 @@ class markasjunk_cmd_learn return; } + if (strpos($command, '%h') !== false) { + preg_match_all('/%h:([\w_-]+)/', $command, $header_names, PREG_SET_ORDER); + $header_names = array_column($header_names, 1); + } + // backwards compatibility %xds removed in markasjunk v1.12 $command = str_replace('%xds', '%h:x-dspam-signature', $command); - $command = str_replace('%u', $_SESSION['username'], $command); - $command = str_replace('%l', $rcube->user->get_username('local'), $command); - $command = str_replace('%d', $rcube->user->get_username('domain'), $command); + $command = str_replace('%u', escapeshellarg($_SESSION['username']), $command); + $command = str_replace('%l', escapeshellarg($rcube->user->get_username('local')), $command); + $command = str_replace('%d', escapeshellarg($rcube->user->get_username('domain')), $command); if (strpos($command, '%i') !== false) { $identity = $rcube->user->get_identity(); - $command = str_replace('%i', $identity['email'], $command); + $command = str_replace('%i', escapeshellarg($identity['email']), $command); } foreach ($uids as $uid) { @@ -68,24 +73,24 @@ class markasjunk_cmd_learn $tmp_command = str_replace('%s', escapeshellarg($message->sender['mailto']), $tmp_command); } - if (strpos($command, '%h') !== false) { + if (!empty($header_names)) { $storage = $rcube->get_storage(); $storage->check_connection(); - $storage->conn->select($src_mbox); + $headers = $storage->conn->fetchHeader($src_mbox, $uid, true, false, $header_names); - preg_match_all('/%h:([\w_-]+)/', $tmp_command, $header_names, PREG_SET_ORDER); foreach ($header_names as $header) { $val = null; - if ($msg = $storage->conn->fetchHeader($src_mbox, $uid, true, false, [$header[1]])) { - $val = !empty($msg->{$header[1]}) ? $msg->{$header[1]} : $msg->others[$header[1]]; + if ($headers) { + $val = $headers->get($header); + $val = is_array($val) ? array_first($val) : $val; } if (!empty($val)) { - $tmp_command = str_replace($header[0], escapeshellarg($val), $tmp_command); + $tmp_command = str_replace('%h:' . $header, escapeshellarg($val), $tmp_command); } else { if ($debug) { - rcube::write_log('markasjunk', 'header ' . $header[1] . ' not found in message ' . $src_mbox . '/' . $uid); + rcube::write_log('markasjunk', "header {$header} not found in message {$src_mbox}/{$uid}"); } continue 2; diff --git a/plugins/markasjunk/drivers/email_learn.php b/plugins/markasjunk/drivers/email_learn.php index 6d41d0406..6b1d83298 100644 --- a/plugins/markasjunk/drivers/email_learn.php +++ b/plugins/markasjunk/drivers/email_learn.php @@ -49,17 +49,16 @@ class markasjunk_email_learn $product = $this->rcube->config->get('product_name'); $temp_dir = unslashify($this->rcube->config->get('temp_dir')); - $mailto = $this->rcube->config->get($spam ? 'markasjunk_email_spam' : 'markasjunk_email_ham'); - $mailto = $this->_parse_vars($mailto, $spam, $from); + $subject = (string) $this->rcube->config->get('markasjunk_email_subject'); + $mailto = (string) $this->rcube->config->get($spam ? 'markasjunk_email_spam' : 'markasjunk_email_ham'); + $subject = $this->_parse_vars($subject, $spam, $from); + $mailto = $this->_parse_vars($mailto, $spam, $from); // no address to send to, exit if (!$mailto) { return; } - $subject = $this->rcube->config->get('markasjunk_email_subject'); - $subject = $this->_parse_vars($subject, $spam, $from); - foreach ($uids as $i => $uid) { $MESSAGE = new rcube_message($uid); $message_file = null; diff --git a/plugins/markasjunk/drivers/sa_blacklist.php b/plugins/markasjunk/drivers/sa_blacklist.php index 5bd0f6a32..f877c9b0f 100644 --- a/plugins/markasjunk/drivers/sa_blacklist.php +++ b/plugins/markasjunk/drivers/sa_blacklist.php @@ -52,6 +52,10 @@ class markasjunk_sa_blacklist $this->sa_preference_field = $rcube->config->get('sauserprefs_sql_preference_field'); $this->sa_value_field = $rcube->config->get('sauserprefs_sql_value_field'); + // SAv4 compatibility + $blocklist_pref_name = $rcube->config->get('sauserprefs_sav4', false) ? "blocklist_from" : "blacklist_from"; + $welcomelist_pref_name = $rcube->config->get('sauserprefs_sav4', false) ? "welcomelist_from" : "whitelist_from"; + $identity = $rcube->user->get_identity(); $identity = $identity['email']; @@ -97,21 +101,21 @@ class markasjunk_sa_blacklist } if ($spam) { - // delete any whitelisting for this address + // delete any welcomelisting for this address $db->query( "DELETE FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? " . "AND `{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?", $this->sa_user, - 'whitelist_from', + $welcomelist_pref_name, $email ); - // check address is not already blacklisted + // check address is not already blocklisted $sql_result = $db->query( "SELECT `value` FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? " . "AND `{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?", $this->sa_user, - 'blacklist_from', + $blocklist_pref_name, $email ); @@ -120,31 +124,31 @@ class markasjunk_sa_blacklist "INSERT INTO `{$this->sa_table}` (`{$this->sa_username_field}`, `{$this->sa_preference_field}`, `{$this->sa_value_field}`)" . " VALUES (?, ?, ?)", $this->sa_user, - 'blacklist_from', + $blocklist_pref_name, $email ); if ($debug) { - rcube::write_log('markasjunk', $this->sa_user . ' blacklist ' . $email); + rcube::write_log('markasjunk', $this->sa_user . ' blocklist ' . $email); } } } else { - // delete any blacklisting for this address + // delete any blocklisting for this address $db->query( "DELETE FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? AND " . "`{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?", $this->sa_user, - 'blacklist_from', + $blocklist_pref_name, $email ); - // check address is not already whitelisted + // check address is not already welcomelisted $sql_result = $db->query( "SELECT `value` FROM `{$this->sa_table}` WHERE `{$this->sa_username_field}` = ? " . "AND `{$this->sa_preference_field}` = ? AND `{$this->sa_value_field}` = ?", $this->sa_user, - 'whitelist_from', + $welcomelist_pref_name, $email ); @@ -153,11 +157,11 @@ class markasjunk_sa_blacklist "INSERT INTO `{$this->sa_table}` (`{$this->sa_username_field}`, `{$this->sa_preference_field}`, `{$this->sa_value_field}`)" . " VALUES (?, ?, ?)", $this->sa_user, - 'whitelist_from', + $welcomelist_pref_name, $email); if ($debug) { - rcube::write_log('markasjunk', $this->sa_user . ' whitelist ' . $email); + rcube::write_log('markasjunk', $this->sa_user . ' welcomelist ' . $email); } } } diff --git a/plugins/newmail_notifier/newmail_notifier.js b/plugins/newmail_notifier/newmail_notifier.js index 49bd7c3c6..e719518d5 100644 --- a/plugins/newmail_notifier/newmail_notifier.js +++ b/plugins/newmail_notifier/newmail_notifier.js @@ -84,25 +84,13 @@ function newmail_notifier_basic() // Sound notification function newmail_notifier_sound() { - var elem, src = rcmail.assets_path('plugins/newmail_notifier/sound'), - plugin = navigator.mimeTypes ? navigator.mimeTypes['audio/mp3'] : {}; + var src = rcmail.assets_path('plugins/newmail_notifier/sound'); - // Internet Explorer does not support wav files, - // support in other browsers depends on enabled plugins, - // so we use wav as a fallback - src += bw.ie || (plugin && plugin.enabledPlugin) ? '.mp3' : '.wav'; - - // HTML5 - try { - elem = $('<audio>').attr('src', src); - elem.get(0).play(); - } - // old method - catch (e) { - elem = $('<embed id="sound" src="' + src + '" hidden=true autostart=true loop=false />'); - elem.appendTo($('body')); - setTimeout("$('#sound').remove()", 5000); - } + (new Audio(src + '.mp3')).play() + .catch(function() { + // fallback to the wav format + (new Audio(src + '.wav')).play(); + }); } // Desktop notification diff --git a/plugins/password/drivers/ldap_simple.php b/plugins/password/drivers/ldap_simple.php index 04279fca6..53f66e4cc 100644 --- a/plugins/password/drivers/ldap_simple.php +++ b/plugins/password/drivers/ldap_simple.php @@ -119,11 +119,12 @@ class rcube_ldap_simple_password $this->debug = $rcmail->config->get('ldap_debug'); $ldap_host = $rcmail->config->get('password_ldap_host', 'localhost'); $ldap_port = $rcmail->config->get('password_ldap_port', '389'); + $ldap_uri = $this->_host2uri($ldap_host, $ldap_port); - $this->_debug("C: Connect to $ldap_host:$ldap_port"); + $this->_debug("C: Connect [{$ldap_uri}]"); // Connect - if (!$ds = ldap_connect($ldap_host, $ldap_port)) { + if (!($ds = ldap_connect($ldap_uri))) { $this->_debug("S: NOT OK"); rcube::raise_error([ @@ -290,4 +291,24 @@ class rcube_ldap_simple_password rcube::write_log('ldap', $str); } } + + /** + * Convert LDAP host/port into URI + */ + private static function _host2uri($host, $port = null) + { + if (stripos($host, 'ldapi://') === 0) { + return $host; + } + + if (strpos($host, '://') === false) { + $host = ($port == 636 ? 'ldaps' : 'ldap') . '://' . $host; + } + + if ($port && !preg_match('/:[0-9]+$/', $host)) { + $host .= ':' . $port; + } + + return $host; + } } diff --git a/plugins/reconnect/composer.json b/plugins/reconnect/composer.json index 23faaa6a6..c7a990bc8 100644 --- a/plugins/reconnect/composer.json +++ b/plugins/reconnect/composer.json @@ -3,7 +3,7 @@ "type": "roundcube-plugin", "description": "Reconnects to server for several attempts.", "license": "GPL-3.0-or-later", - "version": "0.1", + "version": "0.2", "authors": [ { "name": "Sandro Knauß", diff --git a/plugins/reconnect/reconnect.php b/plugins/reconnect/reconnect.php index ecc98ada1..11050dd22 100644 --- a/plugins/reconnect/reconnect.php +++ b/plugins/reconnect/reconnect.php @@ -1,16 +1,27 @@ <?php /** - * RoundCube Reconnect Plugin + * Roundcube Reconnect Plugin * - * @version 0.1 + * @version 0.2 * @author Sandro Knauß <hefee@debian.org> * @license GPLv3+ */ class reconnect extends rcube_plugin { - private $max_attempts; + private $imap_max_attempts; + /** + * Plugin initialization + */ function init() + { + $this->add_hook('storage_connect', [$this, 'storage_connect']); + } + + /** + * Storage_connect hook handler + */ + function storage_connect($args) { $rcmail = rcmail::get_instance(); @@ -18,11 +29,6 @@ class reconnect extends rcube_plugin $this->imap_max_attempts = $rcmail->config->get('reconnect_imap_max_attempts', 5); - $this->add_hook('storage_connect', array($this, 'storage_connect')); - } - - function storage_connect($args) - { $args['retry'] = ($args['attempt'] <= $this->imap_max_attempts); if ($args['attempt'] == 1) { @@ -30,6 +36,7 @@ class reconnect extends rcube_plugin } $storage = rcmail::get_instance()->get_storage(); + switch ($storage->get_error_code()) { case rcube_imap_generic::ERROR_NO: case rcube_imap_generic::ERROR_BAD: diff --git a/program/actions/contacts/export.php b/program/actions/contacts/export.php index 8616578ab..632bdfc58 100644 --- a/program/actions/contacts/export.php +++ b/program/actions/contacts/export.php @@ -150,11 +150,11 @@ class rcmail_action_contacts_export extends rcmail_action_contacts_index $fieldmap = $source ? $source->vcard_map : null; if (empty($record['vcard'])) { - $vcard = new rcube_vcard($record['vcard'], RCUBE_CHARSET, false, $fieldmap); + $vcard = new rcube_vcard(null, RCUBE_CHARSET, false, $fieldmap); $vcard->reset(); foreach ($record as $key => $values) { - list($field, $section) = explode(':', $key); + list($field, $section) = rcube_utils::explode(':', $key); // avoid unwanted casting of DateTime objects to an array // (same as in rcube_contacts::convert_save_data()) if (is_object($values) && is_a($values, 'DateTime')) { @@ -163,7 +163,7 @@ class rcmail_action_contacts_export extends rcmail_action_contacts_index foreach ((array) $values as $value) { if (is_array($value) || is_a($value, 'DateTime') || @strlen($value)) { - $vcard->set($field, $value, strtoupper($section)); + $vcard->set($field, $value, $section ? strtoupper($section) : ''); } } } @@ -176,7 +176,7 @@ class rcmail_action_contacts_export extends rcmail_action_contacts_index $record['vcard'] = $vcard->export(); } // patch categories to already existing vcard block - else if (!empty($record['vcard'])) { + else { $vcard = new rcube_vcard($record['vcard'], RCUBE_CHARSET, false, $fieldmap); // unset CATEGORIES entry, it might be not up-to-date (#1490277) diff --git a/program/actions/contacts/import.php b/program/actions/contacts/import.php index 2c4d25d9d..295d377cc 100644 --- a/program/actions/contacts/import.php +++ b/program/actions/contacts/import.php @@ -184,7 +184,7 @@ class rcmail_action_contacts_import extends rcmail_action_contacts_index if (empty($a_record['name'])) { $a_record['name'] = rcube_addressbook::compose_display_name($a_record, true); // Reset it if equals to email address (from compose_display_name()) - if ($a_record['name'] == $a_record['email'][0]) { + if ($a_record['name'] == ($a_record['email'][0] ?? null)) { $a_record['name'] = ''; } } diff --git a/program/actions/contacts/qrcode.php b/program/actions/contacts/qrcode.php index 8ab71e859..87c31da9f 100644 --- a/program/actions/contacts/qrcode.php +++ b/program/actions/contacts/qrcode.php @@ -108,7 +108,7 @@ class rcmail_action_contacts_qrcode extends rcmail_action_contacts_index $renderer = new BaconQrCode\Renderer\ImageRenderer($renderer_style, $renderer_image); $writer = new BaconQrCode\Writer($renderer); - return $writer->writeString($data); + return $writer->writeString($data, RCUBE_CHARSET); } /** diff --git a/program/actions/mail/compose.php b/program/actions/mail/compose.php index d9a87aca3..1d5d33f8c 100644 --- a/program/actions/mail/compose.php +++ b/program/actions/mail/compose.php @@ -231,7 +231,7 @@ class rcmail_action_mail_compose extends rcmail_action_mail_index self::$COMPOSE['reply_uid'] = self::$MESSAGE->context === null ? $msg_uid : null; if (!empty(self::$COMPOSE['param']['all'])) { - self::$MESSAGE->reply_all = self::$COMPOSE['param']['all']; + self::$COMPOSE['reply_all'] = self::$COMPOSE['param']['all']; } } else { @@ -1615,7 +1615,7 @@ class rcmail_action_mail_compose extends rcmail_action_mail_index 'onclick' => sprintf( "return %s.command('insert-response', '%s', this, event)", rcmail_output::JS_OBJECT_NAME, - rcube::JQ($response['id']), + rcube::JQ($response['id']) ), ], rcube::Q($response['name']) diff --git a/program/actions/mail/index.php b/program/actions/mail/index.php index d142c7a12..41e09bffe 100644 --- a/program/actions/mail/index.php +++ b/program/actions/mail/index.php @@ -528,14 +528,14 @@ class rcmail_action_mail_index extends rcmail_action } } else if ($col == 'subject') { - $cont = trim(rcube_mime::decode_header($header->$col, $header->charset)); + $cont = trim(rcube_mime::decode_header($header->subject, $header->charset)); if (!$cont) { $cont = $rcmail->gettext('nosubject'); } $cont = rcube::SQ($cont); } else if ($col == 'size') { - $cont = self::show_bytes($header->$col); + $cont = self::show_bytes($header->size); } else if ($col == 'date') { $cont = $rcmail->format_date($sort_col == 'arrival' ? $header->internaldate : $header->date); diff --git a/program/actions/mail/mark.php b/program/actions/mail/mark.php index 4fcfbb534..9029eb5a8 100644 --- a/program/actions/mail/mark.php +++ b/program/actions/mail/mark.php @@ -45,8 +45,9 @@ class rcmail_action_mail_mark extends rcmail_action_mail_index $read_deleted = (bool) $rcmail->config->get('read_when_deleted'); $flag = self::imap_flag($flag); $old_count = 0; + $from = $_POST['_from'] ?? null; - if ($flag == 'DELETED' && $skip_deleted && (!isset($_POST['_from']) || $_POST['_from'] != 'show')) { + if ($flag == 'DELETED' && $skip_deleted && $from != 'show') { // count messages before changing anything $old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL'); } @@ -79,7 +80,7 @@ class rcmail_action_mail_mark extends rcmail_action_mail_index if (!$marked) { // send error message - if (empty($_POST['_from']) || $_POST['_from'] != 'show') { + if ($from != 'show') { $rcmail->output->command('list_mailbox'); } @@ -110,7 +111,7 @@ class rcmail_action_mail_mark extends rcmail_action_mail_index $rcmail->output->set_env('last_flag', $flag); } else if ($flag == 'DELETED' && $skip_deleted) { - if ($_POST['_from'] == 'show') { + if ($from == 'show') { if ($next = rcube_utils::get_input_value('_next_uid', rcube_utils::INPUT_GPC)) { $rcmail->output->command('show_message', $next); } diff --git a/program/actions/mail/show.php b/program/actions/mail/show.php index 16be9e023..ff798b817 100644 --- a/program/actions/mail/show.php +++ b/program/actions/mail/show.php @@ -320,8 +320,11 @@ class rcmail_action_mail_show extends rcmail_action_mail_index $dbox = $rcmail->config->get('drafts_mbox'); // the message is not a draft - if (self::$MESSAGE->context - || (self::$MESSAGE->folder != $dbox && strpos(self::$MESSAGE->folder, $dbox.$delim) !== 0) + if (!empty(self::$MESSAGE->context) + || ( + !empty(self::$MESSAGE->folder) + && (self::$MESSAGE->folder != $dbox && strpos(self::$MESSAGE->folder, $dbox.$delim) !== 0) + ) ) { return ''; } @@ -392,7 +395,7 @@ class rcmail_action_mail_show extends rcmail_action_mail_index $attrib['onerror'] = "this.onerror = null; this.src = '$placeholder';"; } - if (self::$MESSAGE->sender) { + if (!empty(self::$MESSAGE->sender)) { $photo_img = $rcmail->url([ '_task' => 'addressbook', '_action' => 'photo', @@ -642,7 +645,10 @@ class rcmail_action_mail_show extends rcmail_action_mail_index */ public static function message_body($attrib) { - if (!is_array(self::$MESSAGE->parts) && empty(self::$MESSAGE->body)) { + if ( + empty(self::$MESSAGE) + || (!is_array(self::$MESSAGE->parts) && empty(self::$MESSAGE->body)) + ) { return ''; } diff --git a/program/actions/mail/viewsource.php b/program/actions/mail/viewsource.php index 40c6f7857..46c3d5f87 100644 --- a/program/actions/mail/viewsource.php +++ b/program/actions/mail/viewsource.php @@ -45,7 +45,7 @@ class rcmail_action_mail_viewsource extends rcmail_action $headers = $rcmail->storage->get_message_headers($uid); } - $charset = $headers->charset ?: $rcmail->config->get('default_charset'); + $charset = $headers->charset ?: $rcmail->config->get('default_charset', RCUBE_CHARSET); if (!empty($_GET['_save'])) { $subject = rcube_mime::decode_header($headers->subject, $headers->charset); @@ -59,6 +59,9 @@ class rcmail_action_mail_viewsource extends rcmail_action ]); } else { + // Make sure it works in an iframe (#9084) + $rcmail->output->page_headers(); + header("Content-Type: text/plain; charset={$charset}"); } diff --git a/program/actions/settings/folder_edit.php b/program/actions/settings/folder_edit.php index 2caebc361..134ccc02e 100644 --- a/program/actions/settings/folder_edit.php +++ b/program/actions/settings/folder_edit.php @@ -84,7 +84,7 @@ class rcmail_action_settings_folder_edit extends rcmail_action_settings_folders if (strlen($path)) { $path_id = $path; $path = $storage->mod_folder($path . $delimiter); - if ($path[strlen($path)-1] == $delimiter) { + if (($path[strlen($path)-1] ?? '') == $delimiter) { $path = substr($path, 0, -1); } } diff --git a/program/include/iniset.php b/program/include/iniset.php index 177895346..c4ab39fc6 100644 --- a/program/include/iniset.php +++ b/program/include/iniset.php @@ -24,7 +24,7 @@ if (PHP_VERSION_ID < 70300) { } // application constants -define('RCMAIL_VERSION', '1.6.1'); +define('RCMAIL_VERSION', '1.6-git'); define('RCMAIL_START', microtime(true)); if (!defined('INSTALL_PATH')) { diff --git a/program/include/rcmail.php b/program/include/rcmail.php index ac3fde321..af186630f 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1043,7 +1043,7 @@ class rcmail extends rcube // Trash subfolders $delimiter = $storage->get_hierarchy_delimiter(); - $subfolders = array_reverse($storage->list_folders('', $trash_mbox . $delimiter . '*')); + $subfolders = array_reverse($storage->list_folders($trash_mbox . $delimiter, '*')); $last = ''; foreach ($subfolders as $folder) { @@ -1180,7 +1180,8 @@ class rcmail extends rcube return rtrim($path, '/') . '/'; } - $path = preg_replace('/[?&].*$/', '', (string) $path); + $path = preg_replace('/index\.php.*$/', '', (string) $path); + $path = preg_replace('/[?&].*$/', '', $path); $path = preg_replace('![^/]+$!', '', $path); return rtrim($path, '/') . '/'; diff --git a/program/include/rcmail_action.php b/program/include/rcmail_action.php index 5d211fb10..9cb41da04 100644 --- a/program/include/rcmail_action.php +++ b/program/include/rcmail_action.php @@ -229,15 +229,15 @@ abstract class rcmail_action $table->add(['colspan' => 3, 'class' => 'root'], rcube::Q($root)); } - if ($storage = $data['storage']) { - $percent = min(100, round(($storage['used']/max(1,$storage['total']))*100)); + if ($storage = ($data['storage'] ?? null)) { + $percent = min(100, round(($storage['used'] / max(1, $storage['total'])) * 100)); $table->add('name', rcube::Q($rcmail->gettext('quotastorage'))); $table->add(null, self::show_bytes($storage['total'] * 1024)); $table->add(null, sprintf('%s (%.0f%%)', self::show_bytes($storage['used'] * 1024), $percent)); } - if ($message = $data['message']) { - $percent = min(100, round(($message['used']/max(1,$message['total']))*100)); + if ($message = ($data['message'] ?? null)) { + $percent = min(100, round(($message['used'] / max(1, $message['total'])) * 100)); $table->add('name', rcube::Q($rcmail->gettext('quotamessage'))); $table->add(null, intval($message['total'])); diff --git a/program/include/rcmail_install.php b/program/include/rcmail_install.php index dc4287726..e8278f8c8 100644 --- a/program/include/rcmail_install.php +++ b/program/include/rcmail_install.php @@ -464,10 +464,10 @@ class rcmail_install else { $this->config[$replacement] = $current[$prop]; } - } - unset($current[$prop]); - unset($current[$replacement]); + unset($current[$prop]); + unset($current[$replacement]); + } } // Merge old *_port options into the new *_host options, where possible @@ -489,9 +489,9 @@ class rcmail_install } // add all ldap_public sources having global_search enabled to autocomplete_addressbooks - if (is_array($current['ldap_public'])) { + if (!empty($current['ldap_public']) && is_array($current['ldap_public'])) { foreach ($current['ldap_public'] as $key => $ldap_public) { - if ($ldap_public['global_search']) { + if (!empty($ldap_public['global_search'])) { $this->config['autocomplete_addressbooks'][] = $key; unset($current['ldap_public'][$key]['global_search']); } @@ -499,10 +499,6 @@ class rcmail_install } $this->config = array_merge($this->config, $current); - - foreach (array_keys((array) $current['ldap_public']) as $key) { - $this->config['ldap_public'][$key] = $current['ldap_public'][$key]; - } } /** diff --git a/program/include/rcmail_oauth.php b/program/include/rcmail_oauth.php index 938a274bc..fc592eeb6 100644 --- a/program/include/rcmail_oauth.php +++ b/program/include/rcmail_oauth.php @@ -17,6 +17,9 @@ +-----------------------------------------------------------------------+ */ +if (stream_resolve_include_path('GuzzleHttp/autoload.php')) + include_once 'GuzzleHttp/autoload.php'; + use GuzzleHttp\Client; use GuzzleHttp\MessageFormatter; use GuzzleHttp\Exception\RequestException; @@ -120,8 +123,12 @@ class rcmail_oauth */ public function get_redirect_uri() { + $url = $this->rcmail->url([], true, true); + // rewrite redirect URL to not contain query parameters because some providers do not support this - return preg_replace('/\/?\?_task=[a-z]+/', '/index.php/login/oauth', $this->rcmail->url([], true, true)); + $url = preg_replace('/\?.*/', '', $url); + + return slashify($url) . 'index.php/login/oauth'; } /** @@ -142,7 +149,7 @@ class rcmail_oauth */ public function jwt_decode($jwt) { - list($headb64, $bodyb64, $cryptob64) = explode('.', $jwt); + list($headb64, $bodyb64, $cryptob64) = explode('.', strtr($jwt, '-_', '+/')); $header = json_decode(base64_decode($headb64), true); $body = json_decode(base64_decode($bodyb64), true); diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 1408ac61a..862ad014a 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -1581,10 +1581,10 @@ EOF; foreach ($this->$source as $name => $vars) { // $vars can be in many forms: // - string - // - array('key' => 'val') - // - array(string, string) - // - array(array(), string) - // - array(array('key' => 'val'), array('key' => 'val')) + // - ['key' => 'val'] + // - [string, string] + // - [[], string] + // - [['key' => 'val'], ['key' => 'val']] // normalise this for processing by checking for string array keys $vars = is_array($vars) ? (count(array_filter(array_keys($vars), 'is_string')) > 0 ? [$vars] : $vars) : [$vars]; @@ -2772,7 +2772,7 @@ EOF; } } } - else { + else if ($type != 'link') { $template_logo = $logo; } } diff --git a/program/include/rcmail_sendmail.php b/program/include/rcmail_sendmail.php index 9e5c3d5f6..6b3ecfbf3 100644 --- a/program/include/rcmail_sendmail.php +++ b/program/include/rcmail_sendmail.php @@ -1058,6 +1058,10 @@ class rcmail_sendmail $charset = !empty($message->headers) ? $message->headers->charset : RCUBE_CHARSET; $separator = ', '; + if (!isset($this->data['recipients'])) { + $this->data['recipients'] = []; + } + // we have a set of recipients stored is session if ( $header == 'to' @@ -1085,7 +1089,7 @@ class rcmail_sendmail if ($header == 'to') { $mailfollowup = $message->headers->others['mail-followup-to'] ?? []; $mailreplyto = $message->headers->others['mail-reply-to'] ?? []; - $reply_all = $message->reply_all ?? null; + $reply_all = $this->data['reply_all'] ?? null; // Reply to mailing list... if ($reply_all == 'list' && $mailfollowup) { @@ -1125,7 +1129,7 @@ class rcmail_sendmail } } // add recipient of original message if reply to all - else if ($header == 'cc' && !empty($message->reply_all) && $message->reply_all != 'list') { + else if ($header == 'cc' && !empty($this->data['reply_all']) && $this->data['reply_all'] != 'list') { if ($v = $message->headers->to) { $fvalue .= $v; } @@ -1176,7 +1180,6 @@ class rcmail_sendmail $from_email = !empty($this->data['ident']['email']) ? mb_strtolower($this->data['ident']['email']) : ''; $to_addresses = rcube_mime::decode_address_list($fvalue, null, $decode_header, $charset); $fvalue = []; - $recipients = []; foreach ($to_addresses as $addr_part) { if (empty($addr_part['mailto'])) { @@ -1190,14 +1193,14 @@ class rcmail_sendmail if ( ($header == 'to' || $mode != self::MODE_REPLY || $mailto_lc != $from_email) - && !in_array($mailto_lc, $recipients) + && !in_array($mailto_lc, $this->data['recipients']) ) { if ($addr_part['name'] && $mailto != $addr_part['name']) { $mailto = format_email_recipient($mailto, $addr_part['name']); } - $fvalue[] = $mailto; - $recipients[] = $mailto_lc; + $fvalue[] = $mailto; + $this->data['recipients'][] = $mailto_lc; } } diff --git a/program/include/rcmail_utils.php b/program/include/rcmail_utils.php index 9036cbcfb..14626d993 100644 --- a/program/include/rcmail_utils.php +++ b/program/include/rcmail_utils.php @@ -170,7 +170,7 @@ class rcmail_utils $dir .= '/' . $db->db_provider; if (!file_exists($dir)) { - if ($opts['errors']) { + if (!empty($opts['errors'])) { rcube::raise_error("DDL Upgrade files for " . $db->db_provider . " driver not found.", false, true); } return false; @@ -253,7 +253,7 @@ class rcmail_utils * * @param string $package Package name * - * @return string Version string + * @return null|string Version string */ public static function db_version($package = 'roundcube') { @@ -265,6 +265,9 @@ class rcmail_utils $package . '-version'); $row = $db->fetch_array(); + if ($row === false) { + return null; + } $version = preg_replace('/[^0-9]/', '', $row[0]); return $version; diff --git a/program/js/app.js b/program/js/app.js index f37089d57..68135eeec 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -2647,11 +2647,13 @@ function rcube_webmail() if (!rc.env.frame_lock) rc.env.frame_lock = rc.set_busy(true, 'loading'); - if (target.frameElement) - $(target.frameElement).on('load.lock', function(e) { - rc.unlock_frame(); - $(this).off('load.lock'); - }); + try { + if (target.frameElement) + $(target.frameElement).on('load.lock', function(e) { + rc.unlock_frame(); + $(this).off('load.lock'); + }); + } catch(e) { /* Ignore permission denied error */ }; }; this.unlock_frame = function() @@ -4242,7 +4244,7 @@ function rcube_webmail() var container = $(this.gui_objects.editform).find('.identity-encryption').first(); var identity_email = $(this.gui_objects.editform).find('.ff_email').val().trim(); - if (!container.length || !identity_email || !this.mailvelope_keyring.createKeyGenContainer) + if (!container.length || !identity_email || !this.mailvelope_keyring.createKeyGenContainer) return; var key_fingerprint; diff --git a/program/js/common.js b/program/js/common.js index f6b4829d3..5384d84b5 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -429,11 +429,10 @@ function rcube_check_email(input, inline, count, strict) ipv6 = '\\[IPv6:[0-9a-f:.]+\\]', ip_addr = '(' + ipv4 + ')|(' + ipv6 + ')', // Use simplified domain matching, because we need to allow Unicode characters here - // So, e-mail address should be validated also on server side after idn_to_ascii() use - //domain_literal = '\\x5b('+dtext+'|'+quoted_pair+')*\\x5d', - //sub_domain = '('+atom+'|'+domain_literal+')', - // allow punycode/unicode top-level domain, allow extended domains (#5588) - domain = '(('+ip_addr+')|(([^@\\x2e]+\\x2e)+([^\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})))', + // So, e-mail address should be validated also on server side after idn_to_ascii() + // Allow punycode/unicode top-level domains, allow extended domains (#5588) + // Allow a domain ending with .s (#8854) + domain = '(('+ip_addr+')|(([^@.]+\\.)+([^\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|s|xn--[a-z0-9]{2,})))', // ICANN e-mail test (http://idn.icann.org/E-mail_test) icann_domains = [ '\\u0645\\u062b\\u0627\\u0644\\x2e\\u0625\\u062e\\u062a\\u0628\\u0627\\u0631', diff --git a/program/js/list.js b/program/js/list.js index 3ab77f014..9aae32b9f 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -643,8 +643,8 @@ drag_row: function(e, id) if (!this.is_event_target(e)) return true; - // accept right-clicks - if (rcube_event.get_button(e) == 2) + // handle only left-clicks + if (rcube_event.get_button(e) != 0) return true; this.in_selection_before = e && e.istouch || this.in_selection(id) ? id : false; diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index b871a0e00..e8be95b64 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -58,7 +58,7 @@ foreach ($config as $optname => $optval) { } // framework constants -define('RCUBE_VERSION', '1.6.1'); +define('RCUBE_VERSION', '1.6-git'); define('RCUBE_CHARSET', 'UTF-8'); define('RCUBE_TEMP_FILE_PREFIX', 'RCMTEMP'); @@ -100,8 +100,9 @@ if (!preg_match($regexp, $path)) { spl_autoload_register('rcube_autoload'); // set PEAR error handling (will also load the PEAR main class) -PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, function($err) { rcube::raise_error($err, true); }); - +if (class_exists('PEAR')) { + PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, function($err) { rcube::raise_error($err, true); }); +} /** * Similar function as in_array() but case-insensitive with multibyte support. diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 00e39ea7b..bf7dc4206 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -543,7 +543,7 @@ class html_checkbox extends html_inputfield } // set 'checked' attribute - $this->attrib['checked'] = (string) $value === ((string) $this->attrib['value'] ?? ''); + $this->attrib['checked'] = (string) $value === (string) ($this->attrib['value'] ?? ''); return parent::show(); } diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 1f7552605..57e4d0fa2 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -1396,7 +1396,7 @@ class rcube 'message' => $arg->getMessage(), ]; } - else if ($arg instanceof PEAR_Error) { + else if (is_object($arg) && is_a($arg, 'PEAR_Error')) { $info = $arg->getUserInfo(); $arg = [ 'code' => $arg->getCode(), diff --git a/program/lib/Roundcube/rcube_charset.php b/program/lib/Roundcube/rcube_charset.php index 94e539612..ce5f81418 100644 --- a/program/lib/Roundcube/rcube_charset.php +++ b/program/lib/Roundcube/rcube_charset.php @@ -66,6 +66,7 @@ class rcube_charset '222' => 'WINDOWS-874', '238' => 'WINDOWS-1250', 'MS950' => 'CP950', + 'WINDOWS31J' => 'CP932', 'WINDOWS949' => 'UHC', 'WINDOWS1257' => 'ISO-8859-13', 'ISO2022JP' => 'ISO-2022-JP-MS', diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 4b64482bd..9c0b5d9fa 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -341,6 +341,16 @@ class rcube_db } } + /** + * Getter for an information about the last error. + * + * @return ?array [SQLSTATE error code, driver specific error code, driver specific error message] + */ + public function error_info() + { + return $this->dbh ? $this->dbh->errorInfo() : null; + } + /** * Getter for error state * diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php index edc35468a..7b964a985 100644 --- a/program/lib/Roundcube/rcube_html2text.php +++ b/program/lib/Roundcube/rcube_html2text.php @@ -143,9 +143,10 @@ class rcube_html2text */ protected $search = [ '/\r/', // Non-legal carriage return - '/<head[^>]*>.*?<\/head>/is', // <head> - '/<script[^>]*>.*?<\/script>/is', // <script> - '/<style[^>]*>.*?<\/style>/is', // <style> + '/\n*<\/?html>\n*/is', // <html> + '/\n*<head[^>]*>.*?<\/head>\n*/is', // <head> + '/\n*<script[^>]*>.*?<\/script>\n*/is', // <script> + '/\n*<style[^>]*>.*?<\/style>\n*/is', // <style> '/[\n\t]+/', // Newlines and tabs '/<p[^>]*>/i', // <p> '/<\/p>[\s\n\t]*<div[^>]*>/i', // </p> before <div> @@ -172,6 +173,7 @@ class rcube_html2text */ protected $replace = [ '', // Non-legal carriage return + '', // <html>|</html> '', // <head> '', // <script> '', // <style> @@ -502,6 +504,7 @@ class rcube_html2text if (($pos = stripos($text, '<body')) !== false) { $pos = strpos($text, '>', $pos); $text = substr($text, $pos + 1); + $text = ltrim($text); } // Run our defined tags search-and-replace diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index aa4282a04..d5ec7d735 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1663,7 +1663,7 @@ class rcube_imap extends rcube_storage $results = $searcher->exec( $folder, $search, - $charset ? $charset : $this->default_charset, + $charset ?: $this->default_charset, $sort_field && $this->get_capability('SORT') ? $sort_field : null, $this->threading ); @@ -2060,15 +2060,17 @@ class rcube_imap extends rcube_storage // find first non-array entry for ($i=1; $i<count($part); $i++) { - if (!is_array($part[$i])) { + if (is_string($part[$i])) { $struct->ctype_secondary = strtolower($part[$i]); // read content type parameters if (isset($part[$i+1]) && is_array($part[$i+1])) { $struct->ctype_parameters = []; for ($j=0; $j<count($part[$i+1]); $j+=2) { - $param = strtolower($part[$i+1][$j]); - $struct->ctype_parameters[$param] = $part[$i+1][$j+1]; + if (is_string($part[$i+1][$j])) { + $param = strtolower($part[$i+1][$j]); + $struct->ctype_parameters[$param] = $part[$i+1][$j+1]; + } } } @@ -2146,7 +2148,9 @@ class rcube_imap extends rcube_storage if (is_array($part[$params_idx])) { $struct->ctype_parameters = []; for ($i=0; $i<count($part[$params_idx]); $i+=2) { - $struct->ctype_parameters[strtolower($part[$params_idx][$i])] = $part[$params_idx][$i+1]; + if (is_string($part[$params_idx][$i])) { + $struct->ctype_parameters[strtolower($part[$params_idx][$i])] = $part[$params_idx][$i+1]; + } } if (isset($struct->ctype_parameters['charset'])) { @@ -2187,7 +2191,9 @@ class rcube_imap extends rcube_storage } if (is_array($part[$di][1])) { for ($n=0; $n<count($part[$di][1]); $n+=2) { - $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; + if (is_string($part[$di][1][$n])) { + $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; + } } } } @@ -2442,11 +2448,11 @@ class rcube_imap extends rcube_storage $part_data = rcube_imap_generic::getStructurePartData($structure, $part); $o_part = new rcube_message_part; - $o_part->ctype_primary = $part_data['type']; - $o_part->ctype_secondary = $part_data['subtype']; - $o_part->encoding = $part_data['encoding']; - $o_part->charset = $part_data['charset']; - $o_part->size = $part_data['size']; + $o_part->ctype_primary = $part_data['type'] ?? null; + $o_part->ctype_secondary = $part_data['subtype'] ?? null; + $o_part->encoding = $part_data['encoding'] ?? null; + $o_part->charset = $part_data['charset'] ?? null; + $o_part->size = $part_data['size'] ?? 0; } $body = ''; @@ -3374,7 +3380,7 @@ class rcube_imap extends rcube_storage // get list of subscribed folders if ((strpos($folder, '%') === false) && (strpos($folder, '*') === false)) { - $a_subscribed = $this->list_folders_subscribed('', $folder . $delm . '*'); + $a_subscribed = $this->list_folders_subscribed($folder . $delm, '*'); $subscribed = $this->folder_exists($folder, true); } else { @@ -3429,7 +3435,7 @@ class rcube_imap extends rcube_storage // get list of sub-folders or all folders // if folder name contains special characters $path = strpos($folder, '*') === false && strpos($folder, '%') === false ? ($folder . $delm) : ''; - $sub_mboxes = $this->list_folders('', $path . '*'); + $sub_mboxes = $this->list_folders($path, '*'); // According to RFC3501 deleting a \Noselect folder // with subfolders may fail. To workaround this we delete @@ -4515,7 +4521,10 @@ class rcube_imap extends rcube_storage $path1 = explode($this->delimiter, $str1); $path2 = explode($this->delimiter, $str2); - foreach ($path1 as $idx => $folder1) { + $len = max(count($path1), count($path2)); + + for ($idx = 0; $idx < $len; $idx++) { + $folder1 = $path1[$idx] ?? ''; $folder2 = $path2[$idx] ?? ''; if ($folder1 === $folder2) { diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index f465f8262..83d4ad0ad 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -1054,7 +1054,8 @@ class rcube_imap_generic } if (!empty($this->prefs['socket_options'])) { - $context = stream_context_create($this->prefs['socket_options']); + $options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1]); + $context = stream_context_create($options); $this->fp = stream_socket_client($host . ':' . $port, $errno, $errstr, $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context); } @@ -2768,7 +2769,7 @@ class rcube_imap_generic $sort_order = $order == 'ASC' ? SORT_ASC : SORT_DESC; $sort_flags = SORT_STRING | SORT_FLAG_CASE; - if (in_array($field, ['arrival', 'date', 'internaldate', 'timestamp'])) { + if (in_array($field, ['arrival', 'date', 'internaldate', 'timestamp', 'size', 'uid', 'id'])) { $sort_flags = SORT_NUMERIC; } @@ -2869,11 +2870,11 @@ class rcube_imap_generic $mode = 3; break; default: - $mode = 0; + $mode = $formatted ? 4 : 0; } // Use BINARY extension when possible (and safe) - $binary = $binary && $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY'); + $binary = $binary && $mode && preg_match('/^[0-9.]+$/', (string) $part) && $this->hasCapability('BINARY'); $fetch_mode = $binary ? 'BINARY' : 'BODY'; $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : ''; @@ -2920,7 +2921,8 @@ class rcube_imap_generic if ($line[0] == '(' && substr($line, -1) == ')') { // tokenize content inside brackets // the content can be e.g.: (UID 9844 BODY[2.4] NIL) - $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line)); + $line = preg_replace('/(^\(|\)$)/', '', $line); + $tokens = $this->tokenizeResponse($line); for ($i=0; $i<count($tokens); $i+=2) { if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) { @@ -2930,16 +2932,16 @@ class rcube_imap_generic } } + // Cyrus IMAP does not return a NO-response on error, but we can detect it + // and fallback to a non-binary fetch (#9097) + if ($binary && !$found) { + $binary = $initiated = false; + $line = trim($this->readLine(1024)); // the OK response line + continue; + } + if ($result !== false) { - if ($mode == 1) { - $result = base64_decode($result); - } - else if ($mode == 2) { - $result = quoted_printable_decode($result); - } - else if ($mode == 3) { - $result = convert_uudecode($result); - } + $result = $this->decodeContent($result, $mode, true); } } // response with string literal @@ -2953,65 +2955,37 @@ class rcube_imap_generic if (!$bytes) { $result = ''; } + // An optimal path for a case when we need the body as-is in a string + else if (!$mode && !$file && !$print) { + $result = $this->readBytes($bytes); + } else while ($bytes > 0) { - $line = $this->readBytes($bytes > $chunkSize ? $chunkSize : $bytes); + $chunk = $this->readBytes($bytes > $chunkSize ? $chunkSize : $bytes); - if ($line === '') { + if ($chunk === '') { break; } - $len = strlen($line); + $len = strlen($chunk); if ($len > $bytes) { - $line = substr($line, 0, $bytes); - $len = strlen($line); + $chunk = substr($chunk, 0, $bytes); + $len = strlen($chunk); } $bytes -= $len; - // BASE64 - if ($mode == 1) { - $line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line); - // create chunks with proper length for base64 decoding - $line = $prev.$line; - $length = strlen($line); - if ($length % 4) { - $length = floor($length / 4) * 4; - $prev = substr($line, $length); - $line = substr($line, 0, $length); - } - else { - $prev = ''; - } - $line = base64_decode($line); - } - // QUOTED-PRINTABLE - else if ($mode == 2) { - $line = rtrim($line, "\t\r\0\x0B"); - $line = quoted_printable_decode($line); - } - // UUENCODE - else if ($mode == 3) { - $line = rtrim($line, "\t\r\n\0\x0B"); - if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) { - continue; - } - $line = convert_uudecode($line); - } - // default - else if ($formatted) { - $line = rtrim($line, "\t\r\n\0\x0B") . "\n"; - } + $chunk = $this->decodeContent($chunk, $mode, $bytes <= 0, $prev); if ($file) { - if (fwrite($file, $line) === false) { + if (fwrite($file, $chunk) === false) { break; } } else if ($print) { - echo $line; + echo $chunk; } else { - $result .= $line; + $result .= $chunk; } } } @@ -3033,6 +3007,105 @@ class rcube_imap_generic return false; } + /** + * Decodes a chunk of a message part content from a FETCH response. + * + * @param string $chunk Content + * @param int $mode Encoding mode + * @param bool $is_last Whether it is a last chunk of data + * @param string $prev Extra content from the previous chunk + * + * @return string Encoded string + */ + protected static function decodeContent($chunk, $mode, $is_last = false, &$prev = '') + { + // BASE64 + if ($mode == 1) { + $chunk = $prev . preg_replace('|[^a-zA-Z0-9+=/]|', '', $chunk); + + // create chunks with proper length for base64 decoding + $length = strlen($chunk); + + if ($length % 4) { + $length = floor($length / 4) * 4; + $prev = substr($chunk, $length); + $chunk = substr($chunk, 0, $length); + } + else { + $prev = ''; + } + + return base64_decode($chunk); + } + + // QUOTED-PRINTABLE + if ($mode == 2) { + if (!self::decodeContentChunk($chunk, $prev, $is_last)) { + return ''; + } + + $chunk = preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk); + + return quoted_printable_decode($chunk); + } + + // X-UUENCODE + if ($mode == 3) { + if (!self::decodeContentChunk($chunk, $prev, $is_last)) { + return ''; + } + + $chunk = preg_replace( + ['/\r?\n/', '/(^|\n)end$/', '/^begin\s+[0-7]{3,4}\s+[^\n]+\n/'], + ["\n", '', ''], + $chunk + ); + + if (!strlen($chunk)) { + return ''; + } + + return convert_uudecode($chunk); + } + + // Plain text formatted + // TODO: Formatting should be handled outside of this class + if ($mode == 4) { + if (!self::decodeContentChunk($chunk, $prev, $is_last)) { + return ''; + } + + if ($is_last) { + $chunk = rtrim($chunk, "\t\r\n\0\x0B"); + } + + return preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk); + } + + return $chunk; + } + + /** + * A helper for a new-line aware parsing. See self::decodeContent(). + */ + private static function decodeContentChunk(&$chunk, &$prev, $is_last) + { + $chunk = $prev . $chunk; + $prev = ''; + + if (!$is_last) { + if (($pos = strrpos($chunk, "\n")) !== false) { + $prev = substr($chunk, $pos + 1); + $chunk = substr($chunk, 0, $pos + 1); + } else { + $prev = $chunk; + return false; + } + } + + return true; + } + /** * Handler for IMAP APPEND command * diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index 69308d603..cee84f8b3 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -84,6 +84,11 @@ class rcube_ldap extends rcube_addressbook $fetch_attributes = ['objectClass']; + // Disable VLV by default + if (!isset($this->prop['vlv'])) { + $this->prop['vlv'] = false; + } + // check if groups are configured if (!empty($p['groups']) && is_array($p['groups'])) { $this->groups = true; @@ -187,7 +192,7 @@ class rcube_ldap extends rcube_addressbook (array) ($this->coltypes['address']['subtypes'] ?? null), (array) ($this->coltypes['locality']['subtypes'] ?? null) ), - ] + (array) $this->coltypes['address']; + ] + (array) ($this->coltypes['address'] ?? []); foreach (['street','locality','zipcode','region','country'] as $childcol) { if (!empty($this->coltypes[$childcol])) { @@ -221,19 +226,15 @@ class rcube_ldap extends rcube_addressbook } } - // make sure 'required_fields' is an array - if (!isset($this->prop['required_fields'])) { - $this->prop['required_fields'] = []; - } - else if (!is_array($this->prop['required_fields'])) { - $this->prop['required_fields'] = (array) $this->prop['required_fields']; - } + // make sure 'required_fields' and 'autovalues' are an array + $this->prop['required_fields'] = (array) ($this->prop['required_fields'] ?? []); + $this->prop['autovalues'] = (array) ($this->prop['autovalues'] ?? []); // make sure LDAP_rdn field is required if ( !empty($this->prop['LDAP_rdn']) && !in_array($this->prop['LDAP_rdn'], $this->prop['required_fields']) - && !in_array($this->prop['LDAP_rdn'], array_keys((array)$this->prop['autovalues'])) + && !array_key_exists($this->prop['LDAP_rdn'], $this->prop['autovalues']) ) { $this->prop['required_fields'][] = $this->prop['LDAP_rdn']; } @@ -328,9 +329,9 @@ class rcube_ldap extends rcube_addressbook $conf = $rcube->plugins->exec_hook('ldap_connected', $this->prop + ['host' => $host]); $bind_pass = $conf['bind_pass'] ?? null; - $bind_user = $conf['bind_user'] ?? null; - $bind_dn = $conf['bind_dn'] ?? null; - $auth_method = $conf['auth_method'] ?? null; + $bind_user = $conf['bind_user'] ?? ''; + $bind_dn = $conf['bind_dn'] ?? ''; + $auth_method = $conf['auth_method'] ?? ''; $this->base_dn = $this->groups_base_dn = $conf['base_dn'] ?? null; if (!empty($conf['groups']['base_dn'])) { @@ -803,7 +804,7 @@ class rcube_ldap extends rcube_addressbook */ function _entry_sort_cmp($a, $b) { - return strcmp($a[$this->sort_col][0], $b[$this->sort_col][0]); + return strcmp($a[$this->sort_col][0] ?? '', $b[$this->sort_col][0] ?? ''); } /** @@ -860,11 +861,13 @@ class rcube_ldap extends rcube_addressbook foreach ($ldap_data as $entry) { $rec = $this->_ldap2result($entry); foreach ($fields as $f) { - foreach ((array)$rec[$f] as $val) { - if ($this->compare_search_value($f, $val, $search, $mode)) { - $this->result->add($rec); - $this->result->count++; - break 2; + if (!empty($rec[$f])) { + foreach ((array)$rec[$f] as $val) { + if ($this->compare_search_value($f, $val, $search, $mode)) { + $this->result->add($rec); + $this->result->count++; + break 2; + } } } } @@ -908,18 +911,19 @@ class rcube_ldap extends rcube_addressbook $filter .= ')'; } else { + $attributes = []; + if ($fields == '*') { - // search_fields are required for fulltext search - if (empty($this->prop['search_fields'])) { - $this->set_error(self::ERROR_SEARCH, 'nofulltextsearch'); - $this->result = new rcube_result_set(); - return $this->result; + $attributes = (array) ($this->prop['search_fields'] ?? []); + + // If search fields aren't configured use some common fields + if (empty($search_fields)) { + $fields = ['name', 'surname', 'firstname', 'email']; } - $attributes = (array) $this->prop['search_fields']; } - else { - // map address book fields into ldap attributes - $attributes = []; + + // map address book fields into ldap attributes + if (empty($attributes)) { foreach ((array) $fields as $field) { if (!empty($this->coltypes[$field]) && !empty($this->coltypes[$field]['attributes'])) { $attributes = array_merge($attributes, (array) $this->coltypes[$field]['attributes']); @@ -1026,11 +1030,14 @@ class rcube_ldap extends rcube_addressbook // add general filter to query if (!empty($this->prop['filter'])) { - $prop['filter'] = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['filter']) . ')' . $prop['filter'] . ')'; + $prop['filter'] = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['filter']) . ')' . $filter . ')'; } } - $result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $attrs, $prop, $count); + $search_scope = $prop['scope'] ?? 'sub'; + $search_filter = $prop['filter'] ?? '(objectclass=*)'; + + $result = $this->ldap->search($base_dn, $search_filter, $search_scope, $attrs, $prop, $count); $result_count = 0; // we have a search result resource, get all entries @@ -1046,18 +1053,19 @@ class rcube_ldap extends rcube_addressbook && is_array($this->prop['group_filters']) && !empty($this->prop['groups']['filter']) ) { - $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['groups']['filter']) . ')' . $filter . ')'; - - // for groups we may use cn instead of displayname... - if ($this->prop['fieldmap']['name'] != $this->prop['groups']['name_attr']) { - $filter = str_replace(strtolower($this->prop['fieldmap']['name']) . '=', $this->prop['groups']['name_attr'] . '=', $filter); - } - $name_attr = $this->prop['groups']['name_attr']; $email_attr = $this->prop['groups']['email_attr'] ?: 'mail'; $attrs = array_unique(['dn', 'objectClass', $name_attr, $email_attr]); - $res = $this->ldap->search($this->groups_base_dn, $filter, $this->prop['groups']['scope'], $attrs, $prop, $count); + $search_scope = $this->prop['groups']['scope'] ?? 'sub'; + $search_filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['groups']['filter']) . ')' . $filter . ')'; + + // for groups we may use cn instead of displayname... + if ($this->prop['fieldmap']['name'] != $name_attr) { + $search_filter = str_replace(strtolower($this->prop['fieldmap']['name']) . '=', $name_attr . '=', $search_filter); + } + + $res = $this->ldap->search($this->groups_base_dn, $search_filter, $search_scope, $attrs, $prop, $count); if ($count && $res) { $result += $res; @@ -1571,7 +1579,7 @@ class rcube_ldap extends rcube_addressbook $attrvals['{'.$k.'}'] = is_array($v) ? $v[0] : $v; } - foreach ((array) $this->prop['autovalues'] as $lf => $templ) { + foreach ($this->prop['autovalues'] as $lf => $templ) { if (empty($attrs[$lf])) { if (strpos($templ, '(') !== false) { // replace {attr} placeholders with (escaped!) attribute values to be safely eval'd @@ -1617,9 +1625,15 @@ class rcube_ldap extends rcube_addressbook // determine record type if ($this->is_group_entry($rec)) { - $out['_type'] = 'group'; - $out['readonly'] = true; - $fieldmap['name'] = $this->group_data['name_attr'] ?: $this->prop['groups']['name_attr']; + $out['_type'] = 'group'; + $out['readonly'] = true; + + if (!empty($this->group_data['name_attr'])) { + $fieldmap['name'] = $this->group_data['name_attr']; + } + else if (!empty($this->prop['groups']['name_attr'])) { + $fieldmap['name'] = $this->prop['groups']['name_attr']; + } } // assign object type from object class mapping @@ -1795,6 +1809,10 @@ class rcube_ldap extends rcube_addressbook */ private function is_group_entry($entry) { + if (empty($entry['objectclass'])) { + return false; + } + $classes = array_map('strtolower', (array)$entry['objectclass']); return count(array_intersect(array_keys($this->group_types), $classes)) > 0; @@ -1900,10 +1918,10 @@ class rcube_ldap extends rcube_addressbook $base_dn = $this->groups_base_dn; $filter = $this->prop['groups']['filter']; - $scope = $this->prop['groups']['scope']; - $name_attr = $this->prop['groups']['name_attr']; - $email_attr = $this->prop['groups']['email_attr'] ?: 'mail'; - $sort_attrs = (array) ($this->prop['groups']['sort'] ? $this->prop['groups']['sort'] : $name_attr); + $scope = $this->prop['groups']['scope'] ?? 'sub'; + $name_attr = !empty($this->prop['groups']['name_attr']) ? $this->prop['groups']['name_attr'] : 'cn'; + $email_attr = !empty($this->prop['groups']['email_attr']) ? $this->prop['groups']['email_attr'] : 'mail'; + $sort_attrs = (array) (!empty($this->prop['groups']['sort']) ? $this->prop['groups']['sort'] : $name_attr); $sort_attr = $sort_attrs[0]; $page_size = 200; @@ -1920,7 +1938,7 @@ class rcube_ldap extends rcube_addressbook $ldap->set_vlv_page($vlv_page+1, $page_size); } - $props = ['sort' => $this->prop['groups']['sort']]; + $props = ['sort' => $this->prop['groups']['sort'] ?? null]; $attrs = array_unique(['dn', 'objectClass', $name_attr, $email_attr, $sort_attr]); // add search filter @@ -2015,11 +2033,12 @@ class rcube_ldap extends rcube_addressbook if ($list = $this->ldap->read_entries($dn, '(objectClass=*)', $attrs)) { $entry = $list[0]; $group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr]; + $classes = !empty($entry['objectclass']) ? $entry['objectclass'] : []; $group_cache[$group_id]['ID'] = $group_id; $group_cache[$group_id]['dn'] = $dn; $group_cache[$group_id]['name'] = $group_name; - $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']); + $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($classes); } else { $group_cache[$group_id] = false; diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 07c8a2346..0326eb8a0 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -68,6 +68,13 @@ class rcube_message_header */ public $cc; + /** + * Message hidden recipients (Bcc) + * + * @var string + */ + public $bcc; + /** * Message Reply-To header * @@ -201,6 +208,21 @@ class rcube_message_header */ public $flags = []; + /** + * Extra flags (for the messages list) + * + * @var array + * @deprecated Use $flags + */ + public $list_flags = []; + + /** + * Extra columns content (for the messages list) + * + * @var array + */ + public $list_cols = []; + /** * Message structure * diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index cce4e6105..001f8683f 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -341,18 +341,20 @@ class rcube_plugin_api if (is_readable($composer) && ($json = json_decode(file_get_contents($composer), true))) { // Build list of plugins required $require = []; - foreach (array_keys((array) $json['require']) as $dname) { - if (!preg_match('|^([^/]+)/([a-zA-Z0-9_-]+)$|', $dname, $m)) { - continue; - } + if (!empty($json['require'])) { + foreach (array_keys((array) $json['require']) as $dname) { + if (!preg_match('|^([^/]+)/([a-zA-Z0-9_-]+)$|', $dname, $m)) { + continue; + } - $vendor = $m[1]; - $name = $m[2]; + $vendor = $m[1]; + $name = $m[2]; - if ($name != 'plugin-installer' && $vendor != 'pear' && $vendor != 'pear-pear') { - $dpath = unslashify($dir->path) . "/$name/$name.php"; - if (is_readable($dpath)) { - $require[] = $name; + if ($name != 'plugin-installer' && $vendor != 'pear' && $vendor != 'pear-pear') { + $dpath = unslashify($dir->path) . "/$name/$name.php"; + if (is_readable($dpath)) { + $require[] = $name; + } } } } diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php index b1b7fcb9e..55795e003 100644 --- a/program/lib/Roundcube/rcube_string_replacer.php +++ b/program/lib/Roundcube/rcube_string_replacer.php @@ -59,8 +59,8 @@ class rcube_string_replacer $link_prefix = "([\w]+:\/\/|{$this->noword}[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)"; $this->options = $options; - $this->linkref_index = '/\[([^\]#]+)\](:?\s*' . substr($this->pattern, 1, -1) . ')/'; - $this->linkref_pattern = '/\[([^\]#]+)\]/'; + $this->linkref_index = '/\[([^<>\]#]+)\](:?\s*' . substr($this->pattern, 1, -1) . ')/'; + $this->linkref_pattern = '/\[([^<>\]#]+)\]/'; $this->link_pattern = "/$link_prefix($utf_domain([$url1]*[$url2]+)*)/"; $this->mailto_pattern = "/(" . "[-\w!\#\$%&*+~\/^`|{}=]+(?:\.[-\w!\#\$%&*+~\/^`|{}=]+)*" // local-part @@ -152,7 +152,7 @@ class rcube_string_replacer $matches[0][1] ]; - return $this->get_replacement($this->add('['.$key.']')) . $matches[2][0]; + return $this->get_replacement($this->add('[' . $key . ']')) . $matches[2][0]; } /** diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index a641497d5..2724c015e 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -748,8 +748,8 @@ class rcube_utils */ public static function parse_host_uri($host, $plain_port = null, $ssl_port = null) { - if (strpos($host, 'unix://') === 0) { - return [$host, 'unix', -1]; + if (preg_match('#^(unix|ldapi)://#i', $host, $matches)) { + return [$host, $matches[1], -1]; } $url = parse_url($host); @@ -1509,12 +1509,17 @@ class rcube_utils } if (strpos($format, 'u') !== false) { - $dt = number_format(microtime(true), 6, '.', ''); - $dt .= '.' . date_default_timezone_get(); + $dt = number_format(microtime(true), 6, '.', ''); + + try { + $date = date_create_from_format('U.u', $dt); + $date->setTimeZone(new DateTimeZone(date_default_timezone_get())); - if ($date = date_create_from_format('U.u.e', $dt)) { return $date->format($format); } + catch (Exception $e) { + // ignore, fallback to date() + } } return date($format); diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index 5fbfb4ed6..4601649c1 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -201,7 +201,7 @@ class rcube_vcard $key = $col; $subtype = ''; - if (!empty($raw['type'])) { + if (!empty($raw['type']) && is_array($raw['type'])) { $raw['type'] = array_map('strtolower', $raw['type']); $combined = implode(',', array_diff($raw['type'], ['internet', 'pref'])); @@ -877,7 +877,17 @@ class rcube_vcard } else { foreach ((array)$attrvalues as $attrvalue) { - $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ','); + $attrname = strtoupper($attrname); + // TYPE=OTHER is non-standard, TYPE=INTERNET is redundant, remove these + if ($attrname == 'TYPE') { + $attrvalue = array_map('strtolower', (array) $attrvalue); + $attrvalue = array_diff($attrvalue, ['other', 'internet']); + if (empty($attrvalue)) { + continue; + } + } + + $attr .= ';' . $attrname . '=' . self::vcard_quote($attrvalue, ','); } } } diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index 78db69e0c..14464fcd4 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -744,7 +744,7 @@ class rcube_washtml // space(s) between <NOBR> '/(<\/nobr>)(\s+)(<nobr>)/i', // PHP bug #32547 workaround: remove title tag - '/<title[^>]*>.*<\/title>/i', + '/<title[^>]*>.*<\/title>/iU', // remove <!doctype> before BOM (#1490291) '/<\!doctype[^>]+>[^<]*/im', // byte-order mark (only outlook?) diff --git a/skins/elastic/ui.js b/skins/elastic/ui.js index 75343e1be..5f5ce0d89 100644 --- a/skins/elastic/ui.js +++ b/skins/elastic/ui.js @@ -870,8 +870,8 @@ function rcube_elastic_ui() $('.popup', context).addClass('formcontent').append( $('<div class="form-group row">') - .append(label.attr('for', id).addClass('col-sm-2 col-form-label')) - .append($('<div class="col-sm-10">').append(input)) + .append(label.attr('for', id).addClass('col-sm-4 col-form-label text-break')) + .append($('<div class="col-sm-8">').append(input)) ); input.focus();
Attachment:
signature.asc
Description: PGP signature