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.OsmPrimitive; 014import org.openstreetmap.josm.data.validation.Severity; 015import org.openstreetmap.josm.data.validation.Test; 016import org.openstreetmap.josm.data.validation.TestError; 017import org.openstreetmap.josm.data.validation.routines.AbstractValidator; 018import org.openstreetmap.josm.data.validation.routines.EmailValidator; 019import org.openstreetmap.josm.data.validation.routines.UrlValidator; 020 021/** 022 * Performs validation tests on internet-related tags (websites, e-mail addresses, etc.). 023 * @since 7489 024 */ 025public class InternetTags extends Test.TagTest { 026 027 /** Error code for an invalid URL */ 028 public static final int INVALID_URL = 3301; 029 /** Error code for an invalid e-mail */ 030 public static final int INVALID_EMAIL = 3302; 031 032 /** 033 * List of keys subject to URL validation. 034 */ 035 private static final String[] URL_KEYS = { 036 "url", "source:url", 037 "website", "contact:website", "heritage:website", "source:website" 038 }; 039 040 /** 041 * List of keys subject to email validation. 042 */ 043 private static final String[] EMAIL_KEYS = { 044 "email", "contact:email" 045 }; 046 047 /** 048 * Constructs a new {@code InternetTags} test. 049 */ 050 public InternetTags() { 051 super(tr("Internet tags"), tr("Checks for errors in internet-related tags.")); 052 } 053 054 /** 055 * Potentially validates a given primitive key against a given validator. 056 * @param p The OSM primitive to test 057 * @param k The key to validate 058 * @param keys The list of keys to check. If {@code k} is not inside this collection, do nothing 059 * @param validator The validator to run if {@code k} is inside {@code keys} 060 * @param code The error code to set if the validation fails 061 * @return {@code true} if the validation fails. In this case, a new error has been created. 062 */ 063 private boolean doTest(OsmPrimitive p, String k, String[] keys, AbstractValidator validator, int code) { 064 for (String i : keys) { 065 if (i.equals(k)) { 066 return errors.addAll(validateTag(p, k, validator, code)); 067 } 068 } 069 return false; 070 } 071 072 /** 073 * Validates a given primitive tag against a given validator. 074 * @param p The OSM primitive to test 075 * @param k The key to validate 076 * @param validator The validator to run 077 * @param code The error code to set if the validation fails 078 * @return The error if the validation fails, {@code null} otherwise 079 * @since 7824 080 * @since 14803 (return type) 081 */ 082 public List<TestError> validateTag(OsmPrimitive p, String k, AbstractValidator validator, int code) { 083 return doValidateTag(p, k, null, validator, code); 084 } 085 086 /** 087 * Validates a given primitive tag against a given validator. 088 * @param p The OSM primitive to test 089 * @param k The key to validate 090 * @param v The value to validate. May be {@code null} to use {@code p.get(k)} 091 * @param validator The validator to run 092 * @param code The error code to set if the validation fails 093 * @return The error if the validation fails, {@code null} otherwise 094 */ 095 private List<TestError> doValidateTag(OsmPrimitive p, String k, String v, AbstractValidator validator, int code) { 096 List<TestError> errors = new ArrayList<>(); 097 String values = v != null ? v : p.get(k); 098 for (String value : values.split(";", -1)) { 099 if (!validator.isValid(value)) { 100 Supplier<Command> fix = null; 101 String errMsg = validator.getErrorMessage(); 102 if (tr("URL contains an invalid protocol: {0}", (String) null).equals(errMsg)) { 103 // Special treatment to allow URLs without protocol. See UrlValidator#isValid 104 String proto = validator instanceof EmailValidator ? "mailto://" : "http://"; 105 return doValidateTag(p, k, proto+value, validator, code); 106 } else if (tr("URL contains an invalid authority: {0}", (String) null).equals(errMsg) 107 && value.contains("\\") && validator.isValid(value.replaceAll("\\\\", "/"))) { 108 // Special treatment to autofix URLs with backslashes. See UrlValidator#isValid 109 errMsg = tr("URL contains backslashes instead of slashes"); 110 fix = () -> new ChangePropertyCommand(p, k, value.replaceAll("\\\\", "/")); 111 } 112 errors.add(TestError.builder(this, Severity.WARNING, code) 113 .message(validator.getValidatorName(), marktr("''{0}'': {1}"), k, errMsg) 114 .primitives(p) 115 .fix(fix) 116 .build()); 117 } 118 } 119 return errors; 120 } 121 122 @Override 123 public void check(OsmPrimitive p) { 124 p.visitKeys((primitive, k, value) -> { 125 // Test key against URL validator 126 if (!doTest(p, k, URL_KEYS, UrlValidator.getInstance(), INVALID_URL)) { 127 // Test key against e-mail validator only if the URL validator did not fail 128 doTest(p, k, EMAIL_KEYS, EmailValidator.getInstance(), INVALID_EMAIL); 129 } 130 }); 131 } 132}