module: allow '+' in package import paths (but not module paths)

For golang/go#44776.

Change-Id: I1bc3df2800a1765296c5164aa8bece495d8f9221
Reviewed-on: https://go-review.googlesource.com/c/mod/+/300149
Trust: Bryan C. Mills <bcmills@google.com>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
diff --git a/module/module.go b/module/module.go
index 272baee..0e03014 100644
--- a/module/module.go
+++ b/module/module.go
@@ -224,12 +224,16 @@
 		'a' <= r && r <= 'z'
 }
 
-// pathOK reports whether r can appear in an import path element.
+// modPathOK reports whether r can appear in a module path element.
 // Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
-// This matches what "go get" has historically recognized in import paths.
+//
+// This matches what "go get" has historically recognized in import paths,
+// and avoids confusing sequences like '%20' or '+' that would change meaning
+// if used in a URL.
+//
 // TODO(rsc): We would like to allow Unicode letters, but that requires additional
 // care in the safe encoding (see "escaped paths" above).
-func pathOK(r rune) bool {
+func modPathOK(r rune) bool {
 	if r < utf8.RuneSelf {
 		return r == '-' || r == '.' || r == '_' || r == '~' ||
 			'0' <= r && r <= '9' ||
@@ -239,6 +243,17 @@
 	return false
 }
 
+// modPathOK reports whether r can appear in a package import path element.
+//
+// Import paths are intermediate between module paths and file paths: we allow
+// disallow characters that would be confusing or ambiguous as arguments to
+// 'go get' (such as '@' and ' ' ), but allow certain characters that are
+// otherwise-unambiguous on the command line and historically used for some
+// binary names (such as '++' as a suffix for compiler binaries and wrappers).
+func importPathOK(r rune) bool {
+	return modPathOK(r) || r == '+'
+}
+
 // fileNameOK reports whether r can appear in a file name.
 // For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
 // If we expand the set of allowed characters here, we have to
@@ -394,12 +409,19 @@
 	if elem[len(elem)-1] == '.' {
 		return fmt.Errorf("trailing dot in path element")
 	}
-	charOK := pathOK
-	if kind == filePath {
-		charOK = fileNameOK
-	}
 	for _, r := range elem {
-		if !charOK(r) {
+		ok := false
+		switch kind {
+		case modulePath:
+			ok = modPathOK(r)
+		case importPath:
+			ok = importPathOK(r)
+		case filePath:
+			ok = fileNameOK(r)
+		default:
+			panic(fmt.Sprintf("internal error: invalid kind %v", kind))
+		}
+		if !ok {
 			return fmt.Errorf("invalid char %q", r)
 		}
 	}
diff --git a/module/module_test.go b/module/module_test.go
index 4fc462b..5e8193d 100644
--- a/module/module_test.go
+++ b/module/module_test.go
@@ -104,7 +104,7 @@
 	{"x.y(/z", false, false, true},
 	{"x.y)/z", false, false, true},
 	{"x.y*/z", false, false, false},
-	{"x.y+/z", false, false, true},
+	{"x.y+/z", false, true, true},
 	{"x.y,/z", false, false, true},
 	{"x.y-/z", true, true, true},
 	{"x.y./zt", false, false, false},
@@ -134,7 +134,7 @@
 	{"x.y/z(", false, false, true},
 	{"x.y/z)", false, false, true},
 	{"x.y/z*", false, false, false},
-	{"x.y/z+", false, false, true},
+	{"x.y/z++", false, true, true},
 	{"x.y/z,", false, false, true},
 	{"x.y/z-", true, true, true},
 	{"x.y/z.t", true, true, true},