Files
hugo/tpl/openapi/openapi3/openapi3.go
T
Bjørn Erik Pedersen a3d9548469 Replace to gopkg.in/yaml with github.com/goccy/go-yaml (note)
This commit also adds validation to prevent the "Billion Laughs" attack (see https://github.com/goccy/go-yaml/issues/461). The limit of non-scalar aliases to the same node is set to 10,000. See benchmarks below.

```                                                            │        sec/op         │
UnmarshalBillionLaughs/Billion_Laughs_no_validation-10                 125.2µ ± ∞ ¹
UnmarshalBillionLaughs/Billion_Laughs_with_validation-10               655.8µ ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_no_validation-10              9.223µ ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_with_validation-10            9.443µ ± ∞ ¹
geomean                                                                51.71µ
¹ need >= 6 samples for confidence interval at level 0.95

                                                            │ fix-goyaml-8822.bench │
                                                            │         B/op          │
UnmarshalBillionLaughs/Billion_Laughs_no_validation-10                177.0Ki ± ∞ ¹
UnmarshalBillionLaughs/Billion_Laughs_with_validation-10              177.0Ki ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_no_validation-10             11.67Ki ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_with_validation-10           11.67Ki ± ∞ ¹
geomean                                                               45.45Ki
¹ need >= 6 samples for confidence interval at level 0.95

                                                            │ fix-goyaml-8822.bench │
                                                            │       allocs/op       │
UnmarshalBillionLaughs/Billion_Laughs_no_validation-10                 3.302k ± ∞ ¹
UnmarshalBillionLaughs/Billion_Laughs_with_validation-10               3.305k ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_no_validation-10               253.0 ± ∞ ¹
UnmarshalBillionLaughs/YAML_Front_Matter_with_validation-10             253.0 ± ∞ ¹
````

Fixes #8822
Fixes #13043
Fixes #14053
Fixes ##8427
2025-10-18 13:52:22 +02:00

99 lines
2.7 KiB
Go

// Copyright 2020 The Hugo Authors. All rights reserved.
//
// 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 openapi3 provides functions for generating OpenAPI v3 (Swagger) documentation.
package openapi3
import (
"errors"
"fmt"
"io"
kopenapi3 "github.com/getkin/kin-openapi/openapi3"
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/resources/resource"
)
// New returns a new instance of the openapi3-namespaced template functions.
func New(deps *deps.Deps) *Namespace {
return &Namespace{
cache: dynacache.GetOrCreatePartition[string, *OpenAPIDocument](deps.MemCache, "/tmpl/openapi3", dynacache.OptionsPartition{Weight: 30, ClearWhen: dynacache.ClearOnChange}),
deps: deps,
}
}
// Namespace provides template functions for the "openapi3".
type Namespace struct {
cache *dynacache.Partition[string, *OpenAPIDocument]
deps *deps.Deps
}
// OpenAPIDocument represents an OpenAPI 3 document.
type OpenAPIDocument struct {
*kopenapi3.T
identityGroup identity.Identity
}
func (o *OpenAPIDocument) GetIdentityGroup() identity.Identity {
return o.identityGroup
}
// Unmarshal unmarshals the given resource into an OpenAPI 3 document.
func (ns *Namespace) Unmarshal(r resource.UnmarshableResource) (*OpenAPIDocument, error) {
key := r.Key()
if key == "" {
return nil, errors.New("no Key set in Resource")
}
v, err := ns.cache.GetOrCreate(key, func(string) (*OpenAPIDocument, error) {
f := metadecoders.FormatFromStrings(r.MediaType().Suffixes()...)
if f == "" {
return nil, fmt.Errorf("MIME %q not supported", r.MediaType())
}
reader, err := r.ReadSeekCloser()
if err != nil {
return nil, err
}
defer reader.Close()
b, err := io.ReadAll(reader)
if err != nil {
return nil, err
}
s := &kopenapi3.T{}
switch f {
case metadecoders.YAML:
err = metadecoders.UnmarshalYaml(b, s)
default:
err = metadecoders.Default.UnmarshalTo(b, f, s)
}
if err != nil {
return nil, err
}
err = kopenapi3.NewLoader().ResolveRefsIn(s, nil)
return &OpenAPIDocument{T: s, identityGroup: identity.FirstIdentity(r)}, err
})
if err != nil {
return nil, err
}
return v, nil
}