diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 5bd719d..33441c8 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -12,7 +12,6 @@ import ( "fmt" "io" "os" - "regexp" "sort" "strconv" "strings" @@ -58,11 +57,6 @@ const ( // documents and nodes. It is set to the OpenVEX public namespace by default. var DefaultNamespace = PublicNamespace -var ( - contextRegExpPattern = fmt.Sprintf(`"@context":\s+"(%s\S*)"`, Context) - contextRegExp *regexp.Regexp -) - // The VEX type represents a VEX document and all of its contained information. type VEX struct { Metadata @@ -265,6 +259,22 @@ func SortDocuments(docs []*VEX) []*VEX { return docs } +// parseContext light parses a JSON document to look for the OpenVEX context locator +func parseContext(rawDoc []byte) (string, error) { + pd := struct { + Context string `json:"@context"` + }{} + + if err := json.Unmarshal(rawDoc, &pd); err != nil { + return "", fmt.Errorf("parsing context from json data: %w", err) + } + + if strings.HasPrefix(pd.Context, Context) { + return pd.Context, nil + } + return "", nil +} + // Open tries to autodetect the vex format and open it func Open(path string) (*VEX, error) { data, err := os.ReadFile(path) @@ -272,21 +282,15 @@ func Open(path string) (*VEX, error) { return nil, fmt.Errorf("opening VEX file: %w", err) } - if bytes.Contains(data, []byte(ContextLocator())) { - logrus.Info("opening current vex") - return Parse(data) - } else if bytes.Contains(data, []byte(Context)) { - logrus.Info("Opening older openvex") - if contextRegExp == nil { - contextRegExp = regexp.MustCompile(contextRegExpPattern) - } - - res := contextRegExp.FindSubmatch(data) - if len(res) == 0 { - return nil, fmt.Errorf("unable to parse OpenVEX version in document context") - } + documentContextLocator, err := parseContext(data) + if err != nil { + return nil, err + } - version := strings.TrimPrefix(string(res[1]), Context) + if documentContextLocator == ContextLocator() { + return Parse(data) + } else if documentContextLocator != "" { + version := strings.TrimPrefix(documentContextLocator, Context) version = strings.TrimPrefix(version, "/") // If version is nil, then we assume v0.0.1 diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index 99c8020..f974a1b 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -413,3 +413,24 @@ func TestDocumentMatches(t *testing.T) { require.Equal(t, tc.numMatches, len(matches), fmt.Sprintf("failed: %s", testCase)) } } + +func TestParseContext(t *testing.T) { + for tCase, tc := range map[string]struct { + docData string + expected string + shouldErr bool + }{ + "Normal": {`{"@context": "https://openvex.dev/ns"}`, "https://openvex.dev/ns", false}, + "Other JSON": {`{"document": { "category": "csaf_vex" } }`, "", false}, + "Invalid JSON": {`@context": "https://openvex.dev/ns`, "", true}, + "Other json-ld": {`{"@context": "https://spdx.dev/"}`, "", false}, + } { + res, err := parseContext([]byte(tc.docData)) + if tc.shouldErr { + require.Error(t, err, tCase) + continue + } + require.NoError(t, err, tCase) + require.Equal(t, res, tc.expected, tCase) + } +}