From 6fa91bd6cb35dcdc89e32bb9e155f4d01ff1e5b4 Mon Sep 17 00:00:00 2001 From: Ingo Bauersachs Date: Sun, 15 May 2016 20:33:26 +0200 Subject: [PATCH] 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. --- resources/languages/resources.properties | 6 ++ .../main/contactlist/AddContactDialog.java | 23 +++++ .../ProtocolProviderServiceJabberImpl.java | 87 ++++++++++++++++++ .../impl/protocol/mock/MockProvider.java | 9 ++ .../sip/ProtocolProviderServiceSipImpl.java | 89 ++++++++++++++++++- .../AbstractProtocolProviderService.java | 13 +++ .../protocol/ProtocolProviderService.java | 17 ++++ 7 files changed, 240 insertions(+), 4 deletions(-) diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index ba631d935..20b0691da 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -902,6 +902,12 @@ impl.ldap.HOMEPHONE_FIELD_EXAMPLE=homePhone impl.ldap.PHONE_PREFIX=Phone prefix impl.ldap.PHONE_PREFIX_EXAMPLE=Ex.: 00 +# SIP Protocol +impl.protocol.sip.INVALID_ADDRESS=''{0}'' is not a valid SIP address. + +#Jabber Protocol +impl.protocol.jabber.INVALID_ADDRESS=''{0}'' is not a valid XMPP JID. + # Address book plugin plugin.addrbook.ADDRESS_BOOKS=Address book plugin.addrbook.ENABLE_MACOSX_ADDRESSBOOK=Enable MacOSX Address Book search 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 856ae5b5e..f92fbbe8c 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.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 void actionPerformed(ActionEvent e) 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 425b70a35..888c0aacc 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -2039,6 +2039,93 @@ public AccountID getAccountID() return accountID; } + /** + * 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 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 bcff799ae..a7b6e5ee2 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockProvider.java @@ -293,6 +293,15 @@ public AccountID getAccountID() return accountID; } + /** + * 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 d0460fb39..a978de8c0 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ProtocolProviderServiceSipImpl.java @@ -245,6 +245,72 @@ public AccountID getAccountID() return accountID; } + /** + * 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 Address parseAddressString(String uriStr) //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 9a78eecb2..897b74b1f 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractProtocolProviderService.java @@ -244,6 +244,19 @@ public String getProtocolDisplayName() return (displayName == null) ? getProtocolName() : displayName; } + /** + * 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 diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java index 15c5b8d38..ffc356cfd 100644 --- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java +++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderService.java @@ -217,6 +217,23 @@ public void removeRegistrationStateChangeListener( */ 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.