Skip to content

Commit 4bf4de4

Browse files
committed
wit: validate WIT identifiers
1 parent ec2fe9e commit 4bf4de4

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

wit/ident.go

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package wit
22

33
import (
44
"errors"
5+
"strconv"
56
"strings"
67

78
"github.com/coreos/go-semver/semver"
@@ -34,24 +35,32 @@ type Ident struct {
3435
// returning any errors encountered. The resulting Ident
3536
// may not be valid.
3637
func ParseIdent(s string) (Ident, error) {
37-
s = strings.ReplaceAll(s, "%", "")
3838
var id Ident
3939
name, ver, hasVer := strings.Cut(s, "@")
40-
base, ext, hasExt := strings.Cut(name, "/")
41-
id.Namespace, id.Package, _ = strings.Cut(base, ":")
4240
if hasVer {
4341
var err error
4442
id.Version, err = semver.NewVersion(ver)
4543
if err != nil {
4644
return id, err
4745
}
4846
}
47+
base, ext, hasExt := strings.Cut(name, "/")
48+
ns, pkg, _ := strings.Cut(base, ":")
49+
id.Namespace = trimPercent(ns)
50+
id.Package = trimPercent(pkg)
4951
if hasExt {
50-
id.Extension = ext
52+
id.Extension = trimPercent(ext)
5153
}
5254
return id, id.Validate()
5355
}
5456

57+
func trimPercent(s string) string {
58+
if len(s) > 0 && s[0] == '%' {
59+
return s[1:]
60+
}
61+
return s
62+
}
63+
5564
// Validate validates id, returning any errors.
5665
func (id *Ident) Validate() error {
5766
switch {
@@ -60,6 +69,59 @@ func (id *Ident) Validate() error {
6069
case id.Package == "":
6170
return errors.New("missing package name")
6271
}
72+
if err := validateName(id.Namespace); err != nil {
73+
return err
74+
}
75+
if err := validateName(id.Package); err != nil {
76+
return err
77+
}
78+
return validateName(id.Extension)
79+
}
80+
81+
func validateName(s string) error {
82+
if len(s) == 0 {
83+
return nil
84+
}
85+
var prev rune
86+
for _, c := range s {
87+
switch {
88+
case c >= 'a' && c <= 'z':
89+
switch {
90+
case prev >= 'A' && prev <= 'Z':
91+
return errors.New("invalid character " + strconv.Quote(string(c)))
92+
}
93+
case c >= 'A' && c <= 'Z':
94+
switch {
95+
case prev == 0: // start of string
96+
case prev >= 'A' && prev <= 'Z':
97+
case prev >= '0' && prev <= '9':
98+
case prev == '-':
99+
default:
100+
return errors.New("invalid character " + strconv.Quote(string(c)))
101+
}
102+
case c >= '0' && c <= '9':
103+
switch {
104+
case prev >= 'a' && prev <= 'z':
105+
case prev >= 'A' && prev <= 'Z':
106+
case prev >= '0' && prev <= '9':
107+
default:
108+
return errors.New("invalid character " + strconv.Quote(string(c)))
109+
}
110+
case c == '-':
111+
switch {
112+
case prev == 0: // start of string
113+
return errors.New("invalid leading -")
114+
case prev == '-':
115+
return errors.New("invalid double --")
116+
}
117+
default:
118+
return errors.New("invalid character " + strconv.Quote(string(c)))
119+
}
120+
prev = c
121+
}
122+
if prev == '-' {
123+
return errors.New("invalid trailing -")
124+
}
63125
return nil
64126
}
65127

wit/ident_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func TestIdent(t *testing.T) {
3434
{"wasi:/", Ident{}, true},
3535
{"wasi:clocks@", Ident{}, true},
3636
{"wasi:clocks/wall-clock@", Ident{}, true},
37+
{"foo%:bar%baz", Ident{Namespace: "foo%", Package: "bar%baz"}, true},
3738
}
3839
for _, tt := range tests {
3940
t.Run(tt.s, func(t *testing.T) {

0 commit comments

Comments
 (0)