Skip to content

Commit

Permalink
Introduce new "identity" option. (#51)
Browse files Browse the repository at this point in the history
This option defines whether identity name & signature should be included when
composing outgoing e-mail, or is anonymous data (name from recipient & no
signature) should be used instead for privacy.
  • Loading branch information
r3c authored Oct 7, 2024
1 parent 2951879 commit 97cb19f
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 116 deletions.
5 changes: 5 additions & 0 deletions config.inc.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ $config = array();
*/
//$config['custom_from_compose_contains'] = '';

/*
** Policy for using parameters (name, signature) of matched identity.
*/
//$config['custom_from_compose_identity'] = 'loose'; // 'exact', 'loose'

/*
** List of allowed matching rules by header type. This define how addresses
** from various e-mail headers can be used to guess custom address (only if
Expand Down
239 changes: 154 additions & 85 deletions custom_from.php

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions localization/de_DE.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
$labels = array();
$labels['preference'] = 'Antwortadresse (Custom From)';
$labels['preference_compose'] = 'Benutzerdefinierte Adresse beim Antworten auf eine E-Mail';
$labels['preference_compose_contains'] = '...und enthält Text (optional)';
$labels['preference_compose_subject'] = 'Aktivieren, wenn ein Empfänger';
$labels['preference_compose_subject_always'] = 'Immer automatisch aktivieren';
$labels['preference_compose_subject_domain'] = 'Verwendet dieselbe Domäne wie eine Identität';
$labels['preference_compose_subject_exact'] = 'Ist genau eine meiner Identitäten';
$labels['preference_compose_contains'] = '...und Empfängeradresse enthält;
$labels['preference_compose_identity'] = 'Verwenden Sie den Namen und die Unterschrift der Identität';
$labels['preference_compose_identity_exact'] = 'Wenn die Adresse genau übereinstimmt';
$labels['preference_compose_identity_loose'] = 'Stets';
$labels['preference_compose_subject'] = 'Aktivieren, wenn ein Empfänger';';
$labels['preference_compose_subject_always'] = 'Immer mit Standardidentität aktivieren';
$labels['preference_compose_subject_domain'] = 'Verwendet dieselbe Domäne wie einer der Empfänger';
$labels['preference_compose_subject_exact'] = 'Ist einer der Empfänger';
$labels['preference_compose_subject_never'] = 'Niemals automatisch aktivieren';
$labels['preference_compose_subject_prefix'] = 'Ist eine meiner Identitäten mit +Suffix';
$labels['preference_compose_subject_prefix'] = 'Ist einer der Empfänger mit dem Suffix +';
$labels['custom_from_off'] = 'Identität auswählen';
$labels['custom_from_off_hint'] = 'Absender aus Liste auswählen';
$labels['custom_from_on'] = 'Frei wählbare Identität';
Expand Down
15 changes: 9 additions & 6 deletions localization/en_US.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
$labels = array();
$labels['preference'] = 'Reply address (Custom From)';
$labels['preference_compose'] = 'Custom address when replying to an e-mail';
$labels['preference_compose_contains'] = '...and contains text (optional)';
$labels['preference_compose_subject'] = 'Enable when one recipient';
$labels['preference_compose_subject_always'] = 'Always enable automatically';
$labels['preference_compose_subject_domain'] = 'Uses same domain than an identity';
$labels['preference_compose_subject_exact'] = 'Is exactly one of my identities';
$labels['preference_compose_contains'] = '...and recipient address contains';
$labels['preference_compose_identity'] = 'Use identity\'s name and signature';
$labels['preference_compose_identity_exact'] = 'If address matches exactly';
$labels['preference_compose_identity_loose'] = 'Always';
$labels['preference_compose_subject'] = 'Enable when one of my identities';
$labels['preference_compose_subject_always'] = 'Always enable with default identity';
$labels['preference_compose_subject_domain'] = 'Uses same domain than one of recipients';
$labels['preference_compose_subject_exact'] = 'Is one of the recipients';
$labels['preference_compose_subject_never'] = 'Never enable automatically';
$labels['preference_compose_subject_prefix'] = 'Is one of my identities with +suffix';
$labels['preference_compose_subject_prefix'] = 'Is one of recipients with +suffix';
$labels['custom_from_off'] = 'Select identity';
$labels['custom_from_off_hint'] = 'Select sender from identities list';
$labels['custom_from_on'] = 'Custom identity';
Expand Down
15 changes: 9 additions & 6 deletions localization/fr_FR.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
$labels = array();
$labels['preference'] = 'Adresse de réponse (Custom From)';
$labels['preference_compose'] = 'Adresse personnalisée en réponse à un e-mail';
$labels['preference_compose_contains'] = '...et contient le texte (optionel)';
$labels['preference_compose_subject'] = 'Activer quand l\'un des destinataires';
$labels['preference_compose_subject_always'] = 'Toujours activer automatiquement';
$labels['preference_compose_subject_domain'] = 'Utilise le même domaine qu\'une identité';
$labels['preference_compose_subject_exact'] = 'Est exactement l\'une de mes identités';
$labels['preference_compose_contains'] = '...et l\'adresse du destinataire contient';
$labels['preference_compose_identity'] = 'Utiliser nom et signature de l\'identité';
$labels['preference_compose_identity_exact'] = 'Si l\'adresse correspond exactement';
$labels['preference_compose_identity_loose'] = 'Toujours';
$labels['preference_compose_subject'] = 'Activer si l\'une de mes identités';
$labels['preference_compose_subject_always'] = 'Toujours activer avec l\'identité par défaut';
$labels['preference_compose_subject_domain'] = 'Utilise le domaine d\'un des destinataires';
$labels['preference_compose_subject_exact'] = 'Est parmis les destinataires';
$labels['preference_compose_subject_never'] = 'Ne jamais activer automatiquement';
$labels['preference_compose_subject_prefix'] = 'Est l\'une de mes identités avec +suffixe';
$labels['preference_compose_subject_prefix'] = 'Est l\'un des destinataires avec +suffixe';
$labels['custom_from_off'] = 'Choisir l\'identité';
$labels['custom_from_off_hint'] = 'Choisir l\'expéditeur dans la liste des identités';
$labels['custom_from_on'] = 'Identité libre';
Expand Down
123 changes: 114 additions & 9 deletions tests/CustomFromTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,44 @@ final class CustomFromTest extends TestCase
{
const CONTAINS = 'custom_from_compose_contains';
const DISABLE = 'custom_from_preference_disable';
const IDENTITY = 'custom_from_compose_identity';
const RULES = 'custom_from_header_rules';
const SUBJECT = 'custom_from_compose_subject';

public static function identity_select_should_select_matched_identity_provider(): array
{
return array(
array('1', 0),
array('2', 1),
array('3', null)
);
}

#[DataProvider('identity_select_should_select_matched_identity_provider')]
public function test_identity_select_should_select_matched_identity($identity, $expected): void
{
$rcmail = rcmail::mock();
$rcmail->mock_config(array());
$rcmail->mock_user(array(), array());

$plugin = self::create_plugin();

rcube_utils::mock_input_value('_id', '42');

self::set_state($plugin, '42', $identity, null);

$params = $plugin->identity_select(array('identities' => array(
array('identity_id' => '1'),
array('identity_id' => '2')
)));

if ($expected !== null) {
$this->assertSame($params['selected'], $expected);
} else {
$this->assertSame(isset($params['selected']), false);
}
}

public static function storage_init_should_fetch_headers_provider(): array
{
return array(
Expand Down Expand Up @@ -69,90 +104,142 @@ public static function message_compose_should_set_state_provider(): array
array('to' => '[email protected]'),
array(self::RULES => 'to=e'),
array(),
array('email' => null, 'id' => '1')
'1',
null
),
// Subject rule "exact" shouldn't match suffix
array(
array('to' => '[email protected]'),
array(self::RULES => 'to=e'),
array(),
null,
null,
),
// Subject rule "prefix" should match address exactly
array(
array('to' => '[email protected]'),
array(),
array(),
array('email' => null, 'id' => '1')
'1',
null
),
// Subject rule "prefix" should match address by prefix
array(
array('to' => '[email protected]'),
array(),
array(),
array('email' => '[email protected]', 'id' => '1')
'1',
'Alice <[email protected]>'
),
// Subject rule "prefix" should not match different user
array(
array('to' => '[email protected]'),
array(),
array(),
null,
null,
),
// Subject rule "domain" on custom header should match address by domain
array(
array('to' => '[email protected]', 'x-custom' => '[email protected]'),
array(self::RULES => 'x-custom=d'),
array(),
array('email' => '[email protected]', 'id' => '3')
'3',
'Carl <[email protected]>'
),
// Subject rule "domain" should not match different domain
array(
array('to' => '[email protected]'),
array(self::RULES => 'to=d'),
array(),
null,
null,
),
// Subject rule "other" should match anything
array(
array('to' => '[email protected]'),
array(self::RULES => 'to=o'),
array(),
array('email' => '[email protected]', 'id' => '2')
'2',
'Bob <[email protected]>'
),
// Subject rule is overridden by user prefrences
array(
array('to' => '[email protected]'),
array(self::RULES => 'to=e'),
array(self::SUBJECT => 'domain'),
array('email' => '[email protected]', 'id' => '3')
'3',
'Carl <[email protected]>'
),
// Contains constraint in configuration options matches address
array(
array('to' => '[email protected]'),
array(self::CONTAINS => 'match'),
array(self::SUBJECT => 'domain'),
array('email' => '[email protected]', 'id' => '1')
'1',
'Alice <[email protected]>'
),
// Contains constraint in configuration options rejects no match
array(
array('to' => '[email protected]'),
array(self::CONTAINS => 'match'),
array(self::SUBJECT => 'domain'),
null,
null
),
// Contains constraint in user preferences rejects no match
array(
array('to' => '[email protected]'),
array(self::CONTAINS => 'other'),
array(self::CONTAINS => 'match', self::SUBJECT => 'always'),
null,
null
),
// Identity behavior "default" returns matched identity with no sender on exact match
array(
array('to' => '[email protected]'),
array(),
array(self::SUBJECT => 'always'),
'3',
null
),
// Identity behavior "default" returns matched identity and sender with identity name on domain match
array(
array('to' => '[email protected]'),
array(),
array(self::SUBJECT => 'domain'),
'3',
'Carl <[email protected]>'
),
// Identity behavior "loose" returns matched identity and sender with identity name on prefix match
array(
array('to' => 'SomeName <[email protected]>'),
array(),
array(self::IDENTITY => 'loose', self::SUBJECT => 'prefix'),
'3',
'Carl <[email protected]>'
),
// Identity behavior "exact" returns matched identity with no sender on exact match
array(
array('to' => '[email protected]'),
array(self::IDENTITY => 'exact'),
array(self::SUBJECT => 'always'),
'3',
null
),
// Identity behavior "exact" returns no identity and sender with recipient name on prefix match
array(
array('to' => 'SomeName <[email protected]>'),
array(),
array(self::IDENTITY => 'exact', self::SUBJECT => 'prefix'),
null,
'SomeName <[email protected]>'
)
);
}

#[DataProvider('message_compose_should_set_state_provider')]
public function test_message_compose_should_set_state($message, $config_values, $user_prefs, $expected): void
public function test_message_compose_should_set_state($message, $config_values, $user_prefs, $expected_identity, $expected_sender): void
{
$identity1 = array('identity_id' => '1', 'email' => '[email protected]', 'name' => 'Alice', 'standard' => '0');
$identity2 = array('identity_id' => '2', 'email' => '[email protected]', 'name' => 'Bob', 'standard' => '1');
Expand All @@ -168,7 +255,9 @@ public function test_message_compose_should_set_state($message, $config_values,
$plugin = self::create_plugin();
$plugin->message_compose(array('id' => $compose_id, 'param' => array('uid' => $message_id)));

$this->assertSame($_SESSION["custom_from_$compose_id"], $expected);
$state = self::get_state($plugin, $compose_id);

$this->assertSame($state, array($expected_identity, $expected_sender));
}

private static function create_plugin()
Expand All @@ -178,4 +267,20 @@ private static function create_plugin()

return $plugin;
}

private static function get_state($plugin, $compose_id)
{
$class = new ReflectionClass($plugin);
$method = $class->getMethod('get_state');

return $method->invokeArgs(null, array($compose_id));
}

private static function set_state($plugin, $compose_id, $identity, $sender)
{
$class = new ReflectionClass($plugin);
$method = $class->getMethod('set_state');

return $method->invokeArgs(null, array($compose_id, $identity, $sender));
}
}
27 changes: 23 additions & 4 deletions tests/rcmail_mock.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

function format_email_recipient($email)
function format_email_recipient($email, $name)
{
return $email;
return $name . ' <' . $email . '>';
}

class rcmail
Expand Down Expand Up @@ -83,9 +83,11 @@ public function get($name)

class rcube_mime
{
public static function decode_address_list($address)
public static function decode_address_list($input)
{
return array(array('mailto' => $address, 'name' => $address));
return preg_match('/(.*) <(.*)>/', $input, $match) === 1
? array(array('mailto' => $match[2], 'name' => $match[1]))
: array(array('mailto' => $input, 'name' => $input));
}
}

Expand Down Expand Up @@ -113,3 +115,20 @@ public function list_identities()
return $this->identities;
}
}

class rcube_utils
{
public const INPUT_GET = 1;

private static $input_values = array();

public static function get_input_value($name, $mode)
{
return $mode === self::INPUT_GET ? self::$input_values[$name] : null;
}

public static function mock_input_value($name, $value)
{
self::$input_values[$name] = $value;
}
}

0 comments on commit 97cb19f

Please sign in to comment.