001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.util.function.Supplier; 008 009import org.openstreetmap.josm.command.ChangePropertyCommand; 010import org.openstreetmap.josm.command.Command; 011import org.openstreetmap.josm.data.osm.Node; 012import org.openstreetmap.josm.data.osm.OsmPrimitive; 013import org.openstreetmap.josm.data.osm.Relation; 014import org.openstreetmap.josm.data.osm.Way; 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 /** 034 * List of keys subject to URL validation. 035 */ 036 private static final 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 final 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 return doValidateTag(p, k, null, validator, code); 088 } 089 090 /** 091 * Validates a given primitive tag against a given validator. 092 * @param p The OSM primitive to test 093 * @param k The key to validate 094 * @param v The value to validate. May be {@code null} to use {@code p.get(k)} 095 * @param validator The validator to run 096 * @param code The error code to set if the validation fails 097 * @return The error if the validation fails, {@code null} otherwise 098 */ 099 private TestError doValidateTag(OsmPrimitive p, String k, String v, AbstractValidator validator, int code) { 100 TestError error = null; 101 String value = v != null ? v : p.get(k); 102 if (!validator.isValid(value)) { 103 Supplier<Command> fix = null; 104 String errMsg = validator.getErrorMessage(); 105 if (tr("URL contains an invalid protocol: {0}", (String) null).equals(errMsg)) { 106 // Special treatment to allow URLs without protocol. See UrlValidator#isValid 107 String proto = validator instanceof EmailValidator ? "mailto://" : "http://"; 108 return doValidateTag(p, k, proto+value, validator, code); 109 } else if (tr("URL contains an invalid authority: {0}", (String) null).equals(errMsg) 110 && value.contains("\\") && validator.isValid(value.replaceAll("\\\\", "/"))) { 111 // Special treatment to autofix URLs with backslashes. See UrlValidator#isValid 112 errMsg = tr("URL contains backslashes instead of slashes"); 113 fix = () -> new ChangePropertyCommand(p, k, value.replaceAll("\\\\", "/")); 114 } 115 error = TestError.builder(this, Severity.WARNING, code) 116 .message(validator.getValidatorName(), marktr("''{0}'': {1}"), k, errMsg) 117 .primitives(p) 118 .fix(fix) 119 .build(); 120 } 121 return error; 122 } 123 124 private void test(OsmPrimitive p) { 125 for (String k : p.keySet()) { 126 // Test key against URL validator 127 if (!doTest(p, k, URL_KEYS, UrlValidator.getInstance(), INVALID_URL)) { 128 // Test key against e-mail validator only if the URL validator did not fail 129 doTest(p, k, EMAIL_KEYS, EmailValidator.getInstance(), INVALID_EMAIL); 130 } 131 } 132 } 133 134 @Override 135 public void visit(Node n) { 136 test(n); 137 } 138 139 @Override 140 public void visit(Way w) { 141 test(w); 142 } 143 144 @Override 145 public void visit(Relation r) { 146 test(r); 147 } 148}