Merge branch 'wip/40-backslash-in-folder-name' into 'master'

Fix mailbox names not being sent to the server quoted if needed

See merge request GNOME/geary!24

Fixes #40
This commit is contained in:
Michael Gratton 2018-07-20 07:07:41 +00:00
commit c9b25e25ec
16 changed files with 246 additions and 99 deletions

View file

@ -282,7 +282,6 @@ src/engine/imap/message/imap-fetch-data-specifier.vala
src/engine/imap/message/imap-flag.vala
src/engine/imap/message/imap-flags.vala
src/engine/imap/message/imap-internal-date.vala
src/engine/imap/message/imap-mailbox-parameter.vala
src/engine/imap/message/imap-mailbox-specifier.vala
src/engine/imap/message/imap-message-data.vala
src/engine/imap/message/imap-message-flag.vala

View file

@ -15,7 +15,7 @@ public class ConversationWebView : ClientWebView {
// Key codes we don't forward on to the super class on key press
// since we want to override them elsewhere, especially
// ConversationListBox.
private const int[] BLACKLISTED_KEY_CODES = {
private const uint[] BLACKLISTED_KEY_CODES = {
Gdk.Key.space,
Gdk.Key.KP_Space,
Gdk.Key.Up,

View file

@ -44,7 +44,6 @@ public class Geary.Endpoint : BaseObject {
public Flags flags { get; private set; }
public uint timeout_sec { get; private set; }
public TlsCertificateFlags tls_validation_flags { get; set; default = TlsCertificateFlags.VALIDATE_ALL; }
public bool force_ssl3 { get; set; default = false; }
/**
* The maximum number of commands that will be pipelined at once.
@ -169,7 +168,6 @@ public class Geary.Endpoint : BaseObject {
}
private void prepare_tls_cx(TlsClientConnection tls_cx, bool starttls) {
tls_cx.use_ssl3 = force_ssl3;
tls_cx.set_validation_flags(tls_validation_flags);
// Vala doesn't do delegates in a ternary operator very well
@ -178,7 +176,7 @@ public class Geary.Endpoint : BaseObject {
else
tls_cx.accept_certificate.connect(on_accept_ssl_certificate);
}
private bool on_accept_starttls_certificate(TlsConnection cx, TlsCertificate cert, TlsCertificateFlags flags) {
return report_tls_warnings(SecurityType.STARTTLS, cx, cert, flags);
}

View file

@ -125,10 +125,15 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
);
final_ops.add(op);
set_invalid();
// Capture these for the closure below, since once it gets
// invoked, this instance may no longer exist.
GenericAccount account = this.account;
Geary.Folder destination = this.destination;
op.wait_for_ready_async.begin(null, (obj, res) => {
try {
op.wait_for_ready_async.end(res);
this.account.update_folder(this.destination);
account.update_folder(destination);
} catch (Error err) {
// Oh well
}

View file

@ -7,7 +7,10 @@
namespace Geary.Imap.DataFormat {
private const char[] ATOM_SPECIALS = {
'(', ')', '{', ' ', '%', '*', '"'
'(', ')', '{', ' ', // CTL chars are handled by is_special_char
'%', '*', // list-wildcards
'"', '\\', // quoted-specials
']' // resp-specials
};
private const char[] TAG_SPECIALS = {
@ -21,12 +24,15 @@ public enum Quoting {
}
private bool is_special_char(char ch, char[] ar, string? exceptions) {
if (ch > 0x7F || ch.iscntrl())
// Check for CTL chars
if (ch <= 0x1F || ch >= 0x7F) {
return true;
if (ch in ar)
}
if (ch in ar) {
return (exceptions != null) ? Ascii.index_of(exceptions, ch) < 0 : true;
}
return false;
}

View file

@ -1,60 +0,0 @@
/* Copyright 2016 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A {@link StringParameter} that holds a mailbox reference (can be wildcarded).
*
* Used to juggle between our internal UTF-8 representation of mailboxes and IMAP's
* odd "modified UTF-7" representation. The value is stored in IMAP's encoded
* format since that's how it comes across the wire.
*/
public class Geary.Imap.MailboxParameter : StringParameter {
public MailboxParameter(string mailbox) {
base (utf8_to_imap_utf7(mailbox));
}
public MailboxParameter.from_string_parameter(StringParameter string_parameter) {
base (string_parameter.ascii);
}
private static string utf8_to_imap_utf7(string utf8) {
try {
return Geary.ImapUtf7.utf8_to_imap_utf7(utf8);
} catch (ConvertError e) {
debug("Error encoding mailbox name '%s': %s", utf8, e.message);
return utf8;
}
}
private static string imap_utf7_to_utf8(string imap_utf7) {
try {
return Geary.ImapUtf7.imap_utf7_to_utf8(imap_utf7);
} catch (ConvertError e) {
debug("Invalid mailbox name '%s': %s", imap_utf7, e.message);
return imap_utf7;
}
}
public string decode() {
return imap_utf7_to_utf8(ascii);
}
/**
* {@inheritDoc}
*/
public override void serialize(Serializer ser, Tag tag) throws Error {
serialize_string(ser);
}
/**
* {@inheritDoc}
*/
public override string to_string() {
return ascii;
}
}

View file

@ -48,15 +48,38 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
* See [[http://tools.ietf.org/html/rfc3501#section-5.1]]
*/
public bool is_inbox { get; private set; }
/**
* Constructs a new specifier from a UTF-8 name.
*/
public MailboxSpecifier(string name) {
init(name);
}
public MailboxSpecifier.from_parameter(MailboxParameter param) {
init(param.decode());
/**
* Constructs a new specifier from a IMAP modified-UTF-7 string.
*
* If a modified-UTF-7 decoding error occurs, the parameter will
* assumed to be UTF-8, repaired, and used instead.
*/
public MailboxSpecifier.from_parameter(StringParameter param) {
string? name = null;
try {
name = Geary.ImapUtf7.imap_utf7_to_utf8(param.ascii);
} catch (ConvertError err) {
// Could no decode the name as IMAP modified UTF7, so per
// https://imapwiki.org/ClientImplementation/MailboxList
// assume UTF8 and repair to make sure it's valid at
// least.
debug(
"Error decoding mailbox name, assuming UTF-8: %s", err.message
);
name = param.ascii.make_valid();
}
init(name);
}
/**
* Returns true if the {@link Geary.FolderPath} points to the IMAP Inbox.
*/
@ -187,11 +210,18 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
return !String.is_empty(basename) ? basename : name;
}
public Parameter to_parameter() {
return new MailboxParameter(name);
string encoded= Geary.ImapUtf7.utf8_to_imap_utf7(this.name);
Parameter? param = null;
try {
param = StringParameter.get_best_for(encoded);
} catch (ImapError err) {
param = new LiteralParameter(new Geary.Memory.StringBuffer(encoded));
}
return param;
}
public uint hash() {
return is_inbox ? Ascii.stri_hash(name) : Ascii.str_hash(name);
}

View file

@ -71,19 +71,21 @@ public class Geary.Imap.MailboxInformation : BaseObject {
// decode everything
MailboxAttributes attributes = new MailboxAttributes(attrlist);
StringParameter? delim = server_data.get_as_nullable_string(3);
MailboxParameter mailbox = new MailboxParameter.from_string_parameter(
server_data.get_as_string(4));
// Set \Inbox to standard path
if (canonical_inbox && Geary.Imap.MailboxAttribute.SPECIAL_FOLDER_INBOX in attributes) {
return new MailboxInformation(MailboxSpecifier.inbox,
(delim != null) ? delim.nullable_ascii : null, attributes);
} else {
return new MailboxInformation(new MailboxSpecifier.from_parameter(mailbox),
(delim != null) ? delim.nullable_ascii : null, attributes);
}
StringParameter mailbox = server_data.get_as_string(4);
// If special-use flag \Inbox is set just use the canonical
// Inbox name, otherwise decode it
MailboxSpecifier? specifier =
(canonical_inbox &&
Geary.Imap.MailboxAttribute.SPECIAL_FOLDER_INBOX in attributes)
? MailboxSpecifier.inbox
: new MailboxSpecifier.from_parameter(mailbox);
return new MailboxInformation(
specifier, (delim != null) ? delim.nullable_ascii : null, attributes
);
}
/**
* The {@link Geary.FolderPath} for the {@link mailbox}.
*

View file

@ -74,10 +74,9 @@ public class Geary.Imap.StatusData : Object {
throw new ImapError.PARSE_ERROR("Bad STATUS command name in response \"%s\"",
server_data.to_string());
}
MailboxParameter mailbox_param = new MailboxParameter.from_string_parameter(
server_data.get_as_string(2));
StringParameter mailbox_param = server_data.get_as_string(2);
int messages = UNSET;
int recent = UNSET;
UID? uid_next = null;

View file

@ -126,7 +126,6 @@ geary_engine_vala_sources = files(
'imap/message/imap-flags.vala',
'imap/message/imap-internal-date.vala',
'imap/message/imap-mailbox-specifier.vala',
'imap/message/imap-mailbox-parameter.vala',
'imap/message/imap-message-data.vala',
'imap/message/imap-message-flag.vala',
'imap/message/imap-message-flags.vala',

View file

@ -94,7 +94,7 @@ private int first_encode_index(string str) {
return -1;
}
public string utf8_to_imap_utf7(string str) throws ConvertError {
public string utf8_to_imap_utf7(string str) {
int p = first_encode_index(str);
if (p < 0) {
/* no characters that need to be encoded */

View file

@ -132,7 +132,7 @@ int sqlite3FtsUnicodeIsalnum(int c){
return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 );
}else if( c<(1<<22) ){
unsigned int key = (((unsigned int)c)<<10) | 0x000003FF;
int iRes;
int iRes= 0;
int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;
int iLo = 0;
while( iHi>=iLo ){

View file

@ -0,0 +1,104 @@
/*
* Copyright 2018 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class Geary.Imap.DataFormatTest : TestCase {
public DataFormatTest() {
base("Geary.Imap.DataFormatTest");
add_test("is_atom_special", is_atom_special);
}
public void is_atom_special() throws Error {
assert_false(
DataFormat.is_atom_special('a') || DataFormat.is_atom_special('z'),
"Lower case ASCII"
);
assert_false(
DataFormat.is_atom_special('A') || DataFormat.is_atom_special('Z'),
"Upper case ASCII"
);
assert_false(
DataFormat.is_atom_special('0') || DataFormat.is_atom_special('9'),
"ASCII numbers"
);
assert_false(
DataFormat.is_atom_special('#') ||
DataFormat.is_atom_special('.') ||
DataFormat.is_atom_special('+') ||
DataFormat.is_atom_special('/') ||
DataFormat.is_atom_special('~') ||
DataFormat.is_atom_special(':'),
"Common mailbox ASCII symbols"
);
// atom-specials
assert_true(
DataFormat.is_atom_special('('),
"Atom-special: ("
);
assert_true(
DataFormat.is_atom_special(')'),
"Atom-special: )"
);
assert_true(
DataFormat.is_atom_special('{'),
"Atom-special: {"
);
assert_true(
DataFormat.is_atom_special(' '),
"Atom-special: SP"
);
assert_true(
DataFormat.is_atom_special(0x00),
"Atom-special: CTL (NUL)"
);
assert_true(
DataFormat.is_atom_special(0x1F),
"Atom-special: CTL (US)"
);
assert_true(
DataFormat.is_atom_special(0x7F),
"Atom-special: CTL (DEL)"
);
assert_true(
DataFormat.is_atom_special(0x80),
"Atom-special: Non-ASCII (0x80)"
);
assert_true(
DataFormat.is_atom_special(0xFE),
"Atom-special: Non-ASCII (0xFE)"
);
// list-wildcards
assert_true(
DataFormat.is_atom_special('%'),
"Atom-special: %"
);
assert_true(
DataFormat.is_atom_special('*'),
"Atom-special: *"
);
// quoted-specials
assert_true(
DataFormat.is_atom_special('\"'),
"Atom-special: \""
);
assert_true(
DataFormat.is_atom_special('\\'),
"Atom-special: \\"
);
// resp-specials
assert_true(
DataFormat.is_atom_special(']'),
"Atom-special: ]"
);
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright 2018 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class Geary.Imap.MailboxSpecifierTest : TestCase {
public MailboxSpecifierTest() {
base("Geary.Imap.MailboxSpecifierTest");
add_test("to_parameter", to_parameter);
add_test("from_parameter", from_parameter);
}
public void to_parameter() throws Error {
assert_string(
"test",
new MailboxSpecifier("test").to_parameter().to_string()
);
assert_string(
"foo/bar",
new MailboxSpecifier("foo/bar").to_parameter().to_string()
);
// The param won't be quoted or escaped since
// QuotedStringParameter doesn't actually handle that, so just
// check that it is correct type
Parameter quoted = new MailboxSpecifier("""foo\bar""").to_parameter();
assert_true(quoted is QuotedStringParameter, "Backslash was not quoted");
assert_string(
"ol&AOk-",
new MailboxSpecifier("olé").to_parameter().to_string()
);
}
public void from_parameter() throws Error {
assert_string(
"test",
new MailboxSpecifier.from_parameter(
new UnquotedStringParameter("test")).name
);
// This won't be quoted or escaped since QuotedStringParameter
// doesn't actually handle that.
assert_string(
"foo\\bar",
new MailboxSpecifier.from_parameter(
new QuotedStringParameter("""foo\bar""")).name
);
assert_string(
"olé",
new MailboxSpecifier.from_parameter(
new UnquotedStringParameter("ol&AOk-")).name
);
}
}

View file

@ -30,6 +30,8 @@ geary_test_engine_sources = [
'engine/db/db-database-test.vala',
'engine/db/db-versioned-database-test.vala',
'engine/imap/command/imap-create-command-test.vala',
'engine/imap/message/imap-data-format-test.vala',
'engine/imap/message/imap-mailbox-specifier-test.vala',
'engine/imap/response/imap-namespace-response-test.vala',
'engine/imap/transport/imap-deserializer-test.vala',
'engine/imap-db/imap-db-attachment-test.vala',

View file

@ -36,8 +36,11 @@ int main(string[] args) {
engine.add_suite(new Geary.Db.DatabaseTest().get_suite());
engine.add_suite(new Geary.Db.VersionedDatabaseTest().get_suite());
engine.add_suite(new Geary.HTML.UtilTest().get_suite());
engine.add_suite(new Geary.Imap.DeserializerTest().get_suite());
// Other IMAP tests rely on DataFormat working, so test that first
engine.add_suite(new Geary.Imap.DataFormatTest().get_suite());
engine.add_suite(new Geary.Imap.CreateCommandTest().get_suite());
engine.add_suite(new Geary.Imap.DeserializerTest().get_suite());
engine.add_suite(new Geary.Imap.MailboxSpecifierTest().get_suite());
engine.add_suite(new Geary.Imap.NamespaceResponseTest().get_suite());
engine.add_suite(new Geary.ImapDB.AttachmentTest().get_suite());
engine.add_suite(new Geary.ImapDB.AttachmentIoTest().get_suite());