From 6fa91bd6cb35dcdc89e32bb9e155f4d01ff1e5b4 Mon Sep 17 00:00:00 2001 From: Ingo Bauersachs Date: Sun, 15 May 2016 20:33:26 +0200 Subject: Validate protocol addresses before creating a contact Instead of simply failing after clicking OK when adding a contact, validate the address/ID supplied by the user, show an error and attempt to correct it. --- .../gui/main/contactlist/AddContactDialog.java | 23 ++++++ .../jabber/ProtocolProviderServiceJabberImpl.java | 87 +++++++++++++++++++++ .../impl/protocol/mock/MockProvider.java | 9 +++ .../sip/ProtocolProviderServiceSipImpl.java | 89 +++++++++++++++++++++- .../protocol/AbstractProtocolProviderService.java | 13 ++++ .../service/protocol/ProtocolProviderService.java | 17 +++++ 6 files changed, 234 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java b/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java index 856ae5b..f92fbbe 100644 --- a/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/contactlist/AddContactDialog.java @@ -21,10 +21,13 @@ import java.awt.*; import java.awt.Container; import java.awt.event.*; import java.util.*; +import java.util.List; import javax.swing.*; import javax.swing.event.*; +import org.jitsi.util.*; + import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.contactlist.addgroup.*; import net.java.sip.communicator.impl.gui.utils.*; @@ -499,6 +502,26 @@ public class AddContactDialog final String contactAddress = contactAddressField.getText().trim(); final String displayName = displayNameField.getText(); + List validationResult = new ArrayList<>(2); + if (!protocolProvider.validateContactAddress(contactAddress, + validationResult)) + { + new ErrorDialog(GuiActivator.getUIService().getMainFrame(), + GuiActivator.getResources() + .getI18NString("service.gui.ADD_CONTACT_ERROR_TITLE"), + validationResult.get(0), ErrorDialog.WARNING).showDialog(); + if (validationResult.size() >= 2) + { + contactAddressField.setText(validationResult.get(1)); + if (StringUtils.isNullOrEmpty(displayName, true)) + { + displayNameField.setText(contactAddress); + } + } + + return; + } + if (!protocolProvider.isRegistered()) { new ErrorDialog( diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 425b70a..888c0aa 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -2040,6 +2040,93 @@ public class ProtocolProviderServiceJabberImpl } /** + * Validates the node part of a JID and returns an error message if + * applicable and a suggested correction. + * + * @param contactId the contact identifier to validate + * @param result Must be supplied as an empty a list. Implementors add + * items: + *
    + *
  1. is the error message if applicable + *
  2. a suggested correction. Index 1 is optional and can only + * be present if there was a validation failure. + *
+ * @return true if the contact id is valid, false otherwise + */ + @Override + public boolean validateContactAddress(String contactId, List result) + { + if (result == null) + { + throw new IllegalArgumentException("result must be an empty list"); + } + + result.clear(); + try + { + contactId = contactId.trim(); + if (contactId.length() == 0) + { + result.add(JabberActivator.getResources().getI18NString( + "impl.protocol.jabber.INVALID_ADDRESS", new String[] + { contactId })); + // no suggestion for an empty id + return false; + } + + String user = contactId; + String remainder = ""; + int at = contactId.indexOf('@'); + if (at > -1) + { + user = contactId.substring(0, at); + remainder = contactId.substring(at); + } + + // ::= #x21 | [#x23-#x25] | [#x28-#x2E] | + // [#x30-#x39] | #x3B | #x3D | #x3F | + // [#x41-#x7E] | [#x80-#xD7FF] | + // [#xE000-#xFFFD] | [#x10000-#x10FFFF] + boolean valid = true; + String suggestion = ""; + for (char c : user.toCharArray()) + { + if (!(c == 0x21 || (c >= 0x23 && c <= 0x25) + || (c >= 0x28 && c <= 0x2e) || (c >= 0x30 && c <= 0x39) + || c == 0x3b || c == 0x3d || c == 0x3f + || (c >= 0x41 && c <= 0x7e) || (c >= 0x80 && c <= 0xd7ff) + || (c >= 0xe000 && c <= 0xfffd))) + { + valid = false; + } + else + { + suggestion += c; + } + } + + if (!valid) + { + result.add(JabberActivator.getResources().getI18NString( + "impl.protocol.jabber.INVALID_ADDRESS", new String[] + { contactId })); + result.add(suggestion + remainder); + return false; + } + + return true; + } + catch (Exception ex) + { + result.add(JabberActivator.getResources().getI18NString( + "impl.protocol.jabber.INVALID_ADDRESS", new String[] + { contactId })); + } + + return false; + } + + /** * Returns the XMPPConnectionopened by this provider * @return a reference to the XMPPConnection last opened by this * provider. diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java b/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java index bcff799..a7b6e5e 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java @@ -294,6 +294,15 @@ public class MockProvider } /** + * Always true. + */ + @Override + public boolean validateContactAddress(String contactId, List result) + { + return true; + } + + /** * Mock implementation of the corresponding ProtocolProviderService method. * We have no icon corresponding to this protocol provider. */ diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java index d0460fb..a978de8 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java @@ -246,6 +246,72 @@ public class ProtocolProviderServiceSipImpl } /** + * Validates the contact identifier and returns an error message if + * applicable and a suggested correction + * + * @param contactId the contact identifier to validate + * @param result Must be supplied as an empty a list. Implementors add + * items: + *
    + *
  1. is the error message if applicable + *
  2. a suggested correction. Index 1 is optional and can only + * be present if there was a validation failure. + *
+ * @return true if the contact id is valid, false otherwise + */ + @Override + public boolean validateContactAddress(String contactId, List result) + { + if (result == null) + { + throw new IllegalArgumentException("result must be an empty list"); + } + + result.clear(); + try + { + Address address = parseAddressString(contactId); + if (address.toString().equals(contactId)) + { + return true; + } + else if (((SipUri) address.getURI()).getUser().equals(contactId)) + { + return true; + } + else + { + result.add(SipActivator.getResources().getI18NString( + "impl.protocol.sip.INVALID_ADDRESS", new String[] + { contactId })); + result.add(((SipUri) address.getURI()).getUser()); + } + } + catch (Exception ex) + { + logger.error("Validating SIP address failed for " + contactId, ex); + result.add(SipActivator.getResources() + .getI18NString("impl.protocol.sip.INVALID_ADDRESS", new String[] + { contactId })); + + String user = contactId; + String remainder = ""; + int at = contactId.indexOf('@'); + if (at > -1) + { + user = contactId.substring(0, at); + remainder = contactId.substring(at); + } + + // replace invalid characters in user part with hex encoding + String banned = "([^a-z0-9-_.!~*'()&=+$,;?/])+"; + result.add(user.replaceAll(banned, "") + remainder); + } + + return false; + } + + /** * Indicates whether or not this provider must registered * when placing outgoing calls. * @@ -2390,21 +2456,36 @@ public class ProtocolProviderServiceSipImpl //we don't know how to handle the "tel:" and "callto:" schemes ... or // rather we handle them same as sip so replace: if(uriStr.toLowerCase().startsWith("tel:")) - uriStr = "sip:" + uriStr.substring("tel:".length()); + uriStr = uriStr.substring("tel:".length()); else if(uriStr.toLowerCase().startsWith("callto:")) - uriStr = "sip:" + uriStr.substring("callto:".length()); + uriStr = uriStr.substring("callto:".length()); + else if(uriStr.toLowerCase().startsWith("sips:")) + uriStr = uriStr.substring("sips:".length()); + + String user = uriStr; + String remainder = ""; + int at = uriStr.indexOf('@'); + if (at > -1) + { + user = uriStr.substring(0, at); + remainder = uriStr.substring(at); + } + + //replace invalid characters in user part with hex encoding + String banned = "([^a-z0-9-_.!~*'()&=+$,;?/])+"; + user = user.replaceAll(banned, "") + remainder; //Handle default domain name (i.e. transform 1234 -> 1234@sip.com) //assuming that if no domain name is specified then it should be the //same as ours. - if (uriStr.indexOf('@') == -1) + if (at == -1) { //if we have a registrar, then we could append its domain name as //default SipRegistrarConnection src = sipRegistrarConnection; if(src != null && !src.isRegistrarless() ) { - uriStr = uriStr + "@" + uriStr = user + "@" + ((SipURI)src.getAddressOfRecord().getURI()).getHost(); } diff --git a/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java b/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java index 9a78eec..897b74b 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java @@ -245,6 +245,19 @@ public abstract class AbstractProtocolProviderService } /** + * Default implementation that always returns true. + * + * @param contactId ignored. + * @param result ignored + * @return true + */ + @Override + public boolean validateContactAddress(String contactId, List result) + { + return true; + } + + /** * Returns an array containing all operation sets supported by the current * implementation. When querying this method users must be prepared to * receive any subset of the OperationSet-s defined by this service. They diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java index 15c5b8d..ffc356c 100644 --- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java +++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java @@ -218,6 +218,23 @@ public interface ProtocolProviderService public AccountID getAccountID(); /** + * Validates the given protocol specific contact identifier and returns an + * error message if applicable and a suggested correction. + * + * @param contactId the contact identifier to validate + * @param result Must be supplied as an empty a list. Implementors add + * items: + *
    + *
  1. is the error message if applicable + *
  2. a suggested correction. Index 1 is optional and can only + * be present if there was a validation failure. + *
+ * @return true if the contact id is valid, false otherwise + */ + public boolean validateContactAddress(String contactId, + List result); + + /** * Indicate if the signaling transport of this protocol instance uses a * secure (e.g. via TLS) connection. * -- cgit v1.1