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