Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TF-3318 Fix black pixel when clicking on attachment with long name #3339

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d339dd8
TF-3298 Fix blink when refresh email list (#3299)
dab246 Nov 25, 2024
e50a23d
Update TMail backend docker to memory-1.0.0
tddang-linagora Nov 27, 2024
1a58542
TF-3265 Fix double scrolling composer
tddang-linagora Nov 18, 2024
c475735
TF-3291 Fix [SEARCH] No results if you add > 1 address in the From field
dab246 Nov 22, 2024
2128a6e
TF-3264 Fix composer drag drop all modes (#3279)
tddang-linagora Nov 28, 2024
9c3954a
Delegate cache control from Flutter to browser (#3289)
tddang-linagora Nov 28, 2024
eaee8a0
TF-3292 Fix [SEARCH] If I filter emails by date and then sort them by…
dab246 Nov 23, 2024
e59fc1f
TF-3292 Write integration test for search emails by date and then sor…
dab246 Nov 27, 2024
c5b1e87
TF-3294 Fix BLUE-BAR mail to attendees duplicated recipients
dab246 Nov 22, 2024
6fff59a
TF-3295 Fix BLUE-BAR attendees mail addresses are not clickable
dab246 Nov 22, 2024
3a5f91b
TF-3296 Fix [SEARCH] Can't apply the "Starred" filter in search
dab246 Nov 22, 2024
9202925
fixup! TF-3296 Fix [SEARCH] Can't apply the "Starred" filter in search
dab246 Dec 2, 2024
fe2062d
TF-3315 Fix TMail web could not display embedded table correctly
dab246 Dec 3, 2024
ed0b14c
TF-3189 new option to enable/disable subaddressing for a personal folder
florentos17 Oct 21, 2024
810e304
TF-3189 new option to copy a folder's subaddress
florentos17 Nov 29, 2024
0f61e3d
TF-3189 subaddressing features only shown if supported by the server
florentos17 Nov 29, 2024
d939ffb
TF-3189 new confirmation popup when enabling subaddressing for a folder
florentos17 Nov 29, 2024
20c4720
TF-3189 new `reply to` field in the mail composer
florentos17 Oct 30, 2024
47b842d
TF-3189 composer now correctly encodes subaddresses
florentos17 Nov 15, 2024
de940d6
TF-3275 Fix FCM iOS foreground desync (#3314)
tddang-linagora Dec 6, 2024
5094fd4
Hotfix identity creator view bug
tddang-linagora Dec 4, 2024
aa99995
TF-3219 display limits of email recovery in a yellow banner and filte…
florentos17 Nov 14, 2024
1597693
Fix patrol test on Firebase Test Lab
tddang-linagora Dec 9, 2024
11960d7
TF-3312 Add logout confirmation dialog
dab246 Dec 5, 2024
547cfd8
TF-3312 Hide system's confirm dialog when logging out
dab246 Dec 5, 2024
2774100
TF-3312 Change bundle name of Twake Mail
dab246 Dec 5, 2024
e5405b5
fixup! TF-3312 Change bundle name of Twake Mail
dab246 Dec 6, 2024
6beca9d
TF-3318 Fix black pixel when clicking on attachment with long name
tddang-linagora Dec 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,5 @@ dependencies {
implementation 'androidx.work:work-runtime-ktx:2.7.0'
implementation 'com.android.support:multidex:1.0.3'
implementation 'androidx.window:window:1.0.0'
androidTestUtil "androidx.test:orchestrator:1.5.0"
androidTestUtil "androidx.test:orchestrator:1.5.1"
}
3 changes: 3 additions & 0 deletions assets/images/ic_copy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/images/ic_subaddressing_allow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions assets/images/ic_subaddressing_disallow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion backend-docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3"

services:
tmail-backend:
image: linagora/tmail-backend:memory-branch-master
image: linagora/tmail-backend:memory-1.0.0
container_name: tmail-backend
volumes:
- ./jwt_publickey:/root/conf/jwt_publickey
Expand Down
3 changes: 3 additions & 0 deletions core/lib/presentation/resources/image_paths.dart
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ class ImagePaths {
String get icBadSignature => _getImagePath('ic_bad_signature.svg');
String get icDeleteSelection => _getImagePath('ic_delete_selection.svg');
String get icLogoTwakeWelcome => _getImagePath('ic_logo_twake_welcome.svg');
String get icCopy => _getImagePath('ic_copy.svg');
String get icSubaddressingAllow => _getImagePath('ic_subaddressing_allow.svg');
String get icSubaddressingDisallow => _getImagePath('ic_subaddressing_disallow.svg');

String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class StandardizeHtmlSanitizingTransformers extends TextTransformer {
'style',
'body',
'section',
'google-sheets-html-origin',
'colgroup',
'col',
];

const StandardizeHtmlSanitizingTransformers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ class ConfirmDialogBuilder {

Key? _key;
String _title = '';
String _content = '';
String _textContent = '';
String _confirmText = '';
String _cancelText = '';
Widget? _iconWidget;
Widget? _additionalWidgetContent;
Color? _colorCancelButton;
Color? _colorConfirmButton;
TextStyle? _styleTextCancelButton;
Expand Down Expand Up @@ -63,7 +64,11 @@ class ConfirmDialogBuilder {
}

void content(String content) {
_content = content;
_textContent = content;
}

void addWidgetContent(Widget? icon) {
_additionalWidgetContent = icon;
}

void addIcon(Widget? icon) {
Expand Down Expand Up @@ -210,11 +215,11 @@ class ConfirmDialogBuilder {
)
)
),
if (_content.isNotEmpty)
if (_textContent.isNotEmpty)
Padding(
padding: _paddingContent ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Center(
child: Text(_content,
child: Text(_textContent,
textAlign: TextAlign.center,
style: _styleContent ?? const TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog)
),
Expand All @@ -233,6 +238,11 @@ class ConfirmDialogBuilder {
),
),
),
if (_additionalWidgetContent != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _additionalWidgetContent,
),
if (isArrangeActionButtonsVertical)
...[
if (_cancelText.isNotEmpty)
Expand Down
128 changes: 128 additions & 0 deletions core/lib/utils/mail/mail_address.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:core/domain/exceptions/address_exception.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/mail/domain.dart';
Expand All @@ -23,8 +24,15 @@ class MailAddress with EquatableMixin {
final String localPart;
final Domain domain;

static const String subaddressingLocalPartDelimiter = '+';

MailAddress({required this.localPart, required this.domain});

MailAddress.fromParts({required String localPartWithoutDetails, required String localPartDetails, required this.domain}) : localPart =
localPartDetails.isEmpty
? localPartWithoutDetails
: '$localPartWithoutDetails$subaddressingLocalPartDelimiter$localPartDetails';

factory MailAddress.validateAddress(String address) {
log('MailAddress::validate: Address = $address');
String localPart;
Expand Down Expand Up @@ -137,6 +145,89 @@ class MailAddress with EquatableMixin {
return localPart;
}

String? getLocalPartDetails() {
int separatorPosition = localPart.indexOf(subaddressingLocalPartDelimiter);
if (separatorPosition <= 0) {
return null;
}
return localPart.substring(separatorPosition + subaddressingLocalPartDelimiter.length);
}

String getLocalPartWithoutDetails() {
int separatorPosition = localPart.indexOf(subaddressingLocalPartDelimiter);
if (separatorPosition <= 0) {
return localPart;
}
return localPart.substring(0, separatorPosition);
}

MailAddress stripDetails() {
return MailAddress(localPart: getLocalPartWithoutDetails(), domain: domain);
}

// cannot use Uri.encodeComponent because it is meant to be compliant with RFC2396
// eg `-_.!~*'()` are not encoded, but we want `!*'()` to be
static final _needsNoEncoding = RegExp(r'^[a-zA-Z0-9._~-]+$');

// this table is adapted from `_unreserved2396Table` found at
// https://github.com/dart-lang/sdk/blob/58f9beb6d4ec9e93430454bb96c0b8f068d0b0bc/sdk/lib/core/uri.dart#L3382
static const _customUnreservedTable = <int>[
// LSB MSB
// | |
0x0000, // 0x00 - 0x0f 0000000000000000
0x0000, // 0x10 - 0x1f 0000000000000000
// -.
0x6000, // 0x20 - 0x2f 0000000000000110
// 0123456789
0x03ff, // 0x30 - 0x3f 1111111111000000
// ABCDEFGHIJKLMNO
0xfffe, // 0x40 - 0x4f 0111111111111111
// PQRSTUVWXYZ _
0x87ff, // 0x50 - 0x5f 1111111111100001
// abcdefghijklmno
0xfffe, // 0x60 - 0x6f 0111111111111111
// pqrstuvwxyz ~
0x47ff, // 0x70 - 0x7f 1111111111100010
];

// this method is adapted from `_uriEncode()` found at:
// https://github.com/dart-lang/sdk/blob/bb8db16297e6b9994b08ecae6ee1dd45a0be587e/sdk/lib/_internal/wasm/lib/uri_patch.dart#L49
static String customUriEncode(String text) {
if (_needsNoEncoding.hasMatch(text)) {
return text;
}

// Encode the string into bytes then generate an ASCII only string
// by percent encoding selected bytes.
StringBuffer result = StringBuffer('');
var bytes = utf8.encode(text);
for (int byte in bytes) {
if (byte < 128 &&
((_customUnreservedTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
result.writeCharCode(byte);
} else {
const String hexDigits = '0123456789ABCDEF';
result.write('%');
result.write(hexDigits[(byte >> 4) & 0x0f]);
result.write(hexDigits[byte & 0x0f]);
}
}
return result.toString();
}

String asEncodedString() {
String? localPartDetails = getLocalPartDetails();
if(localPartDetails == null) {
return asString();
} else {
return MailAddress.fromParts(
localPartWithoutDetails: getLocalPartWithoutDetails(),
localPartDetails: customUriEncode(localPartDetails),
domain: domain
).asString();
}
}

@override
String toString() {
return '$localPart@${domain.asString()}';
Expand Down Expand Up @@ -323,6 +414,10 @@ class MailAddress with EquatableMixin {
lpSB.write('.');
pos++;
lastCharDot = true;
} else if (postChar == subaddressingLocalPartDelimiter) {
// Start of local part details, jump to the `@`
lpSB.write(subaddressingLocalPartDelimiter);
pos = _parseLocalPartDetails(lpSB, address, pos+1);
} else if (postChar == '@') {
// End of local-part
break;
Expand Down Expand Up @@ -416,6 +511,39 @@ class MailAddress with EquatableMixin {
return pos;
}

static int _parseLocalPartDetails(StringBuffer localPartSB, String address, int pos) {
StringBuffer localPartDetailsSB = StringBuffer();

while (true) {
if (pos >= address.length) {
break;
}
var postChar = address[pos];
if (postChar == '@') {
// End of local-part-details
break;
} else {
localPartDetailsSB.write(postChar);
pos++;
}
}

String localPartDetails = localPartDetailsSB.toString();
if (localPartDetails.isEmpty || localPartDetails.trim().isEmpty) {
throw AddressException("target mailbox name should not be empty");
}
if (localPartDetails.startsWith('#')) {
throw AddressException("target mailbox name should not start with #");
}
final forbiddenChars = RegExp(r'[*\r\n]');
if (forbiddenChars.hasMatch(localPartDetails)) {
throw AddressException("target mailbox name should not contain special characters");
}

localPartSB.write(localPartDetails);
return pos;
}

@override
List<Object?> get props => [localPart, domain];
}
48 changes: 48 additions & 0 deletions core/test/utils/mail_address_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ void main() {
"[email protected]",
"user+mailbox/[email protected]",
"[email protected]",
"[email protected]",
"user+my [email protected]",
"user+Dossier d'été@domain.com",
"\"Abc@def\"@example.com",
"\"Fred Bloggs\"@example.com",
"\"Joe.\\Blow\"@example.com",
Expand Down Expand Up @@ -56,6 +59,10 @@ void main() {
"server-dev@#123.apache.org",
"server-dev@[127.0.1.1.1]",
"server-dev@[127.0.1.-1]",
"[email protected]",
"user+ @domain.com",
"user+#[email protected]",
"user+test-_.!~*'() @domain.com",
"\"a..b\"@domain.com", // jakarta.mail is unable to handle this so we better reject it
"server-dev\\[email protected]", // jakarta.mail is unable to handle this so we better reject it
"[email protected]",
Expand Down Expand Up @@ -165,5 +172,46 @@ void main() {
final mailAddress = MailAddress.validateAddress(GOOD_ADDRESS);
expect(mailAddress.toString(), equals(GOOD_ADDRESS));
});

test('MailAddress.encodeLocalPartDetails() should work with characters to encode', () {
final mailAddress = MailAddress.validateAddress("user+my [email protected]");
expect(mailAddress.asEncodedString(), equals("user+my%[email protected]"));
});

test('MailAddress.encodeLocalPartDetails() should work with many characters to encode', () {
final mailAddress = MailAddress.validateAddress("user+Dossier d'été@domain.com");
expect(mailAddress.asEncodedString(), equals("user+Dossier%20d%27%C3%A9t%C3%[email protected]"));
});

test('MailAddress.encodeLocalPartDetails() should encode the rights characters', () {
final mailAddress = MailAddress.validateAddress("user+test-_.!~'() @domain.com");
expect(mailAddress.asEncodedString(), equals("user+test-_.%21~%27%28%29%[email protected]"));
});

test('getLocalPartDetails() should work', () {
final mailAddress = MailAddress.validateAddress("[email protected]");
expect(mailAddress.getLocalPartDetails(), equals("details"));
});

test('getLocalPartWithoutDetails() should work', () {
final mailAddress = MailAddress.validateAddress("[email protected]");
expect(mailAddress.getLocalPartWithoutDetails(), equals("user"));
});

test('stripDetails() should work', () {
final mailAddress = MailAddress.validateAddress("[email protected]");
expect(mailAddress.stripDetails().asString(), equals("[email protected]"));
});

test('stripDetails() should work with encoded local part', () {
final mailAddress = MailAddress.validateAddress("user+Dossier%20d%27%C3%A9t%C3%[email protected]");
expect(mailAddress.stripDetails().asString(), equals("[email protected]"));
});

test('stripDetails() should work when local part needs encoding', () {
final mailAddress = MailAddress.validateAddress("user+super [email protected]");
expect(mailAddress.stripDetails().asString(), equals("[email protected]"));
});

});
}
Loading
Loading