mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 08:28:52 +00:00
## Addresses #15999 - [x] Added/updated tests - [ ] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
166 lines
4.3 KiB
Go
166 lines
4.3 KiB
Go
// 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"
|
|
"unicode"
|
|
)
|
|
|
|
// BindToFmtString binds WFN to formatted string
|
|
func (a Attributes) BindToFmtString() string {
|
|
parts := make([]string, 11)
|
|
for i, s := range []string{
|
|
a.Part,
|
|
a.Vendor,
|
|
a.Product,
|
|
a.Version,
|
|
a.Update,
|
|
a.Edition,
|
|
a.Language,
|
|
a.SWEdition,
|
|
a.TargetSW,
|
|
a.TargetHW,
|
|
a.Other,
|
|
} {
|
|
parts[i] = bindValueFS(s)
|
|
}
|
|
return fsbPrefix + strings.Join(parts, ":")
|
|
}
|
|
|
|
// UnbindFmtString loads WFN from formatted string
|
|
func UnbindFmtString(s string) (*Attributes, error) {
|
|
if !strings.HasPrefix(s, fsbPrefix) {
|
|
return nil, fmt.Errorf("bad prefix in FSB %q", s)
|
|
}
|
|
attr := &Attributes{}
|
|
for i, partN := len(fsbPrefix), 0; i < len(s); i, partN = i+1, partN+1 {
|
|
var err error
|
|
switch partN {
|
|
case 0:
|
|
attr.Part, i, err = unbindValueFSAt(s, i)
|
|
case 1:
|
|
attr.Vendor, i, err = unbindValueFSAt(s, i)
|
|
case 2:
|
|
attr.Product, i, err = unbindValueFSAt(s, i)
|
|
case 3:
|
|
attr.Version, i, err = unbindValueFSAt(s, i)
|
|
case 4:
|
|
attr.Update, i, err = unbindValueFSAt(s, i)
|
|
case 5:
|
|
attr.Edition, i, err = unbindValueFSAt(s, i)
|
|
case 6:
|
|
attr.Language, i, err = unbindValueFSAt(s, i)
|
|
case 7:
|
|
attr.SWEdition, i, err = unbindValueFSAt(s, i)
|
|
case 8:
|
|
attr.TargetSW, i, err = unbindValueFSAt(s, i)
|
|
case 9:
|
|
attr.TargetHW, i, err = unbindValueFSAt(s, i)
|
|
case 10:
|
|
attr.Other, i, err = unbindValueFSAt(s, i)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unbind formatted string: %v", err)
|
|
}
|
|
}
|
|
return attr, nil
|
|
}
|
|
|
|
// StripSlashes removes escaping of punctuation characters from attribute value
|
|
func StripSlashes(s string) string {
|
|
out := make([]byte, 0, len(s)) // might be more than we need, but no reallocs
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == '\\' && i < len(s)-1 {
|
|
switch s[i+1] {
|
|
case '.', '_', '-': // these pass unquoted
|
|
continue
|
|
}
|
|
}
|
|
out = append(out, s[i])
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
func bindValueFS(s string) string {
|
|
switch s {
|
|
case Any:
|
|
return "*"
|
|
case NA:
|
|
return "-"
|
|
default:
|
|
return StripSlashes(s)
|
|
}
|
|
}
|
|
|
|
func unbindValueFSAt(s string, at int) (string, int, error) {
|
|
if len(s)-at < 1 || s[at] == ':' {
|
|
return Any, at, fmt.Errorf("could not unbind attribute at pos %d", at)
|
|
}
|
|
if len(s)-at == 1 || s[at+1] == ':' {
|
|
switch s[at] {
|
|
case '*':
|
|
return Any, at + 1, nil
|
|
case '-':
|
|
return NA, at + 1, nil
|
|
default:
|
|
return s[at : at+1], at + 1, nil
|
|
}
|
|
}
|
|
return addSlashesAt(s, at)
|
|
}
|
|
|
|
func addSlashesAt(s string, at int) (string, int, error) {
|
|
b := make([]byte, 0, len(s)*2) // assume a quote for every character
|
|
embedded := false
|
|
i := at
|
|
for ; i < len(s) && s[i] != ':'; i++ {
|
|
c := s[i]
|
|
if unicode.IsLetter(rune(c)) || unicode.IsDigit(rune(c)) || c == '_' {
|
|
b = append(b, c)
|
|
embedded = true
|
|
continue
|
|
}
|
|
switch c {
|
|
case '\\':
|
|
i++
|
|
if i == len(s) {
|
|
return "", i, fmt.Errorf("unquoted '\\' at the end of the FSB fragment: %q", s)
|
|
}
|
|
b = append(b, c, s[i])
|
|
embedded = true
|
|
case '*':
|
|
// An unquoted asterisk must appear at the beginning or end of the string
|
|
if i != at && i != len(s)-1 && s[i+1] != ':' {
|
|
return Any, i, fmt.Errorf("unquoted '*' inside the FSB fragment: %q", s)
|
|
}
|
|
b = append(b, c)
|
|
embedded = true
|
|
case '?':
|
|
if !(i == at || i == len(s)-1 || s[i+1] == ':' || // at the beginning or at the end of the string
|
|
(!embedded && i > 0 && s[i-1] == c || // not embedded and preceded by the same symbol
|
|
(embedded && s[i+1] == c))) { // embedded and followed by the same symbol
|
|
return Any, i, fmt.Errorf("unquoted '?' inside the FSB fragment %q (%t, %d)", s, embedded, i)
|
|
}
|
|
b = append(b, c)
|
|
embedded = false
|
|
default:
|
|
b = append(b, '\\', c)
|
|
embedded = true
|
|
}
|
|
}
|
|
return string(append([]byte{}, b...)), i, nil
|
|
}
|