Merge "websocket: avoid panic from invalid websocket requests"
diff --git a/lib/glob/glob.go b/lib/glob/glob.go
index 6f0ac86..d7fa168 100644
--- a/lib/glob/glob.go
+++ b/lib/glob/glob.go
@@ -22,24 +22,174 @@
 package glob
 
 import (
-	"path/filepath"
+	"path"
 	"strings"
 )
 
-// Glob represents a slash separated path glob expression.
+// Glob represents a slash separated path glob pattern.
 type Glob struct {
-	elems      []string
+	elems      []*Element
 	recursive  bool
 	restricted bool
 }
 
-func parseElem(pattern string) error {
-	if len(pattern) == 0 {
-		return filepath.ErrBadPattern
+// Parse returns a new Glob.
+func Parse(pattern string) (*Glob, error) {
+	if len(pattern) > 0 && pattern[0] == '/' {
+		return nil, path.ErrBadPattern
+	}
+
+	g := &Glob{}
+	if pattern == "" {
+		return g, nil
+	}
+
+	elems := strings.Split(pattern, "/")
+	if last := len(elems) - 1; last >= 0 {
+		if elems[last] == "..." {
+			elems = elems[:last]
+			g.recursive = true
+		} else if elems[last] == "***" {
+			elems = elems[:last]
+			g.recursive = true
+			g.restricted = true
+		}
+	}
+	g.elems = make([]*Element, len(elems))
+	for i, elem := range elems {
+		g.elems[i] = &Element{pattern: elem}
+		if err := g.elems[i].validate(); err != nil {
+			return nil, err
+		}
+	}
+
+	return g, nil
+}
+
+// Len returns the number of path elements represented by the glob expression.
+func (g *Glob) Len() int {
+	return len(g.elems)
+}
+
+// Empty returns true if the pattern cannot match anything.
+func (g *Glob) Empty() bool {
+	return !g.recursive && len(g.elems) == 0
+}
+
+// Recursive returns true if the pattern is recursive.
+func (g *Glob) Recursive() bool {
+	return g.recursive
+}
+
+// Restricted returns true if recursion is restricted (up to the caller to
+// know what that means).
+func (g *Glob) Restricted() bool {
+	return g.restricted
+}
+
+// Tail returns the suffix of g starting at the second element.
+func (g *Glob) Tail() *Glob {
+	if len(g.elems) <= 1 {
+		return &Glob{elems: nil, recursive: g.recursive, restricted: g.restricted}
+	}
+	return &Glob{elems: g.elems[1:], recursive: g.recursive, restricted: g.restricted}
+}
+
+// Head returns an Element for the first element of the glob pattern.
+func (g *Glob) Head() *Element {
+	if len(g.elems) == 0 {
+		if g.recursive {
+			return &Element{alwaysMatch: true}
+		}
+		return &Element{neverMatch: true}
+	}
+	return g.elems[0]
+}
+
+// SplitFixedElements returns the part of the glob pattern that contains only
+// fixed elements, and the glob that follows it.
+func (g *Glob) SplitFixedElements() ([]string, *Glob) {
+	var prefix []string
+	tail := g
+	for _, elem := range g.elems {
+		if pfx, fixed := elem.FixedPrefix(); fixed {
+			prefix = append(prefix, pfx)
+			tail = tail.Tail()
+		} else {
+			break
+		}
+	}
+	return prefix, tail
+}
+
+// String returns the string representation of the glob pattern.
+func (g *Glob) String() string {
+	elems := make([]string, len(g.elems))
+	for i, e := range g.elems {
+		elems[i] = e.pattern
+	}
+	if g.recursive {
+		if g.restricted {
+			elems = append(elems, "***")
+		} else {
+			elems = append(elems, "...")
+		}
+	}
+	return path.Join(elems...)
+}
+
+// Element represents a single element of a glob pattern.
+type Element struct {
+	pattern     string
+	alwaysMatch bool
+	neverMatch  bool
+}
+
+// Match returns true iff this pattern element matches the given segment.
+func (m *Element) Match(segment string) bool {
+	if m.neverMatch {
+		return false
+	}
+	if m.alwaysMatch {
+		return true
+	}
+	matches, err := path.Match(m.pattern, segment)
+	return err == nil && matches
+}
+
+// FixedPrefix returns the unescaped fixed part of the pattern, and whether the
+// prefix is the whole pattern. The fixed part does not contain any wildcards.
+func (m *Element) FixedPrefix() (string, bool) {
+	if m.neverMatch {
+		return "", true
+	}
+	if m.alwaysMatch {
+		return "", false
+	}
+	unescaped := ""
+	escape := false
+	for _, c := range m.pattern {
+		if escape {
+			escape = false
+			unescaped += string(c)
+		} else if strings.ContainsRune("*?[", c) {
+			return unescaped, false
+		} else if c == '\\' {
+			escape = true
+		} else {
+			unescaped += string(c)
+		}
+	}
+	return unescaped, true
+}
+
+func (m *Element) validate() error {
+	if len(m.pattern) == 0 {
+		return path.ErrBadPattern
 	}
 	escape := false
 	inrange := false
-	for _, c := range pattern {
+	for _, c := range m.pattern {
 		if escape {
 			escape = false
 			continue
@@ -55,68 +205,22 @@
 	}
 	// If we are in the middle of an escape or character range, the expression is incomplete.
 	if escape || inrange {
-		return filepath.ErrBadPattern
+		return path.ErrBadPattern
 	}
 	return nil
 }
 
-// Parse returns a new Glob.
-func Parse(pattern string) (*Glob, error) {
-	if len(pattern) > 0 && pattern[0] == '/' {
-		return nil, filepath.ErrBadPattern
-	}
-
-	g := &Glob{}
-	if pattern != "" {
-		g.elems = strings.Split(pattern, "/")
-	}
-	if last := len(g.elems) - 1; last >= 0 {
-		if g.elems[last] == "..." {
-			g.elems = g.elems[:last]
-			g.recursive = true
-		} else if g.elems[last] == "***" {
-			g.elems = g.elems[:last]
-			g.recursive = true
-			g.restricted = true
-		}
-	}
-
-	// The only error we can get from the filepath library is badpattern.
-	// A future implementation would most likely recognize that here, so for now
-	// I'll just check every part to make sure it's error free.
-	// Note: Match never returns an error when matching against an empty string.
-	for _, elem := range g.elems {
-		if err := parseElem(elem); err != nil {
-			return nil, err
-		}
-	}
-
-	return g, nil
-}
-
-// Len returns the number of path elements represented by the glob expression.
-func (g *Glob) Len() int {
-	return len(g.elems)
-}
+// DEPRECATED CODE
 
 // Finished returns true if the pattern cannot match anything.
+// This method is DEPRECATED.
 func (g *Glob) Finished() bool {
-	return !g.recursive && len(g.elems) == 0
-}
-
-// Recursive returns true if the pattern is recursive.
-func (g *Glob) Recursive() bool {
-	return g.recursive
-}
-
-// Restricted returns true if recursion is restricted (up to the caller to
-// know what that means).
-func (g *Glob) Restricted() bool {
-	return g.restricted
+	return g.Empty()
 }
 
 // Split returns the suffix of g starting at the path element corresponding to
 // start.
+// This method is DEPRECATED.
 func (g *Glob) Split(start int) *Glob {
 	if start >= len(g.elems) {
 		return &Glob{elems: nil, recursive: g.recursive, restricted: g.restricted}
@@ -129,22 +233,13 @@
 // matched, a boolean indicating whether the match was successful;
 // exact, a boolean indicating whether segment matched a fixed string pattern;
 // remainder, a Glob representing the unmatched remainder of g.
+// This method is DEPRECATED.
 func (g *Glob) MatchInitialSegment(segment string) (matched bool, exact bool, remainder *Glob) {
-	if len(g.elems) == 0 {
-		if !g.recursive {
-			return false, false, nil
-		}
-		// The segment matches "...". This is not an exact match.
-		return true, false, g
-	}
-
-	if matches, err := filepath.Match(g.elems[0], segment); err != nil {
-		return false, false, nil
-	} else if matches {
-		_, fixed := isFixed(g.elems[0])
-		return true, fixed, g.Split(1)
-	}
-	return false, false, nil
+	m := g.Head()
+	matched = m.Match(segment)
+	_, exact = m.FixedPrefix()
+	remainder = g.Tail()
+	return
 }
 
 // PartialMatch tries matching elems against part of a glob pattern.
@@ -158,6 +253,7 @@
 //
 // Note that if the glob is recursive elems can have more elements then
 // the glob pattern and still get a true result.
+// This method is DEPRECATED.
 func (g *Glob) PartialMatch(start int, elems []string) (matched bool, exact bool, remainder *Glob) {
 	g = g.Split(start)
 	allExact := true
@@ -172,57 +268,9 @@
 	return true, allExact, g
 }
 
-// isFixed returns the unescaped string and true if 's' is a pattern specifying
-// a fixed string.  Otherwise it returns the original string and false.
-func isFixed(s string) (string, bool) {
-	// No special characters.
-	if !strings.ContainsAny(s, "*?[") {
-		return s, true
-	}
-	// Special characters and no backslash.
-	if !strings.ContainsAny(s, "\\") {
-		return "", false
-	}
-	unescaped := ""
-	escape := false
-	for _, c := range s {
-		if escape {
-			escape = false
-			unescaped += string(c)
-		} else if strings.ContainsRune("*?[", c) {
-			// S contains an unescaped special character.
-			return s, false
-		} else if c == '\\' {
-			escape = true
-		} else {
-			unescaped += string(c)
-		}
-	}
-	return unescaped, true
-}
-
+// SplitFixedPrefix returns the part of the glob pattern that contains only
+// fixed elements, and the glob that follows it.
+// This method is DEPRECATED.
 func (g *Glob) SplitFixedPrefix() ([]string, *Glob) {
-	var prefix []string
-	start := 0
-	for _, elem := range g.elems {
-		if u, q := isFixed(elem); q {
-			prefix = append(prefix, u)
-			start++
-		} else {
-			break
-		}
-	}
-	return prefix, g.Split(start)
-}
-
-func (g *Glob) String() string {
-	e := g.elems
-	if g.recursive {
-		if g.restricted {
-			e = append(e, "***")
-		} else {
-			e = append(e, "...")
-		}
-	}
-	return filepath.Join(e...)
+	return g.SplitFixedElements()
 }
diff --git a/lib/glob/glob_test.go b/lib/glob/glob_test.go
index 2b8aea3..e75be4f 100644
--- a/lib/glob/glob_test.go
+++ b/lib/glob/glob_test.go
@@ -20,7 +20,7 @@
 	return true
 }
 
-func TestStripFixedPrefix(t *testing.T) {
+func TestStripFixedElements(t *testing.T) {
 	tests := []struct {
 		pattern string
 		fixed   []string
@@ -37,8 +37,107 @@
 		if err != nil {
 			t.Fatalf("parsing %q: %q", test.pattern, err.Error())
 		}
-		if f, ng := g.SplitFixedPrefix(); !same(f, test.fixed) || test.rest != ng.String() {
-			t.Fatalf("SplitFixedPrefix(%q) got %q,%q, expected %q,%q", test.pattern, f, ng.String(), test.fixed, test.rest)
+		if f, ng := g.SplitFixedElements(); !same(f, test.fixed) || test.rest != ng.String() {
+			t.Fatalf("SplitFixedElements(%q) got %q,%q, expected %q,%q", test.pattern, f, ng.String(), test.fixed, test.rest)
+		}
+	}
+}
+
+func TestMatch(t *testing.T) {
+	tests := []struct {
+		pattern string
+		name    string
+		matched bool
+	}{
+		{"...", "", true},
+		{"***", "", true},
+		{"...", "a", true},
+		{"***", "a", true},
+		{"a", "", false},
+		{"a", "a", true},
+		{"a", "b", false},
+		{"a*", "a", true},
+		{"a*", "b", false},
+		{"a*b", "ab", true},
+		{"a*b", "afoob", true},
+		{"a\\*", "a", false},
+		{"a\\*", "a*", true},
+		{"\\\\", "\\", true},
+		{"?", "?", true},
+		{"?", "a", true},
+		{"?", "", false},
+		{"*?", "", false},
+		{"*?", "a", true},
+		{"*?", "ab", true},
+		{"*?", "abv", true},
+		{"[abc]", "a", true},
+		{"[abc]", "b", true},
+		{"[abc]", "c", true},
+		{"[abc]", "d", false},
+		{"[a-c]", "a", true},
+		{"[a-c]", "b", true},
+		{"[a-c]", "c", true},
+		{"[a-c]", "d", false},
+		{"\\[abc]", "a", false},
+		{"\\[abc]", "[abc]", true},
+		{"a/*", "a", true},
+		{"a/*", "b", false},
+		{"a/...", "a", true},
+		{"a/...", "b", false},
+	}
+	for i, test := range tests {
+		g, err := Parse(test.pattern)
+		if err != nil {
+			t.Errorf("unexpected parsing error for %q (#%d): %v", test.pattern, i, err)
+			continue
+		}
+		if matched := g.Head().Match(test.name); matched != test.matched {
+			t.Errorf("unexpected result for %q.Match(%q) (#%d). Got %v, expected %v", test.pattern, test.name, i, matched, test.matched)
+		}
+	}
+}
+
+func TestFixedPrefix(t *testing.T) {
+	tests := []struct {
+		pattern string
+		prefix  string
+		full    bool
+	}{
+		{"", "", true},
+		{"...", "", false},
+		{"***", "", false},
+		{"...", "", false},
+		{"***", "", false},
+		{"a", "a", true},
+		{"*a", "", false},
+		{"a*", "a", false},
+		{"a*b", "a", false},
+		{"a\\*", "a*", true},
+		{"\\\\", "\\", true},
+		{"?", "", false},
+		{"\\?", "?", true},
+		{"*?", "", false},
+		{"[abc]", "", false},
+		{"\\[abc]", "[abc]", true},
+		{"\\[abc]*", "[abc]", false},
+	}
+	for i, test := range tests {
+		g, err := Parse(test.pattern)
+		if err != nil {
+			t.Errorf("unexpected parsing error for %q (#%d): %v", test.pattern, i, err)
+			continue
+		}
+		if prefix, full := g.Head().FixedPrefix(); prefix != test.prefix || full != test.full {
+			t.Errorf("unexpected result for %q.FixedPrefix() (#%d). Got (%q,%v), expected (%q,%v)", test.pattern, i, prefix, full, test.prefix, test.full)
+		}
+	}
+}
+
+func TestBadPattern(t *testing.T) {
+	tests := []string{"[", "[foo", "[^foo", "\\", "a\\", "abc[foo", "a//b"}
+	for _, test := range tests {
+		if _, err := Parse(test); err == nil {
+			t.Errorf("Unexpected success for %q", test)
 		}
 	}
 }
@@ -107,12 +206,3 @@
 		}
 	}
 }
-
-func TestBadPattern(t *testing.T) {
-	tests := []string{"[", "[foo", "[^foo", "\\", "a\\", "abc[foo", "a//b"}
-	for _, test := range tests {
-		if _, err := Parse(test); err == nil {
-			t.Errorf("Unexpected success for %q", test)
-		}
-	}
-}
diff --git a/lib/stats/glob.go b/lib/stats/glob.go
index bcb2997..c846f79 100644
--- a/lib/stats/glob.go
+++ b/lib/stats/glob.go
@@ -51,11 +51,12 @@
 			*result = append(*result, KeyValue{prefix, v})
 		}
 	}
-	if g.Finished() {
+	if g.Empty() {
 		return
 	}
+	matcher, left := g.Head(), g.Tail()
 	for name, child := range n.children {
-		if ok, _, left := g.MatchInitialSegment(name); ok {
+		if matcher.Match(name) {
 			globStepLocked(path.Join(prefix, name), left, child, updatedSince, includeValues, result)
 		}
 	}
diff --git a/runtime/internal/naming/namespace/glob.go b/runtime/internal/naming/namespace/glob.go
index 6e334a2..2e33bf8 100644
--- a/runtime/internal/naming/namespace/glob.go
+++ b/runtime/internal/naming/namespace/glob.go
@@ -196,7 +196,10 @@
 		}
 
 		// Get the pattern elements below the current path.
-		suffix := pattern.Split(depth(t.me.Name))
+		suffix := pattern
+		for i := depth(t.me.Name) - 1; i >= 0; i-- {
+			suffix = suffix.Tail()
+		}
 
 		// If we've satisfied the request and this isn't the root,
 		// reply to the caller.
@@ -210,7 +213,7 @@
 		// remote server) and the server is not another MT, then we needn't send the
 		// query on since we know the server will not supply a new address for the
 		// current name.
-		if suffix.Finished() {
+		if suffix.Empty() {
 			if !t.me.ServesMountTable {
 				continue
 			}
diff --git a/runtime/internal/rpc/reserved.go b/runtime/internal/rpc/reserved.go
index 5662edc..31eb4e8 100644
--- a/runtime/internal/rpc/reserved.go
+++ b/runtime/internal/rpc/reserved.go
@@ -320,12 +320,13 @@
 		if state.glob.Len() == 0 {
 			depth++
 		}
+		matcher, left := state.glob.Head(), state.glob.Tail()
 		for child := range children {
 			if len(child) == 0 || strings.Contains(child, "/") {
 				ctx.Errorf("rpc Glob: %q.GlobChildren__() sent an invalid child name: %q", suffix, child)
 				continue
 			}
-			if ok, _, left := state.glob.MatchInitialSegment(child); ok {
+			if matcher.Match(child) {
 				next := naming.Join(state.name, child)
 				queue = append(queue, gState{next, left, depth})
 			}
diff --git a/runtime/internal/rpc/test/glob_test.go b/runtime/internal/rpc/test/glob_test.go
index 3d00d7b..9bc312f 100644
--- a/runtime/internal/rpc/test/glob_test.go
+++ b/runtime/internal/rpc/test/glob_test.go
@@ -305,11 +305,12 @@
 	if g.Len() == 0 {
 		ch <- naming.GlobReplyEntry{naming.MountEntry{Name: name}}
 	}
-	if g.Finished() {
+	if g.Empty() {
 		return
 	}
+	matcher, left := g.Head(), g.Tail()
 	for leaf, child := range n.children {
-		if ok, _, left := g.MatchInitialSegment(leaf); ok {
+		if matcher.Match(leaf) {
 			o.globLoop(ch, naming.Join(name, leaf), left, child)
 		}
 	}
diff --git a/services/debug/debug/impl.go b/services/debug/debug/impl.go
index a35a70b..d2e1fcb 100644
--- a/services/debug/debug/impl.go
+++ b/services/debug/debug/impl.go
@@ -401,7 +401,7 @@
 		return
 	}
 	var prefixElems []string
-	prefixElems, g = g.SplitFixedPrefix()
+	prefixElems, g = g.SplitFixedElements()
 	name := naming.Join(prefixElems...)
 	if len(root) != 0 {
 		name = naming.JoinAddressName(root, name)
diff --git a/services/mounttable/mounttablelib/mounttable.go b/services/mounttable/mounttablelib/mounttable.go
index 7dbb898..555431f 100644
--- a/services/mounttable/mounttablelib/mounttable.go
+++ b/services/mounttable/mounttablelib/mounttable.go
@@ -678,7 +678,7 @@
 		return
 	}
 
-	if !pattern.Finished() {
+	if !pattern.Empty() {
 		// We can only list children to whom we have some access AND either
 		// - we have Read or Admin access to the directory or
 		// - we have Resolve or Create access to the directory and the
@@ -687,7 +687,7 @@
 			if err := n.satisfies(mt, ctx, call, traverseTags); err != nil {
 				goto out
 			}
-			fixed, _ := pattern.SplitFixedPrefix()
+			fixed, _ := pattern.SplitFixedElements()
 			if len(fixed) == 0 {
 				goto out
 			}
@@ -702,9 +702,10 @@
 		n.parent.Unlock()
 
 		// Recurse through the children.
+		matcher, suffix := pattern.Head(), pattern.Tail()
 		for k, c := range children {
 			// At this point, n lock is held.
-			if ok, _, suffix := pattern.MatchInitialSegment(k); ok {
+			if matcher.Match(k) {
 				c.Lock()
 				// If child allows any access show it.  Otherwise, skip.
 				if err := c.satisfies(mt, ctx, call, allTags); err != nil {
diff --git a/services/mounttable/mounttablelib/neighborhood.go b/services/mounttable/mounttablelib/neighborhood.go
index 8939dcd..afdd11e 100644
--- a/services/mounttable/mounttablelib/neighborhood.go
+++ b/services/mounttable/mounttablelib/neighborhood.go
@@ -282,11 +282,11 @@
 		ch := make(chan naming.GlobReply)
 		go func() {
 			defer close(ch)
+			matcher := g.Head()
 			for k, n := range nh.neighbors() {
-				if ok, _, _ := g.MatchInitialSegment(k); !ok {
-					continue
+				if matcher.Match(k) {
+					ch <- naming.GlobReplyEntry{naming.MountEntry{Name: k, Servers: n, ServesMountTable: true}}
 				}
-				ch <- naming.GlobReplyEntry{naming.MountEntry{Name: k, Servers: n, ServesMountTable: true}}
 			}
 		}()
 		return ch, nil