frontend: don’t allow a trailing underscore in a snippet ID

Extend it until it hits a character that isn’t an underscore.

Change-Id: I7faebdbf9c6c2cb26e240518145b4aa8f6f18ef7
Reviewed-on: https://go-review.googlesource.com/85038
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/frontend/server_test.go b/frontend/server_test.go
index 220ff03..01891e3 100644
--- a/frontend/server_test.go
+++ b/frontend/server_test.go
@@ -126,3 +126,21 @@
 		}
 	}
 }
+
+func TestNoTrailingUnderscore(t *testing.T) {
+	const trailingUnderscoreSnip = `package main
+
+import "unsafe"
+
+type T struct{}
+
+func (T) m1()                         {}
+func (T) m2([unsafe.Sizeof(T.m1)]int) {}
+
+func main() {}
+`
+	snip := &snippet{[]byte(trailingUnderscoreSnip)}
+	if got, want := snip.ID(), "WCktUidLyc_3"; got != want {
+		t.Errorf("got %q; want %q", got, want)
+	}
+}
diff --git a/frontend/share.go b/frontend/share.go
index 1635a7d..3beea1d 100644
--- a/frontend/share.go
+++ b/frontend/share.go
@@ -33,7 +33,14 @@
 	sum := h.Sum(nil)
 	b := make([]byte, base64.URLEncoding.EncodedLen(len(sum)))
 	base64.URLEncoding.Encode(b, sum)
-	return string(b)[:11]
+	// Web sites don’t always linkify a trailing underscore, making it seem like
+	// the link is broken. If there is an underscore at the end of the substring,
+	// extend it until there is not.
+	hashLen := 11
+	for hashLen <= len(b) && b[hashLen-1] == '_' {
+		hashLen++
+	}
+	return string(b)[:hashLen]
 }
 
 func (s *server) handleShare(w http.ResponseWriter, r *http.Request) {