CVE-2021-29061 (published 2021-06-21T20:15Z)

A Regular Expression Denial of Service (ReDOS) vulnerability was discovered in Vfsjfilechooser2 version 0.2.9 and below which occurs when the application attempts to validate crafted URIs.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-770
cwe_name: Allocation of Resources Without Limits or Throttling
description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
extended_description: Code frequently has to work with limited resources, so programmers must be careful to ensure that resources are not consumed too quickly, or too easily.  Without use of quotas, resource limits, or other protection mechanisms, it can be easy for an attacker to consume many resources by rapidly making many requests, or causing larger resources to be used than is needed. When too many resources are allocated, or if a single resource is too large, then it can prevent the code from working correctly, possibly leading to a denial of service.
         

Fix information:
repo_url: https://github.com/fracpete/vfsjfilechooser2
commit_details: repo_url: https://github.com/fracpete/vfsjfilechooser2
author: Peter Reutemann
author_date: 2020-10-07 10:18:37+13:00
msg: incorporated Yeting Li's fix for Potential Regex Denial of Service (ReDoS), see https://github.com/fracpete/vfsjfilechooser2/issues/7
passwords can now also contain special characters (eg :), which have to be URL encoded (ie %3A)
repository: repo_url: https://github.com/fracpete/vfsjfilechooser2
repo_name: fracpete/vfsjfilechooser2
description: Updated fork of vfsjfilechooser project on sf.net.
repo_language: Java
owner: fracpete
file_changes: filename: VFSURIValidator.java
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,8 @@
  * VFS URIs validator
  *
  * Copyright (C) 2008 Stan Love
+ * Copyright (C) 2020 University of Waikato, Hamilton, NZ
+ * Copyright (C) 2020 Yeting Li
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -233,11 +235,11 @@ public class VFSURIValidator {
 		//        "(ftp|FTP|sftp|SFTP|http|HTTP|https|HTTPS|webdav|WEBDAV|smb|SMB)://(.*?:.*?@)*([^:]+)([ 	]*:[0-9]+)*([ 	]*:)*(/.*)");
 		//"(ftp|FTP|sftp|SFTP|http|HTTP|https|HTTPS|webdav|WEBDAV|smb|SMB)://(.+:.+@)*([^:]+)([ 	]*:[0-9]+)*([ 	]*:)*(/.*)");
 		Pattern p_ftp2 = Pattern
-				.compile("(ftp|FTP|sftp|SFTP|http|HTTP|https|HTTPS|webdav|WEBDAV|smb|SMB)://(.+:.+@)*([^:]+?/*)([ 	]*:[0-9]+)*([ 	]*:)*(/.*)");
+				.compile("(ftp|FTP|sftp|SFTP|http|HTTP|https|HTTPS|webdav|WEBDAV|smb|SMB)://([^:@]+:[^:@]+@)*([^:]+?/*)([ ]*:[0-9]+)*([ ]*:)*(/.*)");
 		Matcher m_ftp2 = p_ftp2.matcher(_uri);
 
 		Pattern p_ftp3 = Pattern
-				.compile("(ftp|FTP|sftp|SFTP|http|HTTP|https|HTTPS|webdav|WEBDAV|smb|SMB)://(.+:.+@)*([^:]+)([ 	]*:[0-9]+)*([ 	]*:)*(/*?.*)");
+				.compile("(ftp|FTP|sftp|SFTP|http|HTTP|https|HTTPS|webdav|WEBDAV|smb|SMB)://([^:@]+:[^:@]+@)*([^:]+)([ 	]*:[0-9]+)*([ 	]*:)*(/*?.*)");
 		Matcher m_ftp3 = p_ftp3.matcher(_uri);
 
 		if (m_ftp2.matches()) {
@@ -344,6 +346,26 @@ public class VFSURIValidator {
 			if (local_pass.startsWith(":")) {
 				local_pass = local_pass.substring(1);
 			}
+			// decode specials chars (URL encoded %XY)
+			if (local_pass.contains("%")) {
+				String tmp_local_pass = local_pass;
+				StringBuilder new_local_pass = new StringBuilder();
+				while (tmp_local_pass.contains("%")) {
+					new_local_pass.append(tmp_local_pass.substring(0, tmp_local_pass.indexOf('%')));
+					tmp_local_pass = tmp_local_pass.substring(tmp_local_pass.indexOf('%'));
+					if (tmp_local_pass.length() >= 3) {
+						char c = (char) Integer.parseInt(tmp_local_pass.substring(1, 3), 16);
+						new_local_pass.append(c);
+						tmp_local_pass = tmp_local_pass.substring(3);
+					}
+					else {
+						break;
+					}
+				}
+				if (!tmp_local_pass.isEmpty())
+					new_local_pass.append(tmp_local_pass);
+				local_pass = new_local_pass.toString();
+			}
 		}
 		local_hostname = hostname;
 		local_port = port;
@@ -823,26 +845,26 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "ftp://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "ftp://user:pass%3Aa@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
 		}
 		v.assertEquals(v.getProtocol(), "ftp");
 		v.assertEquals(v.getUser(), "user");
-		v.assertEquals(v.getPassword(), "pass:");
+		v.assertEquals(v.getPassword(), "pass:a");
 		v.assertEquals(v.getHostname(), "machine");
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "ftp://user:pass:@machine/the_dir/";
+		s = "ftp://user:pass%3A%3a@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
 		}
 		v.assertEquals(v.getProtocol(), "ftp");
 		v.assertEquals(v.getUser(), "user");
