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.ArrayList; 008import java.util.List; 009import java.util.function.Supplier; 010 011import org.openstreetmap.josm.command.ChangePropertyCommand; 012import org.openstreetmap.josm.command.Command; 013import org.openstreetmap.josm.data.osm.Node; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.Way; 017import org.openstreetmap.josm.data.validation.Severity; 018import org.openstreetmap.josm.data.validation.Test; 019import org.openstreetmap.josm.data.validation.TestError; 020import org.openstreetmap.josm.data.validation.routines.AbstractValidator; 021import org.openstreetmap.josm.data.validation.routines.EmailValidator; 022import org.openstreetmap.josm.data.validation.routines.UrlValidator; 023 024/** 025 * Performs validation tests on internet-related tags (websites, e-mail addresses, etc.). 026 * @since 7489 027 */ 028public class InternetTags extends Test { 029 030 /** Error code for an invalid URL */ 031 public static final int INVALID_URL = 3301; 032 /** Error code for an invalid e-mail */ 033 public static final int INVALID_EMAIL = 3302; 034 035 /** 036 * List of keys subject to URL validation. 037 */ 038 private static final String[] URL_KEYS = { 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 final String[] EMAIL_KEYS = { 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 return errors.addAll(validateTag(p, k, validator, code)); 070 } 071 } 072 return false; 073 } 074 075 /** 076 * Validates a given primitive tag against a given validator. 077 * @param p The OSM primitive to test 078 * @param k The key to validate 079 * @param validator The validator to run 080 * @param code The error code to set if the validation fails 081 * @return The error if the validation fails, {@code null} otherwise 082 * @since 7824 083 * @since 14803 (return type) 084 */ 085 public List<TestError> validateTag(OsmPrimitive p, String k, AbstractValidator validator, int code) { 086 return doValidateTag(p, k, null, validator, code); 087 } 088 089 /** 090 * Validates a given primitive tag against a given validator. 091 * @param p The OSM primitive to test 092 * @param k The key to validate 093 * @param v The value to validate. May be {@code null} to use {@code p.get(k)} 094 * @param validator The validator to run 095 * @param code The error code to set if the validation fails 096 * @return The error if the validation fails, {@code null} otherwise 097 */ 098 private List<TestError> doValidateTag(OsmPrimitive p, String k, String v, AbstractValidator validator, int code) { 099 List<TestError> errors = new ArrayList<>(); 100 String values = v != null ? v : p.get(k); 101 for (String value : values.split(";")) { 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 errors.add(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 } 122 return errors; 123 } 124 125 private void test(OsmPrimitive p) { 126 for (String k : p.keySet()) { 127 // Test key against URL validator 128 if (!doTest(p, k, URL_KEYS, UrlValidator.getInstance(), INVALID_URL)) { 129 // Test key against e-mail validator only if the URL validator did not fail 130 doTest(p, k, EMAIL_KEYS, EmailValidator.getInstance(), INVALID_EMAIL); 131 } 132 } 133 } 134 135 @Override 136 public void visit(Node n) { 137 test(n); 138 } 139 140 @Override 141 public void visit(Way w) { 142 test(w); 143 } 144 145 @Override 146 public void visit(Relation r) { 147 test(r); 148 } 149}