001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.net.IDN; 007import java.util.regex.Pattern; 008 009import org.openstreetmap.josm.command.ChangePropertyCommand; 010import org.openstreetmap.josm.data.osm.Node; 011import org.openstreetmap.josm.data.osm.OsmPrimitive; 012import org.openstreetmap.josm.data.osm.Relation; 013import org.openstreetmap.josm.data.osm.Way; 014import org.openstreetmap.josm.data.validation.FixableTestError; 015import org.openstreetmap.josm.data.validation.Severity; 016import org.openstreetmap.josm.data.validation.Test; 017import org.openstreetmap.josm.data.validation.TestError; 018import org.openstreetmap.josm.data.validation.routines.AbstractValidator; 019import org.openstreetmap.josm.data.validation.routines.EmailValidator; 020import org.openstreetmap.josm.data.validation.routines.UrlValidator; 021 022/** 023 * Performs validation tests on internet-related tags (websites, e-mail addresses, etc.). 024 * @since 7489 025 */ 026public class InternetTags extends Test { 027 028 /** Error code for an invalid URL */ 029 public static final int INVALID_URL = 3301; 030 /** Error code for an invalid e-mail */ 031 public static final int INVALID_EMAIL = 3302; 032 033 private static final Pattern ASCII_PATTERN = Pattern.compile("^\\p{ASCII}+$"); 034 035 /** 036 * List of keys subject to URL validation. 037 */ 038 private static String[] URL_KEYS = new String[] { 039 "url", "source:url", 040 "website", "contact:website", "heritage:website", "source:website" 041 }; 042 043 /** 044 * List of keys subject to email validation. 045 */ 046 private static String[] EMAIL_KEYS = new String[] { 047 "email", "contact:email" 048 }; 049 050 /** 051 * Constructs a new {@code InternetTags} test. 052 */ 053 public InternetTags() { 054 super(tr("Internet tags"), tr("Checks for errors in internet-related tags.")); 055 } 056 057 /** 058 * Potentially validates a given primitive key against a given validator. 059 * @param p The OSM primitive to test 060 * @param k The key to validate 061 * @param keys The list of keys to check. If {@code k} is not inside this collection, do nothing 062 * @param validator The validator to run if {@code k} is inside {@code keys} 063 * @param code The error code to set if the validation fails 064 * @return {@code true} if the validation fails. In this case, a new error has been created. 065 */ 066 private boolean doTest(OsmPrimitive p, String k, String[] keys, AbstractValidator validator, int code) { 067 for (String i : keys) { 068 if (i.equals(k)) { 069 TestError error = validateTag(p, k, validator, code); 070 if (error != null) { 071 errors.add(error); 072 } 073 break; 074 } 075 } 076 return false; 077 } 078 079 /** 080 * Validates a given primitive tag against a given validator. 081 * @param p The OSM primitive to test 082 * @param k The key to validate 083 * @param validator The validator to run 084 * @param code The error code to set if the validation fails 085 * @return The error if the validation fails, {@code null} otherwise 086 * @since 7824 087 */ 088 public TestError validateTag(OsmPrimitive p, String k, AbstractValidator validator, int code) { 089 TestError error = doValidateTag(p, k, null, validator, code); 090 if (error != null) { 091 // Workaround to https://issues.apache.org/jira/browse/VALIDATOR-290 092 // Apache Commons Validator 1.4.1-SNAPSHOT does not support yet IDN URLs 093 // To remove if it gets fixed on Apache side 094 String v = p.get(k); 095 if (!ASCII_PATTERN.matcher(v).matches()) { 096 try { 097 String protocol = ""; 098 if (v.contains("://")) { 099 protocol = v.substring(0, v.indexOf("://")+3); 100 } 101 String domain = !protocol.isEmpty() ? v.substring(protocol.length(), v.length()) : v; 102 String ending = ""; 103 if (domain.contains("/")) { 104 int idx = domain.indexOf("/"); 105 ending = domain.substring(idx, domain.length()); 106 domain = domain.substring(0, idx); 107 } 108 // Try to apply ToASCII algorithm 109 error = doValidateTag(p, k, protocol+IDN.toASCII(domain)+ending, validator, code); 110 } catch (IllegalArgumentException e) { 111 error.setMessage(error.getMessage() + 112 tr(" URL cannot be converted to ASCII: {0}", e.getMessage())); 113 } 114 } 115 } 116 return error; 117 } 118 119 /** 120 * Validates a given primitive tag against a given validator. 121 * @param p The OSM primitive to test 122 * @param k The key to validate 123 * @param v The value to validate. May be {@code null} to use {@code p.get(k)} 124 * @param validator The validator to run 125 * @param code The error code to set if the validation fails 126 * @return The error if the validation fails, {@code null} otherwise 127 */ 128 private TestError doValidateTag(OsmPrimitive p, String k, String v, AbstractValidator validator, int code) { 129 TestError error = null; 130 String value = v != null ? v : p.get(k); 131 if (!validator.isValid(value)) { 132 String errMsg = validator.getErrorMessage(); 133 // Special treatment to allow URLs without protocol. See UrlValidator#isValid 134 if (tr("URL contains an invalid protocol: {0}", (String)null).equals(errMsg)) { 135 String proto = validator instanceof EmailValidator ? "mailto://" : "http://"; 136 return doValidateTag(p, k, proto+value, validator, code); 137 } 138 String msg = tr("''{0}'': {1}", k, errMsg); 139 String fix = validator.getFix(); 140 if (fix != null) { 141 error = new FixableTestError(this, Severity.WARNING, msg, code, p, 142 new ChangePropertyCommand(p, k, fix)); 143 } else { 144 error = new TestError(this, Severity.WARNING, msg, code, p); 145 } 146 } 147 return error; 148 } 149 150 private void test(OsmPrimitive p) { 151 for (String k : p.keySet()) { 152 // Test key against URL validator 153 if (!doTest(p, k, URL_KEYS, UrlValidator.getInstance(), INVALID_URL)) { 154 // Test key against e-mail validator only if the URL validator did not fail 155 doTest(p, k, EMAIL_KEYS, EmailValidator.getInstance(), INVALID_EMAIL); 156 } 157 } 158 } 159 160 @Override 161 public void visit(Node n) { 162 test(n); 163 } 164 165 @Override 166 public void visit(Way w) { 167 test(w); 168 } 169 170 @Override 171 public void visit(Relation r) { 172 test(r); 173 } 174}