-		v.assertEquals(v.getPassword(), "pass:");
+		v.assertEquals(v.getPassword(), "pass::");
 		v.assertEquals(v.getHostname(), "machine");
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
@@ -992,7 +1014,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "FTP://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "FTP://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1004,7 +1026,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "FTP://user:pass:@machine/the_dir/";
+		s = "FTP://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1161,7 +1183,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "sftp://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "sftp://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1173,7 +1195,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "sftp://user:pass:@machine/the_dir/";
+		s = "sftp://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1185,7 +1207,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "sftp: //user:pass:@machine/the_file"; //failure tests
+		s = "sftp: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1197,7 +1219,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "sftp:/ /user:pass:@machine/the_file";
+		s = "sftp:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1209,7 +1231,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "sftp:/ /user:pass:@machine";
+		s = "sftp:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1221,7 +1243,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "sftp://user:pass:@:123/a";
+		s = "sftp://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1233,7 +1255,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "sftp://user:pass:@machine:a/the_file";
+		s = "sftp://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1329,7 +1351,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "SFTP://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "SFTP://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1341,7 +1363,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "SFTP://user:pass:@machine/the_dir/";
+		s = "SFTP://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1498,7 +1520,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "http://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "http://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1510,7 +1532,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "http://user:pass:@machine/the_dir/";
+		s = "http://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1522,7 +1544,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "http: //user:pass:@machine/the_file"; //failure tests
+		s = "http: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1534,7 +1556,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "http:/ /user:pass:@machine/the_file";
+		s = "http:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1546,7 +1568,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "http:/ /user:pass:@machine";
+		s = "http:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1558,7 +1580,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "http://user:pass:@:123/a";
+		s = "http://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1570,7 +1592,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "http://user:pass:@machine:a/the_file";
+		s = "http://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1666,7 +1688,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTP://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "HTTP://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1678,7 +1700,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "HTTP://user:pass:@machine/the_dir/";
+		s = "HTTP://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1690,7 +1712,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "HTTP: //user:pass:@machine/the_file"; //failure tests
+		s = "HTTP: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1702,7 +1724,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTP:/ /user:pass:@machine/the_file";
+		s = "HTTP:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1714,7 +1736,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTP:/ /user:pass:@machine";
+		s = "HTTP:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1726,7 +1748,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTP://user:pass:@:123/a";
+		s = "HTTP://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1738,7 +1760,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTP://user:pass:@machine:a/the_file";
+		s = "HTTP://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1835,7 +1857,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "https://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "https://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1847,7 +1869,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "https://user:pass:@machine/the_dir/";
+		s = "https://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -1859,7 +1881,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "https: //user:pass:@machine/the_file"; //failure tests
+		s = "https: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1871,7 +1893,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "https:/ /user:pass:@machine/the_file";
+		s = "https:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1883,7 +1905,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "https:/ /user:pass:@machine";
+		s = "https:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1895,7 +1917,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "https://user:pass:@:123/a";
+		s = "https://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -1907,7 +1929,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "https://user:pass:@machine:a/the_file";
+		s = "https://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2003,7 +2025,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTPS://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "HTTPS://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2015,7 +2037,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "HTTPS://user:pass:@machine/the_dir/";
+		s = "HTTPS://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2027,7 +2049,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "HTTPS: //user:pass:@machine/the_file"; //failure tests
+		s = "HTTPS: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2039,7 +2061,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTPS:/ /user:pass:@machine/the_file";
+		s = "HTTPS:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2051,7 +2073,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTPS:/ /user:pass:@machine";
+		s = "HTTPS:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2063,7 +2085,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTPS://user:pass:@:123/a";
+		s = "HTTPS://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2075,7 +2097,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "HTTPS://user:pass:@machine:a/the_file";
+		s = "HTTPS://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2172,7 +2194,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "webdav://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "webdav://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2184,13 +2206,13 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "webdav://user:pass:@machine/the_dir/";
+		s = "webdav://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
 		}
 
