diff --git a/.docker/release/files/usr/local/include/application.ini b/.docker/release/files/usr/local/include/application.ini
index 1f5ed1023..36d814643 100644
--- a/.docker/release/files/usr/local/include/application.ini
+++ b/.docker/release/files/usr/local/include/application.ini
@@ -289,5 +289,5 @@ dev_email = ""
dev_password = ""
[version]
-current = "2.22.2"
+current = "2.22.3"
saved = "Fri, 30 Sep 2022 12:34:56 +0000"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9c1d1058..d2dd2edac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+## 2.22.3 – 2022-11-25
+
+### Added
+- application.ini config logs.path and cache.path to improve custom data structure.
+
+### Changed
+- Improved cPanel integration
+ [#697](https://github.com/the-djmaze/snappymail/pull/697)
+- Update to OpenPGP.js v5.5.0
+
+### Fixed
+- drag & drop folder expansion #707
+ [#707](https://github.com/the-djmaze/snappymail/pull/707)
+- Save selected messages as .eml in Nextcloud failed
+ [#704](https://github.com/the-djmaze/snappymail/pull/704)
+
+
## 2.22.2 – 2022-11-24
### Added
diff --git a/README.md b/README.md
index f9bc9a61e..ff6b4eaa0 100644
--- a/README.md
+++ b/README.md
@@ -142,24 +142,24 @@ RainLoop 1.17 vs SnappyMail
|js/* |RainLoop |Snappy |
|--------------- |--------: |--------: |
|admin.js |2.170.153 | 82.419 |
-|app.js |4.207.787 | 407.238 |
+|app.js |4.207.787 | 407.335 |
|boot.js | 868.735 | 1.989 |
|libs.js | 658.812 | 193.075 |
|sieve.js | 0 | 86.121 |
|polyfills.js | 334.608 | 0 |
|serviceworker.js | 0 | 285 |
-|TOTAL |8.240.095 | 771.127 |
+|TOTAL |8.240.095 | 771.224 |
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|admin.min.js | 256.831 | 40.933 | 73.606 | 13.575 | 60.877 | 12.185 |
-|app.min.js | 515.367 | 188.830 |139.456 | 62.816 |110.485 | 53.944 |
+|app.min.js | 515.367 | 188.819 |139.456 | 62.830 |110.485 | 53.925 |
|boot.min.js | 84.659 | 1.216 | 26.998 | 761 | 23.643 | 614 |
|libs.min.js | 584.772 | 92.084 |180.901 | 34.119 |155.182 | 30.621 |
|sieve.min.js | 0 | 41.926 | 0 | 10.484 | 0 | 9.451 |
|polyfills.min.js | 32.837 | 0 | 11.406 | 0 | 10.175 | 0 |
-|TOTAL user |1.217.635 | 282.130 |358.761 | 97.696 |299.485 | 85.179 |
-|TOTAL user+sieve |1.217.635 | 324.056 |358.761 |108.180 |299.485 | 94.630 |
+|TOTAL user |1.217.635 | 282.119 |358.761 | 97.710 |299.485 | 85.160 |
+|TOTAL user+sieve |1.217.635 | 324.045 |358.761 |108.194 |299.485 | 94.611 |
|TOTAL admin | 959.099 | 134.233 |292.911 | 48.455 |249.877 | 43.420 |
For a user its around 70% smaller and faster than traditional RainLoop.
@@ -189,12 +189,12 @@ For a user its around 70% smaller and faster than traditional RainLoop.
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|------------ |-------: |------: |------: |------: |--------: |
-|app.css | 340.331 | 82.425 | 46.946 | 17.049 | 14.595 |
-|app.min.css | 274.947 | 66.502 | 39.647 | 15.049 | 13.221 |
+|app.css | 340.331 | 82.471 | 46.946 | 17.107 | 14.645 |
+|app.min.css | 274.947 | 66.548 | 39.647 | 15.117 | 13.260 |
|boot.css | | 1.326 | | 664 | 545 |
|boot.min.css | | 1.071 | | 590 | 474 |
-|admin.css | | 29.807 | | 6.744 | 5.851 |
-|admin.min.css | | 24.223 | | 6.142 | 5.397 |
+|admin.css | | 29.853 | | 6.812 | 5.933 |
+|admin.min.css | | 24.269 | | 6.210 | 5.480 |
### PGP
RainLoop uses the old OpenPGP.js v2
@@ -208,7 +208,7 @@ See https://github.com/the-djmaze/openpgpjs for development
|OpenPGP |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|--------------- |--------: |--------: |------: |-------: |--------: |--------: |
-|openpgp.min.js | 330.742 | 539.257 |102.388 | 166.998 | 84.241 | 137.186 |
+|openpgp.min.js | 330.742 | 539.988 |102.388 | 167.371 | 84.241 | 137.413 |
|openpgp.worker | 1.499 | | 824 | | 695 | |
diff --git a/build/cpanel.php b/build/cpanel.php
index 2718b75aa..d8280d9e4 100755
--- a/build/cpanel.php
+++ b/build/cpanel.php
@@ -21,9 +21,8 @@ foreach ($files as $file) {
}
$cpanel_tar->addFile('.htaccess', "{$cpanel_path}.htaccess");
-//$cpanel_tar->addFile('_include.php', 'snappymail/app/_include.php');
$cpanel_tar->addFile('README.md', "{$cpanel_path}README.md");
-$cpanel_tar->addFromString('VERSION', $package->version);
+$cpanel_tar->addFromString("{$cpanel_path}VERSION", $package->version);
$index = file_get_contents('index.php');
$index = str_replace('0.0.0', $package->version, $index);
diff --git a/dev/External/User/ko.js b/dev/External/User/ko.js
index c20c06af9..194f1b1af 100644
--- a/dev/External/User/ko.js
+++ b/dev/External/User/ko.js
@@ -29,37 +29,39 @@ const rlContentType = 'snappymail/action',
},
dragTimer = {
- id: 0,
- stop: () => clearTimeout(dragTimer.id),
- start: fn => dragTimer.id = setTimeout(fn, 500)
+ id: 0
},
- fnStop = (e, element) => {
+ dragStop = (e, element) => {
e.preventDefault();
- element.classList.remove('droppableHover');
- dragTimer.stop();
+ element?.classList.remove('droppableHover');
+ if (dragTimer.node == element) {
+ dragTimer.node = null;
+ clearTimeout(dragTimer.id);
+ }
},
- fnHover = (e, element, folder) => {
+ dragEnter = (e, element, folder) => {
let files = false;
// if (e.dataTransfer.types.includes('Files'))
for (const item of e.dataTransfer.items) {
files |= 'file' === item.kind && 'message/rfc822' === item.type;
}
if (files || dragMessages()) {
- fnStop(e, element);
- files && e.stopPropagation();
+ e.stopPropagation();
+ dragStop(e, dragTimer.node);
e.dataTransfer.dropEffect = files ? 'copy' : (e.ctrlKey ? 'copy' : 'move');
element.classList.add('droppableHover');
if (folder.collapsed()) {
- dragTimer.start(() => {
+ dragTimer.node = element;
+ dragTimer.id = setTimeout(() => {
folder.collapsed(false);
setExpandedFolder(folder.fullName, true);
}, 500);
}
}
},
- fnDrop = (e, element, folder, dragData) => {
- fnStop(e, element);
+ dragDrop = (e, element, folder, dragData) => {
+ dragStop(e, element);
if (dragMessages() && 'copyMove' == e.dataTransfer.effectAllowed) {
moveMessagesToFolder(FolderUserStore.currentFolderFullName(), dragData.data, folder.fullName, e.ctrlKey);
} else if (e.dataTransfer.types.includes('Files')) {
@@ -160,10 +162,10 @@ Object.assign(ko.bindingHandlers, {
init: (element, fValueAccessor) => {
const folder = fValueAccessor(); // ko.dataFor(element)
folder && addEventsListeners(element, {
- dragenter: e => fnHover(e, element, folder),
- dragover: e => fnHover(e, element, folder),
- dragleave: e => fnStop(e, element),
- drop: e => fnDrop(e, element, folder, dragData)
+ dragenter: e => dragEnter(e, element, folder),
+ dragover: e => e.preventDefault(),
+ dragleave: e => dragStop(e, element),
+ drop: e => dragDrop(e, element, folder, dragData)
});
}
},
diff --git a/dev/Knoin/AbstractModel.js b/dev/Knoin/AbstractModel.js
index f98deade1..fe5ebab81 100644
--- a/dev/Knoin/AbstractModel.js
+++ b/dev/Knoin/AbstractModel.js
@@ -105,6 +105,7 @@ export class AbstractModel {
// fall through
case 'undefined':
default:
+ this[key] = value;
// console.log((typeof this[key])+' '+(model.name)+'.'+key+' not revived');
}
} catch (e) {
diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js
index d17098331..8862785fe 100644
--- a/dev/View/Popup/Compose.js
+++ b/dev/View/Popup/Compose.js
@@ -942,7 +942,7 @@ export class ComposePopupView extends AbstractViewPopup {
this.editor(editor => {
encrypted || editor.setHtml(sText);
- if (encrypted || isPlainEditor() || !message.isHtml()) {
+ if (encrypted || isPlainEditor()) {
editor.modePlain();
}
encrypted && editor.setPlain(sText);
diff --git a/dev/boot.js b/dev/boot.js
index f4d131326..0d52d9ba9 100644
--- a/dev/boot.js
+++ b/dev/boot.js
@@ -31,6 +31,19 @@ const
navigator.cookieEnabled || redirect('NoCookie');
[].flat || redirect('BadBrowser');
+try {
+ let smctoken = localStorage.getItem('smctoken');
+ if (!smctoken) {
+ let data = new Uint8Array(16);
+ crypto.getRandomValues(data);
+ smctoken = btoa(String.fromCharCode(...data));
+ localStorage.setItem('smctoken', smctoken);
+ }
+ document.cookie = 'smctoken='+encodeURIComponent(smctoken)+"; path=/; samesite=strict";
+} catch (e) {
+ console.error(e);
+}
+
let RL_APP_DATA = {};
doc.documentElement.classList.toggle('rl-mobile', 'mobile' === layout[2] || (!layout && 1000 > innerWidth));
diff --git a/integrations/cloudron/DESCRIPTION.md b/integrations/cloudron/DESCRIPTION.md
index a182909e6..4ca3ec11e 100644
--- a/integrations/cloudron/DESCRIPTION.md
+++ b/integrations/cloudron/DESCRIPTION.md
@@ -1,4 +1,4 @@
-This app packages SnappyMail 2.22.2.
+This app packages SnappyMail 2.22.3.
SnappyMail is a simple, modern, lightweight & fast web-based email client.
diff --git a/integrations/cloudron/Dockerfile b/integrations/cloudron/Dockerfile
index d1d70f1ad..da746c635 100644
--- a/integrations/cloudron/Dockerfile
+++ b/integrations/cloudron/Dockerfile
@@ -4,7 +4,7 @@ RUN mkdir -p /app/code
WORKDIR /app/code
# If you change the extraction below, be sure to test on scaleway
-VERSION=2.22.2
+VERSION=2.22.3
RUN wget https://github.com/the-djmaze/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \
unzip /tmp/snappymail.zip -d /app/code && \
rm /tmp/snappymail.zip && \
diff --git a/integrations/cpanel/usr/local/cpanel/base/3rdparty/snappymail/include.php b/integrations/cpanel/usr/local/cpanel/base/3rdparty/snappymail/include.php
new file mode 100644
index 000000000..0135d1f8d
--- /dev/null
+++ b/integrations/cpanel/usr/local/cpanel/base/3rdparty/snappymail/include.php
@@ -0,0 +1,37 @@
+snappymail
SnappyMail
SnappyMail Webmail
- 2.22.2
+ 2.22.3
agpl
SnappyMail, RainLoop Team, Nextgen-Networks, Tab Fitts, Nathan Kinkade, Pierre-Alain Bandinelli
{
-// if (rl.settings.get('Nextcloud'))
const
queue = [],
avatars = new Map,
@@ -45,6 +44,9 @@
fn = url=>{element.src = url};
if (url) {
fn(url);
+ } else if (msg.avatar) {
+ let bimi = 'pass' == msg.from[0].dkimStatus ? 1 : 0;
+ fn(`?Avatar/${bimi}/${msg.avatar}`);
} else {
queue.push([msg, fn]);
runQueue();
@@ -62,7 +64,7 @@
if (messageItemHeader) {
messageItemHeader.prepend(Element.fromHTML(
- `
`
+ `
`
));
}
@@ -80,6 +82,9 @@
};
if (url) {
fn(url);
+ } else if (msg.avatar) {
+ let bimi = 'pass' == msg.from[0].dkimStatus ? 1 : 0;
+ fn(`?Avatar/${bimi}/${msg.avatar}`);
} else {
// let from = msg.from[0], bimi = 'pass' == from.dkimStatus ? 1 : 0;
// view.viewUserPic(`?Avatar/${bimi}/${encodeURIComponent(from.email)}`);
@@ -93,7 +98,7 @@
if ('MailMessageList' === e.detail.viewModelTemplateID) {
document.getElementById('MailMessageList').content.querySelector('.messageCheckbox')
- .append(Element.fromHTML(`
`));
+ .append(Element.fromHTML(`
`));
}
});
diff --git a/plugins/avatars/index.php b/plugins/avatars/index.php
index fb3145bf9..fcf762763 100644
--- a/plugins/avatars/index.php
+++ b/plugins/avatars/index.php
@@ -3,12 +3,12 @@
class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
- NAME = 'Avatar',
+ NAME = 'Avatars',
AUTHOR = 'SnappyMail',
URL = 'https://snappymail.eu/',
- VERSION = '1.0',
- RELEASE = '2022-11-23',
- REQUIRED = '2.22.0',
+ VERSION = '1.1',
+ RELEASE = '2022-11-27',
+ REQUIRED = '2.22.4',
CATEGORY = 'Contacts',
LICENSE = 'MIT',
DESCRIPTION = 'Show photo of sender in message and messages list (supports BIMI and Gravatar, Contacts is still TODO)';
@@ -19,6 +19,30 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
$this->addJs('avatars.js');
$this->addJsonHook('Avatar', 'DoAvatar');
$this->addPartHook('Avatar', 'ServiceAvatar');
+ // https://github.com/the-djmaze/snappymail/issues/714
+ $this->Config()->Get('plugin', 'delay', true)
+ || $this->addHook('filter.json-response', 'FilterJsonResponse');
+ }
+
+ public function FilterJsonResponse(string $sAction, array &$aResponseItem)
+ {
+ if ('MessageList' === $sAction && !empty($aResponseItem['Result']['@Collection'])) {
+ foreach ($aResponseItem['Result']['@Collection'] as $id => $message) {
+ $aResponseItem['Result']['@Collection'][$id]['Avatar'] = static::encryptFrom($message['From'][0]);
+ }
+ } else if ('Message' === $sAction && !empty($aResponseItem['Result']['From'])) {
+ $aResponseItem['Result']['Avatar'] = static::encryptFrom($aResponseItem['Result']['From'][0]);
+ }
+ }
+
+ private static function encryptFrom($mFrom)
+ {
+ if ($mFrom instanceof \MailSo\Mime\Email) {
+ $mFrom = $mFrom->jsonSerialize();
+ }
+ return \is_array($mFrom)
+ ? \SnappyMail\Crypt::EncryptUrlSafe($mFrom['Email'])
+ : null;
}
/**
@@ -28,7 +52,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
{
$bBimi = !empty($this->jsonParam('bimi'));
$sEmail = $this->jsonParam('email');
- $aResult = $this->getAvatar(\urldecode($sEmail), !empty($sEmail));
+ $aResult = $this->getAvatar($sEmail, !empty($bBimi));
if ($aResult) {
$aResult = [
'type' => $aResult[0],
@@ -39,15 +63,14 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
}
/**
- * GET /?Avatar/${bimi}/${from.email}
- * Not fond of this idea because email address is exposed
- * Maybe use btoa(from.email) or Crypto.subtle.encrypt({name:'AES-GCM',iv:''}, token, from.email)
+ * GET /?Avatar/${bimi}/Encrypted(${from.email})
+ * Nextcloud Mail uses insecure unencrypted 'index.php/apps/mail/api/avatars/url/local%40example.com'
*/
// public function ServiceAvatar(...$aParts)
public function ServiceAvatar(string $sServiceName, string $sBimi, string $sEmail)
{
- $aResult = $this->getAvatar(\urldecode($sEmail), !empty($sEmail));
- if ($aResult) {
+ $sEmail = \SnappyMail\Crypt::DecryptUrlSafe($sEmail);
+ if ($sEmail && ($aResult = $this->getAvatar($sEmail, !empty($sBimi)))) {
\header('Content-Type: '.$aResult[0]);
echo $aResult[1];
return true;
@@ -58,6 +81,9 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
protected function configMapping() : array
{
return array(
+ \RainLoop\Plugins\Property::NewInstance('delay')->SetLabel('Delay loading')
+ ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
+ ->SetDefaultValue(true),
\RainLoop\Plugins\Property::NewInstance('bimi')->SetLabel('Use BIMI (https://bimigroup.org/)')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
->SetDefaultValue(false),
@@ -73,28 +99,30 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
return null;
}
- $oActions = \RainLoop\Api::Actions();
- $oActions->verifyCacheByKey($sEmail);
+ $sAsciiEmail = \mb_strtolower(\MailSo\Base\Utils::IdnToAscii($sEmail, true));
+ $sEmailId = \sha1($sAsciiEmail);
+
+ \MailSo\Base\Http::setETag($sEmailId);
+ \header('Cache-Control: private');
+// \header('Expires: '.\gmdate('D, j M Y H:i:s', \time() + 86400).' UTC');
$aResult = null;
- $sAsciiEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
- $sEmailId = \sha1(\strtolower($sAsciiEmail));
-
$sFile = \APP_PRIVATE_DATA . 'avatars/' . $sEmailId;
$aFiles = \glob("{$sFile}.*");
if ($aFiles) {
+ \MailSo\Base\Http::setLastModified(\filemtime($aFiles[0]));
$aResult = [
\mime_content_type($aFiles[0]),
\file_get_contents($aFiles[0])
];
- $oActions->cacheByKey($sEmail);
return $aResult;
}
// TODO: lookup contacts vCard and return PHOTO value
/*
if (!$aResult) {
+ $oActions = \RainLoop\Api::Actions();
$oAccount = $oActions->getAccountFromToken();
if ($oAccount) {
$oAddressBookProvider = $oActions->AddressBookProvider($oAccount);
@@ -147,6 +175,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
$sFile . \SnappyMail\File\MimeType::toExtension($aResult[0]),
$aResult[1]
);
+ \MailSo\Base\Http::setLastModified(\time());
}
if (!$aResult) {
@@ -157,18 +186,18 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
'empty-contact' // DATA_IMAGE_USER_DOT_PIC
];
foreach ($aServices as $service) {
- if (\file_exists(__DIR__ . "/images/{$service}.png")) {
+ $file = __DIR__ . "/images/{$service}.png";
+ if (\file_exists($file)) {
+ \MailSo\Base\Http::setLastModified(\filemtime($file));
$aResult = [
'image/png',
- \file_get_contents(__DIR__ . "/images/{$service}.png")
+ \file_get_contents($file)
];
break;
}
}
}
- $oActions->cacheByKey($sEmail);
-
return $aResult;
}
diff --git a/plugins/login-remote/index.php b/plugins/login-remote/index.php
index 6c7a2596d..a2e4f1991 100644
--- a/plugins/login-remote/index.php
+++ b/plugins/login-remote/index.php
@@ -6,7 +6,7 @@ class LoginRemotePlugin extends \RainLoop\Plugins\AbstractPlugin
NAME = 'Login Remote',
AUTHOR = 'SnappyMail',
URL = 'https://snappymail.eu/',
- VERSION = '1.1',
+ VERSION = '1.2',
RELEASE = '2022-11-24',
REQUIRED = '2.21.0',
CATEGORY = 'Login',
diff --git a/plugins/nextcloud/index.php b/plugins/nextcloud/index.php
index 2c541439d..023a78ed0 100644
--- a/plugins/nextcloud/index.php
+++ b/plugins/nextcloud/index.php
@@ -4,8 +4,8 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'Nextcloud',
- VERSION = '2.11',
- RELEASE = '2022-11-14',
+ VERSION = '2.12',
+ RELEASE = '2022-11-24',
CATEGORY = 'Integrations',
DESCRIPTION = 'Integrate with Nextcloud v20+',
REQUIRED = '2.19.0';
diff --git a/release.php b/release.php
index 51303e743..ccd3b1f21 100755
--- a/release.php
+++ b/release.php
@@ -42,8 +42,13 @@ if (isset($options['set-version'])) {
$options['aur'] = isset($options['aur']);
// Docker build
-$docker = trim(`which docker`);
-$options['docker'] = isset($options['docker']) || (!$options['aur'] && $docker && strtoupper(readline("Build Docker image? (Y/N): ")) === "Y");
+$options['docker'] = isset($options['docker']);
+if ($options['docker'] && !($docker = trim(`which docker`))) {
+ exit('docker not found');
+}
+if ($options['docker'] && $options['aur']) {
+ exit('Conflict between docker and aur');
+}
$destPath = "build/dist/releases/webmail/{$package->version}/";
is_dir($destPath) || mkdir($destPath, 0777, true);
@@ -263,6 +268,10 @@ if (isset($options['sign'])) {
passthru('gpg --local-user 1016E47079145542F8BA133548208BA13290F3EB --armor --detach-sign '
.escapeshellarg("{$destPath}snappymail-{$package->version}-nextcloud.tar.gz"), $return_var);
}
+ if (isset($options['cpanel'])) {
+ passthru('gpg --local-user 1016E47079145542F8BA133548208BA13290F3EB --armor --detach-sign '
+ .escapeshellarg("{$destPath}snappymail-{$package->version}-cpanel.tar.gz"), $return_var);
+ }
if (isset($options['debian'])) {
passthru('gpg --local-user 1016E47079145542F8BA133548208BA13290F3EB --armor --detach-sign '
. escapeshellarg(__DIR__ . "/build/dist/releases/webmail/{$package->version}/" . basename(DEB_DEST_DIR.'.deb')), $return_var);
diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php
index 16ee13cfe..5ac3fa106 100644
--- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php
+++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php
@@ -36,14 +36,14 @@ class Http
*
* @return mixed
*/
- public function GetServer(string $sKey, $mDefault = null)
+ public static function GetServer(string $sKey, $mDefault = null)
{
return isset($_SERVER[$sKey]) ? $_SERVER[$sKey] : $mDefault;
}
public function GetMethod() : string
{
- return $this->GetServer('REQUEST_METHOD', '');
+ return static::GetServer('REQUEST_METHOD', '');
}
public function IsPost() : bool
@@ -67,7 +67,7 @@ class Http
{
if (empty($sValueToCheck))
{
- $sValueToCheck = $this->GetServer('REMOTE_ADDR', '');
+ $sValueToCheck = static::GetServer('REMOTE_ADDR', '');
}
return $this->CheckLocalhost($sValueToCheck);
@@ -84,21 +84,16 @@ class Http
return $sRawBody;
}
- public function GetHeader(string $sHeader) : string
+ public static function GetHeader(string $sHeader) : string
{
$sServerKey = 'HTTP_'.\strtoupper(\str_replace('-', '_', $sHeader));
- $sResultHeader = $this->GetServer($sServerKey, '');
-
- if (0 === \strlen($sResultHeader) &&
- \MailSo\Base\Utils::FunctionCallable('apache_request_headers'))
- {
+ $sResultHeader = static::GetServer($sServerKey, '');
+ if (0 === \strlen($sResultHeader) && \MailSo\Base\Utils::FunctionCallable('apache_request_headers')) {
$sHeaders = \apache_request_headers();
- if (isset($sHeaders[$sHeader]))
- {
+ if (isset($sHeaders[$sHeader])) {
$sResultHeader = $sHeaders[$sHeader];
}
}
-
return $sResultHeader;
}
@@ -109,15 +104,15 @@ class Http
public function IsSecure(bool $bCheckProxy = true) : bool
{
- $sHttps = \strtolower($this->GetServer('HTTPS', ''));
- if ('on' === $sHttps || ('' === $sHttps && '443' === (string) $this->GetServer('SERVER_PORT', '')))
+ $sHttps = \strtolower(static::GetServer('HTTPS', ''));
+ if ('on' === $sHttps || ('' === $sHttps && '443' === (string) static::GetServer('SERVER_PORT', '')))
{
return true;
}
if ($bCheckProxy && (
- ('https' === \strtolower($this->GetServer('HTTP_X_FORWARDED_PROTO', ''))) ||
- ('on' === \strtolower($this->GetServer('HTTP_X_FORWARDED_SSL', '')))
+ ('https' === \strtolower(static::GetServer('HTTP_X_FORWARDED_PROTO', ''))) ||
+ ('on' === \strtolower(static::GetServer('HTTP_X_FORWARDED_SSL', '')))
))
{
return true;
@@ -128,11 +123,11 @@ class Http
public function GetHost(bool $bWithRemoteUserData = false, bool $bWithoutWWW = true, bool $bWithoutPort = false) : string
{
- $sHost = $this->GetServer('HTTP_HOST', '');
+ $sHost = static::GetServer('HTTP_HOST', '');
if (!\strlen($sHost))
{
- $sName = $this->GetServer('SERVER_NAME');
- $iPort = (int) $this->GetServer('SERVER_PORT', 80);
+ $sName = static::GetServer('SERVER_NAME');
+ $iPort = (int) static::GetServer('SERVER_PORT', 80);
$sHost = (\in_array($iPort, array(80, 433))) ? $sName : $sName.':'.$iPort;
}
@@ -144,7 +139,7 @@ class Http
if ($bWithRemoteUserData)
{
- $sUser = \trim($this->GetServer('REMOTE_USER', ''));
+ $sUser = \trim(static::GetServer('REMOTE_USER', ''));
$sHost = (\strlen($sUser) ? $sUser.'@' : '').$sHost;
}
@@ -159,63 +154,68 @@ class Http
public function GetClientIp(bool $bCheckProxy = false) : string
{
$sIp = '';
- if ($bCheckProxy && null !== $this->GetServer('HTTP_CLIENT_IP', null))
+ if ($bCheckProxy && null !== static::GetServer('HTTP_CLIENT_IP', null))
{
- $sIp = $this->GetServer('HTTP_CLIENT_IP', '');
+ $sIp = static::GetServer('HTTP_CLIENT_IP', '');
}
- else if ($bCheckProxy && null !== $this->GetServer('HTTP_X_FORWARDED_FOR', null))
+ else if ($bCheckProxy && null !== static::GetServer('HTTP_X_FORWARDED_FOR', null))
{
- $sIp = $this->GetServer('HTTP_X_FORWARDED_FOR', '');
+ $sIp = static::GetServer('HTTP_X_FORWARDED_FOR', '');
}
else
{
- $sIp = $this->GetServer('REMOTE_ADDR', '');
+ $sIp = static::GetServer('REMOTE_ADDR', '');
}
return $sIp;
}
- public function ServerNotModifiedCache(int $iExpireTime, bool $bSetCacheHeader = true, string $sEtag = '') : bool
+ public static function checkETag(string $ETag) : void
{
- $bResult = false;
- if (0 < $iExpireTime)
- {
- $iUtcTimeStamp = \time();
- $sIfModifiedSince = $this->GetHeader('If-Modified-Since', '');
- if (0 === \strlen($sIfModifiedSince))
- {
- if ($bSetCacheHeader)
- {
- \header('Cache-Control: public');
- \header('Pragma: public');
- \header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iUtcTimeStamp - $iExpireTime).' UTC');
- \header('Expires: '.\gmdate('D, j M Y H:i:s', $iUtcTimeStamp + $iExpireTime).' UTC');
-
- if (\strlen($sEtag))
- {
- \header('Etag: '.$sEtag);
- }
- }
- }
- else
- {
- static::StatusHeader(304);
- $bResult = true;
- }
+ $sIfNoneMatch = static::GetHeader('If-None-Match');
+ if ($sIfNoneMatch && false !== \strpos($sIfNoneMatch, $ETag)) {
+ static::StatusHeader(304);
+ exit;
+ }
+ $sIfMatch = static::GetHeader('If-Match');
+ if ($sIfMatch && false === \strpos($sIfMatch, $ETag)) {
+ static::StatusHeader(412);
+ exit;
}
-
- return $bResult;
}
- /**
- * @staticvar boolean $bCache
- */
+ public static function setETag(string $ETag) : void
+ {
+ static::checkETag($ETag);
+ \header("ETag: \"{$ETag}\"");
+ }
+
+ public static function checkLastModified(int $mtime) : void
+ {
+ $sIfModifiedSince = static::GetHeader('If-Modified-Since');
+ if ($sIfModifiedSince && $mtime <= \strtotime($sIfModifiedSince)) {
+ static::StatusHeader(304);
+ exit;
+ }
+ $sIfUnmodifiedSince = static::GetHeader('If-Unmodified-Since');
+ if ($sIfUnmodifiedSince && $mtime > \strtotime($sIfUnmodifiedSince)) {
+ static::StatusHeader(412);
+ exit;
+ }
+ }
+
+ public static function setLastModified(int $mtime) : void
+ {
+ static::checkLastModified($mtime);
+ \header('Last-Modified: '.\gmdate('D, d M Y H:i:s \G\M\T', $mtime)); # DATE_RFC1123
+ }
+
+ private static $bCache = false;
+
public function ServerNoCache()
{
- static $bCache = false;
- if (false === $bCache)
- {
- $bCache = true;
+ if (!static::$bCache) {
+ static::$bCache = true;
\header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s').' GMT');
\header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0');
@@ -223,19 +223,14 @@ class Http
}
}
- /**
- * @staticvar boolean $bCache
- */
- public function ServerUseCache(string $sEtag, int $iLastModified, int $iExpires)
+ public static function ServerUseCache(string $sEtag = '', int $iLastModified = 0, int $iExpires = 0) : void
{
- static $bCache = false;
- if (false === $bCache)
- {
- $bCache = true;
+ if (!static::$bCache) {
+ static::$bCache = true;
\header('Cache-Control: private');
- \header('ETag: '.$sEtag);
- \header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iLastModified).' UTC');
- \header('Expires: '.\gmdate('D, j M Y H:i:s', $iExpires).' UTC');
+ $iExpires && \header('Expires: '.\gmdate('D, j M Y H:i:s', \time() + $iExpires).' UTC');
+ $sEtag && static::setETag($sEtag);
+ $iLastModified && static::setLastModified($iLastModified);
}
}
@@ -253,6 +248,7 @@ class Http
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
+ 412 => 'Precondition Failed',
416 => 'Requested range not satisfiable',
500 => 'Internal Server Error'
);
@@ -271,7 +267,7 @@ class Http
public function GetPath() : string
{
- $sUrl = \ltrim(\substr($this->GetServer('SCRIPT_NAME', ''), 0, \strrpos($this->GetServer('SCRIPT_NAME', ''), '/')), '/');
+ $sUrl = \ltrim(\substr(static::GetServer('SCRIPT_NAME', ''), 0, \strrpos(static::GetServer('SCRIPT_NAME', ''), '/')), '/');
return '' === $sUrl ? '/' : '/'.$sUrl.'/';
}
diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php b/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php
index 0c32dc0e6..ed54f97f0 100644
--- a/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php
+++ b/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php
@@ -18,30 +18,20 @@ namespace MailSo\Cache\Drivers;
*/
class File implements \MailSo\Cache\DriverInterface
{
- /**
- * @var string
- */
- private $sCacheFolder;
+ private string $sCacheFolder;
- /**
- * @var string
- */
- private $sKeyPrefix;
+ private string $sKeyPrefix = '';
function __construct(string $sCacheFolder, string $sKeyPrefix = '')
{
- $this->sCacheFolder = $sCacheFolder;
- $this->sCacheFolder = rtrim(trim($this->sCacheFolder), '\\/').'/';
-
- $this->sKeyPrefix = $sKeyPrefix;
- if (!empty($this->sKeyPrefix))
- {
- $this->sKeyPrefix = \str_pad(\preg_replace('/[^a-zA-Z0-9_]/', '_',
- rtrim(trim($this->sKeyPrefix), '\\/')), 5, '_');
+ $this->sCacheFolder = \rtrim(\trim($sCacheFolder), '\\/').'/';
+ if (!empty($sKeyPrefix)) {
+ $sKeyPrefix = \str_pad(\preg_replace('/[^a-zA-Z0-9_]/', '_',
+ \rtrim(\trim($sKeyPrefix), '\\/')), 5, '_');
$this->sKeyPrefix = '__/'.
- \substr($this->sKeyPrefix, 0, 2).'/'.\substr($this->sKeyPrefix, 2, 2).'/'.
- $this->sKeyPrefix.'/';
+ \substr($sKeyPrefix, 0, 2).'/'.\substr($sKeyPrefix, 2, 2).'/'.
+ $sKeyPrefix.'/';
}
}
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
index 9e2c6c203..ee7952367 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
@@ -147,10 +147,10 @@ class Actions
'[SM:' . APP_VERSION . '][IP:'
. $oHttp->GetClientIp($this->oConfig->Get('labs', 'http_client_ip_check_proxy', false))
. '][PID:' . (\MailSo\Base\Utils::FunctionCallable('getmypid') ? \getmypid() : 'unknown')
- . '][' . $oHttp->GetServer('SERVER_SOFTWARE', '~')
+ . '][' . \MailSo\Base\Http::GetServer('SERVER_SOFTWARE', '~')
. '][' . (\MailSo\Base\Utils::FunctionCallable('php_sapi_name') ? \php_sapi_name() : '~')
. '][Streams:' . \implode(',', \stream_get_transports())
- . '][' . $oHttp->GetMethod() . ' ' . $oHttp->GetScheme() . '://' . $oHttp->GetHost(false, false) . $oHttp->GetServer('REQUEST_URI', '') . ']'
+ . '][' . $oHttp->GetMethod() . ' ' . $oHttp->GetScheme() . '://' . $oHttp->GetHost(false, false) . \MailSo\Base\Http::GetServer('REQUEST_URI', '') . ']'
);
}
@@ -510,10 +510,8 @@ class Actions
switch (true) {
default:
case $bForceFile:
- $sCacheDir = \trim($this->oConfig->Get('cache', 'path', '')) ?: APP_PRIVATE_DATA . 'cache';
- \is_dir($sCacheDir) || \mkdir($sCacheDir, 0700, true);
$oDriver = new \MailSo\Cache\Drivers\File(
- \trim($this->oConfig->Get('cache', 'path', APP_PRIVATE_DATA . 'cache')),
+ \trim($this->oConfig->Get('cache', 'path', '')) ?: APP_PRIVATE_DATA . 'cache',
$sKey
);
break;
@@ -1064,36 +1062,28 @@ class Actions
public function etag(string $sKey): string
{
- return \md5('Etag:' . \md5($sKey . \md5($this->oConfig->Get('cache', 'index', ''))));
+ return \md5($sKey . $this->oConfig->Get('cache', 'index', ''));
}
public function cacheByKey(string $sKey, bool $bForce = false): bool
{
- $bResult = false;
- if (!empty($sKey) && ($bForce || ($this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)))) {
- $iExpires = $this->oConfig->Get('cache', 'http_expires', 3600);
- if (0 < $iExpires) {
- $this->Http()->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires);
- $bResult = true;
- }
+ if ($sKey && ($bForce || ($this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)))) {
+ \MailSo\Base\Http::ServerUseCache(
+ $this->etag($sKey),
+ 1382478804,
+ $this->oConfig->Get('cache', 'http_expires', 3600)
+ );
+ return true;
}
-
- if (!$bResult) {
- $this->Http()->ServerNoCache();
- }
-
- return $bResult;
+ $this->Http()->ServerNoCache();
+ return false;
}
public function verifyCacheByKey(string $sKey, bool $bForce = false): void
{
- if (!empty($sKey) && ($bForce || $this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true))) {
- $sIfNoneMatch = $this->Http()->GetHeader('If-None-Match', '');
- if ($this->etag($sKey) === $sIfNoneMatch) {
- \MailSo\Base\Http::StatusHeader(304);
- $this->cacheByKey($sKey);
- exit(0);
- }
+ if ($sKey && ($bForce || ($this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)))) {
+ \MailSo\Base\Http::checkETag($this->etag($sKey));
+// $this->cacheByKey($sKey, $bForce);
}
}
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Localization.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Localization.php
index 58a979b97..4d8d15c91 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Localization.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Localization.php
@@ -69,7 +69,7 @@ trait Localization
private function getUserLanguagesFromHeader(): array
{
$aResult = $aList = array();
- $sAcceptLang = \strtolower($this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en'));
+ $sAcceptLang = \strtolower(\MailSo\Base\Http::GetServer('HTTP_ACCEPT_LANGUAGE', 'en'));
if (!empty($sAcceptLang) && \preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)(?:;q=([0-9.]+))?/', $sAcceptLang, $aList)) {
$aResult = \array_combine($aList[1], $aList[2]);
foreach ($aResult as $n => $v) {
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php
index 0e9c78f42..e0ad8b60a 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php
@@ -117,7 +117,7 @@ trait Raw
$aValues = $this->getDecodedRawKeyValue($sRawKey);
- $sRange = $this->Http()->GetHeader('Range');
+ $sRange = \MailSo\Base\Http::GetHeader('Range');
$aMatch = array();
$sRangeStart = $sRangeEnd = '';
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php
index ebb1ffe65..6c7b858a4 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php
@@ -21,7 +21,7 @@ trait UserAuth
/**
* @throws \RainLoop\Exceptions\ClientException
*/
- protected function resolveLoginCredentials(string &$sEmail, string &$sPassword, string &$sLogin): void
+ public function resolveLoginCredentials(string &$sEmail, string &$sPassword, string &$sLogin): void
{
$this->Plugins()->RunHook('login.credentials.step-1', array(&$sEmail));
diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/crypt.php b/snappymail/v/0.0.0/app/libraries/snappymail/crypt.php
index d49b51454..6ec79cec0 100644
--- a/snappymail/v/0.0.0/app/libraries/snappymail/crypt.php
+++ b/snappymail/v/0.0.0/app/libraries/snappymail/crypt.php
@@ -45,10 +45,12 @@ abstract class Crypt
*/
private static function Passphrase(?string $key) : string
{
- return \sha1(
- ($key ?: \preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0])) . APP_SALT,
- true
- );
+ if (!$key) {
+ $key = isset($_COOKIE['smctoken'])
+ ? $_COOKIE['smctoken']
+ : \preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0]);
+ }
+ return \sha1($key . APP_SALT, true);
}
public static function Decrypt(array $data, string $key = null) /* : mixed */
diff --git a/snappymail/v/0.0.0/cpanel.php b/snappymail/v/0.0.0/cpanel.php
index dabb72ff9..c92ce171a 100644
--- a/snappymail/v/0.0.0/cpanel.php
+++ b/snappymail/v/0.0.0/cpanel.php
@@ -14,7 +14,7 @@ if (defined('APP_PLUGINS_PATH') && !empty($_ENV['CPANEL']) && !is_dir(APP_PLUGIN
$oConfig->Set('plugins', 'enabled_list', \implode(',', \array_unique($aList)));
$oConfig->Set('login', 'default_domain', 'cpanel');
$oConfig->Set('logs', 'path', $_ENV['HOME'] . '/logs/snappymail');
- $oConfig->Set('cache', 'path', $_ENV['HOME'] . '/tmp/snappymail');
+ $oConfig->Set('cache', 'path', $_ENV['TMPDIR'] . '/snappymail');
$oConfig->Save();
$sFile = APP_PRIVATE_DATA.'domains/cpanel.json';
diff --git a/snappymail/v/0.0.0/include.php b/snappymail/v/0.0.0/include.php
index f1577d535..00acfa5c7 100644
--- a/snappymail/v/0.0.0/include.php
+++ b/snappymail/v/0.0.0/include.php
@@ -70,13 +70,7 @@ define('APP_PRIVATE_DATA_NAME', $sPrivateDataFolderInternalName ?: '_default_');
unset($sPrivateDataFolderInternalName);
if (!defined('APP_DATA_FOLDER_PATH')) {
- // cPanel https://github.com/the-djmaze/snappymail/issues/697
- if (!empty($_ENV['CPANEL']) && isset($_ENV['HOME'])) {
- $sCustomDataPath = $_ENV['HOME'] . '/var/snappymail';
-// $sCustomDataPath = $_ENV['TMPDIR'] . '/snappymail';
- } else {
- $sCustomDataPath = function_exists('__get_custom_data_full_path') ? rtrim(trim(__get_custom_data_full_path()), '\\/') : $sCustomDataPath;
- }
+ $sCustomDataPath = rtrim(trim(function_exists('__get_custom_data_full_path') ? __get_custom_data_full_path() : $sCustomDataPath), '\\/');
define('APP_DATA_FOLDER_PATH', strlen($sCustomDataPath) ? $sCustomDataPath.'/' : APP_INDEX_ROOT_PATH.'data/');
}
unset($sCustomDataPath);
diff --git a/vendors/openpgp-5/dist/openpgp.js b/vendors/openpgp-5/dist/openpgp.js
index c7858bd32..28b80bc47 100644
--- a/vendors/openpgp-5/dist/openpgp.js
+++ b/vendors/openpgp-5/dist/openpgp.js
@@ -1,4 +1,4 @@
-/*! OpenPGP.js v5.2.1 - 2022-03-30 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */
+/*! OpenPGP.js v5.5.0 - 2022-11-25 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */
var openpgp = (function (exports) {
'use strict';
@@ -2637,7 +2637,10 @@ var openpgp = (function (exports) {
*/
allowUnauthenticatedMessages: false,
/**
- * Allow streaming unauthenticated data before its integrity has been checked.
+ * Allow streaming unauthenticated data before its integrity has been checked. This would allow the application to
+ * process large streams while limiting memory usage by releasing the decrypted chunks as soon as possible
+ * and deferring checking their integrity until the decrypted stream has been read in full.
+ *
* This setting is **insecure** if the partially decrypted message is processed further or displayed to the user.
* @memberof module:config
* @property {Boolean} allowUnauthenticatedStream
@@ -2733,7 +2736,7 @@ var openpgp = (function (exports) {
* @memberof module:config
* @property {String} versionString A version string to be included in armored messages
*/
- versionString: 'OpenPGP.js 5.2.1',
+ versionString: 'OpenPGP.js 5.5.0',
/**
* @memberof module:config
* @property {String} commentString A comment string to be included in armored messages
@@ -2784,7 +2787,7 @@ var openpgp = (function (exports) {
* @memberof module:config
* @property {Set} rejectCurves {@link module:enums.curve}
*/
- rejectCurves: new Set([enums.curve.brainpoolP256r1, enums.curve.brainpoolP384r1, enums.curve.brainpoolP512r1, enums.curve.secp256k1])
+ rejectCurves: new Set([enums.curve.secp256k1])
};
// GPG4Browsers - An OpenPGP implementation in javascript
@@ -2950,8 +2953,10 @@ var openpgp = (function (exports) {
}
/**
- * Verify armored headers. RFC4880, section 6.3: "OpenPGP should consider improperly formatted
- * Armor Headers to be corruption of the ASCII Armor."
+ * Verify armored headers. crypto-refresh-06, section 6.2:
+ * "An OpenPGP implementation may consider improperly formatted Armor
+ * Headers to be corruption of the ASCII Armor, but SHOULD make an
+ * effort to recover."
* @private
* @param {Array} headers - Armor headers
*/
@@ -3152,7 +3157,7 @@ var openpgp = (function (exports) {
result.push('-----END PGP MESSAGE, PART ' + partIndex + '-----\n');
break;
case enums.armor.signed:
- result.push('\n-----BEGIN PGP SIGNED MESSAGE-----\n');
+ result.push('-----BEGIN PGP SIGNED MESSAGE-----\n');
result.push('Hash: ' + hash + '\n\n');
result.push(text.replace(/^-/mg, '- -'));
result.push('\n-----BEGIN PGP SIGNATURE-----\n');
@@ -11750,6 +11755,301 @@ var openpgp = (function (exports) {
return new elliptic$1.ec(name);
}
+ // GPG4Browsers - An OpenPGP implementation in javascript
+
+ function readSimpleLength(bytes) {
+ let len = 0;
+ let offset;
+ const type = bytes[0];
+
+
+ if (type < 192) {
+ [len] = bytes;
+ offset = 1;
+ } else if (type < 255) {
+ len = ((bytes[0] - 192) << 8) + (bytes[1]) + 192;
+ offset = 2;
+ } else if (type === 255) {
+ len = util.readNumber(bytes.subarray(1, 1 + 4));
+ offset = 5;
+ }
+
+ return {
+ len: len,
+ offset: offset
+ };
+ }
+
+ /**
+ * Encodes a given integer of length to the openpgp length specifier to a
+ * string
+ *
+ * @param {Integer} length - The length to encode
+ * @returns {Uint8Array} String with openpgp length representation.
+ */
+ function writeSimpleLength(length) {
+ if (length < 192) {
+ return new Uint8Array([length]);
+ } else if (length > 191 && length < 8384) {
+ /*
+ * let a = (total data packet length) - 192 let bc = two octet
+ * representation of a let d = b + 192
+ */
+ return new Uint8Array([((length - 192) >> 8) + 192, (length - 192) & 0xFF]);
+ }
+ return util.concatUint8Array([new Uint8Array([255]), util.writeNumber(length, 4)]);
+ }
+
+ function writePartialLength(power) {
+ if (power < 0 || power > 30) {
+ throw new Error('Partial Length power must be between 1 and 30');
+ }
+ return new Uint8Array([224 + power]);
+ }
+
+ function writeTag(tag_type) {
+ /* we're only generating v4 packet headers here */
+ return new Uint8Array([0xC0 | tag_type]);
+ }
+
+ /**
+ * Writes a packet header version 4 with the given tag_type and length to a
+ * string
+ *
+ * @param {Integer} tag_type - Tag type
+ * @param {Integer} length - Length of the payload
+ * @returns {String} String of the header.
+ */
+ function writeHeader(tag_type, length) {
+ /* we're only generating v4 packet headers here */
+ return util.concatUint8Array([writeTag(tag_type), writeSimpleLength(length)]);
+ }
+
+ /**
+ * Whether the packet type supports partial lengths per RFC4880
+ * @param {Integer} tag - Tag type
+ * @returns {Boolean} String of the header.
+ */
+ function supportsStreaming(tag) {
+ return [
+ enums.packet.literalData,
+ enums.packet.compressedData,
+ enums.packet.symmetricallyEncryptedData,
+ enums.packet.symEncryptedIntegrityProtectedData,
+ enums.packet.aeadEncryptedData
+ ].includes(tag);
+ }
+
+ /**
+ * Generic static Packet Parser function
+ *
+ * @param {Uint8Array | ReadableStream} input - Input stream as string
+ * @param {Function} callback - Function to call with the parsed packet
+ * @returns {Boolean} Returns false if the stream was empty and parsing is done, and true otherwise.
+ */
+ async function readPackets(input, callback) {
+ const reader = getReader(input);
+ let writer;
+ let callbackReturned;
+ try {
+ const peekedBytes = await reader.peekBytes(2);
+ // some sanity checks
+ if (!peekedBytes || peekedBytes.length < 2 || (peekedBytes[0] & 0x80) === 0) {
+ throw new Error('Error during parsing. This message / key probably does not conform to a valid OpenPGP format.');
+ }
+ const headerByte = await reader.readByte();
+ let tag = -1;
+ let format = -1;
+ let packetLength;
+
+ format = 0; // 0 = old format; 1 = new format
+ if ((headerByte & 0x40) !== 0) {
+ format = 1;
+ }
+
+ let packetLengthType;
+ if (format) {
+ // new format header
+ tag = headerByte & 0x3F; // bit 5-0
+ } else {
+ // old format header
+ tag = (headerByte & 0x3F) >> 2; // bit 5-2
+ packetLengthType = headerByte & 0x03; // bit 1-0
+ }
+
+ const packetSupportsStreaming = supportsStreaming(tag);
+ let packet = null;
+ if (packetSupportsStreaming) {
+ if (util.isStream(input) === 'array') {
+ const arrayStream = new ArrayStream();
+ writer = getWriter(arrayStream);
+ packet = arrayStream;
+ } else {
+ const transform = new TransformStream$1();
+ writer = getWriter(transform.writable);
+ packet = transform.readable;
+ }
+ callbackReturned = callback({ tag, packet });
+ } else {
+ packet = [];
+ }
+
+ let wasPartialLength;
+ do {
+ if (!format) {
+ // 4.2.1. Old Format Packet Lengths
+ switch (packetLengthType) {
+ case 0:
+ // The packet has a one-octet length. The header is 2 octets
+ // long.
+ packetLength = await reader.readByte();
+ break;
+ case 1:
+ // The packet has a two-octet length. The header is 3 octets
+ // long.
+ packetLength = (await reader.readByte() << 8) | await reader.readByte();
+ break;
+ case 2:
+ // The packet has a four-octet length. The header is 5
+ // octets long.
+ packetLength = (await reader.readByte() << 24) | (await reader.readByte() << 16) | (await reader.readByte() <<
+ 8) | await reader.readByte();
+ break;
+ default:
+ // 3 - The packet is of indeterminate length. The header is 1
+ // octet long, and the implementation must determine how long
+ // the packet is. If the packet is in a file, this means that
+ // the packet extends until the end of the file. In general,
+ // an implementation SHOULD NOT use indeterminate-length
+ // packets except where the end of the data will be clear
+ // from the context, and even then it is better to use a
+ // definite length, or a new format header. The new format
+ // headers described below have a mechanism for precisely
+ // encoding data of indeterminate length.
+ packetLength = Infinity;
+ break;
+ }
+ } else { // 4.2.2. New Format Packet Lengths
+ // 4.2.2.1. One-Octet Lengths
+ const lengthByte = await reader.readByte();
+ wasPartialLength = false;
+ if (lengthByte < 192) {
+ packetLength = lengthByte;
+ // 4.2.2.2. Two-Octet Lengths
+ } else if (lengthByte >= 192 && lengthByte < 224) {
+ packetLength = ((lengthByte - 192) << 8) + (await reader.readByte()) + 192;
+ // 4.2.2.4. Partial Body Lengths
+ } else if (lengthByte > 223 && lengthByte < 255) {
+ packetLength = 1 << (lengthByte & 0x1F);
+ wasPartialLength = true;
+ if (!packetSupportsStreaming) {
+ throw new TypeError('This packet type does not support partial lengths.');
+ }
+ // 4.2.2.3. Five-Octet Lengths
+ } else {
+ packetLength = (await reader.readByte() << 24) | (await reader.readByte() << 16) | (await reader.readByte() <<
+ 8) | await reader.readByte();
+ }
+ }
+ if (packetLength > 0) {
+ let bytesRead = 0;
+ while (true) {
+ if (writer) await writer.ready;
+ const { done, value } = await reader.read();
+ if (done) {
+ if (packetLength === Infinity) break;
+ throw new Error('Unexpected end of packet');
+ }
+ const chunk = packetLength === Infinity ? value : value.subarray(0, packetLength - bytesRead);
+ if (writer) await writer.write(chunk);
+ else packet.push(chunk);
+ bytesRead += value.length;
+ if (bytesRead >= packetLength) {
+ reader.unshift(value.subarray(packetLength - bytesRead + value.length));
+ break;
+ }
+ }
+ }
+ } while (wasPartialLength);
+
+ // If this was not a packet that "supports streaming", we peek to check
+ // whether it is the last packet in the message. We peek 2 bytes instead
+ // of 1 because the beginning of this function also peeks 2 bytes, and we
+ // want to cut a `subarray` of the correct length into `web-stream-tools`'
+ // `externalBuffer` as a tiny optimization here.
+ //
+ // If it *was* a streaming packet (i.e. the data packets), we peek at the
+ // entire remainder of the stream, in order to forward errors in the
+ // remainder of the stream to the packet data. (Note that this means we
+ // read/peek at all signature packets before closing the literal data
+ // packet, for example.) This forwards MDC errors to the literal data
+ // stream, for example, so that they don't get lost / forgotten on
+ // decryptedMessage.packets.stream, which we never look at.
+ //
+ // An example of what we do when stream-parsing a message containing
+ // [ one-pass signature packet, literal data packet, signature packet ]:
+ // 1. Read the one-pass signature packet
+ // 2. Peek 2 bytes of the literal data packet
+ // 3. Parse the one-pass signature packet
+ //
+ // 4. Read the literal data packet, simultaneously stream-parsing it
+ // 5. Peek until the end of the message
+ // 6. Finish parsing the literal data packet
+ //
+ // 7. Read the signature packet again (we already peeked at it in step 5)
+ // 8. Peek at the end of the stream again (`peekBytes` returns undefined)
+ // 9. Parse the signature packet
+ //
+ // Note that this means that if there's an error in the very end of the
+ // stream, such as an MDC error, we throw in step 5 instead of in step 8
+ // (or never), which is the point of this exercise.
+ const nextPacket = await reader.peekBytes(packetSupportsStreaming ? Infinity : 2);
+ if (writer) {
+ await writer.ready;
+ await writer.close();
+ } else {
+ packet = util.concatUint8Array(packet);
+ await callback({ tag, packet });
+ }
+ return !nextPacket || !nextPacket.length;
+ } catch (e) {
+ if (writer) {
+ await writer.abort(e);
+ return true;
+ } else {
+ throw e;
+ }
+ } finally {
+ if (writer) {
+ await callbackReturned;
+ }
+ reader.releaseLock();
+ }
+ }
+
+ class UnsupportedError extends Error {
+ constructor(...params) {
+ super(...params);
+
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, UnsupportedError);
+ }
+
+ this.name = 'UnsupportedError';
+ }
+ }
+
+ class UnparseablePacket {
+ constructor(tag, rawContent) {
+ this.tag = tag;
+ this.rawContent = rawContent;
+ }
+
+ write() {
+ return this.rawContent;
+ }
+ }
+
// OpenPGP.js - An OpenPGP implementation in javascript
const webCrypto$6 = util.getWebCrypto();
@@ -11846,7 +12146,7 @@ var openpgp = (function (exports) {
// by curve name or oid string
this.name = enums.write(enums.curve, oidOrName);
} catch (err) {
- throw new Error('Not valid curve');
+ throw new UnsupportedError('Unknown curve');
}
params = params || curves[this.name];
@@ -13233,7 +13533,7 @@ var openpgp = (function (exports) {
oid, kdfParams, V, C.data, Q, d, fingerprint);
}
default:
- throw new Error('Invalid public key encryption algorithm.');
+ throw new Error('Unknown public key encryption algorithm.');
}
}
@@ -13268,23 +13568,26 @@ var openpgp = (function (exports) {
}
case enums.publicKey.ecdsa: {
const oid = new OID(); read += oid.read(bytes);
+ checkSupportedCurve(oid);
const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
return { read: read, publicParams: { oid, Q } };
}
case enums.publicKey.eddsa: {
const oid = new OID(); read += oid.read(bytes);
+ checkSupportedCurve(oid);
let Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
Q = util.leftPad(Q, 33);
return { read: read, publicParams: { oid, Q } };
}
case enums.publicKey.ecdh: {
const oid = new OID(); read += oid.read(bytes);
+ checkSupportedCurve(oid);
const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
const kdfParams = new KDFParams(); read += kdfParams.read(bytes.subarray(read));
return { read: read, publicParams: { oid, Q, kdfParams } };
}
default:
- throw new Error('Invalid public key encryption algorithm.');
+ throw new UnsupportedError('Unknown public key encryption algorithm.');
}
}
@@ -13320,12 +13623,13 @@ var openpgp = (function (exports) {
return { read, privateParams: { d } };
}
case enums.publicKey.eddsa: {
+ const curve = new Curve(publicParams.oid);
let seed = util.readMPI(bytes.subarray(read)); read += seed.length + 2;
- seed = util.leftPad(seed, 32);
+ seed = util.leftPad(seed, curve.payloadSize);
return { read, privateParams: { seed } };
}
default:
- throw new Error('Invalid public key encryption algorithm.');
+ throw new UnsupportedError('Unknown public key encryption algorithm.');
}
}
@@ -13362,7 +13666,7 @@ var openpgp = (function (exports) {
return { V, C };
}
default:
- throw new Error('Invalid public key encryption algorithm.');
+ throw new UnsupportedError('Unknown public key encryption algorithm.');
}
}
@@ -13421,7 +13725,7 @@ var openpgp = (function (exports) {
case enums.publicKey.elgamal:
throw new Error('Unsupported algorithm for key generation.');
default:
- throw new Error('Invalid public key algorithm.');
+ throw new Error('Unknown public key algorithm.');
}
}
@@ -13468,7 +13772,7 @@ var openpgp = (function (exports) {
return publicKey.elliptic.eddsa.validateParams(oid, Q, seed);
}
default:
- throw new Error('Invalid public key algorithm.');
+ throw new Error('Unknown public key algorithm.');
}
}
@@ -13520,6 +13824,19 @@ var openpgp = (function (exports) {
return cipher[algoName];
}
+ /**
+ * Check whether the given curve OID is supported
+ * @param {module:type/oid} oid - EC object identifier
+ * @throws {UnsupportedError} if curve is not supported
+ */
+ function checkSupportedCurve(oid) {
+ try {
+ oid.getName();
+ } catch (e) {
+ throw new UnsupportedError('Unknown curve OID');
+ }
+ }
+
var crypto$2 = /*#__PURE__*/Object.freeze({
__proto__: null,
publicKeyEncrypt: publicKeyEncrypt,
@@ -14628,7 +14945,7 @@ var openpgp = (function (exports) {
return { r, s };
}
default:
- throw new Error('Invalid signature algorithm.');
+ throw new UnsupportedError('Unknown signature algorithm.');
}
}
@@ -14674,7 +14991,7 @@ var openpgp = (function (exports) {
return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed);
}
default:
- throw new Error('Invalid signature algorithm.');
+ throw new Error('Unknown signature algorithm.');
}
}
@@ -14724,7 +15041,7 @@ var openpgp = (function (exports) {
return publicKey.elliptic.eddsa.sign(oid, hashAlgo, data, Q, seed, hashed);
}
default:
- throw new Error('Invalid signature algorithm.');
+ throw new Error('Unknown signature algorithm.');
}
}
@@ -21899,290 +22216,6 @@ var openpgp = (function (exports) {
// GPG4Browsers - An OpenPGP implementation in javascript
- function readSimpleLength(bytes) {
- let len = 0;
- let offset;
- const type = bytes[0];
-
-
- if (type < 192) {
- [len] = bytes;
- offset = 1;
- } else if (type < 255) {
- len = ((bytes[0] - 192) << 8) + (bytes[1]) + 192;
- offset = 2;
- } else if (type === 255) {
- len = util.readNumber(bytes.subarray(1, 1 + 4));
- offset = 5;
- }
-
- return {
- len: len,
- offset: offset
- };
- }
-
- /**
- * Encodes a given integer of length to the openpgp length specifier to a
- * string
- *
- * @param {Integer} length - The length to encode
- * @returns {Uint8Array} String with openpgp length representation.
- */
- function writeSimpleLength(length) {
- if (length < 192) {
- return new Uint8Array([length]);
- } else if (length > 191 && length < 8384) {
- /*
- * let a = (total data packet length) - 192 let bc = two octet
- * representation of a let d = b + 192
- */
- return new Uint8Array([((length - 192) >> 8) + 192, (length - 192) & 0xFF]);
- }
- return util.concatUint8Array([new Uint8Array([255]), util.writeNumber(length, 4)]);
- }
-
- function writePartialLength(power) {
- if (power < 0 || power > 30) {
- throw new Error('Partial Length power must be between 1 and 30');
- }
- return new Uint8Array([224 + power]);
- }
-
- function writeTag(tag_type) {
- /* we're only generating v4 packet headers here */
- return new Uint8Array([0xC0 | tag_type]);
- }
-
- /**
- * Writes a packet header version 4 with the given tag_type and length to a
- * string
- *
- * @param {Integer} tag_type - Tag type
- * @param {Integer} length - Length of the payload
- * @returns {String} String of the header.
- */
- function writeHeader(tag_type, length) {
- /* we're only generating v4 packet headers here */
- return util.concatUint8Array([writeTag(tag_type), writeSimpleLength(length)]);
- }
-
- /**
- * Whether the packet type supports partial lengths per RFC4880
- * @param {Integer} tag - Tag type
- * @returns {Boolean} String of the header.
- */
- function supportsStreaming(tag) {
- return [
- enums.packet.literalData,
- enums.packet.compressedData,
- enums.packet.symmetricallyEncryptedData,
- enums.packet.symEncryptedIntegrityProtectedData,
- enums.packet.aeadEncryptedData
- ].includes(tag);
- }
-
- /**
- * Generic static Packet Parser function
- *
- * @param {Uint8Array | ReadableStream} input - Input stream as string
- * @param {Function} callback - Function to call with the parsed packet
- * @returns {Boolean} Returns false if the stream was empty and parsing is done, and true otherwise.
- */
- async function readPackets(input, callback) {
- const reader = getReader(input);
- let writer;
- let callbackReturned;
- try {
- const peekedBytes = await reader.peekBytes(2);
- // some sanity checks
- if (!peekedBytes || peekedBytes.length < 2 || (peekedBytes[0] & 0x80) === 0) {
- throw new Error('Error during parsing. This message / key probably does not conform to a valid OpenPGP format.');
- }
- const headerByte = await reader.readByte();
- let tag = -1;
- let format = -1;
- let packetLength;
-
- format = 0; // 0 = old format; 1 = new format
- if ((headerByte & 0x40) !== 0) {
- format = 1;
- }
-
- let packetLengthType;
- if (format) {
- // new format header
- tag = headerByte & 0x3F; // bit 5-0
- } else {
- // old format header
- tag = (headerByte & 0x3F) >> 2; // bit 5-2
- packetLengthType = headerByte & 0x03; // bit 1-0
- }
-
- const packetSupportsStreaming = supportsStreaming(tag);
- let packet = null;
- if (packetSupportsStreaming) {
- if (util.isStream(input) === 'array') {
- const arrayStream = new ArrayStream();
- writer = getWriter(arrayStream);
- packet = arrayStream;
- } else {
- const transform = new TransformStream$1();
- writer = getWriter(transform.writable);
- packet = transform.readable;
- }
- callbackReturned = callback({ tag, packet });
- } else {
- packet = [];
- }
-
- let wasPartialLength;
- do {
- if (!format) {
- // 4.2.1. Old Format Packet Lengths
- switch (packetLengthType) {
- case 0:
- // The packet has a one-octet length. The header is 2 octets
- // long.
- packetLength = await reader.readByte();
- break;
- case 1:
- // The packet has a two-octet length. The header is 3 octets
- // long.
- packetLength = (await reader.readByte() << 8) | await reader.readByte();
- break;
- case 2:
- // The packet has a four-octet length. The header is 5
- // octets long.
- packetLength = (await reader.readByte() << 24) | (await reader.readByte() << 16) | (await reader.readByte() <<
- 8) | await reader.readByte();
- break;
- default:
- // 3 - The packet is of indeterminate length. The header is 1
- // octet long, and the implementation must determine how long
- // the packet is. If the packet is in a file, this means that
- // the packet extends until the end of the file. In general,
- // an implementation SHOULD NOT use indeterminate-length
- // packets except where the end of the data will be clear
- // from the context, and even then it is better to use a
- // definite length, or a new format header. The new format
- // headers described below have a mechanism for precisely
- // encoding data of indeterminate length.
- packetLength = Infinity;
- break;
- }
- } else { // 4.2.2. New Format Packet Lengths
- // 4.2.2.1. One-Octet Lengths
- const lengthByte = await reader.readByte();
- wasPartialLength = false;
- if (lengthByte < 192) {
- packetLength = lengthByte;
- // 4.2.2.2. Two-Octet Lengths
- } else if (lengthByte >= 192 && lengthByte < 224) {
- packetLength = ((lengthByte - 192) << 8) + (await reader.readByte()) + 192;
- // 4.2.2.4. Partial Body Lengths
- } else if (lengthByte > 223 && lengthByte < 255) {
- packetLength = 1 << (lengthByte & 0x1F);
- wasPartialLength = true;
- if (!packetSupportsStreaming) {
- throw new TypeError('This packet type does not support partial lengths.');
- }
- // 4.2.2.3. Five-Octet Lengths
- } else {
- packetLength = (await reader.readByte() << 24) | (await reader.readByte() << 16) | (await reader.readByte() <<
- 8) | await reader.readByte();
- }
- }
- if (packetLength > 0) {
- let bytesRead = 0;
- while (true) {
- if (writer) await writer.ready;
- const { done, value } = await reader.read();
- if (done) {
- if (packetLength === Infinity) break;
- throw new Error('Unexpected end of packet');
- }
- const chunk = packetLength === Infinity ? value : value.subarray(0, packetLength - bytesRead);
- if (writer) await writer.write(chunk);
- else packet.push(chunk);
- bytesRead += value.length;
- if (bytesRead >= packetLength) {
- reader.unshift(value.subarray(packetLength - bytesRead + value.length));
- break;
- }
- }
- }
- } while (wasPartialLength);
-
- // If this was not a packet that "supports streaming", we peek to check
- // whether it is the last packet in the message. We peek 2 bytes instead
- // of 1 because the beginning of this function also peeks 2 bytes, and we
- // want to cut a `subarray` of the correct length into `web-stream-tools`'
- // `externalBuffer` as a tiny optimization here.
- //
- // If it *was* a streaming packet (i.e. the data packets), we peek at the
- // entire remainder of the stream, in order to forward errors in the
- // remainder of the stream to the packet data. (Note that this means we
- // read/peek at all signature packets before closing the literal data
- // packet, for example.) This forwards MDC errors to the literal data
- // stream, for example, so that they don't get lost / forgotten on
- // decryptedMessage.packets.stream, which we never look at.
- //
- // An example of what we do when stream-parsing a message containing
- // [ one-pass signature packet, literal data packet, signature packet ]:
- // 1. Read the one-pass signature packet
- // 2. Peek 2 bytes of the literal data packet
- // 3. Parse the one-pass signature packet
- //
- // 4. Read the literal data packet, simultaneously stream-parsing it
- // 5. Peek until the end of the message
- // 6. Finish parsing the literal data packet
- //
- // 7. Read the signature packet again (we already peeked at it in step 5)
- // 8. Peek at the end of the stream again (`peekBytes` returns undefined)
- // 9. Parse the signature packet
- //
- // Note that this means that if there's an error in the very end of the
- // stream, such as an MDC error, we throw in step 5 instead of in step 8
- // (or never), which is the point of this exercise.
- const nextPacket = await reader.peekBytes(packetSupportsStreaming ? Infinity : 2);
- if (writer) {
- await writer.ready;
- await writer.close();
- } else {
- packet = util.concatUint8Array(packet);
- await callback({ tag, packet });
- }
- return !nextPacket || !nextPacket.length;
- } catch (e) {
- if (writer) {
- await writer.abort(e);
- return true;
- } else {
- throw e;
- }
- } finally {
- if (writer) {
- await callbackReturned;
- }
- reader.releaseLock();
- }
- }
-
- class UnsupportedError extends Error {
- constructor(...params) {
- super(...params);
-
- if (Error.captureStackTrace) {
- Error.captureStackTrace(this, UnsupportedError);
- }
-
- this.name = 'UnsupportedError';
- }
- }
-
- // GPG4Browsers - An OpenPGP implementation in javascript
-
// Symbol to store cryptographic validity of the signature, to avoid recomputing multiple times on verification.
const verified = Symbol('verified');
@@ -22348,6 +22381,11 @@ var openpgp = (function (exports) {
// Add hashed subpackets
arr.push(this.writeHashedSubPackets());
+ // Remove unhashed subpackets, in case some allowed unhashed
+ // subpackets existed, in order not to duplicate them (in both
+ // the hashed and unhashed subpackets) when re-signing.
+ this.unhashedSubpackets = [];
+
this.signatureData = util.concat(arr);
const toHash = this.toHash(this.signatureType, data, detached);
@@ -22410,6 +22448,11 @@ var openpgp = (function (exports) {
bytes = util.concat([bytes, this.revocationKeyFingerprint]);
arr.push(writeSubPacket(sub.revocationKey, bytes));
}
+ if (!this.issuerKeyID.isNull() && this.issuerKeyVersion !== 5) {
+ // If the version of [the] key is greater than 4, this subpacket
+ // MUST NOT be included in the signature.
+ arr.push(writeSubPacket(sub.issuer, this.issuerKeyID.write()));
+ }
this.rawNotations.forEach(([{ name, value, humanReadable }]) => {
bytes = [new Uint8Array([humanReadable ? 0x80 : 0, 0, 0, 0])];
// 2 octets of name length
@@ -22463,6 +22506,14 @@ var openpgp = (function (exports) {
bytes = util.concat(bytes);
arr.push(writeSubPacket(sub.signatureTarget, bytes));
}
+ if (this.embeddedSignature !== null) {
+ arr.push(writeSubPacket(sub.embeddedSignature, this.embeddedSignature.write()));
+ }
+ if (this.issuerFingerprint !== null) {
+ bytes = [new Uint8Array([this.issuerKeyVersion]), this.issuerFingerprint];
+ bytes = util.concat(bytes);
+ arr.push(writeSubPacket(sub.issuerFingerprint, bytes));
+ }
if (this.preferredAEADAlgorithms !== null) {
bytes = util.stringToUint8Array(util.uint8ArrayToString(this.preferredAEADAlgorithms));
arr.push(writeSubPacket(sub.preferredAEADAlgorithms, bytes));
@@ -22475,26 +22526,11 @@ var openpgp = (function (exports) {
}
/**
- * Creates Uint8Array of bytes of Issuer and Embedded Signature subpackets
+ * Creates an Uint8Array containing the unhashed subpackets
* @returns {Uint8Array} Subpacket data.
*/
writeUnhashedSubPackets() {
- const sub = enums.signatureSubpacket;
const arr = [];
- let bytes;
- if (!this.issuerKeyID.isNull() && this.issuerKeyVersion !== 5) {
- // If the version of [the] key is greater than 4, this subpacket
- // MUST NOT be included in the signature.
- arr.push(writeSubPacket(sub.issuer, this.issuerKeyID.write()));
- }
- if (this.embeddedSignature !== null) {
- arr.push(writeSubPacket(sub.embeddedSignature, this.embeddedSignature.write()));
- }
- if (this.issuerFingerprint !== null) {
- bytes = [new Uint8Array([this.issuerKeyVersion]), this.issuerFingerprint];
- bytes = util.concat(bytes);
- arr.push(writeSubPacket(sub.issuerFingerprint, bytes));
- }
this.unhashedSubpackets.forEach(data => {
arr.push(writeSimpleLength(data.length));
arr.push(data);
@@ -22514,9 +22550,11 @@ var openpgp = (function (exports) {
const critical = bytes[mypos] & 0x80;
const type = bytes[mypos] & 0x7F;
- if (!hashed && !allowedUnhashedSubpackets.has(type)) {
+ if (!hashed) {
this.unhashedSubpackets.push(bytes.subarray(mypos, bytes.length));
- return;
+ if (!allowedUnhashedSubpackets.has(type)) {
+ return;
+ }
}
mypos++;
@@ -23127,6 +23165,9 @@ var openpgp = (function (exports) {
// Those are also the ones we want to be more strict about and throw on parse errors
// (since we likely cannot process the message without these packets anyway).
await writer.abort(e);
+ } else {
+ const unparsedPacket = new UnparseablePacket(parsed.tag, parsed.packet);
+ await writer.write(unparsedPacket);
}
console.error(e);
}
@@ -23167,12 +23208,13 @@ var openpgp = (function (exports) {
const arr = [];
for (let i = 0; i < this.length; i++) {
+ const tag = this[i] instanceof UnparseablePacket ? this[i].tag : this[i].constructor.tag;
const packetbytes = this[i].write();
if (util.isStream(packetbytes) && supportsStreaming(this[i].constructor.tag)) {
let buffer = [];
let bufferLength = 0;
const minLength = 512;
- arr.push(writeTag(this[i].constructor.tag));
+ arr.push(writeTag(tag));
arr.push(transform(packetbytes, value => {
buffer.push(value);
bufferLength += value.length;
@@ -23190,9 +23232,9 @@ var openpgp = (function (exports) {
let length = 0;
arr.push(transform(clone(packetbytes), value => {
length += value.length;
- }, () => writeHeader(this[i].constructor.tag, length)));
+ }, () => writeHeader(tag, length)));
} else {
- arr.push(writeHeader(this[i].constructor.tag, packetbytes.length));
+ arr.push(writeHeader(tag, packetbytes.length));
}
arr.push(packetbytes);
}
@@ -24282,13 +24324,9 @@ var openpgp = (function (exports) {
}
// - A series of values comprising the key material.
- try {
- const { read, publicParams } = mod.parsePublicKeyParams(this.algorithm, bytes.subarray(pos));
- this.publicParams = publicParams;
- pos += read;
- } catch (err) {
- throw new Error('Error reading MPIs');
- }
+ const { read, publicParams } = mod.parsePublicKeyParams(this.algorithm, bytes.subarray(pos));
+ this.publicParams = publicParams;
+ pos += read;
// we set the fingerprint and keyID already to make it possible to put together the key packets directly in the Key constructor
await this.computeFingerprintAndKeyID();
@@ -24825,6 +24863,8 @@ var openpgp = (function (exports) {
const { privateParams } = mod.parsePrivateKeyParams(this.algorithm, cleartext, this.publicParams);
this.privateParams = privateParams;
} catch (err) {
+ if (err instanceof UnsupportedError) throw err;
+ // avoid throwing potentially sensitive errors
throw new Error('Error reading MPIs');
}
}
@@ -26722,7 +26762,7 @@ var openpgp = (function (exports) {
try {
options.curve = enums.write(enums.curve, options.curve);
} catch (e) {
- throw new Error('Invalid curve');
+ throw new Error('Unknown curve');
}
if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) {
options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519;
@@ -27244,6 +27284,11 @@ var openpgp = (function (exports) {
// A key revocation certificate can contain the following packets
const allowedRevocationPackets = /*#__PURE__*/ util.constructAllowedPackets([SignaturePacket]);
+ const mainKeyPacketTags = new Set([enums.packet.publicKey, enums.packet.privateKey]);
+ const keyPacketTags = new Set([
+ enums.packet.publicKey, enums.packet.privateKey,
+ enums.packet.publicSubkey, enums.packet.privateSubkey
+ ]);
/**
* Abstract class that represents an OpenPGP key. Must contain a primary key.
@@ -27264,8 +27309,29 @@ var openpgp = (function (exports) {
let user;
let primaryKeyID;
let subkey;
+ let ignoreUntil;
+
for (const packet of packetlist) {
+
+ if (packet instanceof UnparseablePacket) {
+ const isUnparseableKeyPacket = keyPacketTags.has(packet.tag);
+ if (isUnparseableKeyPacket && !ignoreUntil){
+ // Since non-key packets apply to the preceding key packet, if a (sub)key is Unparseable we must
+ // discard all non-key packets that follow, until another (sub)key packet is found.
+ if (mainKeyPacketTags.has(packet.tag)) {
+ ignoreUntil = mainKeyPacketTags;
+ } else {
+ ignoreUntil = keyPacketTags;
+ }
+ }
+ continue;
+ }
+
const tag = packet.constructor.tag;
+ if (ignoreUntil) {
+ if (!ignoreUntil.has(tag)) continue;
+ ignoreUntil = null;
+ }
if (disallowedPackets.has(tag)) {
throw new Error(`Unexpected packet type: ${tag}`);
}
@@ -28669,7 +28735,7 @@ var openpgp = (function (exports) {
);
if (symEncryptedPacketlist.length === 0) {
- return this;
+ throw new Error('No encrypted data found');
}
const symEncryptedPacket = symEncryptedPacketlist[0];
@@ -29496,7 +29562,7 @@ var openpgp = (function (exports) {
* @param {Signature} signature - The detached signature or an empty signature for unsigned messages
*/
constructor(text, signature) {
- // normalize EOL to canonical form
+ // remove trailing whitespace and normalize EOL to canonical form
this.text = util.removeTrailingSpaces(text).replace(/\r?\n/g, '\r\n');
if (signature && !(signature instanceof Signature$2)) {
throw new Error('Invalid signature input');
@@ -29896,7 +29962,7 @@ var openpgp = (function (exports) {
/**
- * Encrypts a message using public keys, passwords or both at once. At least one of `encryptionKeys` or `passwords`
+ * Encrypts a message using public keys, passwords or both at once. At least one of `encryptionKeys`, `passwords` or `sessionKeys`
* must be specified. If signing keys are specified, those will be used to sign the message.
* @param {Object} options
* @param {Message} options.message - Message to be encrypted as created by {@link createMessage}
@@ -30211,6 +30277,10 @@ var openpgp = (function (exports) {
if (rest.publicKeys) throw new Error('The `publicKeys` option has been removed from openpgp.encryptSessionKey, pass `encryptionKeys` instead');
const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`);
+ if ((!encryptionKeys || encryptionKeys.length === 0) && (!passwords || passwords.length === 0)) {
+ throw new Error('No encryption keys or passwords provided.');
+ }
+
try {
const message = await Message.encryptSessionKey(data, algorithm, aeadAlgorithm, encryptionKeys, passwords, wildcard, encryptionKeyIDs, date, encryptionUserIDs, config);
return formatObject(message, format, config);
@@ -42906,6 +42976,7 @@ var openpgp = (function (exports) {
exports.SymEncryptedSessionKeyPacket = SymEncryptedSessionKeyPacket;
exports.SymmetricallyEncryptedDataPacket = SymmetricallyEncryptedDataPacket;
exports.TrustPacket = TrustPacket;
+ exports.UnparseablePacket = UnparseablePacket;
exports.UserAttributePacket = UserAttributePacket;
exports.UserIDPacket = UserIDPacket;
exports.armor = armor;