Index: trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java	(revision 7823)
+++ trunk/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java	(revision 7824)
@@ -46,4 +46,6 @@
  *     <li>{@link #isValidGenericTld} - validates generic TLDs
  *         (<code>.com, .org</code>, etc.)</li>
+ *     <li>{@link #isValidIdnTld} - validates IDN TLDs
+ *         (<code>.xn--*</code>, etc.)</li>
  *     <li>{@link #isValidCountryCodeTld} - validates country code TLDs
  *         (<code>.us, .uk, .cn</code>, etc.)</li>
@@ -64,6 +66,10 @@
     private static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]*\\p{Alnum})*";
     private static final String TOP_LABEL_REGEX = "\\p{Alpha}{2,}";
+    // JOSM PATCH BEGIN
+    // See #10862 - IDN TLDs in ASCII form
+    private static final String TOP_LABEL_IDN_REGEX = "(?:xn|XN)--\\p{Alnum}{2,}(?:-\\p{Alpha}{2,})?";
     private static final String DOMAIN_NAME_REGEX =
-            "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")$";
+            "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + "|" + TOP_LABEL_IDN_REGEX + ")$";
+    // JOSM PATCH END
 
     private final boolean allowLocal;
@@ -152,4 +158,5 @@
         return isValidInfrastructureTld(tld)
                 || isValidGenericTld(tld)
+                || isValidIdnTld(tld)
                 || isValidCountryCodeTld(tld);
     }
@@ -175,4 +182,15 @@
     public boolean isValidGenericTld(String gTld) {
         return Arrays.binarySearch(GENERIC_TLDS, chompLeadingDot(gTld.toLowerCase())) >= 0;
+    }
+
+    /**
+     * Returns true if the specified <code>String</code> matches any
+     * IANA-defined IDN top-level domain. Leading dots are ignored
+     * if present. The search is case-sensitive.
+     * @param iTld the parameter to check for IDN TLD status
+     * @return true if the parameter is an IDN TLD
+     */
+    public boolean isValidIdnTld(String iTld) {
+        return Arrays.binarySearch(IDN_TLDS, chompLeadingDot(iTld.toUpperCase())) >= 0;
     }
 
@@ -634,4 +652,88 @@
     };
 
+    // JOSM PATCH BEGIN
+    // see #10862 - list of IDN TLDs taken from IANA on 2014-12-18
+    private static final String[] IDN_TLDS = new String[] {
+        "XN--1QQW23A",
+        "XN--3BST00M",
+        "XN--3DS443G",
+        "XN--3E0B707E",
+        "XN--45BRJ9C",
+        "XN--45Q11C",
+        "XN--4GBRIM",
+        "XN--55QW42G",
+        "XN--55QX5D",
+        "XN--6FRZ82G",
+        "XN--6QQ986B3XL",
+        "XN--80ADXHKS",
+        "XN--80AO21A",
+        "XN--80ASEHDB",
+        "XN--80ASWG",
+        "XN--90A3AC",
+        "XN--C1AVG",
+        "XN--CG4BKI",
+        "XN--CLCHC0EA0B2G2A9GCD",
+        "XN--CZR694B",
+        "XN--CZRS0T",
+        "XN--CZRU2D",
+        "XN--D1ACJ3B",
+        "XN--D1ALF",
+        "XN--FIQ228C5HS",
+        "XN--FIQ64B",
+        "XN--FIQS8S",
+        "XN--FIQZ9S",
+        "XN--FLW351E",
+        "XN--FPCRJ9C3D",
+        "XN--FZC2C9E2C",
+        "XN--GECRJ9C",
+        "XN--H2BRJ9C",
+        "XN--HXT814E",
+        "XN--I1B6B1A6A2E",
+        "XN--IO0A7I",
+        "XN--J1AMH",
+        "XN--J6W193G",
+        "XN--KPRW13D",
+        "XN--KPRY57D",
+        "XN--KPUT3I",
+        "XN--L1ACC",
+        "XN--LGBBAT1AD8J",
+        "XN--MGB9AWBF",
+        "XN--MGBA3A4F16A",
+        "XN--MGBAAM7A8H",
+        "XN--MGBAB2BD",
+        "XN--MGBAYH7GPA",
+        "XN--MGBBH1A71E",
+        "XN--MGBC0A9AZCG",
+        "XN--MGBERP4A5D4AR",
+        "XN--MGBX4CD0AB",
+        "XN--NGBC5AZD",
+        "XN--NODE",
+        "XN--NQV7F",
+        "XN--NQV7FS00EMA",
+        "XN--O3CW4H",
+        "XN--OGBPF8FL",
+        "XN--P1ACF",
+        "XN--P1AI",
+        "XN--PGBS0DH",
+        "XN--Q9JYB4C",
+        "XN--QCKA1PMC",
+        "XN--RHQV96G",
+        "XN--S9BRJ9C",
+        "XN--SES554G",
+        "XN--UNUP4Y",
+        "XN--VERMGENSBERATER-CTB",
+        "XN--VERMGENSBERATUNG-PWB",
+        "XN--VHQUV",
+        "XN--WGBH1C",
+        "XN--WGBL6A",
+        "XN--XHQ521B",
+        "XN--XKC2AL3HYE2A",
+        "XN--XKC2DL3A5EE0H",
+        "XN--YFRO4I67O",
+        "XN--YGBI2AMMX",
+        "XN--ZFR164B",
+    };
+    // END JOSM PATCH
+
     private static final String[] COUNTRY_CODE_TLDS = new String[] {
         "ac",                 // Ascension Island
@@ -897,4 +999,5 @@
         Arrays.sort(COUNTRY_CODE_TLDS);
         Arrays.sort(GENERIC_TLDS);
+        Arrays.sort(IDN_TLDS);
         Arrays.sort(LOCAL_TLDS);
     }
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/InternetTags.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/InternetTags.java	(revision 7823)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/InternetTags.java	(revision 7824)
@@ -3,4 +3,7 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.net.IDN;
+import java.util.regex.Pattern;
 
 import org.openstreetmap.josm.command.ChangePropertyCommand;
