Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
the-djmaze authored Jun 9, 2024
2 parents 92ab56b + 7beb712 commit 92ed4fc
Show file tree
Hide file tree
Showing 82 changed files with 607 additions and 343 deletions.
5 changes: 3 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ If applicable, add screenshots to help explain your problem.
- SnappyMail Version:
- Mode: [e.g. standalone, nextcloud, cyberpanel, docker]

**[Debug/logging information](https://github.com/the-djmaze/snappymail/wiki/FAQ#how-do-i-enable-logging)**
Place them here (few lines) or as attachments (many lines)
**Debug/logging information**
[Read here how to log](https://github.com/the-djmaze/snappymail/wiki/FAQ#how-do-i-enable-logging)
- [ ] I've placed them here (few lines) or as attachments (many lines)

**Additional context**
Add any other context about the problem here.
10 changes: 6 additions & 4 deletions dev/Model/MimeHeaderAutocrypt.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ export class MimeHeaderAutocryptModel/* extends AbstractModel*/

if (value) {
value.split(';').forEach(entry => {
entry = entry.split('=');
entry = entry.match(/^([^=]+)=(.*)$/);
const trim = str => (str || '').trim().replace(/^["']|["']+$/g, '');
this[trim(entry[0]).replace('-', '_')] = trim(entry[1]);
this[trim(entry[1]).replace('-', '_')] = trim(entry[2]);
});
this.keydata = this.keydata.replace(/\s+/g, '\n');
}
}

toString() {
let result = `addr=${this.addr}; `;
if ('mutual' === this.prefer_encrypt) {
return `addr=${this.addr}; prefer-encrypt=mutual; keydata=${this.keydata}`;
result += 'prefer-encrypt=mutual; ';
}
return `addr=${this.addr}; keydata=${this.keydata}`;
return result + 'keydata=' + this.keydata.replace(/\n/g, '\n ');
}

pem() {
Expand Down
11 changes: 5 additions & 6 deletions dev/View/Popup/AdvancedSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
text: '',
keyword: '',
repliedValue: -1,
selectedDateValue: -1,
selectedDateValue: 0,
selectedTreeValue: '',

hasAttachment: false,
Expand Down Expand Up @@ -58,7 +58,7 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
// We should think about migrating all SEARCH/DATE_ to either SEARCH/SINCE_DATE or SEARCH/BEFORE_DATE
// and adjust the prefix accordingly
// let prefix_since = 'SEARCH/SINCE_DATE_';
let prefix = 'SEARCH/DATE_';
let prefix = 'SEARCH/SINCE_';
let prefix_before = 'SEARCH/BEFORE_DATE_';
return [
{ id: -365, name: i18n(prefix_before + 'YEAR') },
Expand All @@ -67,7 +67,7 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
{ id: -30, name: i18n(prefix_before + 'MONTH') },
{ id: -7, name: i18n(prefix_before + '7_DAYS') },
{ id: -3, name: i18n(prefix_before + '3_DAYS') },
{ id: -1, name: i18n(prefix + 'ALL') },
{ id: 0, name: i18n('SEARCH/DATE_ALL') },
{ id: 3, name: i18n(prefix + '3_DAYS') },
{ id: 7, name: i18n(prefix + '7_DAYS') },
{ id: 30, name: i18n(prefix + 'MONTH') },
Expand Down Expand Up @@ -110,7 +110,7 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
append('text', self.text().trim());
append('keyword', self.keyword());
append('in', self.selectedTreeValue());
if (-1 < self.selectedDateValue()) {
if (0 < self.selectedDateValue()) {
let d = new Date();
d.setDate(d.getDate() - self.selectedDateValue());
append('since', d.toISOString().split('T')[0]);
Expand Down Expand Up @@ -151,8 +151,7 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
self.text(pString(params.get('text')));
self.keyword(pString(params.get('keyword')));
self.selectedTreeValue(pString(params.get('in')));
// self.selectedDateValue(params.get('since'));
self.selectedDateValue(-1);
self.selectedDateValue(0);
self.hasAttachment(params.has('attachment'));
self.starred(params.has('flagged'));
self.unseen(params.has('unseen'));
Expand Down
48 changes: 23 additions & 25 deletions dev/View/Popup/Compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ export class ComposePopupView extends AbstractViewPopup {
this.sendError(true);
this.sendErrorDesc(
getNotification(iError, data?.ErrorMessage, Notifications.CantSendMessage)
+ "\n" + data?.ErrorMessageAdditional
+ "\n" + (data?.ErrorMessageAdditional || data?.ErrorMessage)
);
};
try {
Expand Down Expand Up @@ -535,9 +535,13 @@ export class ComposePopupView extends AbstractViewPopup {
}
this.savedErrorDesc(msg);
} else {
params.signPassphrase && Passphrases.delete(identity);
this.sendError(true);
sendFailed(iError, data);
// Remove remembered passphrase as it could be wrong
let key = ('S/MIME' === params.sign) ? identity : null;
params.signFingerprint
&& this.signOptions.forEach(option => ('GnuPG' === option[0]) && (key = option[1]));
key && Passphrases.delete(key);
}
} else {
if (arrayLength(this.aDraftInfo) > 0) {
Expand Down Expand Up @@ -886,7 +890,7 @@ export class ComposePopupView extends AbstractViewPopup {
}

if (options.mode && oLastMessage) {
let encrypted,
let usePlain,
sCc = '',
sDate = timestampToString(oLastMessage.dateTimestamp(), 'FULL'),
sSubject = oLastMessage.subject(),
Expand Down Expand Up @@ -994,18 +998,14 @@ export class ComposePopupView extends AbstractViewPopup {
break;

default:
encrypted = PgpUserStore.isEncrypted(sText);
if (encrypted) {
usePlain = PgpUserStore.isEncrypted(sText) || isPlainEditor() || !oLastMessage.isHtml();
if (usePlain) {
sText = oLastMessage.plain();
}
}

this.editor(editor => {
encrypted || editor.setHtml(sText);
if (encrypted || isPlainEditor()) {
editor.modePlain();
}
encrypted && editor.setPlain(sText);
usePlain ? (editor.modePlain() | editor.setPlain(sText)) : editor.setHtml(sText);
this.setSignature(identity, options.mode);
this.setFocusInPopup();
});
Expand Down Expand Up @@ -1506,6 +1506,8 @@ export class ComposePopupView extends AbstractViewPopup {
do {
l = Text.length;
Text = Text
// Remove line duplication
.replace(/<br><\/div>/gi, '</div>')
// Remove Microsoft Office styling
.replace(/(<[^>]+[;"'])\s*mso-[a-z-]+\s*:[^;"']+/gi, '$1')
// Remove hubspot data-hs- attributes
Expand Down Expand Up @@ -1546,12 +1548,11 @@ export class ComposePopupView extends AbstractViewPopup {
alternative.children.push(data);
data = alternative;
}
let sign = true;
let isSigned = false;
for (let i = 0; i < signOptions.length; ++i) {
if ('OpenPGP' == signOptions[i][0]) {
try {
// Doesn't sign attachments
params.html = params.plain = '';
let signed = new MimePart;
signed.headers['Content-Type'] =
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"';
Expand All @@ -1562,33 +1563,31 @@ export class ComposePopupView extends AbstractViewPopup {
signature.headers['Content-Transfer-Encoding'] = '7Bit';
signature.body = await OpenPGPUserStore.sign(data.toString(), signOptions[i][1], 1);
signed.children.push(signature);
isSigned = true;
params.html = params.plain = '';
params.signed = signed.toString();
params.boundary = signed.boundary;
data = signed;
/*
Object.entries(PgpUserStore.getPublicKeyOfEmails([getEmail(this.from())]) || {})
.forEach(([k,v]) => params.publicKey = v);
*/
break;
} catch (e) {
sign = false;
console.error(e);
}
break;
}
if ('GnuPG' == signOptions[i][0]) {
} else if ('GnuPG' == signOptions[i][0]) {
// TODO: sign in PHP fails
let pass = await GnuPGUserStore.sign(signOptions[i][1]);
if (null != pass) {
// params.signData = data.toString();
params.signFingerprint = signOptions[i][1].fingerprint;
params.signPassphrase = pass;
// params.attachPublicKey = false;
} else {
sign = false;
isSigned = true;
break;
}
break;
}
if ('S/MIME' == signOptions[i][0]) {
} else if ('S/MIME' == signOptions[i][0]) {
// TODO: sign in PHP fails
params.sign = 'S/MIME';
// params.signCertificate = identity.smimeCertificate();
Expand All @@ -1601,14 +1600,13 @@ export class ComposePopupView extends AbstractViewPopup {
);
if (null != pass) {
params.signPassphrase = pass.password;
// pass.remember && Passphrases.handle(identity, pass.password);
} else {
sign = false;
pass.remember && Passphrases.handle(identity, pass.password);
isSigned = true;
}
}
}
}
if (signOptions.length && !sign) {
if (signOptions.length && !isSigned) {
throw 'Signing failed';
}

Expand Down
8 changes: 6 additions & 2 deletions dev/View/Popup/Domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const
imapMessage_all_headers: false,
imapMessage_list_limit: 10000,
imapSearch_filter: '',
imapSpam_headers: '',
imapVirus_headers: '',

sieveEnabled: false,
sieveHost: '',
Expand Down Expand Up @@ -80,15 +82,17 @@ const
},
disabled_capabilities: oDomain.imapDisabled_capabilities(),
folder_list_limit: pInt(oDomain.imapFolder_list_limit()),
message_list_limit: pInt(oDomain.imapMessage_list_limit())
message_list_limit: pInt(oDomain.imapMessage_list_limit()),
/*
expunge_all_on_delete: ,
fast_simple_search: ,
fetch_new_messages: ,
force_select: ,
message_all_headers: ,
search_filter:
*/
search_filter: oDomain.imapSearch_filter(),
spam_headers: oDomain.imapSpam_headers(),
virus_headers: oDomain.imapVirus_headers()
},
SMTP: {
host: oDomain.smtpHost,
Expand Down
1 change: 0 additions & 1 deletion dev/View/User/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const
SignMeOn = 1,
SignMeUnused = 2;


export class LoginUserView extends AbstractViewLogin {
constructor() {
super();
Expand Down
3 changes: 3 additions & 0 deletions integrations/nextcloud/snappymail/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use OCA\SnappyMail\Controller\PageController;
use OCA\SnappyMail\Dashboard\UnreadMailWidget;
use OCA\SnappyMail\Search\Provider;
use OCA\SnappyMail\Listeners\AccessTokenUpdatedListener;

use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootstrap;
Expand All @@ -16,6 +17,7 @@
use OCP\IUser;
use OCP\User\Events\PostLoginEvent;
use OCP\User\Events\BeforeUserLoggedOutEvent;
use OCA\OIDCLogin\Events\AccessTokenUpdatedEvent;

class Application extends App implements IBootstrap
{
Expand Down Expand Up @@ -62,6 +64,7 @@ public function register(IRegistrationContext $context): void
);

$context->registerSearchProvider(Provider::class);
$context->registerEventListener(AccessTokenUpdatedEvent::class, AccessTokenUpdatedListener::class);

// TODO: Not working yet, needs a Vue UI
// $context->registerDashboardWidget(UnreadMailWidget::class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace OCA\SnappyMail\Listeners;

use OCA\OIDCLogin\Events\AccessTokenUpdatedEvent;
use OCP\App\IAppManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\ISession;
use OCP\IUserSession;

class AccessTokenUpdatedListener implements IEventListener {
private IUserSession $userSession;
private ISession $session;
private IAppManager $appManager;

private const SNAPPYMAIL_APP_ID = 'snappymail';
private const OIDC_LOGIN_APP_ID = 'oidc_login';


public function __construct(IUserSession $userSession, ISession $session, IAppManager $appManager) {
$this->userSession = $userSession;
$this->session = $session;
$this->appManager = $appManager;
}

public function handle(Event $event): void {
if (!($event instanceof AccessTokenUpdatedEvent) || !$this->userSession->isLoggedIn() || !$this->session->exists('is_oidc')) {
return;
}
// just-in-case checks(also maybe useful for selfhosters)
if (!$this->appManager->isEnabledForUser(self::SNAPPYMAIL_APP_ID) || !$this->appManager->isEnabledForUser(self::OIDC_LOGIN_APP_ID)) {
return;
}
$accessToken = $event->getAccessToken();
if (!$accessToken) {
return;
}

$username = $this->userSession->getUser()->getUID();
$this->session->set('snappymail-nc-uid', $username);
}
}
51 changes: 51 additions & 0 deletions plugins/example/example.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,55 @@
(rl => {
/**
* ViewModel class | class.viewModelTemplateID
* User & Admin:
* AskPopupView | PopupsAsk
* LanguagesPopupView | PopupsLanguages
* User:
* LoginUserView | Login
* SystemDropDownUserView | SystemDropDown
* MailFolderList | MailFolderList
* MailMessageList | MailMessageList
* MailMessageView | MailMessageView
* SettingsMenuUserView | SettingsMenu
* SettingsPaneUserView | SettingsPane
* UserSettingsAccounts | SettingsAccounts
* UserSettingsContacts | SettingsContacts
* UserSettingsFilters | SettingsFilters
* UserSettingsFolders | SettingsFolders
* UserSettingsGeneral | SettingsGeneral
* UserSettingsSecurity | SettingsSecurity
* UserSettingsThemes | SettingsThemes
* AccountPopupView | PopupsAccount
* AdvancedSearchPopupView | PopupsAdvancedSearch
* ComposePopupView | PopupsCompose
* ContactsPopupView | PopupsContacts
* FolderPopupView | PopupsFolder
* FolderClearPopupView | PopupsFolderClear
* FolderCreatePopupView | PopupsFolderCreate
* FolderSystemPopupView | PopupsFolderSystem
* IdentityPopupView | PopupsIdentity
* KeyboardShortcutsHelpPopupView | PopupsKeyboardShortcutsHelp
* OpenPgpGeneratePopupView | PopupsOpenPgpGenerate
* OpenPgpImportPopupView | PopupsOpenPgpImport
* OpenPgpKeyPopupView | PopupsOpenPgpKey
* SMimeImportPopupView | PopupsSMimeImport
* Admin:
* AdminLoginView | AdminLogin
* MenuSettingsAdminView | AdminMenu
* PaneSettingsAdminView | AdminPane
* AdminSettingsAbout | AdminSettingsAbout
* AdminSettingsBranding | AdminSettingsBranding
* AdminSettingsConfig | AdminSettingsConfig
* AdminSettingsContacts | AdminSettingsContacts
* AdminSettingsDomains | AdminSettingsDomains
* AdminSettingsGeneral | AdminSettingsGeneral
* AdminSettingsLogin | AdminSettingsLogin
* AdminSettingsPackages | AdminSettingsPackages
* AdminSettingsSecurity | AdminSettingsSecurity
* DomainPopupView | PopupsDomain
* DomainAliasPopupView | PopupsDomainAlias
* PluginPopupView | PopupsPlugin
*/

/**
* Happens immediately after the ViewModel constructor
Expand Down
Loading

0 comments on commit 92ed4fc

Please sign in to comment.