Skip to content

Commit 488c2ae

Browse files
committed
Meta: more correct handling of HTML-unescaping
It turns out that straight byte-based replacements of unicode escape characters back to their ascii representations is invalid if the unicode escape character itself is escaped (e.g. "\u003c" => "\\u003c" => "\<"). To solve this, we will instead unmarshal Meta objects to map[string]interface{}, extract the expected Meta fields from the map, and then use a JSON encoder with SetEscapeHTML(false) to re-encode the map back to JSON to be stored in Meta.Blob. Signed-off-by: Joe Lanford <[email protected]>
1 parent d482f98 commit 488c2ae

File tree

2 files changed

+71
-19
lines changed

2 files changed

+71
-19
lines changed

alpha/declcfg/declcfg.go

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import (
77
"fmt"
88
"strings"
99

10-
"go4.org/bytereplacer"
10+
"golang.org/x/text/cases"
11+
"k8s.io/apimachinery/pkg/util/sets"
1112

1213
"github.com/operator-framework/operator-registry/alpha/property"
1314
)
@@ -100,25 +101,78 @@ func (m Meta) MarshalJSON() ([]byte, error) {
100101
}
101102

102103
func (m *Meta) UnmarshalJSON(blob []byte) error {
103-
blob = bytereplacer.New(`\u003c`, "<", `\u003e`, ">", `\u0026`, "&").Replace(blob)
104+
blobMap := map[string]interface{}{}
105+
if err := json.Unmarshal(blob, &blobMap); err != nil {
106+
return err
107+
}
108+
109+
// TODO: this function ensures we do not break backwards compatibility with
110+
// the documented examples of FBC templates, which use upper camel case
111+
// for JSON field names. We need to decide if we want to continue supporting
112+
// case insensitive JSON field names, or if we want to enforce a specific
113+
// case-sensitive key value for each field.
114+
if err := extractUniqueMetaKeys(blobMap, m); err != nil {
115+
return err
116+
}
117+
118+
buf := bytes.Buffer{}
119+
enc := json.NewEncoder(&buf)
120+
enc.SetEscapeHTML(false)
121+
if err := enc.Encode(blobMap); err != nil {
122+
return err
123+
}
124+
m.Blob = buf.Bytes()
125+
return nil
126+
}
104127

105-
type tmp struct {
106-
Schema string `json:"schema"`
107-
Package string `json:"package,omitempty"`
108-
Name string `json:"name,omitempty"`
109-
Properties []property.Property `json:"properties,omitempty"`
128+
// extractUniqueMetaKeys enables a case-insensitive key lookup for the schema, package, and name
129+
// fields of the Meta struct. If the blobMap contains duplicate keys (that is, keys have the same folded value),
130+
// an error is returned.
131+
func extractUniqueMetaKeys(blobMap map[string]any, m *Meta) error {
132+
keySets := map[string]sets.Set[string]{}
133+
folder := cases.Fold()
134+
for key := range blobMap {
135+
foldKey := folder.String(key)
136+
if _, ok := keySets[foldKey]; !ok {
137+
keySets[foldKey] = sets.New[string]()
138+
}
139+
keySets[foldKey].Insert(key)
110140
}
111-
var t tmp
112-
if err := json.Unmarshal(blob, &t); err != nil {
113-
// TODO: return an error that includes the the full JSON message,
114-
// the offset of the error, and the error message. Let callers
115-
// decide how to format it.
116-
return errors.New(resolveUnmarshalErr(blob, err))
141+
142+
dupErrs := []error{}
143+
for foldedKey, keys := range keySets {
144+
if len(keys) != 1 {
145+
dupErrs = append(dupErrs, fmt.Errorf("duplicate keys for key %q: %v", foldedKey, sets.List(keys)))
146+
}
147+
}
148+
if len(dupErrs) > 0 {
149+
return errors.Join(dupErrs...)
150+
}
151+
152+
metaMap := map[string]*string{
153+
folder.String("schema"): &m.Schema,
154+
folder.String("package"): &m.Package,
155+
folder.String("name"): &m.Name,
156+
}
157+
158+
for foldedKey, ptr := range metaMap {
159+
// if the folded key doesn't exist in the key set derived from the blobMap, that means
160+
// the key doesn't exist in the blobMap, so we can skip it
161+
if _, ok := keySets[foldedKey]; !ok {
162+
continue
163+
}
164+
165+
// reset key to the unfolded key, which we know is the one that appears in the blobMap
166+
key := keySets[foldedKey].UnsortedList()[0]
167+
if _, ok := blobMap[key]; !ok {
168+
continue
169+
}
170+
v, ok := blobMap[key].(string)
171+
if !ok {
172+
return fmt.Errorf("expected value for key %q to be a string, got %t: %v", key, blobMap[key], blobMap[key])
173+
}
174+
*ptr = v
117175
}
118-
m.Schema = t.Schema
119-
m.Package = t.Package
120-
m.Name = t.Name
121-
m.Blob = blob
122176
return nil
123177
}
124178

alpha/template/composite/builder_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ func TestBasicBuilder(t *testing.T) {
5858
defer file.Close()
5959
fileData, err := io.ReadAll(file)
6060
require.NoError(t, err)
61-
fmt.Println(string(fileData))
6261
require.Equal(t, string(fileData), basicBuiltFbcYaml)
6362
},
6463
validateAssertions: func(t *testing.T, validateErr error) {
@@ -90,7 +89,6 @@ func TestBasicBuilder(t *testing.T) {
9089
defer file.Close()
9190
fileData, err := io.ReadAll(file)
9291
require.NoError(t, err)
93-
fmt.Println(string(fileData))
9492
require.Equal(t, string(fileData), basicBuiltFbcJson)
9593
},
9694
validateAssertions: func(t *testing.T, validateErr error) {

0 commit comments

Comments
 (0)