-		s = "webdav: //user:pass:@machine/the_file"; //failure tests
+		s = "webdav: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2202,7 +2224,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "webdav:/ /user:pass:@machine/the_file";
+		s = "webdav:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2214,7 +2236,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "webdav:/ /user:pass:@machine";
+		s = "webdav:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2226,7 +2248,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "webdav://user:pass:@:123/a";
+		s = "webdav://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2238,7 +2260,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "webdav://user:pass:@machine:a/the_file";
+		s = "webdav://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2334,7 +2356,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "WEBDAV://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "WEBDAV://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2346,7 +2368,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "WEBDAV://user:pass:@machine/the_dir/";
+		s = "WEBDAV://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2358,7 +2380,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "WEBDAV: //user:pass:@machine/the_file"; //failure tests
+		s = "WEBDAV: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2370,7 +2392,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "WEBDAV:/ /user:pass:@machine/the_file";
+		s = "WEBDAV:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2382,7 +2404,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "WEBDAV:/ /user:pass:@machine";
+		s = "WEBDAV:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2394,7 +2416,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "WEBDAV://user:pass:@:123/a";
+		s = "WEBDAV://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2406,7 +2428,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "WEBDAV://user:pass:@machine:a/the_file";
+		s = "WEBDAV://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2503,7 +2525,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "smb://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "smb://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2515,7 +2537,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "smb://user:pass:@machine/the_dir/";
+		s = "smb://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2527,7 +2549,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "smb: //user:pass:@machine/the_file"; //failure tests
+		s = "smb: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2539,7 +2561,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "smb:/ /user:pass:@machine/the_file";
+		s = "smb:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2551,7 +2573,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "smb:/ /user:pass:@machine";
+		s = "smb:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2563,7 +2585,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "smb://user:pass:@:123/a";
+		s = "smb://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2575,7 +2597,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "smb://user:pass:@machine:a/the_file";
+		s = "smb://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2671,7 +2693,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "SMB://user:pass:@machine/the_file"; //can ":" be part of a password?
+		s = "SMB://user:pass%3A@machine/the_file"; //if ":" is part of a password, it must be encoded (: -> %3A)
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2683,7 +2705,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_file");
 
-		s = "SMB://user:pass:@machine/the_dir/";
+		s = "SMB://user:pass%3A@machine/the_dir/";
 
 		if (!v.isValid(s)) {
 			v.error_msg(s);
@@ -2695,7 +2717,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertEquals(v.getFile(), "/the_dir/");
 
-		s = "SMB: //user:pass:@machine/the_file"; //failure tests
+		s = "SMB: //user:pass%3A@machine/the_file"; //failure tests
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2707,7 +2729,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "SMB:/ /user:pass:@machine/the_file";
+		s = "SMB:/ /user:pass%3A@machine/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2719,7 +2741,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "SMB:/ /user:pass:@machine";
+		s = "SMB:/ /user:pass%3A@machine";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2731,7 +2753,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "SMB://user:pass:@:123/a";
+		s = "SMB://user:pass%3A@:123/a";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);
@@ -2743,7 +2765,7 @@ public class VFSURIValidator {
 		v.assertNull(v.getPort());
 		v.assertNull(v.getFile());
 
-		s = "SMB://user:pass:@machine:a/the_file";
+		s = "SMB://user:pass%3A@machine:a/the_file";
 
 		if (v.isValid(s)) {
 			v.error_msg(s);

programming_language: Java