@@ -23,11 +26,15 @@
 public class InternetTags extends Test {
 
-    protected static final int INVALID_URL = 3301;
-    protected static final int INVALID_EMAIL = 3302;
+    /** Error code for an invalid URL */
+    public static final int INVALID_URL = 3301;
+    /** Error code for an invalid e-mail */
+    public static final int INVALID_EMAIL = 3302;
+
+    private static final Pattern ASCII_PATTERN = Pattern.compile("^\\p{ASCII}+$");
 
     /**
      * List of keys subject to URL validation.
      */
-    public static String[] URL_KEYS = new String[] {
+    private static String[] URL_KEYS = new String[] {
         "url", "source:url",
         "website", "contact:website", "heritage:website", "source:website"
@@ -37,5 +44,5 @@
      * List of keys subject to email validation.
      */
-    public static String[] EMAIL_KEYS = new String[] {
+    private static String[] EMAIL_KEYS = new String[] {
         "email", "contact:email"
     };
@@ -48,18 +55,19 @@
     }
 
+    /**
+     * Potentially validates a given primitive key against a given validator.
+     * @param p The OSM primitive to test
+     * @param k The key to validate
+     * @param keys The list of keys to check. If {@code k} is not inside this collection, do nothing
+     * @param validator The validator to run if {@code k} is inside {@code keys}
+     * @param code The error code to set if the validation fails
+     * @return {@code true} if the validation fails. In this case, a new error has been created.
+     */
     private boolean doTest(OsmPrimitive p, String k, String[] keys, AbstractValidator validator, int code) {
         for (String i : keys) {
             if (i.equals(k)) {
-                if (!validator.isValid(p.get(k))) {
-                    TestError error;
-                    String msg = tr("''{0}'': {1}", k, validator.getErrorMessage());
-                    String fix = validator.getFix();
-                    if (fix != null) {
-                        error = new FixableTestError(this, Severity.WARNING, msg, code, p,
-                                new ChangePropertyCommand(p, k, fix));
-                    } else {
-                        error = new TestError(this, Severity.WARNING, msg, code, p);
-                    }
-                    return errors.add(error);
+                TestError error = validateTag(p, k, validator, code);
+                if (error != null) {
+                    errors.add(error);
                 }
                 break;
@@ -69,7 +77,73 @@
     }
 
+    /**
+     * Validates a given primitive tag against a given validator.
+     * @param p The OSM primitive to test
+     * @param k The key to validate
+     * @param validator The validator to run
+     * @param code The error code to set if the validation fails
+     * @return The error if the validation fails, {@code null} otherwise
+     * @since 7824
+     */
+    public TestError validateTag(OsmPrimitive p, String k, AbstractValidator validator, int code) {
+        TestError error = doValidateTag(p, k, null, validator, code);
+        if (error != null) {
+            // Workaround to https://issues.apache.org/jira/browse/VALIDATOR-290
+            // Apache Commons Validator 1.4.1-SNAPSHOT does not support yet IDN URLs
+            // To remove if it gets fixed on Apache side
+            String v = p.get(k);
+            if (!ASCII_PATTERN.matcher(v).matches()) {
+                try {
+                    String protocol = "";
+                    if (v.contains("://")) {
+                        protocol = v.substring(0, v.indexOf("://")+3);
+                    }
+                    String domain = !protocol.isEmpty() ? v.substring(protocol.length(), v.length()) : v;
+                    String ending = "";
+                    if (domain.contains("/")) {
+                        int idx = domain.indexOf("/");
+                        ending = domain.substring(idx, domain.length());
+                        domain = domain.substring(0, idx);
+                    }
+                    // Try to apply ToASCII algorithm
+                    error = doValidateTag(p, k, protocol+IDN.toASCII(domain)+ending, validator, code);
+                } catch (IllegalArgumentException e) {
+                    error.setMessage(error.getMessage() +
+                            tr(" URL cannot be converted to ASCII: {0}", e.getMessage()));
+                }
+            }
+        }
+        return error;
+    }
+
+    /**
+     * Validates a given primitive tag against a given validator.
+     * @param p The OSM primitive to test
+     * @param k The key to validate
+     * @param v The value to validate. May be {@code null} to use {@code p.get(k)}
+     * @param validator The validator to run
+     * @param code The error code to set if the validation fails
+     * @return The error if the validation fails, {@code null} otherwise
+     */
+    private TestError doValidateTag(OsmPrimitive p, String k, String v, AbstractValidator validator, int code) {
+        TestError error = null;
+        if (!validator.isValid(v != null ? v : p.get(k))) {
+            String msg = tr("''{0}'': {1}", k, validator.getErrorMessage());
+            String fix = validator.getFix();
+            if (fix != null) {
+                error = new FixableTestError(this, Severity.WARNING, msg, code, p,
+                        new ChangePropertyCommand(p, k, fix));
+            } else {
+                error = new TestError(this, Severity.WARNING, msg, code, p);
+            }
+        }
+        return error;
+    }
+
     private void test(OsmPrimitive p) {
         for (String k : p.keySet()) {
+            // Test key against URL validator
             if (!doTest(p, k, URL_KEYS, UrlValidator.getInstance(), INVALID_URL)) {
+                // Test key against e-mail validator only if the URL validator did not fail
                 doTest(p, k, EMAIL_KEYS, EmailValidator.getInstance(), INVALID_EMAIL);
             }
Index: trunk/test/unit/org/openstreetmap/josm/data/validation/tests/InternetTagsTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/validation/tests/InternetTagsTest.java	(revision 7824)
+++ trunk/test/unit/org/openstreetmap/josm/data/validation/tests/InternetTagsTest.java	(revision 7824)
@@ -0,0 +1,79 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.data.osm.OsmUtils;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.data.validation.routines.AbstractValidator;
+import org.openstreetmap.josm.data.validation.routines.EmailValidator;
+import org.openstreetmap.josm.data.validation.routines.UrlValidator;
+
+/**
+ * JUnit Test of "Internet Tags" validation test.
+ */
+public class InternetTagsTest {
+
+    private static InternetTags TEST;
+
+    /**
+     * Setup test by initializing JOSM preferences and projection.
+     */
+    @BeforeClass
+    public static void setUp() {
+        JOSMFixture.createUnitTestFixture().init();
+        TEST = new InternetTags();
+    }
+
+    /**
+     * Test of "Internet Tags" validation test.
+     */
+    @Test
+    public void test() {
+
+        // Valid URLs
+        testUrl("url", "http://josm.openstreetmap.de", true);                  // Simple HTTP
+        testUrl("url", "http://josm.openstreetmap.de/", true);                 // Simple HTTP + slash
+        testUrl("website", "https://www.openstreetmap.org", true);             // Simple HTTPS
+        testUrl("heritage:website", "http://www.unesco.org", true);            // Key with :
+        testUrl("website", "http://www.nu-lounge.today", true);                // see #10810: new TLD
+        testUrl("website", "http://xn--80akeqobjv1b0d3a.xn--p1ai", true);      // see #10862: IDN URL in ASCII form
+        testUrl("website", "http://xn--80akeqobjv1b0d3a.xn--p1ai/", true);     // see #10862: IDN URL in ASCII form + slash
+        testUrl("website", "http://золотаяцепь.рф", true);                     // see #10862: IDN URL in Unicode form
+        testUrl("website", "http://золотаяцепь.рф/", true);                    // see #10862: IDN URL in Unicode form + slash
+
+        // Invalid URLs
+        testUrl("url", "www.domain.com", false);                               // No protocol
+        testUrl("url", "something://www.domain.com", false);                   // invalid protocol
+        testUrl("url", "http://www.domain.invalidtld", false);                 // invalid TLD
+
+        // Valid E-mails
+        testEmail("email", "contact@www.domain.com", true);                    // Simple email
+        testEmail("contact:email", "john.doe@other-domain.org", true);         // Key with : + dash in domain
+
+        // Invalid E-mails
+        testEmail("email", "contact at www.domain.com", false);                // No @
+        testEmail("contact:email", "john.doe@other-domain.invalidtld", false); // invalid TLD
+    }
+
+    private static void testKey(String key, String value, boolean valid, AbstractValidator validator, int code) {
+        TestError error = TEST.validateTag(OsmUtils.createPrimitive("node "+key+"="+value+""), key, validator, code);
+        if (valid) {
+            assertNull(error != null ? error.getMessage() : null, error);
+        } else {
+            assertNotNull(error);
+        }
+    }
+
+    private static void testUrl(String key, String value, boolean valid) {
+        testKey(key, value, valid, UrlValidator.getInstance(), InternetTags.INVALID_URL);
+    }
+
+    private static void testEmail(String key, String value, boolean valid) {
+        testKey(key, value, valid, EmailValidator.getInstance(), InternetTags.INVALID_EMAIL);
+    }
+}
