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

update imap & smtp URI for ease of maintenance #5111

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.store.imap.ImapStoreSettings;

import static com.fsck.k9.mail.helper.UrlEncodingHelper.buildQuery;
import static com.fsck.k9.mail.helper.UrlEncodingHelper.encodeUtf8;


Expand All @@ -27,6 +29,27 @@ public static String create(ServerSettings server) {
String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
encodeUtf8(server.clientCertificateAlias) : "";

HashMap<String,String> params = new HashMap<>();

// this has a little duplicate logic w.r.t. the legacy uri encoding below, , but it's better that way
// so we can remove the legacy format in the future

if (! clientCertificateAliasEnc.equals("") ) {
params.put("tls-cert",clientCertificateAliasEnc);
}
params.put("auth-type",server.authenticationType.name().toLowerCase());
if ((server.getExtra() == null) ||
( server.getExtra().get(ImapStoreSettings.AUTODETECT_NAMESPACE_KEY).equals("true") )) {
params.put("prefix","auto");
} else if (( server.getExtra().get(ImapStoreSettings.PATH_PREFIX_KEY) != null) &&
(! server.getExtra().get(ImapStoreSettings.PATH_PREFIX_KEY).equals("") ) ) {
params.put("prefix", "/" + server.getExtra().get(ImapStoreSettings.PATH_PREFIX_KEY) );
} else {
params.put("prefix", "/");
}

String query = buildQuery(params);

String scheme;
switch (server.connectionSecurity) {
case SSL_TLS_REQUIRED:
Expand Down Expand Up @@ -61,7 +84,8 @@ public static String create(ServerSettings server) {
} else {
path = "/1|";
}
return new URI(scheme, userInfo, server.host, server.port, path, null, null).toString();
// query is assumed to have at least one entry, and we have to do the encoding ourselves, due to limitations in URI
return new URI(scheme, userInfo, server.host, server.port, path, null, null).toString() + "?" + query;
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Can't create ImapStore URI", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;

import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.store.imap.ImapStoreSettings;


import static com.fsck.k9.mail.helper.UrlEncodingHelper.decodeUtf8;
import static com.fsck.k9.mail.helper.UrlEncodingHelper.splitQuery;


public class ImapStoreUriDecoder {
Expand Down Expand Up @@ -96,6 +99,11 @@ public static ImapStoreSettings decode(String uri) {
authenticationType = AuthType.PLAIN;
username = decodeUtf8(userInfoParts[0]);
}
} else if (userInfoParts.length == 1) {
// clean style of encoding
// username
authenticationType = AuthType.PLAIN; // can be overidden by queuy
username = userinfo;
} else if (userInfoParts.length == 2) {
// Old/standard style of encoding - PLAIN auth only:
// username:password
Expand Down Expand Up @@ -134,6 +142,25 @@ public static ImapStoreSettings decode(String uri) {
}
}
}
String query = imapUri.getRawQuery();
Map<String,String> queryParams = splitQuery(query);
if (queryParams.containsKey("prefix")) {
if (queryParams.get("prefix").toLowerCase().equals("auto")) {
autoDetectNamespace = true;
pathPrefix = null;
} else {
autoDetectNamespace = false;
pathPrefix = queryParams.get("prefix").substring(1);
}
}
if (queryParams.containsKey("auth-type")) {
authenticationType = AuthType.valueOf(queryParams.get("auth-type").toUpperCase());
}
if (queryParams.containsKey("tls-cert")) {
clientCertificateAlias = queryParams.get("tls-cert");
}



return new ImapStoreSettings(host, port, connectionSecurity, authenticationType, username,
password, clientCertificateAlias, autoDetectNamespace, pathPrefix);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,84 @@ public void testDecodeStoreUriImapAutodetectAndPrefix() {
assertNull(settings.getExtra().get("pathPrefix"));
}


@Test
public void testDecodeStoreUriImapHybridPrefix() {
String uri = "imap://PLAIN:user:pass@server:143/0%7CcustomPathPrefix?prefix=%2FcustomPathPrefix&auth-type=plain";

ServerSettings settings = ImapStoreUriDecoder.decode(uri);

assertEquals(AuthType.PLAIN, settings.authenticationType);
assertEquals("user", settings.username);
assertEquals("pass", settings.password);
assertEquals("server", settings.host);
assertEquals(143, settings.port);
assertEquals("false", settings.getExtra().get("autoDetectNamespace"));
assertEquals("customPathPrefix", settings.getExtra().get("pathPrefix"));
}


@Test
public void testDecodeStoreUriImapCleanishPrefix() {
String uri = "imap://user:pass@server?prefix=/customPathPrefix&auth-type=plain";

ServerSettings settings = ImapStoreUriDecoder.decode(uri);

assertEquals(AuthType.PLAIN, settings.authenticationType);
assertEquals("user", settings.username);
assertEquals("pass", settings.password);
assertEquals("server", settings.host);
assertEquals(143, settings.port);
assertEquals("false", settings.getExtra().get("autoDetectNamespace"));
assertEquals("customPathPrefix", settings.getExtra().get("pathPrefix"));
}


@Test
public void testDecodeStoreUriImapCleanishMixExternal() {
String uri = "imap+ssl+://EXTERNAL:user:emailCert@server/1?prefix=/customPathPrefix&auth-type=external";

ServerSettings settings = ImapStoreUriDecoder.decode(uri);

assertEquals(AuthType.EXTERNAL, settings.authenticationType);
assertEquals("user", settings.username);
assertNull(settings.password);
assertEquals("emailCert", settings.clientCertificateAlias);
assertEquals("server", settings.host);
assertEquals(993, settings.port);
assertEquals("false", settings.getExtra().get("autoDetectNamespace"));
assertEquals("customPathPrefix", settings.getExtra().get("pathPrefix"));
}

@Test
public void testDecodeStoreUriImapCleanishPureExternal() {
String uri = "imap+tls+://user@server:1993/?tls-cert=emailCert&prefix=Auto&auth-type=external";

ServerSettings settings = ImapStoreUriDecoder.decode(uri);

assertEquals(AuthType.EXTERNAL, settings.authenticationType);
assertEquals("user", settings.username);
assertNull(settings.password);
assertEquals("emailCert", settings.clientCertificateAlias);
assertEquals("server", settings.host);
assertEquals(1993, settings.port);
assertEquals("true", settings.getExtra().get("autoDetectNamespace"));
assertNull(settings.getExtra().get("pathPrefix"));
}

@Test
public void testDecodeStoreUriImapFunkyPrefixl() {
String uri = "imap+ssl+://PLAIN:user:pass@server:993?prefix=%2FThis%22Is%3F%3DNot%26+%23Healthy%2B&tls-cert=emailCert&auth-type=plain";

ServerSettings settings = ImapStoreUriDecoder.decode(uri);

assertEquals("This\"Is?=Not& #Healthy+", settings.getExtra().get("pathPrefix"));


}



@Test
public void testCreateStoreUriImapPrefix() {
Map<String, String> extra = new HashMap<>();
Expand All @@ -185,7 +263,34 @@ public void testCreateStoreUriImapPrefix() {

String uri = ImapStoreUriCreator.create(settings);

assertEquals("imap://PLAIN:user:pass@server:143/0%7CcustomPathPrefix", uri);
assertEquals("imap://PLAIN:user:pass@server:143/0%7CcustomPathPrefix?prefix=%2FcustomPathPrefix&auth-type=plain", uri);
}

@Test
public void testCreateStoreUriImapExternal() {
Map<String, String> extra = new HashMap<>();
extra.put("autoDetectNamespace", "false");
extra.put("pathPrefix", "");
ServerSettings settings = new ServerSettings("imap", "server", 993,
ConnectionSecurity.SSL_TLS_REQUIRED, AuthType.EXTERNAL, "user", "pass", "emailCert", extra);

String uri = ImapStoreUriCreator.create(settings);

assertEquals("imap+ssl+://EXTERNAL:user:emailCert@server:993/0%7C?prefix=%2F&tls-cert=emailCert&auth-type=external", uri);
}


@Test
public void testCreateStoreUriImapFunkyPrefixl() {
Map<String, String> extra = new HashMap<>();
extra.put("autoDetectNamespace", "false");
extra.put("pathPrefix", "This\"Is?=Not& #Healthy+");
ServerSettings settings = new ServerSettings("imap", "server", 993,
ConnectionSecurity.SSL_TLS_REQUIRED, AuthType.PLAIN, "user", "pass", "emailCert", extra);

String uri = ImapStoreUriCreator.create(settings);

assertEquals("imap+ssl+://PLAIN:user:pass@server:993/0%7CThis%22Is%3F=Not&%20%23Healthy+?prefix=%2FThis%22Is%3F%3DNot%26+%23Healthy%2B&tls-cert=emailCert&auth-type=plain", uri);
}

@Test
Expand All @@ -198,7 +303,7 @@ public void testCreateStoreUriImapEmptyPrefix() {

String uri = ImapStoreUriCreator.create(settings);

assertEquals("imap://PLAIN:user:pass@server:143/0%7C", uri);
assertEquals("imap://PLAIN:user:pass@server:143/0%7C?prefix=%2F&auth-type=plain", uri);
}

@Test
Expand All @@ -208,7 +313,7 @@ public void testCreateStoreUriImapNoExtra() {

String uri = ImapStoreUriCreator.create(settings);

assertEquals("imap://PLAIN:user:pass@server:143/1%7C", uri);
assertEquals("imap://PLAIN:user:pass@server:143/1%7C?prefix=auto&auth-type=plain", uri);
}

@Test
Expand All @@ -221,7 +326,7 @@ public void testCreateStoreUriImapAutoDetectNamespace() {

String uri = ImapStoreUriCreator.create(settings);

assertEquals("imap://PLAIN:user:pass@server:143/1%7C", uri);
assertEquals("imap://PLAIN:user:pass@server:143/1%7C?prefix=auto&auth-type=plain", uri);
}

@Test
Expand All @@ -231,7 +336,7 @@ public void testCreateDecodeStoreUriWithSpecialCharactersInUsernameAndPassword()

String uri = ImapStoreUriCreator.create(settings);

assertEquals("imap://PLAIN:user%2540doma%253An:p%2540ssw%253Ard%2525@server:143/1%7C", uri);
assertEquals("imap://PLAIN:user%2540doma%253An:p%2540ssw%253Ard%2525@server:143/1%7C?prefix=auto&auth-type=plain", uri);

ServerSettings outSettings = ImapStoreUriDecoder.decode(uri);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,42 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;


public final class UrlEncodingHelper {
private UrlEncodingHelper() {
}

public static Map<String, String> splitQuery(String query) {
Map<String, String> queryParams = new HashMap<>();
if (query == null) {
return queryParams;
}
String[] params = query.split("&");
for(String param: params) {
String[] parts = param.split("=");
queryParams.put(parts[0], decodeUtf8(parts[1]));
}
return queryParams;
}

public static String buildQuery(Map<String, String> params) {
if (params.size() == 0) {
return null;
}
StringBuffer query = new StringBuffer("");
for (String param: params.keySet()) {
query.append(param);
query.append('=');
query.append(encodeUtf8(params.get(param)));
query.append('&');
}
query.deleteCharAt(query.length()-1);
return query.toString();
}

public static String decodeUtf8(String s) {
try {
return URLDecoder.decode(s, "UTF-8");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.HashMap;

import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ServerSettings;
import static com.fsck.k9.mail.helper.UrlEncodingHelper.buildQuery;


public class SmtpTransportUriCreator {
Expand All @@ -28,6 +30,22 @@ public static String createSmtpUri(ServerSettings server) {
String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
encodeUtf8(server.clientCertificateAlias) : "";


HashMap<String,String> params = new HashMap<>();

// this has a little duplicate logic w.r.t. the legacy uri encoding below, , but it's better that way
// so we can remove the legacy format in the future

if (! clientCertificateAliasEnc.equals("") ) {
params.put("tls-cert",clientCertificateAliasEnc);
}
if (server.authenticationType != null) {
params.put("auth-type", server.authenticationType.name().toLowerCase());
}
String query = buildQuery(params);



String scheme;
switch (server.connectionSecurity) {
case SSL_TLS_REQUIRED:
Expand All @@ -53,9 +71,14 @@ public static String createSmtpUri(ServerSettings server) {
}
} else {
userInfo = userEnc + ":" + passwordEnc;
if (userInfo.equals(":")) {
userInfo = null;
} else if ((passwordEnc == null) || (passwordEnc.equals(""))) {
userInfo = userEnc;
}
}
try {
return new URI(scheme, userInfo, server.host, server.port, null, null,
return new URI(scheme, userInfo, server.host, server.port, null, query,
null).toString();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Can't create SmtpTransport URI", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.Map;

import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
import static com.fsck.k9.mail.helper.UrlEncodingHelper.splitQuery;


public class SmtpTransportUriDecoder {
Expand Down Expand Up @@ -96,6 +98,15 @@ public static ServerSettings decodeSmtpUri(String uri) {
}
}

String query = smtpUri.getQuery();
Map<String,String> queryParams = splitQuery(query);
if (queryParams.containsKey("auth-type")) {
authType = AuthType.valueOf(queryParams.get("auth-type").toUpperCase());
}
if (queryParams.containsKey("tls-cert")) {
clientCertificateAlias = queryParams.get("tls-cert");
}

return new ServerSettings("smtp", host, port, connectionSecurity,
authType, username, password, clientCertificateAlias);
}
Expand Down