wfn/wfn.go (114 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package wfn
import (
"fmt"
"strings"
)
// KnownParts is a map of known WFN attribute parts.
var KnownParts = map[string]string{
"a": "application",
"o": "operating system",
"h": "hardware",
}
// Possible logical value of Attributes
// empty string considered ANY when parsing and unquoted "-" is illegal in WFN attribute-value
const (
Any = ""
NA = "-"
)
const (
uriPrefix = "cpe:/"
fsbPrefix = "cpe:2.3:"
)
var parsers = map[string]func(s string) (*Attributes, error){
uriPrefix: UnbindURI,
fsbPrefix: UnbindFmtString,
}
// Parse parses Attributes from URI or formatted string binding.
func Parse(s string) (*Attributes, error) {
for prefix, parserFunc := range parsers {
if strings.HasPrefix(s, prefix) {
return parserFunc(s)
}
}
return nil, fmt.Errorf("wfn: unsupported format %q", s)
}
// Attributes defines the WFN Data Model Attributes.
type Attributes struct {
Part string
Vendor string
Product string
Version string
Update string
Edition string
SWEdition string
TargetSW string
TargetHW string
Other string
Language string
}
// NewAttributesWithNA allocates Attributes object with all fields initialized to NA logical value
func NewAttributesWithNA() *Attributes {
return newAttributes(NA)
}
// NewAttributesWithAny allocates Attributes object with all fields initialized to Any logical value
func NewAttributesWithAny() *Attributes {
return newAttributes(Any)
}
func newAttributes(defaultValue string) *Attributes {
return &Attributes{
Part: defaultValue,
Vendor: defaultValue,
Product: defaultValue,
Version: defaultValue,
Update: defaultValue,
Edition: defaultValue,
SWEdition: defaultValue,
TargetSW: defaultValue,
TargetHW: defaultValue,
Other: defaultValue,
Language: defaultValue,
}
}
// WFNize transforms a string into CPE23-NAME compliant avstring value.
// This function isn't a part of standard. Quoted wildcards (*?) become unquoted ones (i.e. act as wildcards,
// not a literal '*' and '?')
// If wildcards are used, it is a responsibility of the user to make sure they comply with the standard, i.e.
// only appear at the beginning or at the end of the string and, in case of asterisk, only once in each case.
// Uppercase letters are valid avstring characters, but they are rarely (if ever) used in WFNs. It is recommended
// to strings.ToLower() the string before passing it to this function.
func WFNize(s string) (string, error) {
const allowedPunct = "-!\"#$%&'()+,./:;<=>@[]^`{|}!~"
// replace spaces with underscores
in := strings.Replace(s, " ", "_", -1)
buf := make([]byte, 0, len(in))
// remove illegal characters
for n, c := range in {
c := byte(c)
if c >= 'A' && c <= 'Z' ||
c >= 'a' && c <= 'z' ||
c >= '0' && c <= '9' ||
c == '_' ||
strings.IndexByte(allowedPunct, c) != -1 {
buf = append(buf, c)
}
// handle wildcard characters
if c == '*' || c == '?' {
if n == 0 || in[n-1] != '\\' {
buf = append(buf, '\\')
}
buf = append(buf, c)
}
}
// quote everything that requires quoting
s, _, err := addSlashesAt(string(buf), 0)
return s, err
}
// String returns a string representation of the wfn
func (a Attributes) String() string {
parts := make([]string, 0, 11)
// these are always displayed
parts = append(parts, keyValueString("part", a.Part))
parts = append(parts, keyValueString("vendor", a.Vendor))
parts = append(parts, keyValueString("product", a.Product))
parts = append(parts, keyValueString("version", a.Version))
parts = append(parts, keyValueString("update", a.Update))
parts = append(parts, keyValueString("edition", a.Edition))
// these are present only if one of them isn't ANY (cpe:2.2 compartibility)
if a.SWEdition != Any || a.TargetHW != Any || a.TargetSW != Any || a.Other != Any {
parts = append(parts, keyValueString("sw_edition", a.SWEdition))
parts = append(parts, keyValueString("target_sw", a.TargetSW))
parts = append(parts, keyValueString("target_hw", a.TargetHW))
parts = append(parts, keyValueString("other", a.Other))
}
// also always displayed
parts = append(parts, keyValueString("language", a.Language))
return fmt.Sprintf("wfn:[%s]", strings.Join(parts, ","))
}
func keyValueString(k, v string) string {
switch v {
case Any:
return fmt.Sprintf("%s=ANY", k)
case NA:
return fmt.Sprintf("%s=NA", k)
default:
return fmt.Sprintf("%s=\"%s\"", k, v)
}
}