[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1052629: bookworm-pu: package roundcube/1.6.3+dfsg-1~deb12u1



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(/&nbsp;/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


Reply to: