updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Paul 2022-11-02 17:49:03 +01:00
parent 99a03bac5d
commit b20b1719bd
625 changed files with 70735 additions and 23575 deletions

View File

@ -40,7 +40,7 @@ ovhck=
## License
```text
Copyright (c) 2020, 2021 PaulBSD
Copyright (c) 2020, 2021, 2022 PaulBSD
All rights reserved.
Redistribution and use in source and binary forms, with or without

49
go.mod
View File

@ -1,41 +1,42 @@
module git.paulbsd.com/paulbsd/pki
go 1.17
go 1.19
require (
github.com/go-acme/lego/v4 v4.4.0
github.com/go-acme/lego/v4 v4.9.0
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/gopherjs/gopherjs v0.0.0-20210406100015-1e088ea4ee04 // indirect
github.com/labstack/echo/v4 v4.5.0
github.com/lib/pq v1.10.3
github.com/miekg/dns v1.1.43 // indirect
github.com/labstack/echo/v4 v4.9.1
github.com/lib/pq v1.10.7
github.com/miekg/dns v1.1.50 // indirect
github.com/onsi/ginkgo v1.16.0 // indirect
github.com/onsi/gomega v1.11.0 // indirect
github.com/smartystreets/assertions v1.2.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
gopkg.in/ini.v1 v1.62.1
xorm.io/builder v0.3.9 // indirect
xorm.io/xorm v1.2.3
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.1.0 // indirect
gopkg.in/ini.v1 v1.67.0
xorm.io/builder v0.3.12 // indirect
xorm.io/xorm v1.3.2
)
require (
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/goccy/go-json v0.7.8 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/labstack/gommon v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/ovh/go-ovh v1.1.0 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ovh/go-ovh v1.3.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/tools v0.2.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)

571
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -147,6 +147,9 @@ func (b *ExponentialBackOff) incrementCurrentInterval() {
// Returns a random value from the following interval:
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
if randomizationFactor == 0 {
return currentInterval // make sure no randomness is used when randomizationFactor is 0.
}
var delta = randomizationFactor * float64(currentInterval)
var minInterval = float64(currentInterval) - delta
var maxInterval = float64(currentInterval) + delta

View File

@ -2,7 +2,6 @@ package api
import (
"bytes"
"context"
"crypto"
"encoding/json"
"errors"
@ -71,7 +70,7 @@ func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response,
}
// postAsGet performs an HTTP POST ("POST-as-GET") request.
// https://tools.ietf.org/html/rfc8555#section-6.3
// https://www.rfc-editor.org/rfc/rfc8555.html#section-6.3
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
return a.retrievablePost(uri, []byte{}, response)
}
@ -83,8 +82,6 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
bo.MaxInterval = 5 * time.Second
bo.MaxElapsedTime = 20 * time.Second
ctx, cancel := context.WithCancel(context.Background())
var resp *http.Response
operation := func() error {
var err error
@ -96,8 +93,7 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
return err
}
cancel()
return err
return backoff.Permanent(err)
}
return nil
@ -107,7 +103,7 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{})
log.Infof("retry due to: %v", err)
}
err := backoff.RetryNotify(operation, backoff.WithContext(bo, ctx), notify)
err := backoff.RetryNotify(operation, bo, notify)
if err != nil {
return resp, err
}

View File

@ -1,10 +1,11 @@
package api
import (
"bytes"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"io"
"net/http"
"github.com/go-acme/lego/v4/acme"
@ -39,7 +40,7 @@ func (c *CertificateService) GetAll(certURL string, bundle bool) (map[string]*ac
certs := map[string]*acme.RawCertificate{certURL: cert}
// URLs of "alternate" link relation
// - https://tools.ietf.org/html/rfc8555#section-7.4.2
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2
alts := getLinks(headers, "alternate")
for _, alt := range alts {
@ -71,7 +72,7 @@ func (c *CertificateService) get(certURL string, bundle bool) (*acme.RawCertific
return nil, nil, err
}
data, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
data, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if err != nil {
return nil, resp.Header, err
}
@ -87,12 +88,17 @@ func (c *CertificateService) getCertificateChain(cert []byte, headers http.Heade
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
_, issuer := pem.Decode(cert)
if issuer != nil {
// If bundle is false, we want to return a single certificate.
// To do this, we remove the issuer cert(s) from the issued cert.
if !bundle {
cert = bytes.TrimSuffix(cert, issuer)
}
return &acme.RawCertificate{Cert: cert, Issuer: issuer}
}
// The issuer certificate link may be supplied via an "up" link
// in the response headers of a new certificate.
// See https://tools.ietf.org/html/rfc8555#section-7.4.2
// See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2
up := getLink(headers, "up")
issuer, err := c.getIssuerFromLink(up)

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"runtime"
"strings"
@ -96,7 +95,7 @@ func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, erro
}
if response != nil {
raw, err := ioutil.ReadAll(resp.Body)
raw, err := io.ReadAll(resp.Body)
if err != nil {
return resp, err
}
@ -120,7 +119,7 @@ func (d *Doer) formatUserAgent() string {
func checkError(req *http.Request, resp *http.Response) error {
if resp.StatusCode >= http.StatusBadRequest {
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
}

View File

@ -5,7 +5,7 @@ package sender
const (
// ourUserAgent is the User-Agent of this underlying library package.
ourUserAgent = "xenolf-acme/4.4.0"
ourUserAgent = "xenolf-acme/4.9.0"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release

View File

@ -1,5 +1,5 @@
// Package acme contains all objects related the ACME endpoints.
// https://tools.ietf.org/html/rfc8555
// https://www.rfc-editor.org/rfc/rfc8555.html
package acme
import (
@ -7,20 +7,37 @@ import (
"time"
)
// Challenge statuses.
// https://tools.ietf.org/html/rfc8555#section-7.1.6
// ACME status values of Account, Order, Authorization and Challenge objects.
// See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.6 for details.
const (
StatusPending = "pending"
StatusInvalid = "invalid"
StatusValid = "valid"
StatusProcessing = "processing"
StatusDeactivated = "deactivated"
StatusExpired = "expired"
StatusInvalid = "invalid"
StatusPending = "pending"
StatusProcessing = "processing"
StatusReady = "ready"
StatusRevoked = "revoked"
StatusUnknown = "unknown"
StatusValid = "valid"
)
// CRL reason codes as defined in RFC 5280.
// https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1
const (
CRLReasonUnspecified uint = 0
CRLReasonKeyCompromise uint = 1
CRLReasonCACompromise uint = 2
CRLReasonAffiliationChanged uint = 3
CRLReasonSuperseded uint = 4
CRLReasonCessationOfOperation uint = 5
CRLReasonCertificateHold uint = 6
CRLReasonRemoveFromCRL uint = 8
CRLReasonPrivilegeWithdrawn uint = 9
CRLReasonAACompromise uint = 10
)
// Directory the ACME directory object.
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1
type Directory struct {
NewNonceURL string `json:"newNonce"`
NewAccountURL string `json:"newAccount"`
@ -32,7 +49,7 @@ type Directory struct {
}
// Meta the ACME meta object (related to Directory).
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1
type Meta struct {
// termsOfService (optional, string):
// A URL identifying the current terms of service.
@ -65,8 +82,8 @@ type ExtendedAccount struct {
}
// Account the ACME account Object.
// - https://tools.ietf.org/html/rfc8555#section-7.1.2
// - https://tools.ietf.org/html/rfc8555#section-7.3
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.2
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3
type Account struct {
// status (required, string):
// The status of this account.
@ -112,7 +129,7 @@ type ExtendedOrder struct {
}
// Order the ACME order Object.
// - https://tools.ietf.org/html/rfc8555#section-7.1.3
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.3
type Order struct {
// status (required, string):
// The status of this order.
@ -165,7 +182,7 @@ type Order struct {
}
// Authorization the ACME authorization object.
// - https://tools.ietf.org/html/rfc8555#section-7.1.4
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.4
type Authorization struct {
// status (required, string):
// The status of this authorization.
@ -207,8 +224,8 @@ type ExtendedChallenge struct {
}
// Challenge the ACME challenge object.
// - https://tools.ietf.org/html/rfc8555#section-7.1.5
// - https://tools.ietf.org/html/rfc8555#section-8
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.5
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-8
type Challenge struct {
// type (required, string):
// The type of challenge encoded in the object.
@ -241,23 +258,23 @@ type Challenge struct {
// It MUST NOT contain any characters outside the base64url alphabet,
// and MUST NOT include base64 padding characters ("=").
// See [RFC4086] for additional information on randomness requirements.
// https://tools.ietf.org/html/rfc8555#section-8.3
// https://tools.ietf.org/html/rfc8555#section-8.4
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.3
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.4
Token string `json:"token"`
// https://tools.ietf.org/html/rfc8555#section-8.1
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.1
KeyAuthorization string `json:"keyAuthorization"`
}
// Identifier the ACME identifier object.
// - https://tools.ietf.org/html/rfc8555#section-9.7.7
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-9.7.7
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
// CSRMessage Certificate Signing Request.
// - https://tools.ietf.org/html/rfc8555#section-7.4
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
type CSRMessage struct {
// csr (required, string):
// A CSR encoding the parameters for the certificate being requested [RFC2986].
@ -267,8 +284,8 @@ type CSRMessage struct {
}
// RevokeCertMessage a certificate revocation message.
// - https://tools.ietf.org/html/rfc8555#section-7.6
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.6
// - https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1
type RevokeCertMessage struct {
// certificate (required, string):
// The certificate to be revoked, in the base64url-encoded version of the DER format.

View File

@ -11,8 +11,8 @@ const (
)
// ProblemDetails the problem details object.
// - https://tools.ietf.org/html/rfc7807#section-3.1
// - https://tools.ietf.org/html/rfc8555#section-7.3.3
// - https://www.rfc-editor.org/rfc/rfc7807.html#section-3.1
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3.3
type ProblemDetails struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`
@ -26,7 +26,7 @@ type ProblemDetails struct {
}
// SubProblem a "subproblems".
// - https://tools.ietf.org/html/rfc8555#section-6.7.1
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-6.7.1
type SubProblem struct {
Type string `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`

View File

@ -85,6 +85,9 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
keyBlockDER, _ := pem.Decode(key)
if keyBlockDER == nil {
return nil, fmt.Errorf("invalid PEM block")
}
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
@ -261,7 +264,7 @@ func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain st
}
if expiration.IsZero() {
expiration = time.Now().Add(365)
expiration = time.Now().AddDate(1, 0, 0)
}
template := x509.Certificate{

View File

@ -60,7 +60,7 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
return responses, nil
}
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) {
for _, authzURL := range order.Authorizations {
auth, err := c.core.Authorizations.Get(authzURL)
if err != nil {
@ -68,7 +68,7 @@ func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
continue
}
if auth.Status == acme.StatusValid {
if auth.Status == acme.StatusValid && !force {
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
continue
}

View File

@ -7,7 +7,7 @@ import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
"time"
@ -49,22 +49,30 @@ type Resource struct {
// If you do not want that you can supply your own private key in the privateKey parameter.
// If this parameter is non-nil it will be used instead of generating a new one.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle.
//
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
type ObtainRequest struct {
Domains []string
Bundle bool
PrivateKey crypto.PrivateKey
MustStaple bool
PreferredChain string
AlwaysDeactivateAuthorizations bool
}
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
//
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle.
//
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
type ObtainForCSRRequest struct {
CSR *x509.CertificateRequest
Bundle bool
PreferredChain string
AlwaysDeactivateAuthorizations bool
}
type resolver interface {
@ -117,14 +125,14 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
authz, err := c.getAuthorizations(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
return nil, err
}
err = c.resolver.Solve(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
return nil, err
}
@ -138,6 +146,10 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
}
}
if request.AlwaysDeactivateAuthorizations {
c.deactivateAuthorizations(order, true)
}
// Do not return an empty failures map, because
// it would still be a non-nil error value
if len(failures) > 0 {
@ -178,14 +190,14 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
authz, err := c.getAuthorizations(order)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
return nil, err
}
err = c.resolver.Solve(authz)
if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates.
c.deactivateAuthorizations(order)
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
return nil, err
}
@ -199,6 +211,10 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
}
}
if request.AlwaysDeactivateAuthorizations {
c.deactivateAuthorizations(order, true)
}
if cert != nil {
// Add the CSR to the certificate so that it can be used for renewals.
cert.CSR = certcrypto.PEMEncode(request.CSR)
@ -225,7 +241,7 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund
commonName := domains[0]
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
// https://tools.ietf.org/html/rfc8555#section-7.4
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
// says:
// Clients SHOULD NOT make any assumptions about the sort order of
// "identifiers" or "authorizations" elements in the returned order
@ -349,6 +365,11 @@ func (c *Certifier) checkResponse(order acme.ExtendedOrder, certRes *Resource, b
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
func (c *Certifier) Revoke(cert []byte) error {
return c.RevokeWithReason(cert, nil)
}
// RevokeWithReason takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
func (c *Certifier) RevokeWithReason(cert []byte, reason *uint) error {
certificates, err := certcrypto.ParsePEMBundle(cert)
if err != nil {
return err
@ -361,6 +382,7 @@ func (c *Certifier) Revoke(cert []byte) error {
revokeMsg := acme.RevokeCertMessage{
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
Reason: reason,
}
return c.core.Certificates.Revoke(revokeMsg)
@ -423,6 +445,7 @@ func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredCh
Bundle: bundle,
PrivateKey: privateKey,
MustStaple: mustStaple,
PreferredChain: preferredChain,
}
return c.Obtain(query)
}
@ -465,7 +488,7 @@ func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
}
defer resp.Body.Close()
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
issuerBytes, errC := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if errC != nil {
return nil, nil, errC
}
@ -494,7 +517,7 @@ func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
}
defer resp.Body.Close()
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
ocspResBytes, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
if err != nil {
return nil, nil, err
}
@ -560,11 +583,11 @@ func checkOrderStatus(order acme.ExtendedOrder) (bool, error) {
}
}
// https://tools.ietf.org/html/rfc8555#section-7.1.4
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.4
// The domain name MUST be encoded in the form in which it would appear in a certificate.
// That is, it MUST be encoded according to the rules in Section 7 of [RFC5280].
//
// https://tools.ietf.org/html/rfc5280#section-7
// https://www.rfc-editor.org/rfc/rfc5280.html#section-7
func sanitizeDomain(domains []string) []string {
var sanitizedDomains []string
for _, domain := range domains {

View File

@ -10,15 +10,15 @@ import (
type Type string
const (
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.3
// HTTP01 is the "http-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8555.html#section-8.3
// Note: ChallengePath returns the URL path to fulfill this challenge.
HTTP01 = Type("http-01")
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.4
// DNS01 is the "dns-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8555.html#section-8.4
// Note: GetRecord returns a DNS record which will fulfill this challenge.
DNS01 = Type("dns-01")
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8737.html
TLSALPN01 = Type("tls-alpn-01")
)

View File

@ -176,15 +176,33 @@ func GetRecord(domain, keyAuth string) (fqdn, value string) {
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
// base64URL encoding without padding
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_CNAME_SUPPORT")); ok {
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
// Check if the domain has CNAME then return that
if err == nil && r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn)
}
}
fqdn = getChallengeFqdn(domain)
return
}
func getChallengeFqdn(domain string) string {
fqdn := fmt.Sprintf("_acme-challenge.%s.", domain)
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_DISABLE_CNAME_SUPPORT")); ok {
return fqdn
}
// recursion counter so it doesn't spin out of control
for limit := 0; limit < 50; limit++ {
// Keep following CNAMEs
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
// Check if the domain has CNAME then use that
if err == nil && r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn)
continue
}
// No more CNAME records to follow, exit
break
}
return fqdn
}

View File

@ -8,7 +8,7 @@ import (
)
const (
dnsTemplate = `%s %d IN TXT "%s"`
dnsTemplate = `%s %d IN TXT %q`
)
// DNSProviderManual is an implementation of the ChallengeProvider interface.

View File

@ -13,9 +13,6 @@ import (
const defaultResolvConf = "/etc/resolv.conf"
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
var dnsTimeout = 10 * time.Second
var (
fqdnSoaCache = map[string]*soaCacheEntry{}
muFqdnSoaCache sync.Mutex

View File

@ -0,0 +1,8 @@
//go:build !windows
package dns01
import "time"
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
var dnsTimeout = 10 * time.Second

View File

@ -0,0 +1,8 @@
//go:build windows
package dns01
import "time"
// dnsTimeout is used to override the default DNS timeout of 20 seconds.
var dnsTimeout = 20 * time.Second

View File

@ -23,13 +23,16 @@ import (
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
// The header value has a different format, so you should use forwardedMatcher
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
// https://tools.ietf.org/html/rfc7239
// https://www.rfc-editor.org/rfc/rfc7239.html
//
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
// meaning that
//
// X-Header: a
// X-Header: b
//
// is equal to
//
// X-Header: a, b
//
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
@ -66,7 +69,7 @@ func (m arbitraryMatcher) matches(r *http.Request, domain string) bool {
}
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
// See https://tools.ietf.org/html/rfc7239 for details.
// See https://www.rfc-editor.org/rfc/rfc7239.html for details.
type forwardedMatcher struct{}
func (m *forwardedMatcher) name() string {

View File

@ -2,9 +2,11 @@ package http01
import (
"fmt"
"io/fs"
"net"
"net/http"
"net/textproto"
"os"
"strings"
"github.com/go-acme/lego/v4/log"
@ -14,8 +16,11 @@ import (
// It may be instantiated without using the NewProviderServer function if
// you want only to use the default values.
type ProviderServer struct {
iface string
port string
address string
network string // must be valid argument to net.Listen
socketMode fs.FileMode
matcher domainMatcher
done chan bool
listener net.Listener
@ -29,24 +34,34 @@ func NewProviderServer(iface, port string) *ProviderServer {
port = "80"
}
return &ProviderServer{iface: iface, port: port, matcher: &hostMatcher{}}
return &ProviderServer{network: "tcp", address: net.JoinHostPort(iface, port), matcher: &hostMatcher{}}
}
func NewUnixProviderServer(socketPath string, mode fs.FileMode) *ProviderServer {
return &ProviderServer{network: "unix", address: socketPath, socketMode: mode, matcher: &hostMatcher{}}
}
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
var err error
s.listener, err = net.Listen("tcp", s.GetAddress())
s.listener, err = net.Listen(s.network, s.GetAddress())
if err != nil {
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
}
if s.network == "unix" {
if err = os.Chmod(s.address, s.socketMode); err != nil {
return fmt.Errorf("chmod %s: %w", s.address, err)
}
}
s.done = make(chan bool)
go s.serve(domain, token, keyAuth)
return nil
}
func (s *ProviderServer) GetAddress() string {
return net.JoinHostPort(s.iface, s.port)
return s.address
}
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`.
@ -69,7 +84,7 @@ func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
//
// The exact behavior depends on the value of headerName:
// - "" (the empty string) and "Host" will restore the default and only check the Host header
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://tools.ietf.org/html/rfc7239
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://www.rfc-editor.org/rfc/rfc7239.html
// - any other value will check the header value with the same name.
func (s *ProviderServer) SetProxyHeader(headerName string) {
switch h := textproto.CanonicalMIMEHeaderKey(headerName); h {
@ -85,7 +100,7 @@ func (s *ProviderServer) SetProxyHeader(headerName string) {
func (s *ProviderServer) serve(domain, token, keyAuth string) {
path := ChallengePath(token)
// The incoming request must will be validated to prevent DNS rebind attacks.
// The incoming request will be validated to prevent DNS rebind attacks.
// We only respond with the keyAuth, when we're receiving a GET requests with
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
mux := http.NewServeMux()
@ -99,7 +114,7 @@ func (s *ProviderServer) serve(domain, token, keyAuth string) {
}
log.Infof("[%s] Served key authentication", domain)
} else {
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure you are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
_, err := w.Write([]byte("TEST"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@ -1,7 +1,6 @@
package resolver
import (
"context"
"errors"
"fmt"
"sort"
@ -107,21 +106,17 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
bo.MaxInterval = 10 * initialInterval
bo.MaxElapsedTime = 100 * initialInterval
ctx, cancel := context.WithCancel(context.Background())
// After the path is sent, the ACME server will access our server.
// Repeatedly check the server for an updated status on our request.
operation := func() error {
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
if err != nil {
cancel()
return err
return backoff.Permanent(err)
}
valid, err := checkAuthorizationStatus(authz)
if err != nil {
cancel()
return err
return backoff.Permanent(err)
}
if valid {
@ -132,7 +127,7 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
return errors.New("the server didn't respond to our request")
}
return backoff.Retry(operation, backoff.WithContext(bo, ctx))
return backoff.Retry(operation, bo)
}
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {

View File

@ -16,7 +16,7 @@ import (
)
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.1
// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-6.1
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
@ -83,7 +83,7 @@ func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
// Add the keyAuth digest as the acmeValidation-v1 extension
// (marked as critical such that it won't be used by non-ACME software).
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-3
// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-3
extensions := []pkix.Extension{
{
Id: idPeAcmeIdentifierV1,

View File

@ -61,7 +61,7 @@ func (s *ProviderServer) Present(domain, token, keyAuth string) error {
// We must set that the `acme-tls/1` application level protocol is supported
// so that the protocol negotiation can succeed. Reference:
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.2
// https://www.rfc-editor.org/rfc/rfc8737.html#section-6.2
tlsConf.NextProtos = []string{ACMETLS1Protocol}
// Create the listener with the created tls.Config.

View File

@ -4,10 +4,11 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/go-acme/lego/v4/certcrypto"
@ -17,10 +18,15 @@ import (
const (
// caCertificatesEnvVar is the environment variable name that can be used to
// specify the path to PEM encoded CA Certificates that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// authenticate an ACME server with an HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
// Multiple file paths can be added by using os.PathListSeparator as a separator.
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
// caSystemCertPool is the environment variable name that can be used to define
// if the certificates pool must use a copy of the system cert pool.
caSystemCertPool = "LEGO_CA_SYSTEM_CERT_POOL"
// caServerNameEnvVar is the environment variable name that can be used to
// specify the CA server name that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
@ -82,23 +88,44 @@ func createDefaultHTTPClient() *http.Client {
}
// initCertPool creates a *x509.CertPool populated with the PEM certificates
// found in the filepath specified in the caCertificatesEnvVar OS environment
// variable. If the caCertificatesEnvVar is not set then initCertPool will
// return nil. If there is an error creating a *x509.CertPool from the provided
// caCertificatesEnvVar value then initCertPool will panic.
// found in the filepath specified in the caCertificatesEnvVar OS environment variable.
// If the caCertificatesEnvVar is not set then initCertPool will return nil.
// If there is an error creating a *x509.CertPool from the provided caCertificatesEnvVar value then initCertPool will panic.
// If the caSystemCertPool is set to a "truthy value" (`1`, `t`, `T`, `TRUE`, `true`, `True`) then a copy of system cert pool will be used.
// caSystemCertPool requires caCertificatesEnvVar to be set.
func initCertPool() *x509.CertPool {
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
customCAs, err := ioutil.ReadFile(customCACertsPath)
customCACertsPath := os.Getenv(caCertificatesEnvVar)
if customCACertsPath == "" {
return nil
}
certPool := getCertPool()
for _, customPath := range strings.Split(customCACertsPath, string(os.PathListSeparator)) {
customCAs, err := os.ReadFile(customPath)
if err != nil {
panic(fmt.Sprintf("error reading %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
caCertificatesEnvVar, customPath, err))
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
caCertificatesEnvVar, customPath, err))
}
}
return certPool
}
return nil
}
func getCertPool() *x509.CertPool {
useSystemCertPool, _ := strconv.ParseBool(os.Getenv(caSystemCertPool))
if !useSystemCertPool {
return x509.NewCertPool()
}
pool, err := x509.SystemCertPool()
if err == nil {
return pool
}
return x509.NewCertPool()
}

View File

@ -3,7 +3,6 @@ package env
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
@ -55,7 +54,6 @@ func Get(names ...string) (map[string]string, error) {
// // LEGO_TWO=""
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
// // => error
//
func GetWithFallback(groups ...[]string) (map[string]string, error) {
values := map[string]string{}
@ -155,7 +153,7 @@ func GetOrFile(envVar string) string {
return envVarValue
}
fileContents, err := ioutil.ReadFile(fileVarValue)
fileContents, err := os.ReadFile(fileVarValue)
if err != nil {
log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
return ""

View File

@ -126,9 +126,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
// Parse domain name
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
authZone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", domain, err)
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", fqdn, err)
}
authZone = dns01.UnFqdn(authZone)
@ -170,9 +170,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("ovh: unknown record ID for '%s'", fqdn)
}
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
authZone, err := dns01.FindZoneByFqdn(fqdn)
if err != nil {
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", domain, err)
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", fqdn, err)
}
authZone = dns01.UnFqdn(authZone)
@ -210,5 +210,5 @@ func extractRecordName(fqdn, zone string) string {
if idx := strings.Index(name, "."+zone); idx != -1 {
return name[:idx]
}
return name
return ""
}

View File

@ -9,7 +9,7 @@ OVH_APPLICATION_KEY=1234567898765432 \
OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \
OVH_CONSUMER_KEY=256vfsd347245sdfg \
OVH_ENDPOINT=ovh-eu \
lego --email myemail@example.com --dns ovh --domains my.example.org run
lego --email you@example.com --dns ovh --domains my.example.org run
'''
Additional = '''

View File

@ -11,7 +11,7 @@ import (
// Resource represents all important information about a registration
// of which the client needs to keep track itself.
// WARNING: will be remove in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
// WARNING: will be removed in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
type Resource struct {
Body acme.Account `json:"body,omitempty"`
URI string `json:"uri,omitempty"`

View File

@ -48,6 +48,14 @@ linters:
- nlreturn
- testpackage
- wsl
- varnamelen
- nilnil
- ireturn
- govet
- forcetypeassert
- cyclop
- containedctx
- revive
issues:
exclude-rules:

View File

@ -1,3 +1,136 @@
# v0.9.11 - 2022/08/18
### Fix bugs
* Fix unexpected behavior when buffer ends with backslash ( #383 )
* Fix stream decoding of escaped character ( #387 )
# v0.9.10 - 2022/07/15
### Fix bugs
* Fix boundary exception of type caching ( #382 )
# v0.9.9 - 2022/07/15
### Fix bugs
* Fix encoding of directed interface with typed nil ( #377 )
* Fix embedded primitive type encoding using alias ( #378 )
* Fix slice/array type encoding with types implementing MarshalJSON ( #379 )
* Fix unicode decoding when the expected buffer state is not met after reading ( #380 )
# v0.9.8 - 2022/06/30
### Fix bugs
* Fix decoding of surrogate-pair ( #365 )
* Fix handling of embedded primitive type ( #366 )
* Add validation of escape sequence for decoder ( #367 )
* Fix stream tokenizing respecting UseNumber ( #369 )
* Fix encoding when struct pointer type that implements Marshal JSON is embedded ( #375 )
### Improve performance
* Improve performance of linkRecursiveCode ( #368 )
# v0.9.7 - 2022/04/22
### Fix bugs
#### Encoder
* Add filtering process for encoding on slow path ( #355 )
* Fix encoding of interface{} with pointer type ( #363 )
#### Decoder
* Fix map key decoder that implements UnmarshalJSON ( #353 )
* Fix decoding of []uint8 type ( #361 )
### New features
* Add DebugWith option for encoder ( #356 )
# v0.9.6 - 2022/03/22
### Fix bugs
* Correct the handling of the minimum value of int type for decoder ( #344 )
* Fix bugs of stream decoder's bufferSize ( #349 )
* Add a guard to use typeptr more safely ( #351 )
### Improve decoder performance
* Improve escapeString's performance ( #345 )
### Others
* Update go version for CI ( #347 )
# v0.9.5 - 2022/03/04
### Fix bugs
* Fix panic when decoding time.Time with context ( #328 )
* Fix reading the next character in buffer to nul consideration ( #338 )
* Fix incorrect handling on skipValue ( #341 )
### Improve decoder performance
* Improve performance when a payload contains escape sequence ( #334 )
# v0.9.4 - 2022/01/21
* Fix IsNilForMarshaler for string type with omitempty ( #323 )
* Fix the case where the embedded field is at the end ( #326 )
# v0.9.3 - 2022/01/14
* Fix logic of removing struct field for decoder ( #322 )
# v0.9.2 - 2022/01/14
* Add invalid decoder to delay type error judgment at decode ( #321 )
# v0.9.1 - 2022/01/11
* Fix encoding of MarshalText/MarshalJSON operation with head offset ( #319 )
# v0.9.0 - 2022/01/05
### New feature
* Supports dynamic filtering of struct fields ( #314 )
### Improve encoding performance
* Improve map encoding performance ( #310 )
* Optimize encoding path for escaped string ( #311 )
* Add encoding option for performance ( #312 )
### Fix bugs
* Fix panic at encoding map value on 1.18 ( #310 )
* Fix MarshalIndent for interface type ( #317 )
# v0.8.1 - 2021/12/05
* Fix operation conversion from PtrHead to Head in Recursive type ( #305 )
# v0.8.0 - 2021/12/02
* Fix embedded field conflict behavior ( #300 )
* Refactor compiler for encoder ( #301 #302 )
# v0.7.10 - 2021/10/16
* Fix conversion from pointer to uint64 ( #294 )
# v0.7.9 - 2021/09/28
* Fix encoding of nil value about interface type that has method ( #291 )
# v0.7.8 - 2021/09/01
* Fix mapassign_faststr for indirect struct type ( #283 )

View File

@ -13,7 +13,7 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
```
* version ( expected release date )
* v0.7.0
* v0.9.0
|
| while maintaining compatibility with encoding/json, we will add convenient APIs
|
@ -21,9 +21,8 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
* v1.0.0
```
We are accepting requests for features that will be implemented between v0.7.0 and v.1.0.0.
We are accepting requests for features that will be implemented between v0.9.0 and v.1.0.0.
If you have the API you need, please submit your issue [here](https://github.com/goccy/go-json/issues).
For example, I'm thinking of supporting `context.Context` of `json.Marshaler` and decoding using JSON Path.
# Features
@ -32,6 +31,7 @@ For example, I'm thinking of supporting `context.Context` of `json.Marshaler` an
- Flexible customization with options
- Coloring the encoded string
- Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON`
- Can dynamically filter the fields of the structure type-safely
# Installation

View File

@ -1,7 +1,7 @@
version: '2'
services:
go-json:
image: golang:1.16
image: golang:1.18
volumes:
- '.:/go/src/go-json'
deploy:

View File

@ -3,6 +3,7 @@ package json
import (
"context"
"io"
"os"
"unsafe"
"github.com/goccy/go-json/internal/encoder"
@ -61,6 +62,8 @@ func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v interface{}, o
if e.enabledHTMLEscape {
ctx.Option.Flag |= encoder.HTMLEscapeOption
}
ctx.Option.Flag |= encoder.NormalizeUTF8Option
ctx.Option.DebugOut = os.Stdout
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
@ -111,7 +114,7 @@ func (e *Encoder) SetIndent(prefix, indent string) {
func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
rctx := encoder.TakeRuntimeContext()
rctx.Option.Flag = 0
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.ContextOption
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.ContextOption
rctx.Option.Context = ctx
for _, optFunc := range optFuncs {
optFunc(rctx.Option)
@ -139,7 +142,7 @@ func marshal(v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
ctx := encoder.TakeRuntimeContext()
ctx.Option.Flag = 0
ctx.Option.Flag |= encoder.HTMLEscapeOption
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
@ -166,7 +169,7 @@ func marshalNoEscape(v interface{}) ([]byte, error) {
ctx := encoder.TakeRuntimeContext()
ctx.Option.Flag = 0
ctx.Option.Flag |= encoder.HTMLEscapeOption
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
buf, err := encodeNoEscape(ctx, v)
if err != nil {
@ -190,7 +193,7 @@ func marshalIndent(v interface{}, prefix, indent string, optFuncs ...EncodeOptio
ctx := encoder.TakeRuntimeContext()
ctx.Option.Flag = 0
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.IndentOption)
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.IndentOption)
for _, optFunc := range optFuncs {
optFunc(ctx.Option)
}
@ -220,7 +223,7 @@ func encode(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error) {
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
if err != nil {
return nil, err
}
@ -248,7 +251,7 @@ func encodeNoEscape(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error)
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
if err != nil {
return nil, err
}
@ -275,7 +278,7 @@ func encodeIndent(ctx *encoder.RuntimeContext, v interface{}, prefix, indent str
typ := header.typ
typeptr := uintptr(unsafe.Pointer(typ))
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
if err != nil {
return nil, err
}

View File

@ -23,9 +23,8 @@ func byteUnmarshalerSliceDecoder(typ *runtime.Type, structName string, fieldName
unmarshalDecoder = newUnmarshalJSONDecoder(runtime.PtrTo(typ), structName, fieldName)
case runtime.PtrTo(typ).Implements(unmarshalTextType):
unmarshalDecoder = newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName)
}
if unmarshalDecoder == nil {
return nil
default:
unmarshalDecoder, _ = compileUint8(typ, structName, fieldName)
}
return newSliceDecoder(unmarshalDecoder, typ, 1, structName, fieldName)
}

View File

@ -9,7 +9,6 @@ import (
"unicode"
"unsafe"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
@ -25,7 +24,7 @@ func init() {
if typeAddr == nil {
typeAddr = &runtime.TypeAddr{}
}
cachedDecoder = make([]Decoder, typeAddr.AddrRange>>typeAddr.AddrShift)
cachedDecoder = make([]Decoder, typeAddr.AddrRange>>typeAddr.AddrShift+1)
}
func loadDecoderMap() map[uintptr]Decoder {
@ -126,13 +125,7 @@ func compile(typ *runtime.Type, structName, fieldName string, structTypeToDecode
case reflect.Func:
return compileFunc(typ, structName, fieldName)
}
return nil, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(typ),
Offset: 0,
Struct: structName,
Field: fieldName,
}
return newInvalidDecoder(typ, structName, fieldName), nil
}
func isStringTagSupportedType(typ *runtime.Type) bool {
@ -161,6 +154,9 @@ func compileMapKey(typ *runtime.Type, structName, fieldName string, structTypeTo
if runtime.PtrTo(typ).Implements(unmarshalTextType) {
return newUnmarshalTextDecoder(runtime.PtrTo(typ), structName, fieldName), nil
}
if typ.Kind() == reflect.String {
return newStringDecoder(structName, fieldName), nil
}
dec, err := compile(typ, structName, fieldName, structTypeToDecoder)
if err != nil {
return nil, err
@ -174,17 +170,9 @@ func compileMapKey(typ *runtime.Type, structName, fieldName string, structTypeTo
case *ptrDecoder:
dec = t.dec
default:
goto ERROR
return newInvalidDecoder(typ, structName, fieldName), nil
}
}
ERROR:
return nil, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(typ),
Offset: 0,
Struct: structName,
Field: fieldName,
}
}
func compilePtr(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) {
@ -322,64 +310,21 @@ func compileFunc(typ *runtime.Type, strutName, fieldName string) (Decoder, error
return newFuncDecoder(typ, strutName, fieldName), nil
}
func removeConflictFields(fieldMap map[string]*structFieldSet, conflictedMap map[string]struct{}, dec *structDecoder, field reflect.StructField) {
for k, v := range dec.fieldMap {
if _, exists := conflictedMap[k]; exists {
// already conflicted key
func typeToStructTags(typ *runtime.Type) runtime.StructTags {
tags := runtime.StructTags{}
fieldNum := typ.NumField()
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if runtime.IsIgnoredStructField(field) {
continue
}
set, exists := fieldMap[k]
if !exists {
fieldSet := &structFieldSet{
dec: v.dec,
offset: field.Offset + v.offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
continue
}
if set.isTaggedKey {
if v.isTaggedKey {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
}
} else {
if v.isTaggedKey {
fieldSet := &structFieldSet{
dec: v.dec,
offset: field.Offset + v.offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
} else {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
}
}
tags = append(tags, runtime.StructTagFromField(field))
}
return tags
}
func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeToDecoder map[uintptr]Decoder) (Decoder, error) {
fieldNum := typ.NumField()
conflictedMap := map[string]struct{}{}
fieldMap := map[string]*structFieldSet{}
typeptr := uintptr(unsafe.Pointer(typ))
if dec, exists := structTypeToDecoder[typeptr]; exists {
@ -388,6 +333,8 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
structDec := newStructDecoder(structName, fieldName, fieldMap)
structTypeToDecoder[typeptr] = structDec
structName = typ.Name()
tags := typeToStructTags(typ)
allFields := []*structFieldSet{}
for i := 0; i < fieldNum; i++ {
field := typ.Field(i)
if runtime.IsIgnoredStructField(field) {
@ -405,7 +352,19 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
// recursive definition
continue
}
removeConflictFields(fieldMap, conflictedMap, stDec, field)
for k, v := range stDec.fieldMap {
if tags.ExistsKey(k) {
continue
}
fieldSet := &structFieldSet{
dec: v.dec,
offset: field.Offset + v.offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
}
allFields = append(allFields, fieldSet)
}
} else if pdec, ok := dec.(*ptrDecoder); ok {
contentDec := pdec.contentDecoder()
if pdec.typ == typ {
@ -421,12 +380,9 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
}
if dec, ok := contentDec.(*structDecoder); ok {
for k, v := range dec.fieldMap {
if _, exists := conflictedMap[k]; exists {
// already conflicted key
if tags.ExistsKey(k) {
continue
}
set, exists := fieldMap[k]
if !exists {
fieldSet := &structFieldSet{
dec: newAnonymousFieldDecoder(pdec.typ, v.offset, v.dec),
offset: field.Offset,
@ -435,46 +391,27 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
keyLen: int64(len(k)),
err: fieldSetErr,
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
}
continue
}
if set.isTaggedKey {
if v.isTaggedKey {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
allFields = append(allFields, fieldSet)
}
} else {
if v.isTaggedKey {
fieldSet := &structFieldSet{
dec: newAnonymousFieldDecoder(pdec.typ, v.offset, v.dec),
dec: pdec,
offset: field.Offset,
isTaggedKey: v.isTaggedKey,
key: k,
keyLen: int64(len(k)),
err: fieldSetErr,
isTaggedKey: tag.IsTaggedKey,
key: field.Name,
keyLen: int64(len(field.Name)),
}
fieldMap[k] = fieldSet
lower := strings.ToLower(k)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
allFields = append(allFields, fieldSet)
}
} else {
// conflict tag key
delete(fieldMap, k)
delete(fieldMap, strings.ToLower(k))
conflictedMap[k] = struct{}{}
conflictedMap[strings.ToLower(k)] = struct{}{}
}
}
}
fieldSet := &structFieldSet{
dec: dec,
offset: field.Offset,
isTaggedKey: tag.IsTaggedKey,
key: field.Name,
keyLen: int64(len(field.Name)),
}
allFields = append(allFields, fieldSet)
}
} else {
if tag.IsString && isStringTagSupportedType(runtime.Type2RType(field.Type)) {
@ -493,18 +430,58 @@ func compileStruct(typ *runtime.Type, structName, fieldName string, structTypeTo
key: key,
keyLen: int64(len(key)),
}
fieldMap[key] = fieldSet
lower := strings.ToLower(key)
if _, exists := fieldMap[lower]; !exists {
fieldMap[lower] = fieldSet
allFields = append(allFields, fieldSet)
}
}
for _, set := range filterDuplicatedFields(allFields) {
fieldMap[set.key] = set
lower := strings.ToLower(set.key)
if _, exists := fieldMap[lower]; !exists {
// first win
fieldMap[lower] = set
}
}
delete(structTypeToDecoder, typeptr)
structDec.tryOptimize()
return structDec, nil
}
func filterDuplicatedFields(allFields []*structFieldSet) []*structFieldSet {
fieldMap := map[string][]*structFieldSet{}
for _, field := range allFields {
fieldMap[field.key] = append(fieldMap[field.key], field)
}
duplicatedFieldMap := map[string]struct{}{}
for k, sets := range fieldMap {
sets = filterFieldSets(sets)
if len(sets) != 1 {
duplicatedFieldMap[k] = struct{}{}
}
}
filtered := make([]*structFieldSet, 0, len(allFields))
for _, field := range allFields {
if _, exists := duplicatedFieldMap[field.key]; exists {
continue
}
filtered = append(filtered, field)
}
return filtered
}
func filterFieldSets(sets []*structFieldSet) []*structFieldSet {
if len(sets) == 1 {
return sets
}
filtered := make([]*structFieldSet, 0, len(sets))
for _, set := range sets {
if set.isTaggedKey {
filtered = append(filtered, set)
}
}
return filtered
}
func implementsUnmarshalJSONType(typ *runtime.Type) bool {
return typ.Implements(unmarshalJSONType) || typ.Implements(unmarshalJSONContextType)
}

View File

@ -1,3 +1,4 @@
//go:build !race
// +build !race
package decoder

View File

@ -1,3 +1,4 @@
//go:build race
// +build race
package decoder

View File

@ -192,15 +192,15 @@ func (d *intDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
}
switch d.kind {
case reflect.Int8:
if i64 <= -1*(1<<7) || (1<<7) <= i64 {
if i64 < -1*(1<<7) || (1<<7) <= i64 {
return d.typeError(bytes, s.totalOffset())
}
case reflect.Int16:
if i64 <= -1*(1<<15) || (1<<15) <= i64 {
if i64 < -1*(1<<15) || (1<<15) <= i64 {
return d.typeError(bytes, s.totalOffset())
}
case reflect.Int32:
if i64 <= -1*(1<<31) || (1<<31) <= i64 {
if i64 < -1*(1<<31) || (1<<31) <= i64 {
return d.typeError(bytes, s.totalOffset())
}
}
@ -225,15 +225,15 @@ func (d *intDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.P
}
switch d.kind {
case reflect.Int8:
if i64 <= -1*(1<<7) || (1<<7) <= i64 {
if i64 < -1*(1<<7) || (1<<7) <= i64 {
return 0, d.typeError(bytes, cursor)
}
case reflect.Int16:
if i64 <= -1*(1<<15) || (1<<15) <= i64 {
if i64 < -1*(1<<15) || (1<<15) <= i64 {
return 0, d.typeError(bytes, cursor)
}
case reflect.Int32:
if i64 <= -1*(1<<31) || (1<<31) <= i64 {
if i64 < -1*(1<<31) || (1<<31) <= i64 {
return 0, d.typeError(bytes, cursor)
}
}

View File

@ -0,0 +1,45 @@
package decoder
import (
"reflect"
"unsafe"
"github.com/goccy/go-json/internal/errors"
"github.com/goccy/go-json/internal/runtime"
)
type invalidDecoder struct {
typ *runtime.Type
kind reflect.Kind
structName string
fieldName string
}
func newInvalidDecoder(typ *runtime.Type, structName, fieldName string) *invalidDecoder {
return &invalidDecoder{
typ: typ,
kind: typ.Kind(),
structName: structName,
fieldName: fieldName,
}
}
func (d *invalidDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) error {
return &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(d.typ),
Offset: s.totalOffset(),
Struct: d.structName,
Field: d.fieldName,
}
}
func (d *invalidDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe.Pointer) (int64, error) {
return 0, &errors.UnmarshalTypeError{
Value: "object",
Type: runtime.RType2Type(d.typ),
Offset: cursor,
Struct: d.structName,
Field: d.fieldName,
}
}

View File

@ -87,13 +87,13 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
if mapValue == nil {
mapValue = makemap(d.mapType, 0)
}
if s.buf[s.cursor+1] == '}' {
s.cursor++
if s.equalChar('}') {
*(*unsafe.Pointer)(p) = mapValue
s.cursor += 2
s.cursor++
return nil
}
for {
s.cursor++
k := unsafe_New(d.keyType)
if err := d.keyDecoder.DecodeStream(s, depth, k); err != nil {
return err
@ -117,6 +117,7 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
if !s.equalChar(',') {
return errors.ErrExpected("comma after object value", s.totalOffset())
}
s.cursor++
}
}

View File

@ -103,7 +103,7 @@ func (s *Stream) statForRetry() ([]byte, int64, unsafe.Pointer) {
func (s *Stream) Reset() {
s.reset()
s.bufSize = initBufSize
s.bufSize = int64(len(s.buf))
}
func (s *Stream) More() bool {
@ -138,8 +138,11 @@ func (s *Stream) Token() (interface{}, error) {
s.cursor++
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
bytes := floatBytes(s)
s := *(*string)(unsafe.Pointer(&bytes))
f64, err := strconv.ParseFloat(s, 64)
str := *(*string)(unsafe.Pointer(&bytes))
if s.UseNumber {
return json.Number(str), nil
}
f64, err := strconv.ParseFloat(str, 64)
if err != nil {
return nil, err
}
@ -277,7 +280,7 @@ func (s *Stream) skipObject(depth int64) error {
if char(p, cursor) == nul {
s.cursor = cursor
if s.read() {
_, cursor, p = s.statForRetry()
_, cursor, p = s.stat()
continue
}
return errors.ErrUnexpectedEndOfJSON("string of object", cursor)
@ -340,7 +343,7 @@ func (s *Stream) skipArray(depth int64) error {
if char(p, cursor) == nul {
s.cursor = cursor
if s.read() {
_, cursor, p = s.statForRetry()
_, cursor, p = s.stat()
continue
}
return errors.ErrUnexpectedEndOfJSON("string of object", cursor)
@ -398,7 +401,7 @@ func (s *Stream) skipValue(depth int64) error {
if char(p, cursor) == nul {
s.cursor = cursor
if s.read() {
_, cursor, p = s.statForRetry()
_, cursor, p = s.stat()
continue
}
return errors.ErrUnexpectedEndOfJSON("value of string", s.totalOffset())
@ -423,7 +426,6 @@ func (s *Stream) skipValue(depth int64) error {
continue
} else if c == nul {
if s.read() {
s.cursor-- // for retry current character
_, cursor, p = s.stat()
continue
}

View File

@ -1,6 +1,8 @@
package decoder
import (
"bytes"
"fmt"
"reflect"
"unicode"
"unicode/utf16"
@ -93,24 +95,30 @@ func unicodeToRune(code []byte) rune {
return r
}
func readAtLeast(s *Stream, n int64, p *unsafe.Pointer) bool {
for s.cursor+n >= s.length {
if !s.read() {
return false
}
*p = s.bufptr()
}
return true
}
func decodeUnicodeRune(s *Stream, p unsafe.Pointer) (rune, int64, unsafe.Pointer, error) {
const defaultOffset = 5
const surrogateOffset = 11
if s.cursor+defaultOffset >= s.length {
if !s.read() {
if !readAtLeast(s, defaultOffset, &p) {
return rune(0), 0, nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
p = s.bufptr()
}
r := unicodeToRune(s.buf[s.cursor+1 : s.cursor+defaultOffset])
if utf16.IsSurrogate(r) {
if s.cursor+surrogateOffset >= s.length {
s.read()
p = s.bufptr()
if !readAtLeast(s, surrogateOffset, &p) {
return unicode.ReplacementChar, defaultOffset, p, nil
}
if s.cursor+surrogateOffset >= s.length || s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' {
if s.buf[s.cursor+defaultOffset] != '\\' || s.buf[s.cursor+defaultOffset+1] != 'u' {
return unicode.ReplacementChar, defaultOffset, p, nil
}
r2 := unicodeToRune(s.buf[s.cursor+defaultOffset+2 : s.cursor+surrogateOffset])
@ -163,6 +171,7 @@ RETRY:
if !s.read() {
return nil, errors.ErrInvalidCharacter(s.char(), "escaped string", s.totalOffset())
}
p = s.bufptr()
goto RETRY
default:
return nil, errors.ErrUnexpectedEndOfJSON("string", s.totalOffset())
@ -308,49 +317,36 @@ func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, err
cursor++
start := cursor
b := (*sliceHeader)(unsafe.Pointer(&buf)).data
escaped := 0
for {
switch char(b, cursor) {
case '\\':
escaped++
cursor++
switch char(b, cursor) {
case '"':
buf[cursor] = '"'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '\\':
buf[cursor] = '\\'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '/':
buf[cursor] = '/'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'b':
buf[cursor] = '\b'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'f':
buf[cursor] = '\f'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'n':
buf[cursor] = '\n'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 'r':
buf[cursor] = '\r'
buf = append(buf[:cursor-1], buf[cursor:]...)
case 't':
buf[cursor] = '\t'
buf = append(buf[:cursor-1], buf[cursor:]...)
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
cursor++
case 'u':
buflen := int64(len(buf))
if cursor+5 >= buflen {
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
}
code := unicodeToRune(buf[cursor+1 : cursor+5])
unicode := []byte(string(code))
buf = append(append(buf[:cursor-1], unicode...), buf[cursor+5:]...)
for i := int64(1); i <= 4; i++ {
c := char(b, cursor+i)
if !(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')) {
return nil, 0, errors.ErrSyntax(fmt.Sprintf("json: invalid character %c in \\u hexadecimal character escape", c), cursor+i)
}
}
cursor += 5
default:
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
}
continue
case '"':
literal := buf[start:cursor]
if escaped > 0 {
literal = literal[:unescapeString(literal)]
}
cursor++
return literal, cursor, nil
case nul:
@ -369,3 +365,77 @@ func (d *stringDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, err
}
}
}
var unescapeMap = [256]byte{
'"': '"',
'\\': '\\',
'/': '/',
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t',
}
func unsafeAdd(ptr unsafe.Pointer, offset int) unsafe.Pointer {
return unsafe.Pointer(uintptr(ptr) + uintptr(offset))
}
func unescapeString(buf []byte) int {
p := (*sliceHeader)(unsafe.Pointer(&buf)).data
end := unsafeAdd(p, len(buf))
src := unsafeAdd(p, bytes.IndexByte(buf, '\\'))
dst := src
for src != end {
c := char(src, 0)
if c == '\\' {
escapeChar := char(src, 1)
if escapeChar != 'u' {
*(*byte)(dst) = unescapeMap[escapeChar]
src = unsafeAdd(src, 2)
dst = unsafeAdd(dst, 1)
} else {
v1 := hexToInt[char(src, 2)]
v2 := hexToInt[char(src, 3)]
v3 := hexToInt[char(src, 4)]
v4 := hexToInt[char(src, 5)]
code := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4)
if code >= 0xd800 && code < 0xdc00 && uintptr(unsafeAdd(src, 11)) < uintptr(end) {
if char(src, 6) == '\\' && char(src, 7) == 'u' {
v1 := hexToInt[char(src, 8)]
v2 := hexToInt[char(src, 9)]
v3 := hexToInt[char(src, 10)]
v4 := hexToInt[char(src, 11)]
lo := rune((v1 << 12) | (v2 << 8) | (v3 << 4) | v4)
if lo >= 0xdc00 && lo < 0xe000 {
code = (code-0xd800)<<10 | (lo - 0xdc00) + 0x10000
src = unsafeAdd(src, 6)
}
}
}
var b [utf8.UTFMax]byte
n := utf8.EncodeRune(b[:], code)
switch n {
case 4:
*(*byte)(unsafeAdd(dst, 3)) = b[3]
fallthrough
case 3:
*(*byte)(unsafeAdd(dst, 2)) = b[2]
fallthrough
case 2:
*(*byte)(unsafeAdd(dst, 1)) = b[1]
fallthrough
case 1:
*(*byte)(unsafeAdd(dst, 0)) = b[0]
}
src = unsafeAdd(src, 6)
dst = unsafeAdd(dst, n)
}
} else {
*(*byte)(dst) = c
src = unsafeAdd(src, 1)
dst = unsafeAdd(dst, 1)
}
}
return int(uintptr(dst) - uintptr(p))
}

View File

@ -1,6 +1,7 @@
package decoder
import (
"context"
"encoding/json"
"unsafe"
@ -46,13 +47,20 @@ func (d *unmarshalJSONDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Poi
typ: d.typ,
ptr: p,
}))
switch v := v.(type) {
case unmarshalerContext:
var ctx context.Context
if (s.Option.Flags & ContextOption) != 0 {
if err := v.(unmarshalerContext).UnmarshalJSON(s.Option.Context, dst); err != nil {
ctx = s.Option.Context
} else {
ctx = context.Background()
}
if err := v.UnmarshalJSON(ctx, dst); err != nil {
d.annotateError(s.cursor, err)
return err
}
} else {
if err := v.(json.Unmarshaler).UnmarshalJSON(dst); err != nil {
case json.Unmarshaler:
if err := v.UnmarshalJSON(dst); err != nil {
d.annotateError(s.cursor, err)
return err
}

1017
vendor/github.com/goccy/go-json/internal/encoder/code.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +1,32 @@
//go:build !race
// +build !race
package encoder
import (
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr {
return compileToGetCodeSetSlowPath(typeptr)
func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr {
codeSet, err := compileToGetCodeSetSlowPath(typeptr)
if err != nil {
return nil, err
}
return getFilteredCodeSetIfNeeded(ctx, codeSet)
}
index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
return codeSet, nil
}
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
noescapeKeyCode, err := compileHead(&compileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
})
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
return nil, err
}
escapeKeyCode, err := compileHead(&compileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
escapeKey: true,
})
return filtered, nil
}
codeSet, err := newCompiler().compile(typeptr)
if err != nil {
return nil, err
}
noescapeKeyCode = copyOpcode(noescapeKeyCode)
escapeKeyCode = copyOpcode(escapeKeyCode)
setTotalLengthToInterfaceOp(noescapeKeyCode)
setTotalLengthToInterfaceOp(escapeKeyCode)
interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode)
interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode)
codeLength := noescapeKeyCode.TotalLength()
codeSet := &OpcodeSet{
Type: copiedType,
NoescapeKeyCode: noescapeKeyCode,
EscapeKeyCode: escapeKeyCode,
InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode,
InterfaceEscapeKeyCode: interfaceEscapeKeyCode,
CodeLength: codeLength,
EndCode: ToEndCode(interfaceNoescapeKeyCode),
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
return nil, err
}
cachedOpcodeSets[index] = codeSet
return codeSet, nil
return filtered, nil
}

View File

@ -1,65 +1,45 @@
//go:build race
// +build race
package encoder
import (
"sync"
"unsafe"
"github.com/goccy/go-json/internal/runtime"
)
var setsMu sync.RWMutex
func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr {
return compileToGetCodeSetSlowPath(typeptr)
func CompileToGetCodeSet(ctx *RuntimeContext, typeptr uintptr) (*OpcodeSet, error) {
if typeptr > typeAddr.MaxTypeAddr || typeptr < typeAddr.BaseTypeAddr {
codeSet, err := compileToGetCodeSetSlowPath(typeptr)
if err != nil {
return nil, err
}
return getFilteredCodeSetIfNeeded(ctx, codeSet)
}
index := (typeptr - typeAddr.BaseTypeAddr) >> typeAddr.AddrShift
setsMu.RLock()
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
setsMu.RUnlock()
return codeSet, nil
return nil, err
}
setsMu.RUnlock()
return filtered, nil
}
setsMu.RUnlock()
// noescape trick for header.typ ( reflect.*rtype )
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
noescapeKeyCode, err := compileHead(&compileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
})
codeSet, err := newCompiler().compile(typeptr)
if err != nil {
return nil, err
}
escapeKeyCode, err := compileHead(&compileContext{
typ: copiedType,
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
escapeKey: true,
})
filtered, err := getFilteredCodeSetIfNeeded(ctx, codeSet)
if err != nil {
return nil, err
}
noescapeKeyCode = copyOpcode(noescapeKeyCode)
escapeKeyCode = copyOpcode(escapeKeyCode)
setTotalLengthToInterfaceOp(noescapeKeyCode)
setTotalLengthToInterfaceOp(escapeKeyCode)
interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode)
interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode)
codeLength := noescapeKeyCode.TotalLength()
codeSet := &OpcodeSet{
Type: copiedType,
NoescapeKeyCode: noescapeKeyCode,
EscapeKeyCode: escapeKeyCode,
InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode,
InterfaceEscapeKeyCode: interfaceEscapeKeyCode,
CodeLength: codeLength,
EndCode: ToEndCode(interfaceNoescapeKeyCode),
}
setsMu.Lock()
cachedOpcodeSets[index] = codeSet
setsMu.Unlock()
return codeSet, nil
return filtered, nil
}

View File

@ -9,44 +9,20 @@ import (
)
type compileContext struct {
typ *runtime.Type
opcodeIndex uint32
ptrIndex int
indent uint32
escapeKey bool
structTypeToCompiledCode map[uintptr]*CompiledCode
parent *compileContext
structTypeToCodes map[uintptr]Opcodes
recursiveCodes *Opcodes
}
func (c *compileContext) context() *compileContext {
return &compileContext{
typ: c.typ,
opcodeIndex: c.opcodeIndex,
ptrIndex: c.ptrIndex,
indent: c.indent,
escapeKey: c.escapeKey,
structTypeToCompiledCode: c.structTypeToCompiledCode,
parent: c,
}
func (c *compileContext) incIndent() {
c.indent++
}
func (c *compileContext) withType(typ *runtime.Type) *compileContext {
ctx := c.context()
ctx.typ = typ
return ctx
}
func (c *compileContext) incIndent() *compileContext {
ctx := c.context()
ctx.indent++
return ctx
}
func (c *compileContext) decIndent() *compileContext {
ctx := c.context()
ctx.indent--
return ctx
func (c *compileContext) decIndent() {
c.indent--
}
func (c *compileContext) incIndex() {
@ -61,30 +37,18 @@ func (c *compileContext) decIndex() {
func (c *compileContext) incOpcodeIndex() {
c.opcodeIndex++
if c.parent != nil {
c.parent.incOpcodeIndex()
}
}
func (c *compileContext) decOpcodeIndex() {
c.opcodeIndex--
if c.parent != nil {
c.parent.decOpcodeIndex()
}
}
func (c *compileContext) incPtrIndex() {
c.ptrIndex++
if c.parent != nil {
c.parent.incPtrIndex()
}
}
func (c *compileContext) decPtrIndex() {
c.ptrIndex--
if c.parent != nil {
c.parent.decPtrIndex()
}
}
const (

View File

@ -0,0 +1,126 @@
package encoder
import "unicode/utf8"
const (
// The default lowest and highest continuation byte.
locb = 128 //0b10000000
hicb = 191 //0b10111111
// These names of these constants are chosen to give nice alignment in the
// table below. The first nibble is an index into acceptRanges or F for
// special one-byte cases. The second nibble is the Rune length or the
// Status for the special one-byte case.
xx = 0xF1 // invalid: size 1
as = 0xF0 // ASCII: size 1
s1 = 0x02 // accept 0, size 2
s2 = 0x13 // accept 1, size 3
s3 = 0x03 // accept 0, size 3
s4 = 0x23 // accept 2, size 3
s5 = 0x34 // accept 3, size 4
s6 = 0x04 // accept 0, size 4
s7 = 0x44 // accept 4, size 4
)
// first is information about the first byte in a UTF-8 sequence.
var first = [256]uint8{
// 1 2 3 4 5 6 7 8 9 A B C D E F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x00-0x0F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x10-0x1F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x20-0x2F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x30-0x3F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x40-0x4F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x50-0x5F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x60-0x6F
as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x70-0x7F
// 1 2 3 4 5 6 7 8 9 A B C D E F
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x80-0x8F
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x90-0x9F
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xA0-0xAF
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xB0-0xBF
xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xC0-0xCF
s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xD0-0xDF
s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3, // 0xE0-0xEF
s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xF0-0xFF
}
const (
lineSep = byte(168) //'\u2028'
paragraphSep = byte(169) //'\u2029'
)
type decodeRuneState int
const (
validUTF8State decodeRuneState = iota
runeErrorState
lineSepState
paragraphSepState
)
func decodeRuneInString(s string) (decodeRuneState, int) {
n := len(s)
s0 := s[0]
x := first[s0]
if x >= as {
// The following code simulates an additional check for x == xx and
// handling the ASCII and invalid cases accordingly. This mask-and-or
// approach prevents an additional branch.
mask := rune(x) << 31 >> 31 // Create 0x0000 or 0xFFFF.
if rune(s[0])&^mask|utf8.RuneError&mask == utf8.RuneError {
return runeErrorState, 1
}
return validUTF8State, 1
}
sz := int(x & 7)
if n < sz {
return runeErrorState, 1
}
s1 := s[1]
switch x >> 4 {
case 0:
if s1 < locb || hicb < s1 {
return runeErrorState, 1
}
case 1:
if s1 < 0xA0 || hicb < s1 {
return runeErrorState, 1
}
case 2:
if s1 < locb || 0x9F < s1 {
return runeErrorState, 1
}
case 3:
if s1 < 0x90 || hicb < s1 {
return runeErrorState, 1
}
case 4:
if s1 < locb || 0x8F < s1 {
return runeErrorState, 1
}
}
if sz <= 2 {
return validUTF8State, 2
}
s2 := s[2]
if s2 < locb || hicb < s2 {
return runeErrorState, 1
}
if sz <= 3 {
// separator character prefixes: [2]byte{226, 128}
if s0 == 226 && s1 == 128 {
switch s2 {
case lineSep:
return lineSepState, 3
case paragraphSep:
return paragraphSepState, 3
}
}
return validUTF8State, 3
}
s3 := s[3]
if s3 < locb || hicb < s3 {
return runeErrorState, 1
}
return validUTF8State, 4
}

View File

@ -101,6 +101,22 @@ type OpcodeSet struct {
InterfaceEscapeKeyCode *Opcode
CodeLength int
EndCode *Opcode
Code Code
QueryCache map[string]*OpcodeSet
cacheMu sync.RWMutex
}
func (s *OpcodeSet) getQueryCache(hash string) *OpcodeSet {
s.cacheMu.RLock()
codeSet := s.QueryCache[hash]
s.cacheMu.RUnlock()
return codeSet
}
func (s *OpcodeSet) setQueryCache(hash string, codeSet *OpcodeSet) {
s.cacheMu.Lock()
s.QueryCache[hash] = codeSet
s.cacheMu.Unlock()
}
type CompiledCode struct {
@ -222,33 +238,56 @@ func (m *Mapslice) Swap(i, j int) {
m.Items[i], m.Items[j] = m.Items[j], m.Items[i]
}
//nolint:structcheck,unused
type mapIter struct {
key unsafe.Pointer
elem unsafe.Pointer
t unsafe.Pointer
h unsafe.Pointer
buckets unsafe.Pointer
bptr unsafe.Pointer
overflow unsafe.Pointer
oldoverflow unsafe.Pointer
startBucket uintptr
offset uint8
wrapped bool
B uint8
i uint8
bucket uintptr
checkBucket uintptr
}
type MapContext struct {
Pos []int
Start int
First int
Idx int
Slice *Mapslice
Buf []byte
Len int
Iter mapIter
}
var mapContextPool = sync.Pool{
New: func() interface{} {
return &MapContext{}
return &MapContext{
Slice: &Mapslice{},
}
},
}
func NewMapContext(mapLen int) *MapContext {
func NewMapContext(mapLen int, unorderedMap bool) *MapContext {
ctx := mapContextPool.Get().(*MapContext)
if ctx.Slice == nil {
ctx.Slice = &Mapslice{
Items: make([]MapItem, 0, mapLen),
}
}
if cap(ctx.Pos) < (mapLen*2 + 1) {
ctx.Pos = make([]int, 0, mapLen*2+1)
ctx.Slice.Items = make([]MapItem, 0, mapLen)
if !unorderedMap {
if len(ctx.Slice.Items) < mapLen {
ctx.Slice.Items = make([]MapItem, mapLen)
} else {
ctx.Pos = ctx.Pos[:0]
ctx.Slice.Items = ctx.Slice.Items[:0]
ctx.Slice.Items = ctx.Slice.Items[:mapLen]
}
}
ctx.Buf = ctx.Buf[:0]
ctx.Iter = mapIter{}
ctx.Idx = 0
ctx.Len = mapLen
return ctx
}
@ -256,17 +295,17 @@ func ReleaseMapContext(c *MapContext) {
mapContextPool.Put(c)
}
//go:linkname MapIterInit reflect.mapiterinit
//go:linkname MapIterInit runtime.mapiterinit
//go:noescape
func MapIterInit(mapType *runtime.Type, m unsafe.Pointer) unsafe.Pointer
func MapIterInit(mapType *runtime.Type, m unsafe.Pointer, it *mapIter)
//go:linkname MapIterKey reflect.mapiterkey
//go:noescape
func MapIterKey(it unsafe.Pointer) unsafe.Pointer
func MapIterKey(it *mapIter) unsafe.Pointer
//go:linkname MapIterNext reflect.mapiternext
//go:noescape
func MapIterNext(it unsafe.Pointer)
func MapIterNext(it *mapIter)
//go:linkname MapLen reflect.maplen
//go:noescape
@ -374,7 +413,11 @@ func AppendMarshalJSON(ctx *RuntimeContext, code *Opcode, b []byte, v interface{
if !ok {
return AppendNull(ctx, b), nil
}
b, err := marshaler.MarshalJSON(ctx.Option.Context)
stdctx := ctx.Option.Context
if ctx.Option.Flag&FieldQueryOption != 0 {
stdctx = SetFieldQueryToContext(stdctx, code.FieldQuery)
}
b, err := marshaler.MarshalJSON(stdctx)
if err != nil {
return nil, &errors.MarshalerError{Type: reflect.TypeOf(v), Err: err}
}
@ -546,6 +589,8 @@ func IsNilForMarshaler(v interface{}) bool {
return rv.IsNil()
case reflect.Slice:
return rv.IsNil() || rv.Len() == 0
case reflect.String:
return rv.Len() == 0
}
return false
}

View File

@ -53,7 +53,18 @@ func numMask(numBitSize uint8) uint64 {
return 1<<numBitSize - 1
}
func AppendInt(_ *RuntimeContext, out []byte, u64 uint64, code *Opcode) []byte {
func AppendInt(_ *RuntimeContext, out []byte, p uintptr, code *Opcode) []byte {
var u64 uint64
switch code.NumBitSize {
case 8:
u64 = (uint64)(**(**uint8)(unsafe.Pointer(&p)))
case 16:
u64 = (uint64)(**(**uint16)(unsafe.Pointer(&p)))
case 32:
u64 = (uint64)(**(**uint32)(unsafe.Pointer(&p)))
case 64:
u64 = **(**uint64)(unsafe.Pointer(&p))
}
mask := numMask(code.NumBitSize)
n := u64 & mask
negative := (u64>>(code.NumBitSize-1))&1 == 1
@ -96,7 +107,18 @@ func AppendInt(_ *RuntimeContext, out []byte, u64 uint64, code *Opcode) []byte {
return append(out, b[i:]...)
}
func AppendUint(_ *RuntimeContext, out []byte, u64 uint64, code *Opcode) []byte {
func AppendUint(_ *RuntimeContext, out []byte, p uintptr, code *Opcode) []byte {
var u64 uint64
switch code.NumBitSize {
case 8:
u64 = (uint64)(**(**uint8)(unsafe.Pointer(&p)))
case 16:
u64 = (uint64)(**(**uint16)(unsafe.Pointer(&p)))
case 32:
u64 = (uint64)(**(**uint32)(unsafe.Pointer(&p)))
case 64:
u64 = **(**uint64)(unsafe.Pointer(&p))
}
mask := numMask(code.NumBitSize)
n := u64 & mask
if n < 10 {

View File

@ -1,3 +1,4 @@
//go:build !go1.13
// +build !go1.13
package encoder
@ -5,4 +6,4 @@ package encoder
import "unsafe"
//go:linkname MapIterValue reflect.mapitervalue
func MapIterValue(it unsafe.Pointer) unsafe.Pointer
func MapIterValue(it *mapIter) unsafe.Pointer

View File

@ -1,3 +1,4 @@
//go:build go1.13
// +build go1.13
package encoder
@ -5,4 +6,4 @@ package encoder
import "unsafe"
//go:linkname MapIterValue reflect.mapiterelem
func MapIterValue(it unsafe.Pointer) unsafe.Pointer
func MapIterValue(it *mapIter) unsafe.Pointer

View File

@ -38,26 +38,58 @@ type Opcode struct {
Flags OpFlags
Type *runtime.Type // go type
PrevField *Opcode // prev struct field
Jmp *CompiledCode // for recursive call
ElemIdx uint32 // offset to access array/slice/map elem
Length uint32 // offset to access slice/map length or array length
MapIter uint32 // offset to access map iterator
MapPos uint32 // offset to access position list for sorted map
FieldQuery *FieldQuery // field query for Interface / MarshalJSON / MarshalText
ElemIdx uint32 // offset to access array/slice elem
Length uint32 // offset to access slice length or array length
Indent uint32 // indent number
Size uint32 // array/slice elem size
DisplayIdx uint32 // opcode index
DisplayKey string // key text to display
}
func (c *Opcode) Validate() error {
var prevIdx uint32
for code := c; !code.IsEnd(); {
if prevIdx != 0 {
if code.DisplayIdx != prevIdx+1 {
return fmt.Errorf(
"invalid index. previous display index is %d but next is %d. dump = %s",
prevIdx, code.DisplayIdx, c.Dump(),
)
}
}
prevIdx = code.DisplayIdx
code = code.IterNext()
}
return nil
}
func (c *Opcode) IterNext() *Opcode {
if c == nil {
return nil
}
switch c.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
return c.End
default:
return c.Next
}
}
func (c *Opcode) IsEnd() bool {
if c == nil {
return true
}
return c.Op == OpEnd || c.Op == OpInterfaceEnd || c.Op == OpRecursiveEnd
}
func (c *Opcode) MaxIdx() uint32 {
max := uint32(0)
for _, value := range []uint32{
c.Idx,
c.ElemIdx,
c.Length,
c.MapIter,
c.MapPos,
c.Size,
} {
if max < value {
@ -273,43 +305,75 @@ func (c *Opcode) ToFieldType(isString bool) OpType {
return OpStructField
}
func newOpCode(ctx *compileContext, op OpType) *Opcode {
return newOpCodeWithNext(ctx, op, newEndOp(ctx))
func newOpCode(ctx *compileContext, typ *runtime.Type, op OpType) *Opcode {
return newOpCodeWithNext(ctx, typ, op, newEndOp(ctx, typ))
}
func opcodeOffset(idx int) uint32 {
return uint32(idx) * uintptrSize
}
func getCodeAddrByIdx(head *Opcode, idx uint32) *Opcode {
addr := uintptr(unsafe.Pointer(head)) + uintptr(idx)*unsafe.Sizeof(Opcode{})
return *(**Opcode)(unsafe.Pointer(&addr))
}
func copyOpcode(code *Opcode) *Opcode {
codeMap := map[uintptr]*Opcode{}
return code.copy(codeMap)
codeNum := ToEndCode(code).DisplayIdx + 1
codeSlice := make([]Opcode, codeNum)
head := (*Opcode)((*runtime.SliceHeader)(unsafe.Pointer(&codeSlice)).Data)
ptr := head
c := code
for {
*ptr = Opcode{
Op: c.Op,
Key: c.Key,
PtrNum: c.PtrNum,
NumBitSize: c.NumBitSize,
Flags: c.Flags,
Idx: c.Idx,
Offset: c.Offset,
Type: c.Type,
FieldQuery: c.FieldQuery,
DisplayIdx: c.DisplayIdx,
DisplayKey: c.DisplayKey,
ElemIdx: c.ElemIdx,
Length: c.Length,
Size: c.Size,
Indent: c.Indent,
Jmp: c.Jmp,
}
if c.End != nil {
ptr.End = getCodeAddrByIdx(head, c.End.DisplayIdx)
}
if c.NextField != nil {
ptr.NextField = getCodeAddrByIdx(head, c.NextField.DisplayIdx)
}
if c.Next != nil {
ptr.Next = getCodeAddrByIdx(head, c.Next.DisplayIdx)
}
if c.IsEnd() {
break
}
ptr = getCodeAddrByIdx(head, c.DisplayIdx+1)
c = c.IterNext()
}
return head
}
func setTotalLengthToInterfaceOp(code *Opcode) {
c := code
for c.Op != OpEnd && c.Op != OpInterfaceEnd {
if c.Op == OpInterface {
for c := code; !c.IsEnd(); {
if c.Op == OpInterface || c.Op == OpInterfacePtr {
c.Length = uint32(code.TotalLength())
}
switch c.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
c = c.End
default:
c = c.Next
}
c = c.IterNext()
}
}
func ToEndCode(code *Opcode) *Opcode {
c := code
for c.Op != OpEnd && c.Op != OpInterfaceEnd {
switch c.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
c = c.End
default:
c = c.Next
}
for !c.IsEnd() {
c = c.IterNext()
}
return c
}
@ -325,77 +389,25 @@ func copyToInterfaceOpcode(code *Opcode) *Opcode {
return copied
}
func newOpCodeWithNext(ctx *compileContext, op OpType, next *Opcode) *Opcode {
func newOpCodeWithNext(ctx *compileContext, typ *runtime.Type, op OpType, next *Opcode) *Opcode {
return &Opcode{
Op: op,
Idx: opcodeOffset(ctx.ptrIndex),
Next: next,
Type: ctx.typ,
Type: typ,
DisplayIdx: ctx.opcodeIndex,
Indent: ctx.indent,
}
}
func newEndOp(ctx *compileContext) *Opcode {
return newOpCodeWithNext(ctx, OpEnd, nil)
}
func (c *Opcode) copy(codeMap map[uintptr]*Opcode) *Opcode {
if c == nil {
return nil
}
addr := uintptr(unsafe.Pointer(c))
if code, exists := codeMap[addr]; exists {
return code
}
copied := &Opcode{
Op: c.Op,
Key: c.Key,
PtrNum: c.PtrNum,
NumBitSize: c.NumBitSize,
Flags: c.Flags,
Idx: c.Idx,
Offset: c.Offset,
Type: c.Type,
DisplayIdx: c.DisplayIdx,
DisplayKey: c.DisplayKey,
ElemIdx: c.ElemIdx,
Length: c.Length,
MapIter: c.MapIter,
MapPos: c.MapPos,
Size: c.Size,
Indent: c.Indent,
}
codeMap[addr] = copied
copied.End = c.End.copy(codeMap)
copied.PrevField = c.PrevField.copy(codeMap)
copied.NextField = c.NextField.copy(codeMap)
copied.Next = c.Next.copy(codeMap)
copied.Jmp = c.Jmp
return copied
}
func (c *Opcode) BeforeLastCode() *Opcode {
code := c
for {
var nextCode *Opcode
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
nextCode = code.End
default:
nextCode = code.Next
}
if nextCode.Op == OpEnd {
return code
}
code = nextCode
}
func newEndOp(ctx *compileContext, typ *runtime.Type) *Opcode {
return newOpCodeWithNext(ctx, typ, OpEnd, nil)
}
func (c *Opcode) TotalLength() int {
var idx int
code := c
for code.Op != OpEnd && code.Op != OpInterfaceEnd {
for !code.IsEnd() {
maxIdx := int(code.MaxIdx() / uintptrSize)
if idx < maxIdx {
idx = maxIdx
@ -403,12 +415,7 @@ func (c *Opcode) TotalLength() int {
if code.Op == OpRecursiveEnd {
break
}
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
code = code.IterNext()
}
maxIdx := int(code.MaxIdx() / uintptrSize)
if idx < maxIdx {
@ -417,42 +424,6 @@ func (c *Opcode) TotalLength() int {
return idx + 1
}
func (c *Opcode) decOpcodeIndex() {
for code := c; code.Op != OpEnd; {
code.DisplayIdx--
if code.Idx > 0 {
code.Idx -= uintptrSize
}
if code.ElemIdx > 0 {
code.ElemIdx -= uintptrSize
}
if code.MapIter > 0 {
code.MapIter -= uintptrSize
}
if code.Length > 0 && code.Op.CodeType() != CodeArrayHead && code.Op.CodeType() != CodeArrayElem {
code.Length -= uintptrSize
}
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
}
}
func (c *Opcode) decIndent() {
for code := c; code.Op != OpEnd; {
code.Indent--
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
code = code.End
default:
code = code.Next
}
}
}
func (c *Opcode) dumpHead(code *Opcode) string {
var length uint32
if code.Op.CodeType() == CodeArrayHead {
@ -461,7 +432,7 @@ func (c *Opcode) dumpHead(code *Opcode) string {
length = code.Length / uintptrSize
}
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d])`,
`[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d])`,
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
@ -473,26 +444,21 @@ func (c *Opcode) dumpHead(code *Opcode) string {
func (c *Opcode) dumpMapHead(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
`[%03d]%s%s ([idx:%d])`,
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
code.Idx/uintptrSize,
code.ElemIdx/uintptrSize,
code.Length/uintptrSize,
code.MapIter/uintptrSize,
)
}
func (c *Opcode) dumpMapEnd(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][mapPos:%d][length:%d])`,
`[%03d]%s%s ([idx:%d])`,
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
code.Idx/uintptrSize,
code.MapPos/uintptrSize,
code.Length/uintptrSize,
)
}
@ -504,7 +470,7 @@ func (c *Opcode) dumpElem(code *Opcode) string {
length = code.Length / uintptrSize
}
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`,
`[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`,
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
@ -517,7 +483,7 @@ func (c *Opcode) dumpElem(code *Opcode) string {
func (c *Opcode) dumpField(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][key:%s][offset:%d])`,
`[%03d]%s%s ([idx:%d][key:%s][offset:%d])`,
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
@ -529,31 +495,27 @@ func (c *Opcode) dumpField(code *Opcode) string {
func (c *Opcode) dumpKey(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
`[%03d]%s%s ([idx:%d])`,
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
code.Idx/uintptrSize,
code.ElemIdx/uintptrSize,
code.Length/uintptrSize,
code.MapIter/uintptrSize,
)
}
func (c *Opcode) dumpValue(code *Opcode) string {
return fmt.Sprintf(
`[%d]%s%s ([idx:%d][mapIter:%d])`,
`[%03d]%s%s ([idx:%d])`,
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
code.Idx/uintptrSize,
code.MapIter/uintptrSize,
)
}
func (c *Opcode) Dump() string {
codes := []string{}
for code := c; code.Op != OpEnd && code.Op != OpInterfaceEnd; {
for code := c; !code.IsEnd(); {
switch code.Op.CodeType() {
case CodeSliceHead:
codes = append(codes, c.dumpHead(code))
@ -581,7 +543,7 @@ func (c *Opcode) Dump() string {
code = code.Next
default:
codes = append(codes, fmt.Sprintf(
"[%d]%s%s ([idx:%d])",
"[%03d]%s%s ([idx:%d])",
code.DisplayIdx,
strings.Repeat("-", int(code.Indent)),
code.Op,
@ -593,44 +555,7 @@ func (c *Opcode) Dump() string {
return strings.Join(codes, "\n")
}
func prevField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode {
if _, exists := removedFields[code]; exists {
return prevField(code.PrevField, removedFields)
}
return code
}
func nextField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode {
if _, exists := removedFields[code]; exists {
return nextField(code.NextField, removedFields)
}
return code
}
func linkPrevToNextField(cur *Opcode, removedFields map[*Opcode]struct{}) {
prev := prevField(cur.PrevField, removedFields)
prev.NextField = nextField(cur.NextField, removedFields)
code := prev
fcode := cur
for {
var nextCode *Opcode
switch code.Op.CodeType() {
case CodeArrayElem, CodeSliceElem, CodeMapKey:
nextCode = code.End
default:
nextCode = code.Next
}
if nextCode == fcode {
code.Next = fcode.Next
break
} else if nextCode.Op == OpEnd {
break
}
code = nextCode
}
}
func newSliceHeaderCode(ctx *compileContext) *Opcode {
func newSliceHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
@ -638,6 +563,7 @@ func newSliceHeaderCode(ctx *compileContext) *Opcode {
length := opcodeOffset(ctx.ptrIndex)
return &Opcode{
Op: OpSlice,
Type: typ,
Idx: idx,
DisplayIdx: ctx.opcodeIndex,
ElemIdx: elemIdx,
@ -646,9 +572,10 @@ func newSliceHeaderCode(ctx *compileContext) *Opcode {
}
}
func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode {
func newSliceElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, size uintptr) *Opcode {
return &Opcode{
Op: OpSliceElem,
Type: typ,
Idx: head.Idx,
DisplayIdx: ctx.opcodeIndex,
ElemIdx: head.ElemIdx,
@ -658,12 +585,13 @@ func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode {
}
}
func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode {
func newArrayHeaderCode(ctx *compileContext, typ *runtime.Type, alen int) *Opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
return &Opcode{
Op: OpArray,
Type: typ,
Idx: idx,
DisplayIdx: ctx.opcodeIndex,
ElemIdx: elemIdx,
@ -672,9 +600,10 @@ func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode {
}
}
func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintptr) *Opcode {
func newArrayElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, length int, size uintptr) *Opcode {
return &Opcode{
Op: OpArrayElem,
Type: typ,
Idx: head.Idx,
DisplayIdx: ctx.opcodeIndex,
ElemIdx: head.ElemIdx,
@ -684,87 +613,55 @@ func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintpt
}
}
func newMapHeaderCode(ctx *compileContext) *Opcode {
func newMapHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode {
idx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
elemIdx := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
length := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
mapIter := opcodeOffset(ctx.ptrIndex)
return &Opcode{
Op: OpMap,
Type: typ,
Idx: idx,
Type: ctx.typ,
DisplayIdx: ctx.opcodeIndex,
ElemIdx: elemIdx,
Length: length,
MapIter: mapIter,
Indent: ctx.indent,
}
}
func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode {
func newMapKeyCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode {
return &Opcode{
Op: OpMapKey,
Idx: opcodeOffset(ctx.ptrIndex),
Type: typ,
Idx: head.Idx,
DisplayIdx: ctx.opcodeIndex,
ElemIdx: head.ElemIdx,
Length: head.Length,
MapIter: head.MapIter,
Indent: ctx.indent,
}
}
func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode {
func newMapValueCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode {
return &Opcode{
Op: OpMapValue,
Idx: opcodeOffset(ctx.ptrIndex),
Type: typ,
Idx: head.Idx,
DisplayIdx: ctx.opcodeIndex,
ElemIdx: head.ElemIdx,
Length: head.Length,
MapIter: head.MapIter,
Indent: ctx.indent,
}
}
func newMapEndCode(ctx *compileContext, head *Opcode) *Opcode {
mapPos := opcodeOffset(ctx.ptrIndex)
ctx.incPtrIndex()
idx := opcodeOffset(ctx.ptrIndex)
func newMapEndCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode {
return &Opcode{
Op: OpMapEnd,
Idx: idx,
Next: newEndOp(ctx),
Type: typ,
Idx: head.Idx,
DisplayIdx: ctx.opcodeIndex,
Length: head.Length,
MapPos: mapPos,
Indent: ctx.indent,
Next: newEndOp(ctx, typ),
}
}
func newInterfaceCode(ctx *compileContext) *Opcode {
var flag OpFlags
if ctx.typ.NumMethod() > 0 {
flag |= NonEmptyInterfaceFlags
}
return &Opcode{
Op: OpInterface,
Idx: opcodeOffset(ctx.ptrIndex),
Next: newEndOp(ctx),
Type: ctx.typ,
DisplayIdx: ctx.opcodeIndex,
Indent: ctx.indent,
Flags: flag,
}
}
func newRecursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode {
func newRecursiveCode(ctx *compileContext, typ *runtime.Type, jmp *CompiledCode) *Opcode {
return &Opcode{
Op: OpRecursive,
Type: typ,
Idx: opcodeOffset(ctx.ptrIndex),
Next: newEndOp(ctx),
Type: ctx.typ,
Next: newEndOp(ctx, typ),
DisplayIdx: ctx.opcodeIndex,
Indent: ctx.indent,
Jmp: jmp,

View File

@ -1,6 +1,9 @@
package encoder
import "context"
import (
"context"
"io"
)
type OptionFlag uint8
@ -11,12 +14,15 @@ const (
DebugOption
ColorizeOption
ContextOption
NormalizeUTF8Option
FieldQueryOption
)
type Option struct {
Flag OptionFlag
ColorScheme *ColorScheme
Context context.Context
DebugOut io.Writer
}
type EncodeFormat struct {

View File

@ -22,7 +22,7 @@ const (
CodeStructEnd CodeType = 11
)
var opTypeStrings = [401]string{
var opTypeStrings = [400]string{
"End",
"Interface",
"Ptr",
@ -37,7 +37,6 @@ var opTypeStrings = [401]string{
"RecursivePtr",
"RecursiveEnd",
"InterfaceEnd",
"StructAnonymousEnd",
"Int",
"Uint",
"Float32",
@ -443,397 +442,396 @@ const (
OpRecursivePtr OpType = 11
OpRecursiveEnd OpType = 12
OpInterfaceEnd OpType = 13
OpStructAnonymousEnd OpType = 14
OpInt OpType = 15
OpUint OpType = 16
OpFloat32 OpType = 17
OpFloat64 OpType = 18
OpBool OpType = 19
OpString OpType = 20
OpBytes OpType = 21
OpNumber OpType = 22
OpArray OpType = 23
OpMap OpType = 24
OpSlice OpType = 25
OpStruct OpType = 26
OpMarshalJSON OpType = 27
OpMarshalText OpType = 28
OpIntString OpType = 29
OpUintString OpType = 30
OpFloat32String OpType = 31
OpFloat64String OpType = 32
OpBoolString OpType = 33
OpStringString OpType = 34
OpNumberString OpType = 35
OpIntPtr OpType = 36
OpUintPtr OpType = 37
OpFloat32Ptr OpType = 38
OpFloat64Ptr OpType = 39
OpBoolPtr OpType = 40
OpStringPtr OpType = 41
OpBytesPtr OpType = 42
OpNumberPtr OpType = 43
OpArrayPtr OpType = 44
OpMapPtr OpType = 45
OpSlicePtr OpType = 46
OpMarshalJSONPtr OpType = 47
OpMarshalTextPtr OpType = 48
OpInterfacePtr OpType = 49
OpIntPtrString OpType = 50
OpUintPtrString OpType = 51
OpFloat32PtrString OpType = 52
OpFloat64PtrString OpType = 53
OpBoolPtrString OpType = 54
OpStringPtrString OpType = 55
OpNumberPtrString OpType = 56
OpStructHeadInt OpType = 57
OpStructHeadOmitEmptyInt OpType = 58
OpStructPtrHeadInt OpType = 59
OpStructPtrHeadOmitEmptyInt OpType = 60
OpStructHeadUint OpType = 61
OpStructHeadOmitEmptyUint OpType = 62
OpStructPtrHeadUint OpType = 63
OpStructPtrHeadOmitEmptyUint OpType = 64
OpStructHeadFloat32 OpType = 65
OpStructHeadOmitEmptyFloat32 OpType = 66
OpStructPtrHeadFloat32 OpType = 67
OpStructPtrHeadOmitEmptyFloat32 OpType = 68
OpStructHeadFloat64 OpType = 69
OpStructHeadOmitEmptyFloat64 OpType = 70
OpStructPtrHeadFloat64 OpType = 71
OpStructPtrHeadOmitEmptyFloat64 OpType = 72
OpStructHeadBool OpType = 73
OpStructHeadOmitEmptyBool OpType = 74
OpStructPtrHeadBool OpType = 75
OpStructPtrHeadOmitEmptyBool OpType = 76
OpStructHeadString OpType = 77
OpStructHeadOmitEmptyString OpType = 78
OpStructPtrHeadString OpType = 79
OpStructPtrHeadOmitEmptyString OpType = 80
OpStructHeadBytes OpType = 81
OpStructHeadOmitEmptyBytes OpType = 82
OpStructPtrHeadBytes OpType = 83
OpStructPtrHeadOmitEmptyBytes OpType = 84
OpStructHeadNumber OpType = 85
OpStructHeadOmitEmptyNumber OpType = 86
OpStructPtrHeadNumber OpType = 87
OpStructPtrHeadOmitEmptyNumber OpType = 88
OpStructHeadArray OpType = 89
OpStructHeadOmitEmptyArray OpType = 90
OpStructPtrHeadArray OpType = 91
OpStructPtrHeadOmitEmptyArray OpType = 92
OpStructHeadMap OpType = 93
OpStructHeadOmitEmptyMap OpType = 94
OpStructPtrHeadMap OpType = 95
OpStructPtrHeadOmitEmptyMap OpType = 96
OpStructHeadSlice OpType = 97
OpStructHeadOmitEmptySlice OpType = 98
OpStructPtrHeadSlice OpType = 99
OpStructPtrHeadOmitEmptySlice OpType = 100
OpStructHeadStruct OpType = 101
OpStructHeadOmitEmptyStruct OpType = 102
OpStructPtrHeadStruct OpType = 103
OpStructPtrHeadOmitEmptyStruct OpType = 104
OpStructHeadMarshalJSON OpType = 105
OpStructHeadOmitEmptyMarshalJSON OpType = 106
OpStructPtrHeadMarshalJSON OpType = 107
OpStructPtrHeadOmitEmptyMarshalJSON OpType = 108
OpStructHeadMarshalText OpType = 109
OpStructHeadOmitEmptyMarshalText OpType = 110
OpStructPtrHeadMarshalText OpType = 111
OpStructPtrHeadOmitEmptyMarshalText OpType = 112
OpStructHeadIntString OpType = 113
OpStructHeadOmitEmptyIntString OpType = 114
OpStructPtrHeadIntString OpType = 115
OpStructPtrHeadOmitEmptyIntString OpType = 116
OpStructHeadUintString OpType = 117
OpStructHeadOmitEmptyUintString OpType = 118
OpStructPtrHeadUintString OpType = 119
OpStructPtrHeadOmitEmptyUintString OpType = 120
OpStructHeadFloat32String OpType = 121
OpStructHeadOmitEmptyFloat32String OpType = 122
OpStructPtrHeadFloat32String OpType = 123
OpStructPtrHeadOmitEmptyFloat32String OpType = 124
OpStructHeadFloat64String OpType = 125
OpStructHeadOmitEmptyFloat64String OpType = 126
OpStructPtrHeadFloat64String OpType = 127
OpStructPtrHeadOmitEmptyFloat64String OpType = 128
OpStructHeadBoolString OpType = 129
OpStructHeadOmitEmptyBoolString OpType = 130
OpStructPtrHeadBoolString OpType = 131
OpStructPtrHeadOmitEmptyBoolString OpType = 132
OpStructHeadStringString OpType = 133
OpStructHeadOmitEmptyStringString OpType = 134
OpStructPtrHeadStringString OpType = 135
OpStructPtrHeadOmitEmptyStringString OpType = 136
OpStructHeadNumberString OpType = 137
OpStructHeadOmitEmptyNumberString OpType = 138
OpStructPtrHeadNumberString OpType = 139
OpStructPtrHeadOmitEmptyNumberString OpType = 140
OpStructHeadIntPtr OpType = 141
OpStructHeadOmitEmptyIntPtr OpType = 142
OpStructPtrHeadIntPtr OpType = 143
OpStructPtrHeadOmitEmptyIntPtr OpType = 144
OpStructHeadUintPtr OpType = 145
OpStructHeadOmitEmptyUintPtr OpType = 146
OpStructPtrHeadUintPtr OpType = 147
OpStructPtrHeadOmitEmptyUintPtr OpType = 148
OpStructHeadFloat32Ptr OpType = 149
OpStructHeadOmitEmptyFloat32Ptr OpType = 150
OpStructPtrHeadFloat32Ptr OpType = 151
OpStructPtrHeadOmitEmptyFloat32Ptr OpType = 152
OpStructHeadFloat64Ptr OpType = 153
OpStructHeadOmitEmptyFloat64Ptr OpType = 154
OpStructPtrHeadFloat64Ptr OpType = 155
OpStructPtrHeadOmitEmptyFloat64Ptr OpType = 156
OpStructHeadBoolPtr OpType = 157
OpStructHeadOmitEmptyBoolPtr OpType = 158
OpStructPtrHeadBoolPtr OpType = 159
OpStructPtrHeadOmitEmptyBoolPtr OpType = 160
OpStructHeadStringPtr OpType = 161
OpStructHeadOmitEmptyStringPtr OpType = 162
OpStructPtrHeadStringPtr OpType = 163
OpStructPtrHeadOmitEmptyStringPtr OpType = 164
OpStructHeadBytesPtr OpType = 165
OpStructHeadOmitEmptyBytesPtr OpType = 166
OpStructPtrHeadBytesPtr OpType = 167
OpStructPtrHeadOmitEmptyBytesPtr OpType = 168
OpStructHeadNumberPtr OpType = 169
OpStructHeadOmitEmptyNumberPtr OpType = 170
OpStructPtrHeadNumberPtr OpType = 171
OpStructPtrHeadOmitEmptyNumberPtr OpType = 172
OpStructHeadArrayPtr OpType = 173
OpStructHeadOmitEmptyArrayPtr OpType = 174
OpStructPtrHeadArrayPtr OpType = 175
OpStructPtrHeadOmitEmptyArrayPtr OpType = 176
OpStructHeadMapPtr OpType = 177
OpStructHeadOmitEmptyMapPtr OpType = 178
OpStructPtrHeadMapPtr OpType = 179
OpStructPtrHeadOmitEmptyMapPtr OpType = 180
OpStructHeadSlicePtr OpType = 181
OpStructHeadOmitEmptySlicePtr OpType = 182
OpStructPtrHeadSlicePtr OpType = 183
OpStructPtrHeadOmitEmptySlicePtr OpType = 184
OpStructHeadMarshalJSONPtr OpType = 185
OpStructHeadOmitEmptyMarshalJSONPtr OpType = 186
OpStructPtrHeadMarshalJSONPtr OpType = 187
OpStructPtrHeadOmitEmptyMarshalJSONPtr OpType = 188
OpStructHeadMarshalTextPtr OpType = 189
OpStructHeadOmitEmptyMarshalTextPtr OpType = 190
OpStructPtrHeadMarshalTextPtr OpType = 191
OpStructPtrHeadOmitEmptyMarshalTextPtr OpType = 192
OpStructHeadInterfacePtr OpType = 193
OpStructHeadOmitEmptyInterfacePtr OpType = 194
OpStructPtrHeadInterfacePtr OpType = 195
OpStructPtrHeadOmitEmptyInterfacePtr OpType = 196
OpStructHeadIntPtrString OpType = 197
OpStructHeadOmitEmptyIntPtrString OpType = 198
OpStructPtrHeadIntPtrString OpType = 199
OpStructPtrHeadOmitEmptyIntPtrString OpType = 200
OpStructHeadUintPtrString OpType = 201
OpStructHeadOmitEmptyUintPtrString OpType = 202
OpStructPtrHeadUintPtrString OpType = 203
OpStructPtrHeadOmitEmptyUintPtrString OpType = 204
OpStructHeadFloat32PtrString OpType = 205
OpStructHeadOmitEmptyFloat32PtrString OpType = 206
OpStructPtrHeadFloat32PtrString OpType = 207
OpStructPtrHeadOmitEmptyFloat32PtrString OpType = 208
OpStructHeadFloat64PtrString OpType = 209
OpStructHeadOmitEmptyFloat64PtrString OpType = 210
OpStructPtrHeadFloat64PtrString OpType = 211
OpStructPtrHeadOmitEmptyFloat64PtrString OpType = 212
OpStructHeadBoolPtrString OpType = 213
OpStructHeadOmitEmptyBoolPtrString OpType = 214
OpStructPtrHeadBoolPtrString OpType = 215
OpStructPtrHeadOmitEmptyBoolPtrString OpType = 216
OpStructHeadStringPtrString OpType = 217
OpStructHeadOmitEmptyStringPtrString OpType = 218
OpStructPtrHeadStringPtrString OpType = 219
OpStructPtrHeadOmitEmptyStringPtrString OpType = 220
OpStructHeadNumberPtrString OpType = 221
OpStructHeadOmitEmptyNumberPtrString OpType = 222
OpStructPtrHeadNumberPtrString OpType = 223
OpStructPtrHeadOmitEmptyNumberPtrString OpType = 224
OpStructHead OpType = 225
OpStructHeadOmitEmpty OpType = 226
OpStructPtrHead OpType = 227
OpStructPtrHeadOmitEmpty OpType = 228
OpStructFieldInt OpType = 229
OpStructFieldOmitEmptyInt OpType = 230
OpStructEndInt OpType = 231
OpStructEndOmitEmptyInt OpType = 232
OpStructFieldUint OpType = 233
OpStructFieldOmitEmptyUint OpType = 234
OpStructEndUint OpType = 235
OpStructEndOmitEmptyUint OpType = 236
OpStructFieldFloat32 OpType = 237
OpStructFieldOmitEmptyFloat32 OpType = 238
OpStructEndFloat32 OpType = 239
OpStructEndOmitEmptyFloat32 OpType = 240
OpStructFieldFloat64 OpType = 241
OpStructFieldOmitEmptyFloat64 OpType = 242
OpStructEndFloat64 OpType = 243
OpStructEndOmitEmptyFloat64 OpType = 244
OpStructFieldBool OpType = 245
OpStructFieldOmitEmptyBool OpType = 246
OpStructEndBool OpType = 247
OpStructEndOmitEmptyBool OpType = 248
OpStructFieldString OpType = 249
OpStructFieldOmitEmptyString OpType = 250
OpStructEndString OpType = 251
OpStructEndOmitEmptyString OpType = 252
OpStructFieldBytes OpType = 253
OpStructFieldOmitEmptyBytes OpType = 254
OpStructEndBytes OpType = 255
OpStructEndOmitEmptyBytes OpType = 256
OpStructFieldNumber OpType = 257
OpStructFieldOmitEmptyNumber OpType = 258
OpStructEndNumber OpType = 259
OpStructEndOmitEmptyNumber OpType = 260
OpStructFieldArray OpType = 261
OpStructFieldOmitEmptyArray OpType = 262
OpStructEndArray OpType = 263
OpStructEndOmitEmptyArray OpType = 264
OpStructFieldMap OpType = 265
OpStructFieldOmitEmptyMap OpType = 266
OpStructEndMap OpType = 267
OpStructEndOmitEmptyMap OpType = 268
OpStructFieldSlice OpType = 269
OpStructFieldOmitEmptySlice OpType = 270
OpStructEndSlice OpType = 271
OpStructEndOmitEmptySlice OpType = 272
OpStructFieldStruct OpType = 273
OpStructFieldOmitEmptyStruct OpType = 274
OpStructEndStruct OpType = 275
OpStructEndOmitEmptyStruct OpType = 276
OpStructFieldMarshalJSON OpType = 277
OpStructFieldOmitEmptyMarshalJSON OpType = 278
OpStructEndMarshalJSON OpType = 279
OpStructEndOmitEmptyMarshalJSON OpType = 280
OpStructFieldMarshalText OpType = 281
OpStructFieldOmitEmptyMarshalText OpType = 282
OpStructEndMarshalText OpType = 283
OpStructEndOmitEmptyMarshalText OpType = 284
OpStructFieldIntString OpType = 285
OpStructFieldOmitEmptyIntString OpType = 286
OpStructEndIntString OpType = 287
OpStructEndOmitEmptyIntString OpType = 288
OpStructFieldUintString OpType = 289
OpStructFieldOmitEmptyUintString OpType = 290
OpStructEndUintString OpType = 291
OpStructEndOmitEmptyUintString OpType = 292
OpStructFieldFloat32String OpType = 293
OpStructFieldOmitEmptyFloat32String OpType = 294
OpStructEndFloat32String OpType = 295
OpStructEndOmitEmptyFloat32String OpType = 296
OpStructFieldFloat64String OpType = 297
OpStructFieldOmitEmptyFloat64String OpType = 298
OpStructEndFloat64String OpType = 299
OpStructEndOmitEmptyFloat64String OpType = 300
OpStructFieldBoolString OpType = 301
OpStructFieldOmitEmptyBoolString OpType = 302
OpStructEndBoolString OpType = 303
OpStructEndOmitEmptyBoolString OpType = 304
OpStructFieldStringString OpType = 305
OpStructFieldOmitEmptyStringString OpType = 306
OpStructEndStringString OpType = 307
OpStructEndOmitEmptyStringString OpType = 308
OpStructFieldNumberString OpType = 309
OpStructFieldOmitEmptyNumberString OpType = 310
OpStructEndNumberString OpType = 311
OpStructEndOmitEmptyNumberString OpType = 312
OpStructFieldIntPtr OpType = 313
OpStructFieldOmitEmptyIntPtr OpType = 314
OpStructEndIntPtr OpType = 315
OpStructEndOmitEmptyIntPtr OpType = 316
OpStructFieldUintPtr OpType = 317
OpStructFieldOmitEmptyUintPtr OpType = 318
OpStructEndUintPtr OpType = 319
OpStructEndOmitEmptyUintPtr OpType = 320
OpStructFieldFloat32Ptr OpType = 321
OpStructFieldOmitEmptyFloat32Ptr OpType = 322
OpStructEndFloat32Ptr OpType = 323
OpStructEndOmitEmptyFloat32Ptr OpType = 324
OpStructFieldFloat64Ptr OpType = 325
OpStructFieldOmitEmptyFloat64Ptr OpType = 326
OpStructEndFloat64Ptr OpType = 327
OpStructEndOmitEmptyFloat64Ptr OpType = 328
OpStructFieldBoolPtr OpType = 329
OpStructFieldOmitEmptyBoolPtr OpType = 330
OpStructEndBoolPtr OpType = 331
OpStructEndOmitEmptyBoolPtr OpType = 332
OpStructFieldStringPtr OpType = 333
OpStructFieldOmitEmptyStringPtr OpType = 334
OpStructEndStringPtr OpType = 335
OpStructEndOmitEmptyStringPtr OpType = 336
OpStructFieldBytesPtr OpType = 337
OpStructFieldOmitEmptyBytesPtr OpType = 338
OpStructEndBytesPtr OpType = 339
OpStructEndOmitEmptyBytesPtr OpType = 340
OpStructFieldNumberPtr OpType = 341
OpStructFieldOmitEmptyNumberPtr OpType = 342
OpStructEndNumberPtr OpType = 343
OpStructEndOmitEmptyNumberPtr OpType = 344
OpStructFieldArrayPtr OpType = 345
OpStructFieldOmitEmptyArrayPtr OpType = 346
OpStructEndArrayPtr OpType = 347
OpStructEndOmitEmptyArrayPtr OpType = 348
OpStructFieldMapPtr OpType = 349
OpStructFieldOmitEmptyMapPtr OpType = 350
OpStructEndMapPtr OpType = 351
OpStructEndOmitEmptyMapPtr OpType = 352
OpStructFieldSlicePtr OpType = 353
OpStructFieldOmitEmptySlicePtr OpType = 354
OpStructEndSlicePtr OpType = 355
OpStructEndOmitEmptySlicePtr OpType = 356
OpStructFieldMarshalJSONPtr OpType = 357
OpStructFieldOmitEmptyMarshalJSONPtr OpType = 358
OpStructEndMarshalJSONPtr OpType = 359
OpStructEndOmitEmptyMarshalJSONPtr OpType = 360
OpStructFieldMarshalTextPtr OpType = 361
OpStructFieldOmitEmptyMarshalTextPtr OpType = 362
OpStructEndMarshalTextPtr OpType = 363
OpStructEndOmitEmptyMarshalTextPtr OpType = 364
OpStructFieldInterfacePtr OpType = 365
OpStructFieldOmitEmptyInterfacePtr OpType = 366
OpStructEndInterfacePtr OpType = 367
OpStructEndOmitEmptyInterfacePtr OpType = 368
OpStructFieldIntPtrString OpType = 369
OpStructFieldOmitEmptyIntPtrString OpType = 370
OpStructEndIntPtrString OpType = 371
OpStructEndOmitEmptyIntPtrString OpType = 372
OpStructFieldUintPtrString OpType = 373
OpStructFieldOmitEmptyUintPtrString OpType = 374
OpStructEndUintPtrString OpType = 375
OpStructEndOmitEmptyUintPtrString OpType = 376
OpStructFieldFloat32PtrString OpType = 377
OpStructFieldOmitEmptyFloat32PtrString OpType = 378
OpStructEndFloat32PtrString OpType = 379
OpStructEndOmitEmptyFloat32PtrString OpType = 380
OpStructFieldFloat64PtrString OpType = 381
OpStructFieldOmitEmptyFloat64PtrString OpType = 382
OpStructEndFloat64PtrString OpType = 383
OpStructEndOmitEmptyFloat64PtrString OpType = 384
OpStructFieldBoolPtrString OpType = 385
OpStructFieldOmitEmptyBoolPtrString OpType = 386
OpStructEndBoolPtrString OpType = 387
OpStructEndOmitEmptyBoolPtrString OpType = 388
OpStructFieldStringPtrString OpType = 389
OpStructFieldOmitEmptyStringPtrString OpType = 390
OpStructEndStringPtrString OpType = 391
OpStructEndOmitEmptyStringPtrString OpType = 392
OpStructFieldNumberPtrString OpType = 393
OpStructFieldOmitEmptyNumberPtrString OpType = 394
OpStructEndNumberPtrString OpType = 395
OpStructEndOmitEmptyNumberPtrString OpType = 396
OpStructField OpType = 397
OpStructFieldOmitEmpty OpType = 398
OpStructEnd OpType = 399
OpStructEndOmitEmpty OpType = 400
OpInt OpType = 14
OpUint OpType = 15
OpFloat32 OpType = 16
OpFloat64 OpType = 17
OpBool OpType = 18
OpString OpType = 19
OpBytes OpType = 20
OpNumber OpType = 21
OpArray OpType = 22
OpMap OpType = 23
OpSlice OpType = 24
OpStruct OpType = 25
OpMarshalJSON OpType = 26
OpMarshalText OpType = 27
OpIntString OpType = 28
OpUintString OpType = 29
OpFloat32String OpType = 30
OpFloat64String OpType = 31
OpBoolString OpType = 32
OpStringString OpType = 33
OpNumberString OpType = 34
OpIntPtr OpType = 35
OpUintPtr OpType = 36
OpFloat32Ptr OpType = 37
OpFloat64Ptr OpType = 38
OpBoolPtr OpType = 39
OpStringPtr OpType = 40
OpBytesPtr OpType = 41
OpNumberPtr OpType = 42
OpArrayPtr OpType = 43
OpMapPtr OpType = 44
OpSlicePtr OpType = 45
OpMarshalJSONPtr OpType = 46
OpMarshalTextPtr OpType = 47
OpInterfacePtr OpType = 48
OpIntPtrString OpType = 49
OpUintPtrString OpType = 50
OpFloat32PtrString OpType = 51
OpFloat64PtrString OpType = 52
OpBoolPtrString OpType = 53
OpStringPtrString OpType = 54
OpNumberPtrString OpType = 55
OpStructHeadInt OpType = 56
OpStructHeadOmitEmptyInt OpType = 57
OpStructPtrHeadInt OpType = 58
OpStructPtrHeadOmitEmptyInt OpType = 59
OpStructHeadUint OpType = 60
OpStructHeadOmitEmptyUint OpType = 61
OpStructPtrHeadUint OpType = 62
OpStructPtrHeadOmitEmptyUint OpType = 63
OpStructHeadFloat32 OpType = 64
OpStructHeadOmitEmptyFloat32 OpType = 65
OpStructPtrHeadFloat32 OpType = 66
OpStructPtrHeadOmitEmptyFloat32 OpType = 67
OpStructHeadFloat64 OpType = 68
OpStructHeadOmitEmptyFloat64 OpType = 69
OpStructPtrHeadFloat64 OpType = 70
OpStructPtrHeadOmitEmptyFloat64 OpType = 71
OpStructHeadBool OpType = 72
OpStructHeadOmitEmptyBool OpType = 73
OpStructPtrHeadBool OpType = 74
OpStructPtrHeadOmitEmptyBool OpType = 75
OpStructHeadString OpType = 76
OpStructHeadOmitEmptyString OpType = 77
OpStructPtrHeadString OpType = 78
OpStructPtrHeadOmitEmptyString OpType = 79
OpStructHeadBytes OpType = 80
OpStructHeadOmitEmptyBytes OpType = 81
OpStructPtrHeadBytes OpType = 82
OpStructPtrHeadOmitEmptyBytes OpType = 83
OpStructHeadNumber OpType = 84
OpStructHeadOmitEmptyNumber OpType = 85
OpStructPtrHeadNumber OpType = 86
OpStructPtrHeadOmitEmptyNumber OpType = 87
OpStructHeadArray OpType = 88
OpStructHeadOmitEmptyArray OpType = 89
OpStructPtrHeadArray OpType = 90
OpStructPtrHeadOmitEmptyArray OpType = 91
OpStructHeadMap OpType = 92
OpStructHeadOmitEmptyMap OpType = 93
OpStructPtrHeadMap OpType = 94
OpStructPtrHeadOmitEmptyMap OpType = 95
OpStructHeadSlice OpType = 96
OpStructHeadOmitEmptySlice OpType = 97
OpStructPtrHeadSlice OpType = 98
OpStructPtrHeadOmitEmptySlice OpType = 99
OpStructHeadStruct OpType = 100
OpStructHeadOmitEmptyStruct OpType = 101
OpStructPtrHeadStruct OpType = 102
OpStructPtrHeadOmitEmptyStruct OpType = 103
OpStructHeadMarshalJSON OpType = 104
OpStructHeadOmitEmptyMarshalJSON OpType = 105
OpStructPtrHeadMarshalJSON OpType = 106
OpStructPtrHeadOmitEmptyMarshalJSON OpType = 107
OpStructHeadMarshalText OpType = 108
OpStructHeadOmitEmptyMarshalText OpType = 109
OpStructPtrHeadMarshalText OpType = 110
OpStructPtrHeadOmitEmptyMarshalText OpType = 111
OpStructHeadIntString OpType = 112
OpStructHeadOmitEmptyIntString OpType = 113
OpStructPtrHeadIntString OpType = 114
OpStructPtrHeadOmitEmptyIntString OpType = 115
OpStructHeadUintString OpType = 116
OpStructHeadOmitEmptyUintString OpType = 117
OpStructPtrHeadUintString OpType = 118
OpStructPtrHeadOmitEmptyUintString OpType = 119
OpStructHeadFloat32String OpType = 120
OpStructHeadOmitEmptyFloat32String OpType = 121
OpStructPtrHeadFloat32String OpType = 122
OpStructPtrHeadOmitEmptyFloat32String OpType = 123
OpStructHeadFloat64String OpType = 124
OpStructHeadOmitEmptyFloat64String OpType = 125
OpStructPtrHeadFloat64String OpType = 126
OpStructPtrHeadOmitEmptyFloat64String OpType = 127
OpStructHeadBoolString OpType = 128
OpStructHeadOmitEmptyBoolString OpType = 129
OpStructPtrHeadBoolString OpType = 130
OpStructPtrHeadOmitEmptyBoolString OpType = 131
OpStructHeadStringString OpType = 132
OpStructHeadOmitEmptyStringString OpType = 133
OpStructPtrHeadStringString OpType = 134
OpStructPtrHeadOmitEmptyStringString OpType = 135
OpStructHeadNumberString OpType = 136
OpStructHeadOmitEmptyNumberString OpType = 137
OpStructPtrHeadNumberString OpType = 138
OpStructPtrHeadOmitEmptyNumberString OpType = 139
OpStructHeadIntPtr OpType = 140
OpStructHeadOmitEmptyIntPtr OpType = 141
OpStructPtrHeadIntPtr OpType = 142
OpStructPtrHeadOmitEmptyIntPtr OpType = 143
OpStructHeadUintPtr OpType = 144
OpStructHeadOmitEmptyUintPtr OpType = 145
OpStructPtrHeadUintPtr OpType = 146
OpStructPtrHeadOmitEmptyUintPtr OpType = 147
OpStructHeadFloat32Ptr OpType = 148
OpStructHeadOmitEmptyFloat32Ptr OpType = 149
OpStructPtrHeadFloat32Ptr OpType = 150
OpStructPtrHeadOmitEmptyFloat32Ptr OpType = 151
OpStructHeadFloat64Ptr OpType = 152
OpStructHeadOmitEmptyFloat64Ptr OpType = 153
OpStructPtrHeadFloat64Ptr OpType = 154
OpStructPtrHeadOmitEmptyFloat64Ptr OpType = 155
OpStructHeadBoolPtr OpType = 156
OpStructHeadOmitEmptyBoolPtr OpType = 157
OpStructPtrHeadBoolPtr OpType = 158
OpStructPtrHeadOmitEmptyBoolPtr OpType = 159
OpStructHeadStringPtr OpType = 160
OpStructHeadOmitEmptyStringPtr OpType = 161
OpStructPtrHeadStringPtr OpType = 162
OpStructPtrHeadOmitEmptyStringPtr OpType = 163
OpStructHeadBytesPtr OpType = 164
OpStructHeadOmitEmptyBytesPtr OpType = 165
OpStructPtrHeadBytesPtr OpType = 166
OpStructPtrHeadOmitEmptyBytesPtr OpType = 167
OpStructHeadNumberPtr OpType = 168
OpStructHeadOmitEmptyNumberPtr OpType = 169
OpStructPtrHeadNumberPtr OpType = 170
OpStructPtrHeadOmitEmptyNumberPtr OpType = 171
OpStructHeadArrayPtr OpType = 172
OpStructHeadOmitEmptyArrayPtr OpType = 173
OpStructPtrHeadArrayPtr OpType = 174
OpStructPtrHeadOmitEmptyArrayPtr OpType = 175
OpStructHeadMapPtr OpType = 176
OpStructHeadOmitEmptyMapPtr OpType = 177
OpStructPtrHeadMapPtr OpType = 178
OpStructPtrHeadOmitEmptyMapPtr OpType = 179
OpStructHeadSlicePtr OpType = 180
OpStructHeadOmitEmptySlicePtr OpType = 181
OpStructPtrHeadSlicePtr OpType = 182
OpStructPtrHeadOmitEmptySlicePtr OpType = 183
OpStructHeadMarshalJSONPtr OpType = 184
OpStructHeadOmitEmptyMarshalJSONPtr OpType = 185
OpStructPtrHeadMarshalJSONPtr OpType = 186
OpStructPtrHeadOmitEmptyMarshalJSONPtr OpType = 187
OpStructHeadMarshalTextPtr OpType = 188
OpStructHeadOmitEmptyMarshalTextPtr OpType = 189
OpStructPtrHeadMarshalTextPtr OpType = 190
OpStructPtrHeadOmitEmptyMarshalTextPtr OpType = 191
OpStructHeadInterfacePtr OpType = 192
OpStructHeadOmitEmptyInterfacePtr OpType = 193
OpStructPtrHeadInterfacePtr OpType = 194
OpStructPtrHeadOmitEmptyInterfacePtr OpType = 195
OpStructHeadIntPtrString OpType = 196
OpStructHeadOmitEmptyIntPtrString OpType = 197
OpStructPtrHeadIntPtrString OpType = 198
OpStructPtrHeadOmitEmptyIntPtrString OpType = 199
OpStructHeadUintPtrString OpType = 200
OpStructHeadOmitEmptyUintPtrString OpType = 201
OpStructPtrHeadUintPtrString OpType = 202
OpStructPtrHeadOmitEmptyUintPtrString OpType = 203
OpStructHeadFloat32PtrString OpType = 204
OpStructHeadOmitEmptyFloat32PtrString OpType = 205
OpStructPtrHeadFloat32PtrString OpType = 206
OpStructPtrHeadOmitEmptyFloat32PtrString OpType = 207
OpStructHeadFloat64PtrString OpType = 208
OpStructHeadOmitEmptyFloat64PtrString OpType = 209
OpStructPtrHeadFloat64PtrString OpType = 210
OpStructPtrHeadOmitEmptyFloat64PtrString OpType = 211
OpStructHeadBoolPtrString OpType = 212
OpStructHeadOmitEmptyBoolPtrString OpType = 213
OpStructPtrHeadBoolPtrString OpType = 214
OpStructPtrHeadOmitEmptyBoolPtrString OpType = 215
OpStructHeadStringPtrString OpType = 216
OpStructHeadOmitEmptyStringPtrString OpType = 217
OpStructPtrHeadStringPtrString OpType = 218
OpStructPtrHeadOmitEmptyStringPtrString OpType = 219
OpStructHeadNumberPtrString OpType = 220
OpStructHeadOmitEmptyNumberPtrString OpType = 221
OpStructPtrHeadNumberPtrString OpType = 222
OpStructPtrHeadOmitEmptyNumberPtrString OpType = 223
OpStructHead OpType = 224
OpStructHeadOmitEmpty OpType = 225
OpStructPtrHead OpType = 226
OpStructPtrHeadOmitEmpty OpType = 227
OpStructFieldInt OpType = 228
OpStructFieldOmitEmptyInt OpType = 229
OpStructEndInt OpType = 230
OpStructEndOmitEmptyInt OpType = 231
OpStructFieldUint OpType = 232
OpStructFieldOmitEmptyUint OpType = 233
OpStructEndUint OpType = 234
OpStructEndOmitEmptyUint OpType = 235
OpStructFieldFloat32 OpType = 236
OpStructFieldOmitEmptyFloat32 OpType = 237
OpStructEndFloat32 OpType = 238
OpStructEndOmitEmptyFloat32 OpType = 239
OpStructFieldFloat64 OpType = 240
OpStructFieldOmitEmptyFloat64 OpType = 241
OpStructEndFloat64 OpType = 242
OpStructEndOmitEmptyFloat64 OpType = 243
OpStructFieldBool OpType = 244
OpStructFieldOmitEmptyBool OpType = 245
OpStructEndBool OpType = 246
OpStructEndOmitEmptyBool OpType = 247
OpStructFieldString OpType = 248
OpStructFieldOmitEmptyString OpType = 249
OpStructEndString OpType = 250
OpStructEndOmitEmptyString OpType = 251
OpStructFieldBytes OpType = 252
OpStructFieldOmitEmptyBytes OpType = 253
OpStructEndBytes OpType = 254
OpStructEndOmitEmptyBytes OpType = 255
OpStructFieldNumber OpType = 256
OpStructFieldOmitEmptyNumber OpType = 257
OpStructEndNumber OpType = 258
OpStructEndOmitEmptyNumber OpType = 259
OpStructFieldArray OpType = 260
OpStructFieldOmitEmptyArray OpType = 261
OpStructEndArray OpType = 262
OpStructEndOmitEmptyArray OpType = 263
OpStructFieldMap OpType = 264
OpStructFieldOmitEmptyMap OpType = 265
OpStructEndMap OpType = 266
OpStructEndOmitEmptyMap OpType = 267
OpStructFieldSlice OpType = 268
OpStructFieldOmitEmptySlice OpType = 269
OpStructEndSlice OpType = 270
OpStructEndOmitEmptySlice OpType = 271
OpStructFieldStruct OpType = 272
OpStructFieldOmitEmptyStruct OpType = 273
OpStructEndStruct OpType = 274
OpStructEndOmitEmptyStruct OpType = 275
OpStructFieldMarshalJSON OpType = 276
OpStructFieldOmitEmptyMarshalJSON OpType = 277
OpStructEndMarshalJSON OpType = 278
OpStructEndOmitEmptyMarshalJSON OpType = 279
OpStructFieldMarshalText OpType = 280
OpStructFieldOmitEmptyMarshalText OpType = 281
OpStructEndMarshalText OpType = 282
OpStructEndOmitEmptyMarshalText OpType = 283
OpStructFieldIntString OpType = 284
OpStructFieldOmitEmptyIntString OpType = 285
OpStructEndIntString OpType = 286
OpStructEndOmitEmptyIntString OpType = 287
OpStructFieldUintString OpType = 288
OpStructFieldOmitEmptyUintString OpType = 289
OpStructEndUintString OpType = 290
OpStructEndOmitEmptyUintString OpType = 291
OpStructFieldFloat32String OpType = 292
OpStructFieldOmitEmptyFloat32String OpType = 293
OpStructEndFloat32String OpType = 294
OpStructEndOmitEmptyFloat32String OpType = 295
OpStructFieldFloat64String OpType = 296
OpStructFieldOmitEmptyFloat64String OpType = 297
OpStructEndFloat64String OpType = 298
OpStructEndOmitEmptyFloat64String OpType = 299
OpStructFieldBoolString OpType = 300
OpStructFieldOmitEmptyBoolString OpType = 301
OpStructEndBoolString OpType = 302
OpStructEndOmitEmptyBoolString OpType = 303
OpStructFieldStringString OpType = 304
OpStructFieldOmitEmptyStringString OpType = 305
OpStructEndStringString OpType = 306
OpStructEndOmitEmptyStringString OpType = 307
OpStructFieldNumberString OpType = 308
OpStructFieldOmitEmptyNumberString OpType = 309
OpStructEndNumberString OpType = 310
OpStructEndOmitEmptyNumberString OpType = 311
OpStructFieldIntPtr OpType = 312
OpStructFieldOmitEmptyIntPtr OpType = 313
OpStructEndIntPtr OpType = 314
OpStructEndOmitEmptyIntPtr OpType = 315
OpStructFieldUintPtr OpType = 316
OpStructFieldOmitEmptyUintPtr OpType = 317
OpStructEndUintPtr OpType = 318
OpStructEndOmitEmptyUintPtr OpType = 319
OpStructFieldFloat32Ptr OpType = 320
OpStructFieldOmitEmptyFloat32Ptr OpType = 321
OpStructEndFloat32Ptr OpType = 322
OpStructEndOmitEmptyFloat32Ptr OpType = 323
OpStructFieldFloat64Ptr OpType = 324
OpStructFieldOmitEmptyFloat64Ptr OpType = 325
OpStructEndFloat64Ptr OpType = 326
OpStructEndOmitEmptyFloat64Ptr OpType = 327
OpStructFieldBoolPtr OpType = 328
OpStructFieldOmitEmptyBoolPtr OpType = 329
OpStructEndBoolPtr OpType = 330
OpStructEndOmitEmptyBoolPtr OpType = 331
OpStructFieldStringPtr OpType = 332
OpStructFieldOmitEmptyStringPtr OpType = 333
OpStructEndStringPtr OpType = 334
OpStructEndOmitEmptyStringPtr OpType = 335
OpStructFieldBytesPtr OpType = 336
OpStructFieldOmitEmptyBytesPtr OpType = 337
OpStructEndBytesPtr OpType = 338
OpStructEndOmitEmptyBytesPtr OpType = 339
OpStructFieldNumberPtr OpType = 340
OpStructFieldOmitEmptyNumberPtr OpType = 341
OpStructEndNumberPtr OpType = 342
OpStructEndOmitEmptyNumberPtr OpType = 343
OpStructFieldArrayPtr OpType = 344
OpStructFieldOmitEmptyArrayPtr OpType = 345
OpStructEndArrayPtr OpType = 346
OpStructEndOmitEmptyArrayPtr OpType = 347
OpStructFieldMapPtr OpType = 348
OpStructFieldOmitEmptyMapPtr OpType = 349
OpStructEndMapPtr OpType = 350
OpStructEndOmitEmptyMapPtr OpType = 351
OpStructFieldSlicePtr OpType = 352
OpStructFieldOmitEmptySlicePtr OpType = 353
OpStructEndSlicePtr OpType = 354
OpStructEndOmitEmptySlicePtr OpType = 355
OpStructFieldMarshalJSONPtr OpType = 356
OpStructFieldOmitEmptyMarshalJSONPtr OpType = 357
OpStructEndMarshalJSONPtr OpType = 358
OpStructEndOmitEmptyMarshalJSONPtr OpType = 359
OpStructFieldMarshalTextPtr OpType = 360
OpStructFieldOmitEmptyMarshalTextPtr OpType = 361
OpStructEndMarshalTextPtr OpType = 362
OpStructEndOmitEmptyMarshalTextPtr OpType = 363
OpStructFieldInterfacePtr OpType = 364
OpStructFieldOmitEmptyInterfacePtr OpType = 365
OpStructEndInterfacePtr OpType = 366
OpStructEndOmitEmptyInterfacePtr OpType = 367
OpStructFieldIntPtrString OpType = 368
OpStructFieldOmitEmptyIntPtrString OpType = 369
OpStructEndIntPtrString OpType = 370
OpStructEndOmitEmptyIntPtrString OpType = 371
OpStructFieldUintPtrString OpType = 372
OpStructFieldOmitEmptyUintPtrString OpType = 373
OpStructEndUintPtrString OpType = 374
OpStructEndOmitEmptyUintPtrString OpType = 375
OpStructFieldFloat32PtrString OpType = 376
OpStructFieldOmitEmptyFloat32PtrString OpType = 377
OpStructEndFloat32PtrString OpType = 378
OpStructEndOmitEmptyFloat32PtrString OpType = 379
OpStructFieldFloat64PtrString OpType = 380
OpStructFieldOmitEmptyFloat64PtrString OpType = 381
OpStructEndFloat64PtrString OpType = 382
OpStructEndOmitEmptyFloat64PtrString OpType = 383
OpStructFieldBoolPtrString OpType = 384
OpStructFieldOmitEmptyBoolPtrString OpType = 385
OpStructEndBoolPtrString OpType = 386
OpStructEndOmitEmptyBoolPtrString OpType = 387
OpStructFieldStringPtrString OpType = 388
OpStructFieldOmitEmptyStringPtrString OpType = 389
OpStructEndStringPtrString OpType = 390
OpStructEndOmitEmptyStringPtrString OpType = 391
OpStructFieldNumberPtrString OpType = 392
OpStructFieldOmitEmptyNumberPtrString OpType = 393
OpStructEndNumberPtrString OpType = 394
OpStructEndOmitEmptyNumberPtrString OpType = 395
OpStructField OpType = 396
OpStructFieldOmitEmpty OpType = 397
OpStructEnd OpType = 398
OpStructEndOmitEmpty OpType = 399
)
func (t OpType) String() string {
if int(t) >= 401 {
if int(t) >= 400 {
return ""
}
return opTypeStrings[int(t)]
@ -896,7 +894,7 @@ func (t OpType) HeadToOmitEmptyHead() OpType {
}
func (t OpType) PtrHeadToHead() OpType {
idx := strings.Index(t.String(), "Ptr")
idx := strings.Index(t.String(), "PtrHead")
if idx == -1 {
return t
}

View File

@ -0,0 +1,135 @@
package encoder
import (
"context"
"fmt"
"reflect"
)
var (
Marshal func(interface{}) ([]byte, error)
Unmarshal func([]byte, interface{}) error
)
type FieldQuery struct {
Name string
Fields []*FieldQuery
hash string
}
func (q *FieldQuery) Hash() string {
if q.hash != "" {
return q.hash
}
b, _ := Marshal(q)
q.hash = string(b)
return q.hash
}
func (q *FieldQuery) MarshalJSON() ([]byte, error) {
if q.Name != "" {
if len(q.Fields) > 0 {
return Marshal(map[string][]*FieldQuery{q.Name: q.Fields})
}
return Marshal(q.Name)
}
return Marshal(q.Fields)
}
func (q *FieldQuery) QueryString() (FieldQueryString, error) {
b, err := Marshal(q)
if err != nil {
return "", err
}
return FieldQueryString(b), nil
}
type FieldQueryString string
func (s FieldQueryString) Build() (*FieldQuery, error) {
var query interface{}
if err := Unmarshal([]byte(s), &query); err != nil {
return nil, err
}
return s.build(reflect.ValueOf(query))
}
func (s FieldQueryString) build(v reflect.Value) (*FieldQuery, error) {
switch v.Type().Kind() {
case reflect.String:
return s.buildString(v)
case reflect.Map:
return s.buildMap(v)
case reflect.Slice:
return s.buildSlice(v)
case reflect.Interface:
return s.build(reflect.ValueOf(v.Interface()))
}
return nil, fmt.Errorf("failed to build field query")
}
func (s FieldQueryString) buildString(v reflect.Value) (*FieldQuery, error) {
b := []byte(v.String())
switch b[0] {
case '[', '{':
var query interface{}
if err := Unmarshal(b, &query); err != nil {
return nil, err
}
if str, ok := query.(string); ok {
return &FieldQuery{Name: str}, nil
}
return s.build(reflect.ValueOf(query))
}
return &FieldQuery{Name: string(b)}, nil
}
func (s FieldQueryString) buildSlice(v reflect.Value) (*FieldQuery, error) {
fields := make([]*FieldQuery, 0, v.Len())
for i := 0; i < v.Len(); i++ {
def, err := s.build(v.Index(i))
if err != nil {
return nil, err
}
fields = append(fields, def)
}
return &FieldQuery{Fields: fields}, nil
}
func (s FieldQueryString) buildMap(v reflect.Value) (*FieldQuery, error) {
keys := v.MapKeys()
if len(keys) != 1 {
return nil, fmt.Errorf("failed to build field query object")
}
key := keys[0]
if key.Type().Kind() != reflect.String {
return nil, fmt.Errorf("failed to build field query. invalid object key type")
}
name := key.String()
def, err := s.build(v.MapIndex(key))
if err != nil {
return nil, err
}
return &FieldQuery{
Name: name,
Fields: def.Fields,
}, nil
}
type queryKey struct{}
func FieldQueryFromContext(ctx context.Context) *FieldQuery {
query := ctx.Value(queryKey{})
if query == nil {
return nil
}
q, ok := query.(*FieldQuery)
if !ok {
return nil
}
return q
}
func SetFieldQueryToContext(ctx context.Context, query *FieldQuery) context.Context {
return context.WithValue(ctx, queryKey{}, query)
}

View File

@ -3,7 +3,6 @@ package encoder
import (
"math/bits"
"reflect"
"unicode/utf8"
"unsafe"
)
@ -12,390 +11,8 @@ const (
msb = 0x8080808080808080
)
var needEscapeWithHTML = [256]bool{
'"': true,
'&': true,
'<': true,
'>': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var needEscape = [256]bool{
'"': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var hex = "0123456789abcdef"
// escapeIndex finds the index of the first char in `s` that requires escaping.
// A char requires escaping if it's outside of the range of [0x20, 0x7F] or if
// it includes a double quote or backslash.
// If no chars in `s` require escaping, the return value is -1.
func escapeIndex(s string) int {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | below(n, 0x20) | contains(n, '"') | contains(n, '\\')
if (mask & msb) != 0 {
return bits.TrailingZeros64(mask&msb) / 8
}
}
valLen := len(s)
for i := len(chunks) * 8; i < valLen; i++ {
if needEscape[s[i]] {
return i
}
}
return -1
}
// below return a mask that can be used to determine if any of the bytes
// in `n` are below `b`. If a byte's MSB is set in the mask then that byte was
// below `b`. The result is only valid if `b`, and each byte in `n`, is below
// 0x80.
func below(n uint64, b byte) uint64 {
return n - expand(b)
}
// contains returns a mask that can be used to determine if any of the
// bytes in `n` are equal to `b`. If a byte's MSB is set in the mask then
// that byte is equal to `b`. The result is only valid if `b`, and each
// byte in `n`, is below 0x80.
func contains(n uint64, b byte) uint64 {
return (n ^ expand(b)) - lsb
}
// expand puts the specified byte into each of the 8 bytes of a uint64.
func expand(b byte) uint64 {
return lsb * uint64(b)
}
//nolint:govet
func stringToUint64Slice(s string) []uint64 {
return *(*[]uint64)(unsafe.Pointer(&reflect.SliceHeader{
@ -406,9 +23,19 @@ func stringToUint64Slice(s string) []uint64 {
}
func AppendString(ctx *RuntimeContext, buf []byte, s string) []byte {
if ctx.Option.Flag&HTMLEscapeOption == 0 {
return appendString(buf, s)
if ctx.Option.Flag&HTMLEscapeOption != 0 {
if ctx.Option.Flag&NormalizeUTF8Option != 0 {
return appendNormalizedHTMLString(buf, s)
}
return appendHTMLString(buf, s)
}
if ctx.Option.Flag&NormalizeUTF8Option != 0 {
return appendNormalizedString(buf, s)
}
return appendString(buf, s)
}
func appendNormalizedHTMLString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)
@ -435,7 +62,7 @@ func AppendString(ctx *RuntimeContext, buf []byte, s string) []byte {
}
}
for i := len(chunks) * 8; i < valLen; i++ {
if needEscapeWithHTML[s[i]] {
if needEscapeHTMLNormalizeUTF8[s[i]] {
j = i
goto ESCAPE_END
}
@ -447,7 +74,7 @@ ESCAPE_END:
for j < valLen {
c := s[j]
if !needEscapeWithHTML[c] {
if !needEscapeHTMLNormalizeUTF8[c] {
// fast path: most of the time, printable ascii characters are used
j++
continue
@ -489,10 +116,220 @@ ESCAPE_END:
i = j + 1
j = j + 1
continue
case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
i = j + 1
j = j + 1
continue
}
state, size := decodeRuneInString(s[j:])
switch state {
case runeErrorState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\ufffd`...)
i = j + 1
j = j + 1
continue
// U+2028 is LINE SEPARATOR.
// U+2029 is PARAGRAPH SEPARATOR.
// They are both technically valid characters in JSON strings,
// but don't work in JSONP, which has to be evaluated as JavaScript,
// and can lead to security holes there. It is valid JSON to
// escape them, so we do so unconditionally.
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
case lineSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u2028`...)
i = j + 3
j = j + 3
continue
case paragraphSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u2029`...)
i = j + 3
j = j + 3
continue
}
j += size
}
// This encodes bytes < 0x20 except for \t, \n and \r.
if c < 0x20 {
return append(append(buf, s[i:]...), '"')
}
func appendHTMLString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)
}
buf = append(buf, '"')
var (
i, j int
)
if valLen >= 8 {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb) |
((n ^ (lsb * '<')) - lsb) |
((n ^ (lsb * '>')) - lsb) |
((n ^ (lsb * '&')) - lsb)
if (mask & msb) != 0 {
j = bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE_END
}
}
for i := len(chunks) * 8; i < valLen; i++ {
if needEscapeHTML[s[i]] {
j = i
goto ESCAPE_END
}
}
// no found any escape characters.
return append(append(buf, s...), '"')
}
ESCAPE_END:
for j < valLen {
c := s[j]
if !needEscapeHTML[c] {
// fast path: most of the time, printable ascii characters are used
j++
continue
}
switch c {
case '\\', '"':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', c)
i = j + 1
j = j + 1
continue
case '\n':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'n')
i = j + 1
j = j + 1
continue
case '\r':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'r')
i = j + 1
j = j + 1
continue
case '\t':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 't')
i = j + 1
j = j + 1
continue
case '<', '>', '&':
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
i = j + 1
j = j + 1
continue
case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
i = j + 1
j = j + 1
continue
}
j++
}
return append(append(buf, s[i:]...), '"')
}
func appendNormalizedString(buf []byte, s string) []byte {
valLen := len(s)
if valLen == 0 {
return append(buf, `""`...)
}
buf = append(buf, '"')
var (
i, j int
)
if valLen >= 8 {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb)
if (mask & msb) != 0 {
j = bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE_END
}
}
valLen := len(s)
for i := len(chunks) * 8; i < valLen; i++ {
if needEscapeNormalizeUTF8[s[i]] {
j = i
goto ESCAPE_END
}
}
return append(append(buf, s...), '"')
}
ESCAPE_END:
for j < valLen {
c := s[j]
if !needEscapeNormalizeUTF8[c] {
// fast path: most of the time, printable ascii characters are used
j++
continue
}
switch c {
case '\\', '"':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', c)
i = j + 1
j = j + 1
continue
case '\n':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'n')
i = j + 1
j = j + 1
continue
case '\r':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 'r')
i = j + 1
j = j + 1
continue
case '\t':
buf = append(buf, s[i:j]...)
buf = append(buf, '\\', 't')
i = j + 1
j = j + 1
continue
case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
@ -501,18 +338,14 @@ ESCAPE_END:
continue
}
r, size := utf8.DecodeRuneInString(s[j:])
if r == utf8.RuneError && size == 1 {
state, size := decodeRuneInString(s[j:])
switch state {
case runeErrorState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\ufffd`...)
i = j + size
j = j + size
i = j + 1
j = j + 1
continue
}
switch r {
case '\u2028', '\u2029':
// U+2028 is LINE SEPARATOR.
// U+2029 is PARAGRAPH SEPARATOR.
// They are both technically valid characters in JSON strings,
@ -520,14 +353,19 @@ ESCAPE_END:
// and can lead to security holes there. It is valid JSON to
// escape them, so we do so unconditionally.
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
case lineSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u202`...)
buf = append(buf, hex[r&0xF])
i = j + size
j = j + size
buf = append(buf, `\u2028`...)
i = j + 3
j = j + 3
continue
case paragraphSepState:
buf = append(buf, s[i:j]...)
buf = append(buf, `\u2029`...)
i = j + 3
j = j + 3
continue
}
j += size
}
@ -540,19 +378,37 @@ func appendString(buf []byte, s string) []byte {
return append(buf, `""`...)
}
buf = append(buf, '"')
var escapeIdx int
var (
i, j int
)
if valLen >= 8 {
if escapeIdx = escapeIndex(s); escapeIdx < 0 {
chunks := stringToUint64Slice(s)
for _, n := range chunks {
// combine masks before checking for the MSB of each byte. We include
// `n` in the mask to check whether any of the *input* byte MSBs were
// set (i.e. the byte was outside the ASCII range).
mask := n | (n - (lsb * 0x20)) |
((n ^ (lsb * '"')) - lsb) |
((n ^ (lsb * '\\')) - lsb)
if (mask & msb) != 0 {
j = bits.TrailingZeros64(mask&msb) / 8
goto ESCAPE_END
}
}
valLen := len(s)
for i := len(chunks) * 8; i < valLen; i++ {
if needEscape[s[i]] {
j = i
goto ESCAPE_END
}
}
return append(append(buf, s...), '"')
}
}
i := 0
j := escapeIdx
ESCAPE_END:
for j < valLen {
c := s[j]
if c >= 0x20 && c <= 0x7f && c != '\\' && c != '"' {
if !needEscape[c] {
// fast path: most of the time, printable ascii characters are used
j++
continue
@ -587,7 +443,8 @@ func appendString(buf []byte, s string) []byte {
j = j + 1
continue
case '<', '>', '&':
case 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B, 0x0C, 0x0E, 0x0F, // 0x00-0x0F
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F: // 0x10-0x1F
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
@ -595,45 +452,7 @@ func appendString(buf []byte, s string) []byte {
j = j + 1
continue
}
// This encodes bytes < 0x20 except for \t, \n and \r.
if c < 0x20 {
buf = append(buf, s[i:j]...)
buf = append(buf, `\u00`...)
buf = append(buf, hex[c>>4], hex[c&0xF])
i = j + 1
j = j + 1
continue
}
r, size := utf8.DecodeRuneInString(s[j:])
if r == utf8.RuneError && size == 1 {
buf = append(buf, s[i:j]...)
buf = append(buf, `\ufffd`...)
i = j + size
j = j + size
continue
}
switch r {
case '\u2028', '\u2029':
// U+2028 is LINE SEPARATOR.
// U+2029 is PARAGRAPH SEPARATOR.
// They are both technically valid characters in JSON strings,
// but don't work in JSONP, which has to be evaluated as JavaScript,
// and can lead to security holes there. It is valid JSON to
// escape them, so we do so unconditionally.
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
buf = append(buf, s[i:j]...)
buf = append(buf, `\u202`...)
buf = append(buf, hex[r&0xF])
i = j + size
j = j + size
continue
}
j += size
j++
}
return append(append(buf, s[i:]...), '"')

View File

@ -0,0 +1,415 @@
package encoder
var needEscapeHTMLNormalizeUTF8 = [256]bool{
'"': true,
'&': true,
'<': true,
'>': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var needEscapeNormalizeUTF8 = [256]bool{
'"': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0x7f */
0x80: true,
0x81: true,
0x82: true,
0x83: true,
0x84: true,
0x85: true,
0x86: true,
0x87: true,
0x88: true,
0x89: true,
0x8a: true,
0x8b: true,
0x8c: true,
0x8d: true,
0x8e: true,
0x8f: true,
0x90: true,
0x91: true,
0x92: true,
0x93: true,
0x94: true,
0x95: true,
0x96: true,
0x97: true,
0x98: true,
0x99: true,
0x9a: true,
0x9b: true,
0x9c: true,
0x9d: true,
0x9e: true,
0x9f: true,
0xa0: true,
0xa1: true,
0xa2: true,
0xa3: true,
0xa4: true,
0xa5: true,
0xa6: true,
0xa7: true,
0xa8: true,
0xa9: true,
0xaa: true,
0xab: true,
0xac: true,
0xad: true,
0xae: true,
0xaf: true,
0xb0: true,
0xb1: true,
0xb2: true,
0xb3: true,
0xb4: true,
0xb5: true,
0xb6: true,
0xb7: true,
0xb8: true,
0xb9: true,
0xba: true,
0xbb: true,
0xbc: true,
0xbd: true,
0xbe: true,
0xbf: true,
0xc0: true,
0xc1: true,
0xc2: true,
0xc3: true,
0xc4: true,
0xc5: true,
0xc6: true,
0xc7: true,
0xc8: true,
0xc9: true,
0xca: true,
0xcb: true,
0xcc: true,
0xcd: true,
0xce: true,
0xcf: true,
0xd0: true,
0xd1: true,
0xd2: true,
0xd3: true,
0xd4: true,
0xd5: true,
0xd6: true,
0xd7: true,
0xd8: true,
0xd9: true,
0xda: true,
0xdb: true,
0xdc: true,
0xdd: true,
0xde: true,
0xdf: true,
0xe0: true,
0xe1: true,
0xe2: true,
0xe3: true,
0xe4: true,
0xe5: true,
0xe6: true,
0xe7: true,
0xe8: true,
0xe9: true,
0xea: true,
0xeb: true,
0xec: true,
0xed: true,
0xee: true,
0xef: true,
0xf0: true,
0xf1: true,
0xf2: true,
0xf3: true,
0xf4: true,
0xf5: true,
0xf6: true,
0xf7: true,
0xf8: true,
0xf9: true,
0xfa: true,
0xfb: true,
0xfc: true,
0xfd: true,
0xfe: true,
0xff: true,
}
var needEscapeHTML = [256]bool{
'"': true,
'&': true,
'<': true,
'>': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0xff */
}
var needEscape = [256]bool{
'"': true,
'\\': true,
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
0x09: true,
0x0a: true,
0x0b: true,
0x0c: true,
0x0d: true,
0x0e: true,
0x0f: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1a: true,
0x1b: true,
0x1c: true,
0x1d: true,
0x1e: true,
0x1f: true,
/* 0x20 - 0xff */
}

View File

@ -16,16 +16,17 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
}
if err := recover(); err != nil {
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err)
}
}()

View File

@ -68,7 +68,19 @@ func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr {
return p
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToUint64(p uintptr, bitSize uint8) uint64 {
switch bitSize {
case 8:
return (uint64)(**(**uint8)(unsafe.Pointer(&p)))
case 16:
return (uint64)(**(**uint16)(unsafe.Pointer(&p)))
case 32:
return (uint64)(**(**uint32)(unsafe.Pointer(&p)))
case 64:
return **(**uint64)(unsafe.Pointer(&p))
}
return 0
}
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
@ -114,6 +126,10 @@ func appendComma(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ',')
}
func appendNullComma(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, "null,"...)
}
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
last := len(b) - 1
b[last] = ':'

File diff suppressed because it is too large Load Diff

View File

@ -16,16 +16,17 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() {
if err := recover(); err != nil {
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err)
}
}()

View File

@ -61,7 +61,19 @@ func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr {
return p
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToUint64(p uintptr, bitSize uint8) uint64 {
switch bitSize {
case 8:
return (uint64)(**(**uint8)(unsafe.Pointer(&p)))
case 16:
return (uint64)(**(**uint16)(unsafe.Pointer(&p)))
case 32:
return (uint64)(**(**uint32)(unsafe.Pointer(&p)))
case 64:
return **(**uint64)(unsafe.Pointer(&p))
}
return 0
}
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
@ -92,17 +104,17 @@ func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} {
}))
}
func appendInt(ctx *encoder.RuntimeContext, b []byte, v uint64, code *encoder.Opcode) []byte {
func appendInt(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte {
format := ctx.Option.ColorScheme.Int
b = append(b, format.Header...)
b = encoder.AppendInt(ctx, b, v, code)
b = encoder.AppendInt(ctx, b, p, code)
return append(b, format.Footer...)
}
func appendUint(ctx *encoder.RuntimeContext, b []byte, v uint64, code *encoder.Opcode) []byte {
func appendUint(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte {
format := ctx.Option.ColorScheme.Uint
b = append(b, format.Header...)
b = encoder.AppendUint(ctx, b, v, code)
b = encoder.AppendUint(ctx, b, p, code)
return append(b, format.Footer...)
}
@ -166,6 +178,13 @@ func appendComma(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ',')
}
func appendNullComma(ctx *encoder.RuntimeContext, b []byte) []byte {
format := ctx.Option.ColorScheme.Null
b = append(b, format.Header...)
b = append(b, "null"...)
return append(append(b, format.Footer...), ',')
}
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
last := len(b) - 1
b[last] = ':'

File diff suppressed because it is too large Load Diff

View File

@ -16,16 +16,17 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() {
if err := recover(); err != nil {
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err)
}
}()

View File

@ -63,7 +63,20 @@ func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr {
return p
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToUint64(p uintptr, bitSize uint8) uint64 {
switch bitSize {
case 8:
return (uint64)(**(**uint8)(unsafe.Pointer(&p)))
case 16:
return (uint64)(**(**uint16)(unsafe.Pointer(&p)))
case 32:
return (uint64)(**(**uint32)(unsafe.Pointer(&p)))
case 64:
return **(**uint64)(unsafe.Pointer(&p))
}
return 0
}
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
@ -94,17 +107,17 @@ func ptrToInterface(code *encoder.Opcode, p uintptr) interface{} {
}))
}
func appendInt(ctx *encoder.RuntimeContext, b []byte, v uint64, code *encoder.Opcode) []byte {
func appendInt(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte {
format := ctx.Option.ColorScheme.Int
b = append(b, format.Header...)
b = encoder.AppendInt(ctx, b, v, code)
b = encoder.AppendInt(ctx, b, p, code)
return append(b, format.Footer...)
}
func appendUint(ctx *encoder.RuntimeContext, b []byte, v uint64, code *encoder.Opcode) []byte {
func appendUint(ctx *encoder.RuntimeContext, b []byte, p uintptr, code *encoder.Opcode) []byte {
format := ctx.Option.ColorScheme.Uint
b = append(b, format.Header...)
b = encoder.AppendUint(ctx, b, v, code)
b = encoder.AppendUint(ctx, b, p, code)
return append(b, format.Footer...)
}
@ -168,6 +181,13 @@ func appendComma(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ',', '\n')
}
func appendNullComma(ctx *encoder.RuntimeContext, b []byte) []byte {
format := ctx.Option.ColorScheme.Null
b = append(b, format.Header...)
b = append(b, "null"...)
return append(append(b, format.Footer...), ',', '\n')
}
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ':', ' ')
}

File diff suppressed because it is too large Load Diff

View File

@ -16,16 +16,17 @@ func DebugRun(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet)
defer func() {
if err := recover(); err != nil {
fmt.Println("=============[DEBUG]===============")
fmt.Println("* [TYPE]")
fmt.Println(codeSet.Type)
fmt.Printf("\n")
fmt.Println("* [ALL OPCODE]")
fmt.Println(code.Dump())
fmt.Printf("\n")
fmt.Println("* [CONTEXT]")
fmt.Printf("%+v\n", ctx)
fmt.Println("===================================")
w := ctx.Option.DebugOut
fmt.Fprintln(w, "=============[DEBUG]===============")
fmt.Fprintln(w, "* [TYPE]")
fmt.Fprintln(w, codeSet.Type)
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [ALL OPCODE]")
fmt.Fprintln(w, code.Dump())
fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "* [CONTEXT]")
fmt.Fprintf(w, "%+v\n", ctx)
fmt.Fprintln(w, "===================================")
panic(err)
}
}()

View File

@ -70,7 +70,19 @@ func loadNPtr(base uintptr, idx uint32, ptrNum uint8) uintptr {
return p
}
func ptrToUint64(p uintptr) uint64 { return **(**uint64)(unsafe.Pointer(&p)) }
func ptrToUint64(p uintptr, bitSize uint8) uint64 {
switch bitSize {
case 8:
return (uint64)(**(**uint8)(unsafe.Pointer(&p)))
case 16:
return (uint64)(**(**uint16)(unsafe.Pointer(&p)))
case 32:
return (uint64)(**(**uint32)(unsafe.Pointer(&p)))
case 64:
return **(**uint64)(unsafe.Pointer(&p))
}
return 0
}
func ptrToFloat32(p uintptr) float32 { return **(**float32)(unsafe.Pointer(&p)) }
func ptrToFloat64(p uintptr) float64 { return **(**float64)(unsafe.Pointer(&p)) }
func ptrToBool(p uintptr) bool { return **(**bool)(unsafe.Pointer(&p)) }
@ -116,6 +128,10 @@ func appendComma(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ',', '\n')
}
func appendNullComma(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, "null,\n"...)
}
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
return append(b, ':', ' ')
}

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,11 @@ func getTag(field reflect.StructField) string {
func IsIgnoredStructField(field reflect.StructField) bool {
if field.PkgPath != "" {
if field.Anonymous {
if !(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct) && field.Type.Kind() != reflect.Struct {
t := field.Type
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return true
}
} else {

View File

@ -364,3 +364,8 @@ func Valid(data []byte) bool {
}
return decoder.InputOffset() >= int64(len(data))
}
func init() {
encoder.Marshal = Marshal
encoder.Unmarshal = Unmarshal
}

View File

@ -1,6 +1,8 @@
package json
import (
"io"
"github.com/goccy/go-json/internal/decoder"
"github.com/goccy/go-json/internal/encoder"
)
@ -15,6 +17,23 @@ func UnorderedMap() EncodeOptionFunc {
}
}
// DisableHTMLEscape disables escaping of HTML characters ( '&', '<', '>' ) when encoding string.
func DisableHTMLEscape() EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.Flag &= ^encoder.HTMLEscapeOption
}
}
// DisableNormalizeUTF8
// By default, when encoding string, UTF8 characters in the range of 0x80 - 0xFF are processed by applying \ufffd for invalid code and escaping for \u2028 and \u2029.
// This option disables this behaviour. You can expect faster speeds by applying this option, but be careful.
// encoding/json implements here: https://github.com/golang/go/blob/6178d25fc0b28724b1b5aec2b1b74fc06d9294c7/src/encoding/json/encode.go#L1067-L1093.
func DisableNormalizeUTF8() EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.Flag &= ^encoder.NormalizeUTF8Option
}
}
// Debug outputs debug information when panic occurs during encoding.
func Debug() EncodeOptionFunc {
return func(opt *EncodeOption) {
@ -22,6 +41,13 @@ func Debug() EncodeOptionFunc {
}
}
// DebugWith sets the destination to write debug messages.
func DebugWith(w io.Writer) EncodeOptionFunc {
return func(opt *EncodeOption) {
opt.DebugOut = w
}
}
// Colorize add an identifier for coloring to the string of the encoded result.
func Colorize(scheme *ColorScheme) EncodeOptionFunc {
return func(opt *EncodeOption) {

47
vendor/github.com/goccy/go-json/query.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package json
import (
"github.com/goccy/go-json/internal/encoder"
)
type (
// FieldQuery you can dynamically filter the fields in the structure by creating a FieldQuery,
// adding it to context.Context using SetFieldQueryToContext and then passing it to MarshalContext.
// This is a type-safe operation, so it is faster than filtering using map[string]interface{}.
FieldQuery = encoder.FieldQuery
FieldQueryString = encoder.FieldQueryString
)
var (
// FieldQueryFromContext get current FieldQuery from context.Context.
FieldQueryFromContext = encoder.FieldQueryFromContext
// SetFieldQueryToContext set current FieldQuery to context.Context.
SetFieldQueryToContext = encoder.SetFieldQueryToContext
)
// BuildFieldQuery builds FieldQuery by fieldName or sub field query.
// First, specify the field name that you want to keep in structure type.
// If the field you want to keep is a structure type, by creating a sub field query using BuildSubFieldQuery,
// you can select the fields you want to keep in the structure.
// This description can be written recursively.
func BuildFieldQuery(fields ...FieldQueryString) (*FieldQuery, error) {
query, err := Marshal(fields)
if err != nil {
return nil, err
}
return FieldQueryString(query).Build()
}
// BuildSubFieldQuery builds sub field query.
func BuildSubFieldQuery(name string) *SubFieldQuery {
return &SubFieldQuery{name: name}
}
type SubFieldQuery struct {
name string
}
func (q *SubFieldQuery) Fields(fields ...FieldQueryString) FieldQueryString {
query, _ := Marshal(map[string][]FieldQueryString{q.name: fields})
return FieldQueryString(query)
}

View File

@ -8,8 +8,6 @@
A high-performance 100% compatible drop-in replacement of "encoding/json"
You can also use thrift like JSON using [thrift-iterator](https://github.com/thrift-iterator/go)
# Benchmark
![benchmark](http://jsoniter.com/benchmarks/go-benchmark.png)

View File

@ -1,5 +1,153 @@
# Changelog
## v4.9.1 - 2022-10-12
**Fixes**
* Fix logger panicing (when template is set to empty) by bumping dependency version [#2295](https://github.com/labstack/echo/issues/2295)
**Enhancements**
* Improve CORS documentation [#2272](https://github.com/labstack/echo/pull/2272)
* Update readme about supported Go versions [#2291](https://github.com/labstack/echo/pull/2291)
* Tests: improve error handling on closing body [#2254](https://github.com/labstack/echo/pull/2254)
* Tests: refactor some of the assertions in tests [#2275](https://github.com/labstack/echo/pull/2275)
* Tests: refactor assertions [#2301](https://github.com/labstack/echo/pull/2301)
## v4.9.0 - 2022-09-04
**Security**
* Fix open redirect vulnerability in handlers serving static directories (e.Static, e.StaticFs, echo.StaticDirectoryHandler) [#2260](https://github.com/labstack/echo/pull/2260)
**Enhancements**
* Allow configuring ErrorHandler in CSRF middleware [#2257](https://github.com/labstack/echo/pull/2257)
* Replace HTTP method constants in tests with stdlib constants [#2247](https://github.com/labstack/echo/pull/2247)
## v4.8.0 - 2022-08-10
**Most notable things**
You can now add any arbitrary HTTP method type as a route [#2237](https://github.com/labstack/echo/pull/2237)
```go
e.Add("COPY", "/*", func(c echo.Context) error
return c.String(http.StatusOK, "OK COPY")
})
```
You can add custom 404 handler for specific paths [#2217](https://github.com/labstack/echo/pull/2217)
```go
e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
g := e.Group("/images")
g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
```
**Enhancements**
* Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder [#2127](https://github.com/labstack/echo/pull/2127)
* Refactor: body_limit middleware unit test [#2145](https://github.com/labstack/echo/pull/2145)
* Refactor: Timeout mw: rework how test waits for timeout. [#2187](https://github.com/labstack/echo/pull/2187)
* BasicAuth middleware returns 500 InternalServerError on invalid base64 strings but should return 400 [#2191](https://github.com/labstack/echo/pull/2191)
* Refactor: duplicated findStaticChild process at findChildWithLabel [#2176](https://github.com/labstack/echo/pull/2176)
* Allow different param names in different methods with same path scheme [#2209](https://github.com/labstack/echo/pull/2209)
* Add support for registering handlers for different 404 routes [#2217](https://github.com/labstack/echo/pull/2217)
* Middlewares should use errors.As() instead of type assertion on HTTPError [#2227](https://github.com/labstack/echo/pull/2227)
* Allow arbitrary HTTP method types to be added as routes [#2237](https://github.com/labstack/echo/pull/2237)
## v4.7.2 - 2022-03-16
**Fixes**
* Fix nil pointer exception when calling Start again after address binding error [#2131](https://github.com/labstack/echo/pull/2131)
* Fix CSRF middleware not being able to extract token from multipart/form-data form [#2136](https://github.com/labstack/echo/pull/2136)
* Fix Timeout middleware write race [#2126](https://github.com/labstack/echo/pull/2126)
**Enhancements**
* Recover middleware should not log panic for aborted handler [#2134](https://github.com/labstack/echo/pull/2134)
## v4.7.1 - 2022-03-13
**Fixes**
* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+) [#2123](https://github.com/labstack/echo/pull/2123)
**Enhancements**
* Remove some unused code [#2116](https://github.com/labstack/echo/pull/2116)
## v4.7.0 - 2022-03-01
**Enhancements**
* Add JWT, KeyAuth, CSRF multivalue extractors [#2060](https://github.com/labstack/echo/pull/2060)
* Add LogErrorFunc to recover middleware [#2072](https://github.com/labstack/echo/pull/2072)
* Add support for HEAD method query params binding [#2027](https://github.com/labstack/echo/pull/2027)
* Improve filesystem support with echo.FileFS, echo.StaticFS, group.FileFS, group.StaticFS [#2064](https://github.com/labstack/echo/pull/2064)
**Fixes**
* Fix X-Real-IP bug, improve tests [#2007](https://github.com/labstack/echo/pull/2007)
* Minor syntax fixes [#1994](https://github.com/labstack/echo/pull/1994), [#2102](https://github.com/labstack/echo/pull/2102), [#2102](https://github.com/labstack/echo/pull/2102)
**General**
* Add cache-control and connection headers [#2103](https://github.com/labstack/echo/pull/2103)
* Add Retry-After header constant [#2078](https://github.com/labstack/echo/pull/2078)
* Upgrade `go` directive in `go.mod` to 1.17 [#2049](https://github.com/labstack/echo/pull/2049)
* Add Pagoda [#2077](https://github.com/labstack/echo/pull/2077) and Souin [#2069](https://github.com/labstack/echo/pull/2069) to 3rd-party middlewares in README
## v4.6.3 - 2022-01-10
**Fixes**
* Fixed Echo version number in greeting message which was not incremented to `4.6.2` [#2066](https://github.com/labstack/echo/issues/2066)
## v4.6.2 - 2022-01-08
**Fixes**
* Fixed route containing escaped colon should be matchable but is not matched to request path [#2047](https://github.com/labstack/echo/pull/2047)
* Fixed a problem that returned wrong content-encoding when the gzip compressed content was empty. [#1921](https://github.com/labstack/echo/pull/1921)
* Update (test) dependencies [#2021](https://github.com/labstack/echo/pull/2021)
**Enhancements**
* Add support for configurable target header for the request_id middleware [#2040](https://github.com/labstack/echo/pull/2040)
* Change decompress middleware to use stream decompression instead of buffering [#2018](https://github.com/labstack/echo/pull/2018)
* Documentation updates
## v4.6.1 - 2021-09-26
**Enhancements**
* Add start time to request logger middleware values [#1991](https://github.com/labstack/echo/pull/1991)
## v4.6.0 - 2021-09-20
Introduced a new [request logger](https://github.com/labstack/echo/blob/master/middleware/request_logger.go) middleware
to help with cases when you want to use some other logging library in your application.
**Fixes**
* fix timeout middleware warning: superfluous response.WriteHeader [#1905](https://github.com/labstack/echo/issues/1905)
**Enhancements**
* Add Cookie to KeyAuth middleware's KeyLookup [#1929](https://github.com/labstack/echo/pull/1929)
* JWT middleware should ignore case of auth scheme in request header [#1951](https://github.com/labstack/echo/pull/1951)
* Refactor default error handler to return first if response is already committed [#1956](https://github.com/labstack/echo/pull/1956)
* Added request logger middleware which helps to use custom logger library for logging requests. [#1980](https://github.com/labstack/echo/pull/1980)
* Allow escaping of colon in route path so Google Cloud API "custom methods" could be implemented [#1988](https://github.com/labstack/echo/pull/1988)
## v4.5.0 - 2021-08-01
**Important notes**

View File

@ -9,7 +9,7 @@ tag:
check: lint vet race ## Check project
init:
@go get -u golang.org/x/lint/golint
@go install golang.org/x/lint/golint@latest
lint: ## Lint the files
@golint -set_exit_status ${PKG_LIST}
@ -29,6 +29,6 @@ benchmark: ## Run benchmarks
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
goversion ?= "1.15"
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15
goversion ?= "1.16"
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.16
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"

View File

@ -5,19 +5,17 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
## Supported Go versions
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
Therefore a Go version capable of understanding /vN suffixed imports is required:
- 1.9.7+
- 1.10.3+
- 1.14+
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
way of using Echo going forward.
@ -66,9 +64,9 @@ go get github.com/labstack/echo/v4
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net/http"
)
func main() {
@ -92,10 +90,24 @@ func hello(c echo.Context) error {
}
```
# Third-party middlewares
| Repository | Description |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | (by Echo team) [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares |
| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator |
| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. |
| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. |
| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. |
| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo. |
| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo) | ProtoBuf generate Echo server side code |
Please send a PR to add your own library here.
## Help
- [Forum](https://github.com/labstack/echo/discussions)
- [Chat](https://gitter.im/labstack/echo)
## Contribute
@ -114,8 +126,11 @@ func hello(c echo.Context) error {
## Credits
- [Vishal Rana](https://github.com/vishr) - Author
- [Nitin Rana](https://github.com/nr17) - Consultant
- [Vishal Rana](https://github.com/vishr) (Author)
- [Nitin Rana](https://github.com/nr17) (Consultant)
- [Roland Lammel](https://github.com/lammel) (Maintainer)
- [Martti T.](https://github.com/aldas) (Maintainer)
- [Pablo Andres Fuente](https://github.com/pafuent) (Maintainer)
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
## License

View File

@ -111,11 +111,11 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.BindPathParams(c, i); err != nil {
return err
}
// Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
// Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
// i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding.
// This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
// Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
// For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
// The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
method := c.Request().Method
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
if err = b.BindQueryParams(c, i); err != nil {
return err
}

View File

@ -1,6 +1,8 @@
package echo
import (
"encoding"
"encoding/json"
"fmt"
"net/http"
"strconv"
@ -52,8 +54,11 @@ import (
* time
* duration
* BindUnmarshaler() interface
* TextUnmarshaler() interface
* JSONUnmarshaler() interface
* UnixTime() - converts unix time (integer) to time.Time
* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
* UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time
* UnixTimeNano() - converts unix time with nanosecond precision (integer) to time.Time
* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
*/
@ -204,7 +209,7 @@ func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []st
return b.customFunc(sourceParam, customFunc, false)
}
// MustCustomFunc requires parameter values to exist to be bind with Func. Returns error when value does not exist.
// MustCustomFunc requires parameter values to exist to bind with Func. Returns error when value does not exist.
func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
return b.customFunc(sourceParam, customFunc, true)
}
@ -241,7 +246,7 @@ func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder {
return b
}
// MustString requires parameter value to exist to be bind to string variable. Returns error when value does not exist
// MustString requires parameter value to exist to bind to string variable. Returns error when value does not exist
func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder {
if b.failFast && b.errors != nil {
return b
@ -270,7 +275,7 @@ func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder {
return b
}
// MustStrings requires parameter values to exist to be bind to slice of string variables. Returns error when value does not exist
// MustStrings requires parameter values to exist to bind to slice of string variables. Returns error when value does not exist
func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder {
if b.failFast && b.errors != nil {
return b
@ -302,7 +307,7 @@ func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler)
return b
}
// MustBindUnmarshaler requires parameter value to exist to be bind to destination implementing BindUnmarshaler interface.
// MustBindUnmarshaler requires parameter value to exist to bind to destination implementing BindUnmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
@ -321,13 +326,85 @@ func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshal
return b
}
// JSONUnmarshaler binds parameter to destination implementing json.Unmarshaler interface
func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
return b
}
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
}
return b
}
// MustJSONUnmarshaler requires parameter value to exist to bind to destination implementing json.Unmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
return b
}
if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
}
return b
}
// TextUnmarshaler binds parameter to destination implementing encoding.TextUnmarshaler interface
func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
return b
}
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
}
return b
}
// MustTextUnmarshaler requires parameter value to exist to bind to destination implementing encoding.TextUnmarshaler interface.
// Returns error when value does not exist
func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
tmp := b.ValueFunc(sourceParam)
if tmp == "" {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
return b
}
if err := dest.UnmarshalText([]byte(tmp)); err != nil {
b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
}
return b
}
// BindWithDelimiter binds parameter to destination by suitable conversion function.
// Delimiter is used before conversion to split parameter value to separate values
func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
return b.bindWithDelimiter(sourceParam, dest, delimiter, false)
}
// MustBindWithDelimiter requires parameter value to exist to be bind destination by suitable conversion function.
// MustBindWithDelimiter requires parameter value to exist to bind destination by suitable conversion function.
// Delimiter is used before conversion to split parameter value to separate values
func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
return b.bindWithDelimiter(sourceParam, dest, delimiter, true)
@ -376,7 +453,7 @@ func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder {
return b.intValue(sourceParam, dest, 64, false)
}
// MustInt64 requires parameter value to exist to be bind to int64 variable. Returns error when value does not exist
// MustInt64 requires parameter value to exist to bind to int64 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder {
return b.intValue(sourceParam, dest, 64, true)
}
@ -386,7 +463,7 @@ func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder {
return b.intValue(sourceParam, dest, 32, false)
}
// MustInt32 requires parameter value to exist to be bind to int32 variable. Returns error when value does not exist
// MustInt32 requires parameter value to exist to bind to int32 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder {
return b.intValue(sourceParam, dest, 32, true)
}
@ -396,7 +473,7 @@ func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder {
return b.intValue(sourceParam, dest, 16, false)
}
// MustInt16 requires parameter value to exist to be bind to int16 variable. Returns error when value does not exist
// MustInt16 requires parameter value to exist to bind to int16 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder {
return b.intValue(sourceParam, dest, 16, true)
}
@ -406,7 +483,7 @@ func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder {
return b.intValue(sourceParam, dest, 8, false)
}
// MustInt8 requires parameter value to exist to be bind to int8 variable. Returns error when value does not exist
// MustInt8 requires parameter value to exist to bind to int8 variable. Returns error when value does not exist
func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder {
return b.intValue(sourceParam, dest, 8, true)
}
@ -416,7 +493,7 @@ func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder {
return b.intValue(sourceParam, dest, 0, false)
}
// MustInt requires parameter value to exist to be bind to int variable. Returns error when value does not exist
// MustInt requires parameter value to exist to bind to int variable. Returns error when value does not exist
func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder {
return b.intValue(sourceParam, dest, 0, true)
}
@ -544,7 +621,7 @@ func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt64s requires parameter value to exist to be bind to int64 slice variable. Returns error when value does not exist
// MustInt64s requires parameter value to exist to bind to int64 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
@ -554,7 +631,7 @@ func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt32s requires parameter value to exist to be bind to int32 slice variable. Returns error when value does not exist
// MustInt32s requires parameter value to exist to bind to int32 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
@ -564,7 +641,7 @@ func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt16s requires parameter value to exist to be bind to int16 slice variable. Returns error when value does not exist
// MustInt16s requires parameter value to exist to bind to int16 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
@ -574,7 +651,7 @@ func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInt8s requires parameter value to exist to be bind to int8 slice variable. Returns error when value does not exist
// MustInt8s requires parameter value to exist to bind to int8 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
@ -584,7 +661,7 @@ func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder {
return b.intsValue(sourceParam, dest, false)
}
// MustInts requires parameter value to exist to be bind to int slice variable. Returns error when value does not exist
// MustInts requires parameter value to exist to bind to int slice variable. Returns error when value does not exist
func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder {
return b.intsValue(sourceParam, dest, true)
}
@ -594,7 +671,7 @@ func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder {
return b.uintValue(sourceParam, dest, 64, false)
}
// MustUint64 requires parameter value to exist to be bind to uint64 variable. Returns error when value does not exist
// MustUint64 requires parameter value to exist to bind to uint64 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder {
return b.uintValue(sourceParam, dest, 64, true)
}
@ -604,7 +681,7 @@ func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder {
return b.uintValue(sourceParam, dest, 32, false)
}
// MustUint32 requires parameter value to exist to be bind to uint32 variable. Returns error when value does not exist
// MustUint32 requires parameter value to exist to bind to uint32 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder {
return b.uintValue(sourceParam, dest, 32, true)
}
@ -614,7 +691,7 @@ func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder {
return b.uintValue(sourceParam, dest, 16, false)
}
// MustUint16 requires parameter value to exist to be bind to uint16 variable. Returns error when value does not exist
// MustUint16 requires parameter value to exist to bind to uint16 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder {
return b.uintValue(sourceParam, dest, 16, true)
}
@ -624,7 +701,7 @@ func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, false)
}
// MustUint8 requires parameter value to exist to be bind to uint8 variable. Returns error when value does not exist
// MustUint8 requires parameter value to exist to bind to uint8 variable. Returns error when value does not exist
func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, true)
}
@ -634,7 +711,7 @@ func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, false)
}
// MustByte requires parameter value to exist to be bind to byte variable. Returns error when value does not exist
// MustByte requires parameter value to exist to bind to byte variable. Returns error when value does not exist
func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder {
return b.uintValue(sourceParam, dest, 8, true)
}
@ -644,7 +721,7 @@ func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder {
return b.uintValue(sourceParam, dest, 0, false)
}
// MustUint requires parameter value to exist to be bind to uint variable. Returns error when value does not exist
// MustUint requires parameter value to exist to bind to uint variable. Returns error when value does not exist
func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder {
return b.uintValue(sourceParam, dest, 0, true)
}
@ -772,7 +849,7 @@ func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUint64s requires parameter value to exist to be bind to uint64 slice variable. Returns error when value does not exist
// MustUint64s requires parameter value to exist to bind to uint64 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
@ -782,7 +859,7 @@ func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUint32s requires parameter value to exist to be bind to uint32 slice variable. Returns error when value does not exist
// MustUint32s requires parameter value to exist to bind to uint32 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
@ -792,7 +869,7 @@ func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUint16s requires parameter value to exist to be bind to uint16 slice variable. Returns error when value does not exist
// MustUint16s requires parameter value to exist to bind to uint16 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
@ -802,7 +879,7 @@ func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUint8s requires parameter value to exist to be bind to uint8 slice variable. Returns error when value does not exist
// MustUint8s requires parameter value to exist to bind to uint8 slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
@ -812,7 +889,7 @@ func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBinder {
return b.uintsValue(sourceParam, dest, false)
}
// MustUints requires parameter value to exist to be bind to uint slice variable. Returns error when value does not exist
// MustUints requires parameter value to exist to bind to uint slice variable. Returns error when value does not exist
func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder {
return b.uintsValue(sourceParam, dest, true)
}
@ -822,7 +899,7 @@ func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder {
return b.boolValue(sourceParam, dest, false)
}
// MustBool requires parameter value to exist to be bind to bool variable. Returns error when value does not exist
// MustBool requires parameter value to exist to bind to bool variable. Returns error when value does not exist
func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder {
return b.boolValue(sourceParam, dest, true)
}
@ -887,7 +964,7 @@ func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBinder {
return b.boolsValue(sourceParam, dest, false)
}
// MustBools requires parameter values to exist to be bind to slice of bool variables. Returns error when values does not exist
// MustBools requires parameter values to exist to bind to slice of bool variables. Returns error when values does not exist
func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder {
return b.boolsValue(sourceParam, dest, true)
}
@ -897,7 +974,7 @@ func (b *ValueBinder) Float64(sourceParam string, dest *float64) *ValueBinder {
return b.floatValue(sourceParam, dest, 64, false)
}
// MustFloat64 requires parameter value to exist to be bind to float64 variable. Returns error when value does not exist
// MustFloat64 requires parameter value to exist to bind to float64 variable. Returns error when value does not exist
func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder {
return b.floatValue(sourceParam, dest, 64, true)
}
@ -907,7 +984,7 @@ func (b *ValueBinder) Float32(sourceParam string, dest *float32) *ValueBinder {
return b.floatValue(sourceParam, dest, 32, false)
}
// MustFloat32 requires parameter value to exist to be bind to float32 variable. Returns error when value does not exist
// MustFloat32 requires parameter value to exist to bind to float32 variable. Returns error when value does not exist
func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder {
return b.floatValue(sourceParam, dest, 32, true)
}
@ -992,7 +1069,7 @@ func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *ValueBinder
return b.floatsValue(sourceParam, dest, false)
}
// MustFloat64s requires parameter values to exist to be bind to slice of float64 variables. Returns error when values does not exist
// MustFloat64s requires parameter values to exist to bind to slice of float64 variables. Returns error when values does not exist
func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder {
return b.floatsValue(sourceParam, dest, true)
}
@ -1002,7 +1079,7 @@ func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *ValueBinder
return b.floatsValue(sourceParam, dest, false)
}
// MustFloat32s requires parameter values to exist to be bind to slice of float32 variables. Returns error when values does not exist
// MustFloat32s requires parameter values to exist to bind to slice of float32 variables. Returns error when values does not exist
func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder {
return b.floatsValue(sourceParam, dest, true)
}
@ -1012,7 +1089,7 @@ func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout string) *
return b.time(sourceParam, dest, layout, false)
}
// MustTime requires parameter value to exist to be bind to time.Time variable. Returns error when value does not exist
// MustTime requires parameter value to exist to bind to time.Time variable. Returns error when value does not exist
func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder {
return b.time(sourceParam, dest, layout, true)
}
@ -1043,7 +1120,7 @@ func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, layout string
return b.times(sourceParam, dest, layout, false)
}
// MustTimes requires parameter values to exist to be bind to slice of time.Time variables. Returns error when values does not exist
// MustTimes requires parameter values to exist to bind to slice of time.Time variables. Returns error when values does not exist
func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder {
return b.times(sourceParam, dest, layout, true)
}
@ -1084,7 +1161,7 @@ func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration) *ValueBi
return b.duration(sourceParam, dest, false)
}
// MustDuration requires parameter value to exist to be bind to time.Duration variable. Returns error when value does not exist
// MustDuration requires parameter value to exist to bind to time.Duration variable. Returns error when value does not exist
func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder {
return b.duration(sourceParam, dest, true)
}
@ -1115,7 +1192,7 @@ func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Duration) *Valu
return b.durationsValue(sourceParam, dest, false)
}
// MustDurations requires parameter values to exist to be bind to slice of time.Duration variables. Returns error when values does not exist
// MustDurations requires parameter values to exist to bind to slice of time.Duration variables. Returns error when values does not exist
func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder {
return b.durationsValue(sourceParam, dest, true)
}
@ -1161,10 +1238,10 @@ func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]tim
// Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, false)
return b.unixTime(sourceParam, dest, false, time.Second)
}
// MustUnixTime requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
// MustUnixTime requires parameter value to exist to bind to time.Duration variable (in local time corresponding
// to the given Unix time). Returns error when value does not exist.
//
// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
@ -1172,10 +1249,31 @@ func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder
// Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, false)
return b.unixTime(sourceParam, dest, true, time.Second)
}
// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision).
// UnixTimeMilli binds parameter to time.Time variable (in local time corresponding to the given Unix time in millisecond precision).
//
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
//
// Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, time.Millisecond)
}
// MustUnixTimeMilli requires parameter value to exist to bind to time.Duration variable (in local time corresponding
// to the given Unix time in millisecond precision). Returns error when value does not exist.
//
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
//
// Note:
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, time.Millisecond)
}
// UnixTimeNano binds parameter to time.Time variable (in local time corresponding to the given Unix time in nanosecond precision).
//
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
@ -1185,10 +1283,10 @@ func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBi
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, true)
return b.unixTime(sourceParam, dest, false, time.Nanosecond)
}
// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
// MustUnixTimeNano requires parameter value to exist to bind to time.Duration variable (in local Time corresponding
// to the given Unix time value in nano second precision). Returns error when value does not exist.
//
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
@ -1199,10 +1297,10 @@ func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBi
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, true)
return b.unixTime(sourceParam, dest, true, time.Nanosecond)
}
func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder {
func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, precision time.Duration) *ValueBinder {
if b.failFast && b.errors != nil {
return b
}
@ -1221,10 +1319,13 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
return b
}
if isNano {
*dest = time.Unix(0, n)
} else {
switch precision {
case time.Second:
*dest = time.Unix(n, 0)
case time.Millisecond:
*dest = time.Unix(n/1e3, (n%1e3)*1e6) // TODO: time.UnixMilli(n) exists since Go1.17 switch to that when min version allows
case time.Nanosecond:
*dest = time.Unix(0, n)
}
return b
}

View File

@ -9,8 +9,6 @@ import (
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
)
@ -183,7 +181,7 @@ type (
// Logger returns the `Logger` instance.
Logger() Logger
// Set the logger
// SetLogger Set the logger
SetLogger(l Logger)
// Echo returns the `Echo` instance.
@ -210,6 +208,13 @@ type (
}
)
const (
// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
// Allow header is mandatory for status 405 (method not found) and useful for OPTIONS method requests.
// It is added to context only when Router does not find matching method handler for request.
ContextKeyHeaderAllow = "echo_header_allow"
)
const (
defaultMemory = 32 << 20 // 32 MB
indexPage = "index.html"
@ -562,29 +567,6 @@ func (c *context) Stream(code int, contentType string, r io.Reader) (err error)
return
}
func (c *context) File(file string) (err error) {
f, err := os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.Join(file, indexPage)
f, err = os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return
}
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
return
}
func (c *context) Attachment(file, name string) error {
return c.contentDisposition(file, name, "attachment")
}

49
vendor/github.com/labstack/echo/v4/context_fs.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package echo
import (
"errors"
"io"
"io/fs"
"net/http"
"path/filepath"
)
func (c *context) File(file string) error {
return fsFile(c, file, c.echo.Filesystem)
}
// FileFS serves file from given file system.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (c *context) FileFS(file string, filesystem fs.FS) error {
return fsFile(c, file, filesystem)
}
func fsFile(c Context, file string, filesystem fs.FS) error {
f, err := filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.ToSlash(filepath.Join(file, indexPage)) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect.
f, err = filesystem.Open(file)
if err != nil {
return ErrNotFound
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return err
}
}
ff, ok := f.(io.ReadSeeker)
if !ok {
return errors.New("file does not implement io.ReadSeeker")
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff)
return nil
}

View File

@ -47,9 +47,6 @@ import (
stdLog "log"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
"runtime"
"sync"
@ -66,6 +63,7 @@ import (
type (
// Echo is the top-level framework instance.
Echo struct {
filesystem
common
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// listener address info (on which interface/port was listener binded) without having data races.
@ -77,7 +75,6 @@ type (
maxParam *int
router *Router
routers map[string]*Router
notFoundHandler HandlerFunc
pool sync.Pool
Server *http.Server
TLSServer *http.Server
@ -113,10 +110,10 @@ type (
}
// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(HandlerFunc) HandlerFunc
MiddlewareFunc func(next HandlerFunc) HandlerFunc
// HandlerFunc defines a function to serve HTTP requests.
HandlerFunc func(Context) error
HandlerFunc func(c Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, Context)
@ -186,12 +183,18 @@ const (
PROPFIND = "PROPFIND"
// REPORT Method can be used to get information about a resource, see rfc 3253
REPORT = "REPORT"
// RouteNotFound is special method type for routes handling "route not found" (404) cases
RouteNotFound = "echo_route_not_found"
)
// Headers
const (
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding"
// HeaderAllow is the name of the "Allow" header field used to list the set of methods
// advertised as supported by the target resource. Returning an Allow header is mandatory
// for status 405 (method not found) and useful for the OPTIONS method in responses.
// See RFC 7231: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
@ -203,6 +206,7 @@ const (
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderRetryAfter = "Retry-After"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
@ -212,11 +216,14 @@ const (
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXRealIP = "X-Real-IP"
HeaderXRequestID = "X-Request-ID"
HeaderXRealIP = "X-Real-Ip"
HeaderXRequestID = "X-Request-Id"
HeaderXCorrelationID = "X-Correlation-Id"
HeaderXRequestedWith = "X-Requested-With"
HeaderServer = "Server"
HeaderOrigin = "Origin"
HeaderCacheControl = "Cache-Control"
HeaderConnection = "Connection"
// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
@ -241,7 +248,7 @@ const (
const (
// Version of Echo
Version = "4.5.0"
Version = "4.9.0"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
@ -301,6 +308,12 @@ var (
}
MethodNotAllowedHandler = func(c Context) error {
// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
if ok && routerAllowMethods != "" {
c.Response().Header().Set(HeaderAllow, routerAllowMethods)
}
return ErrMethodNotAllowed
}
)
@ -308,6 +321,7 @@ var (
// New creates an instance of Echo.
func New() (e *Echo) {
e = &Echo{
filesystem: createFilesystem(),
Server: new(http.Server),
TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{
@ -357,7 +371,17 @@ func (e *Echo) Routers() map[string]*Router {
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
// with status code.
//
// NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error).
// When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from
// handler. Then the error that global error handler received will be ignored because we have already "commited" the
// response and status code header has been sent to the client.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
if c.Response().Committed {
return
}
he, ok := err.(*HTTPError)
if ok {
if he.Internal != nil {
@ -384,7 +408,6 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
}
// Send response
if !c.Response().Committed {
if c.Request().Method == http.MethodHead { // Issue #608
err = c.NoContent(he.Code)
} else {
@ -393,7 +416,6 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
if err != nil {
e.Logger.Error(err)
}
}
}
// Pre adds middleware to the chain which is run before router.
@ -460,8 +482,21 @@ func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodTrace, path, h, m...)
}
// Any registers a new route for all HTTP methods and path with matching handler
// RouteNotFound registers a special-case route which is executed when no other route is found (i.e. HTTP 404 cases)
// for current request URL.
// Path supports static and named/any parameters just like other http method is defined. Generally path is ended with
// wildcard/match-any character (`/*`, `/download/*` etc).
//
// Example: `e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(RouteNotFound, path, h, m...)
}
// Any registers a new route for all HTTP methods (supported by Echo) and path with matching handler
// in the router with optional route-level middleware.
//
// Note: this method only adds specific set of supported HTTP methods as handler and is not true
// "catch-any-arbitrary-method" way of matching requests.
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
@ -480,50 +515,6 @@ func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middlew
return routes
}
// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (e *Echo) Static(prefix, root string) *Route {
if root == "" {
root = "." // For security we want to restrict to CWD.
}
return e.static(prefix, root, e.GET)
}
func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
h := func(c Context) error {
p, err := url.PathUnescape(c.Param("*"))
if err != nil {
return err
}
name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
fi, err := os.Stat(name)
if err != nil {
// The access path does not exist
return NotFoundHandler(c)
}
// If the request is for a directory and does not end with "/"
p = c.Request().URL.Path // path must not be empty.
if fi.IsDir() && p[len(p)-1] != '/' {
// Redirect to ends with "/"
return c.Redirect(http.StatusMovedPermanently, p+"/")
}
return c.File(name)
}
// Handle added routes based on trailing slash:
// /prefix => exact route "/prefix" + any route "/prefix/*"
// /prefix/ => only any route "/prefix/*"
if prefix != "" {
if prefix[len(prefix)-1] == '/' {
// Only add any route for intentional trailing slash
return get(prefix+"*", h)
}
get(prefix, h)
}
return get(prefix+"/*", h)
}
func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route,
m ...MiddlewareFunc) *Route {
return get(path, func(c Context) error {
@ -539,6 +530,7 @@ func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
name := handlerName(handler)
router := e.findRouter(host)
// FIXME: when handler+middleware are both nil ... make it behave like handler removal
router.Add(method, path, func(c Context) error {
h := applyMiddleware(handler, middleware...)
return h(c)
@ -634,7 +626,7 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
c := e.pool.Get().(*context)
c.Reset(r, w)
h := NotFoundHandler
var h func(Context) error
if e.premiddleware == nil {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
@ -756,7 +748,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
return s.Serve(e.Listener)
}
func (e *Echo) configureServer(s *http.Server) (err error) {
func (e *Echo) configureServer(s *http.Server) error {
// Setup
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger
@ -771,10 +763,11 @@ func (e *Echo) configureServer(s *http.Server) (err error) {
if s.TLSConfig == nil {
if e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil {
return err
}
e.Listener = l
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
@ -815,7 +808,7 @@ func (e *Echo) TLSListenerAddr() net.Addr {
}
// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error {
e.startupMutex.Lock()
// Setup
s := e.Server
@ -832,11 +825,12 @@ func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
}
if e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil {
e.startupMutex.Unlock()
return err
}
e.Listener = l
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))

175
vendor/github.com/labstack/echo/v4/echo_fs.go generated vendored Normal file
View File

@ -0,0 +1,175 @@
package echo
import (
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
)
type filesystem struct {
// Filesystem is file system used by Static and File handlers to access files.
// Defaults to os.DirFS(".")
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
Filesystem fs.FS
}
func createFilesystem() filesystem {
return filesystem{
Filesystem: newDefaultFS(),
}
}
// Static registers a new route with path prefix to serve static files from the provided root directory.
func (e *Echo) Static(pathPrefix, fsRoot string) *Route {
subFs := MustSubFS(e.Filesystem, fsRoot)
return e.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(subFs, false),
)
}
// StaticFS registers a new route with path prefix to serve static files from the provided file system.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route {
return e.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(filesystem, false),
)
}
// StaticDirectoryHandler creates handler function to serve files from provided file system
// When disablePathUnescaping is set then file name from path is not unescaped and is served as is.
func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc {
return func(c Context) error {
p := c.Param("*")
if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
tmpPath, err := url.PathUnescape(p)
if err != nil {
return fmt.Errorf("failed to unescape path variable: %w", err)
}
p = tmpPath
}
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
fi, err := fs.Stat(fileSystem, name)
if err != nil {
return ErrNotFound
}
// If the request is for a directory and does not end with "/"
p = c.Request().URL.Path // path must not be empty.
if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' {
// Redirect to ends with "/"
return c.Redirect(http.StatusMovedPermanently, sanitizeURI(p+"/"))
}
return fsFile(c, name, fileSystem)
}
}
// FileFS registers a new route with path to serve file from the provided file system.
func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
return e.GET(path, StaticFileHandler(file, filesystem), m...)
}
// StaticFileHandler creates handler function to serve file from provided file system
func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
return func(c Context) error {
return fsFile(c, file, filesystem)
}
}
// defaultFS exists to preserve pre v4.7.0 behaviour where files were open by `os.Open`.
// v4.7 introduced `echo.Filesystem` field which is Go1.16+ `fs.Fs` interface.
// Difference between `os.Open` and `fs.Open` is that FS does not allow opening path that start with `.`, `..` or `/`
// etc. For example previously you could have `../images` in your application but `fs := os.DirFS("./")` would not
// allow you to use `fs.Open("../images")` and this would break all old applications that rely on being able to
// traverse up from current executable run path.
// NB: private because you really should use fs.FS implementation instances
type defaultFS struct {
prefix string
fs fs.FS
}
func newDefaultFS() *defaultFS {
dir, _ := os.Getwd()
return &defaultFS{
prefix: dir,
fs: nil,
}
}
func (fs defaultFS) Open(name string) (fs.File, error) {
if fs.fs == nil {
return os.Open(name)
}
return fs.fs.Open(name)
}
func subFS(currentFs fs.FS, root string) (fs.FS, error) {
root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
if dFS, ok := currentFs.(*defaultFS); ok {
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS.
// fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we
// were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs
if isRelativePath(root) {
root = filepath.Join(dFS.prefix, root)
}
return &defaultFS{
prefix: root,
fs: os.DirFS(root),
}, nil
}
return fs.Sub(currentFs, root)
}
func isRelativePath(path string) bool {
if path == "" {
return true
}
if path[0] == '/' {
return false
}
if runtime.GOOS == "windows" && strings.IndexByte(path, ':') != -1 {
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#file_and_directory_names
// https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats
return false
}
return true
}
// MustSubFS creates sub FS from current filesystem or panic on failure.
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
//
// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with
// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to
// create sub fs which uses necessary prefix for directory path.
func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
subFs, err := subFS(currentFs, fsRoot)
if err != nil {
panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err))
}
return subFs
}
func sanitizeURI(uri string) string {
// double slash `\\`, `//` or even `\/` is absolute uri for browsers and by redirecting request to that uri
// we are vulnerable to open redirect attack. so replace all slashes from the beginning with single slash
if len(uri) > 1 && (uri[0] == '\\' || uri[0] == '/') && (uri[1] == '\\' || uri[1] == '/') {
uri = "/" + strings.TrimLeft(uri, `/\`)
}
return uri
}

View File

@ -102,16 +102,18 @@ func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
return
}
// Static implements `Echo#Static()` for sub-routes within the Group.
func (g *Group) Static(prefix, root string) {
g.static(prefix, root, g.GET)
}
// File implements `Echo#File()` for sub-routes within the Group.
func (g *Group) File(path, file string) {
g.file(path, file, g.GET)
}
// RouteNotFound implements `Echo#RouteNotFound()` for sub-routes within the Group.
//
// Example: `g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(RouteNotFound, path, h, m...)
}
// Add implements `Echo#Add()` for sub-routes within the Group.
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
// Combine into a new slice to avoid accidentally passing the same slice for

30
vendor/github.com/labstack/echo/v4/group_fs.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package echo
import (
"io/fs"
"net/http"
)
// Static implements `Echo#Static()` for sub-routes within the Group.
func (g *Group) Static(pathPrefix, fsRoot string) {
subFs := MustSubFS(g.echo.Filesystem, fsRoot)
g.StaticFS(pathPrefix, subFs)
}
// StaticFS implements `Echo#StaticFS()` for sub-routes within the Group.
//
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
// including `assets/images` as their prefix.
func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS) {
g.Add(
http.MethodGet,
pathPrefix+"*",
StaticDirectoryHandler(filesystem, false),
)
}
// FileFS implements `Echo#FileFS()` for sub-routes within the Group.
func (g *Group) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
return g.GET(path, StaticFileHandler(file, filesystem), m...)
}

View File

@ -6,6 +6,130 @@ import (
"strings"
)
/**
By: https://github.com/tmshn (See: https://github.com/labstack/echo/pull/1478 , https://github.com/labstack/echox/pull/134 )
Source: https://echo.labstack.com/guide/ip-address/
IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more.
Echo provides handy method [`Context#RealIP()`](https://godoc.org/github.com/labstack/echo#Context) for that.
However, it is not trivial to retrieve the _real_ IP address from requests especially when you put L7 proxies before the application.
In such situation, _real_ IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.
Otherwise, you might give someone a chance of deceiving you. **A security risk!**
To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructure.
In Echo, this can be done by configuring `Echo#IPExtractor` appropriately.
This guides show you why and how.
> Note: if you dont' set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.
Let's start from two questions to know the right direction:
1. Do you put any HTTP (L7) proxy in front of the application?
- It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway).
2. If yes, what HTTP header do your proxies use to pass client IP to the application?
## Case 1. With no proxy
If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer.
Any HTTP header is untrustable because the clients have full control what headers to be set.
In this case, use `echo.ExtractIPDirect()`.
```go
e.IPExtractor = echo.ExtractIPDirect()
```
## Case 2. With proxies using `X-Forwarded-For` header
[`X-Forwared-For` (XFF)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) is the popular header
to relay clients' IP addresses.
At each hop on the proxies, they append the request IP address at the end of the header.
Following example diagram illustrates this behavior.
```text
"Origin" > Proxy 1 > Proxy 2 > Your app
(IP: a) (IP: b) (IP: c)
Case 1.
XFF: "" "a" "a, b"
~~~~~~
Case 2.
XFF: "x" "x, a" "x, a, b"
~~~~~~~~~
What your app will see
```
In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre".
In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
```go
e.IPExtractor = echo.ExtractIPFromXFFHeader()
```
By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address
from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
[RFC4193](https://tools.ietf.org/html/rfc4193)).
To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
E.g.:
```go
e.IPExtractor = echo.ExtractIPFromXFFHeader(
TrustLinkLocal(false),
TrustIPRanges(lbIPRange),
)
```
- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
## Case 3. With proxies using `X-Real-IP` header
`X-Real-IP` is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF.
If your proxies set this header, use `ExtractIPFromRealIPHeader(...TrustOption)`.
```go
e.IPExtractor = echo.ExtractIPFromRealIPHeader()
```
Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address
from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
[RFC4193](https://tools.ietf.org/html/rfc4193)).
To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
> **Never forget** to configure the outermost proxy (i.e.; at the edge of your infrastructure) **not to pass through incoming headers**.
> Otherwise there is a chance of fraud, as it is what clients can control.
## About default behavior
In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer.
As you might already notice, after reading this article, this is not good.
Sole reason this is default is just backward compatibility.
## Private IP ranges
See: https://en.wikipedia.org/wiki/Private_network
Private IPv4 address ranges (RFC 1918):
* 10.0.0.0 10.255.255.255 (24-bit block)
* 172.16.0.0 172.31.255.255 (20-bit block)
* 192.168.0.0 192.168.255.255 (16-bit block)
Private IPv6 address ranges:
* fc00::/7 address block = RFC 4193 Unique Local Addresses (ULA)
*/
type ipChecker struct {
trustLoopback bool
trustLinkLocal bool
@ -52,6 +176,7 @@ func newIPChecker(configs []TrustOption) *ipChecker {
return checker
}
// Go1.16+ added `ip.IsPrivate()` but until that use this implementation
func isPrivateIPRange(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
return ip4[0] == 10 ||
@ -87,10 +212,12 @@ type IPExtractor func(*http.Request) string
// ExtractIPDirect extracts IP address using actual IP address.
// Use this if your server faces to internet directory (i.e.: uses no proxy).
func ExtractIPDirect() IPExtractor {
return func(req *http.Request) string {
return extractIP
}
func extractIP(req *http.Request) string {
ra, _, _ := net.SplitHostPort(req.RemoteAddr)
return ra
}
}
// ExtractIPFromRealIPHeader extracts IP address using x-real-ip header.
@ -98,14 +225,13 @@ func ExtractIPDirect() IPExtractor {
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
directIP := ExtractIPDirect()(req)
realIP := req.Header.Get(HeaderXRealIP)
if realIP != "" {
if ip := net.ParseIP(directIP); ip != nil && checker.trust(ip) {
if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) {
return realIP
}
}
return directIP
return extractIP(req)
}
}
@ -115,7 +241,7 @@ func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
directIP := ExtractIPDirect()(req)
directIP := extractIP(req)
xffs := req.Header[HeaderXForwardedFor]
if len(xffs) == 0 {
return directIP

View File

@ -4,6 +4,7 @@ import (
"encoding/base64"
"strconv"
"strings"
"net/http"
"github.com/labstack/echo/v4"
)
@ -74,10 +75,13 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
l := len(basic)
if len(auth) > l+1 && strings.EqualFold(auth[:l], basic) {
// Invalid base64 shouldn't be treated as error
// instead should be treated as invalid client input
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
if err != nil {
return err
return echo.NewHTTPError(http.StatusBadRequest).SetInternal(err)
}
cred := string(b)
for i := 0; i < len(cred); i++ {
if cred[i] == ':' {

View File

@ -27,6 +27,7 @@ type (
gzipResponseWriter struct {
io.Writer
http.ResponseWriter
wroteBody bool
}
)
@ -78,8 +79,9 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
}
rw := res.Writer
w.Reset(rw)
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
defer func() {
if res.Size == 0 {
if !grw.wroteBody {
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
res.Header().Del(echo.HeaderContentEncoding)
}
@ -92,7 +94,6 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
w.Close()
pool.Put(w)
}()
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
res.Writer = grw
}
return next(c)
@ -101,9 +102,6 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
}
func (w *gzipResponseWriter) WriteHeader(code int) {
if code == http.StatusNoContent { // Issue #489
w.ResponseWriter.Header().Del(echo.HeaderContentEncoding)
}
w.Header().Del(echo.HeaderContentLength) // Issue #444
w.ResponseWriter.WriteHeader(code)
}
@ -112,6 +110,7 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) {
if w.Header().Get(echo.HeaderContentType) == "" {
w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
}
w.wroteBody = true
return w.Writer.Write(b)
}

View File

@ -15,42 +15,85 @@ type (
// Skipper defines a function to skip middleware.
Skipper Skipper
// AllowOrigin defines a list of origins that may access the resource.
// AllowOrigins determines the value of the Access-Control-Allow-Origin
// response header. This header defines a list of origins that may access the
// resource. The wildcard characters '*' and '?' are supported and are
// converted to regex fragments '.*' and '.' accordingly.
//
// Security: use extreme caution when handling the origin, and carefully
// validate any logic. Remember that attackers may register hostile domain names.
// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
//
// Optional. Default value []string{"*"}.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
AllowOrigins []string `yaml:"allow_origins"`
// AllowOriginFunc is a custom function to validate the origin. It takes the
// origin as an argument and returns true if allowed or false otherwise. If
// an error is returned, it is returned by the handler. If this option is
// set, AllowOrigins is ignored.
//
// Security: use extreme caution when handling the origin, and carefully
// validate any logic. Remember that attackers may register hostile domain names.
// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
//
// Optional.
AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"`
// AllowMethods defines a list methods allowed when accessing the resource.
// This is used in response to a preflight request.
// AllowMethods determines the value of the Access-Control-Allow-Methods
// response header. This header specified the list of methods allowed when
// accessing the resource. This is used in response to a preflight request.
//
// Optional. Default value DefaultCORSConfig.AllowMethods.
// If `allowMethods` is left empty, this middleware will fill for preflight
// request `Access-Control-Allow-Methods` header value
// from `Allow` header that echo.Router set into context.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
AllowMethods []string `yaml:"allow_methods"`
// AllowHeaders defines a list of request headers that can be used when
// making the actual request. This is in response to a preflight request.
// AllowHeaders determines the value of the Access-Control-Allow-Headers
// response header. This header is used in response to a preflight request to
// indicate which HTTP headers can be used when making the actual request.
//
// Optional. Default value []string{}.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
AllowHeaders []string `yaml:"allow_headers"`
// AllowCredentials indicates whether or not the response to the request
// can be exposed when the credentials flag is true. When used as part of
// a response to a preflight request, this indicates whether or not the
// actual request can be made using credentials.
// Optional. Default value false.
// AllowCredentials determines the value of the
// Access-Control-Allow-Credentials response header. This header indicates
// whether or not the response to the request can be exposed when the
// credentials mode (Request.credentials) is true. When used as part of a
// response to a preflight request, this indicates whether or not the actual
// request can be made using credentials. See also
// [MDN: Access-Control-Allow-Credentials].
//
// Optional. Default value false, in which case the header is not set.
//
// Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`.
// See "Exploiting CORS misconfigurations for Bitcoins and bounties",
// https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
AllowCredentials bool `yaml:"allow_credentials"`
// ExposeHeaders defines a whitelist headers that clients are allowed to
// access.
// Optional. Default value []string{}.
// ExposeHeaders determines the value of Access-Control-Expose-Headers, which
// defines a list of headers that clients are allowed to access.
//
// Optional. Default value []string{}, in which case the header is not set.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header
ExposeHeaders []string `yaml:"expose_headers"`
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached.
// Optional. Default value 0.
// MaxAge determines the value of the Access-Control-Max-Age response header.
// This header indicates how long (in seconds) the results of a preflight
// request can be cached.
//
// Optional. Default value 0. The header is set only if MaxAge > 0.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
MaxAge int `yaml:"max_age"`
}
)
@ -65,13 +108,22 @@ var (
)
// CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
// See also [MDN: Cross-Origin Resource Sharing (CORS)].
//
// Security: Poorly configured CORS can compromise security because it allows
// relaxation of the browser's Same-Origin policy. See [Exploiting CORS
// misconfigurations for Bitcoins and bounties] and [Portswigger: Cross-origin
// resource sharing (CORS)] for more details.
//
// [MDN: Cross-Origin Resource Sharing (CORS)]: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
// [Exploiting CORS misconfigurations for Bitcoins and bounties]: https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
// [Portswigger: Cross-origin resource sharing (CORS)]: https://portswigger.net/web-security/cors
func CORS() echo.MiddlewareFunc {
return CORSWithConfig(DefaultCORSConfig)
}
// CORSWithConfig returns a CORS middleware with config.
// See: `CORS()`.
// See: [CORS].
func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
@ -80,7 +132,9 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
if len(config.AllowOrigins) == 0 {
config.AllowOrigins = DefaultCORSConfig.AllowOrigins
}
hasCustomAllowMethods := true
if len(config.AllowMethods) == 0 {
hasCustomAllowMethods = false
config.AllowMethods = DefaultCORSConfig.AllowMethods
}
@ -109,10 +163,28 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
origin := req.Header.Get(echo.HeaderOrigin)
allowOrigin := ""
preflight := req.Method == http.MethodOptions
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
// No Origin provided
// Preflight request is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method,
// Access-Control-Request-Headers, and the Origin header. See: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
// For simplicity we just consider method type and later `Origin` header.
preflight := req.Method == http.MethodOptions
// Although router adds special handler in case of OPTIONS method we avoid calling next for OPTIONS in this middleware
// as CORS requests do not have cookies / authentication headers by default, so we could get stuck in auth
// middlewares by calling next(c).
// But we still want to send `Allow` header as response in case of Non-CORS OPTIONS request as router default
// handler does.
routerAllowMethods := ""
if preflight {
tmpAllowMethods, ok := c.Get(echo.ContextKeyHeaderAllow).(string)
if ok && tmpAllowMethods != "" {
routerAllowMethods = tmpAllowMethods
c.Response().Header().Set(echo.HeaderAllow, routerAllowMethods)
}
}
// No Origin provided. This is (probably) not request from actual browser - proceed executing middleware chain
if origin == "" {
if !preflight {
return next(c)
@ -145,19 +217,15 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
}
}
// Check allowed origin patterns
for _, re := range allowOriginPatterns {
checkPatterns := false
if allowOrigin == "" {
didx := strings.Index(origin, "://")
if didx == -1 {
continue
// to avoid regex cost by invalid (long) domains (253 is domain name max limit)
if len(origin) <= (253+3+5) && strings.Contains(origin, "://") {
checkPatterns = true
}
domAuth := origin[didx+3:]
// to avoid regex cost by invalid long domain
if len(domAuth) > 253 {
break
}
if checkPatterns {
for _, re := range allowOriginPatterns {
if match, _ := regexp.MatchString(re, origin); match {
allowOrigin = origin
break
@ -174,12 +242,13 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
return c.NoContent(http.StatusNoContent)
}
// Simple request
if !preflight {
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
}
// Simple request
if !preflight {
if exposeHeaders != "" {
res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
}
@ -189,11 +258,13 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
// Preflight request
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
if !hasCustomAllowMethods && routerAllowMethods != "" {
res.Header().Set(echo.HeaderAccessControlAllowMethods, routerAllowMethods)
} else {
res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
}
if allowHeaders != "" {
res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
} else {

View File

@ -2,9 +2,7 @@ package middleware
import (
"crypto/subtle"
"errors"
"net/http"
"strings"
"time"
"github.com/labstack/echo/v4"
@ -21,13 +19,15 @@ type (
TokenLength uint8 `yaml:"token_length"`
// Optional. Default value 32.
// TokenLookup is a string in the form of "<source>:<key>" that is used
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:X-CSRF-Token".
// Possible values:
// - "header:<name>"
// - "form:<name>"
// - "header:<name>" or "header:<name>:<cut-prefix>"
// - "query:<name>"
// - "form:<name>"
// Multiple sources example:
// - "header:X-CSRF-Token,query:csrf"
TokenLookup string `yaml:"token_lookup"`
// Context key to store generated CSRF token into context.
@ -61,13 +61,18 @@ type (
// Indicates SameSite mode of the CSRF cookie.
// Optional. Default value SameSiteDefaultMode.
CookieSameSite http.SameSite `yaml:"cookie_same_site"`
// ErrorHandler defines a function which is executed for returning custom errors.
ErrorHandler CSRFErrorHandler
}
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
// either a token or an error.
csrfTokenExtractor func(echo.Context) (string, error)
// CSRFErrorHandler is a function which is executed for creating custom errors.
CSRFErrorHandler func(err error, c echo.Context) error
)
// ErrCSRFInvalid is returned when CSRF check fails
var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
var (
// DefaultCSRFConfig is the default CSRF middleware config.
DefaultCSRFConfig = CSRFConfig{
@ -114,14 +119,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
config.CookieSecure = true
}
// Initialize
parts := strings.Split(config.TokenLookup, ":")
extractor := csrfTokenFromHeader(parts[1])
switch parts[0] {
case "form":
extractor = csrfTokenFromForm(parts[1])
case "query":
extractor = csrfTokenFromQuery(parts[1])
extractors, err := createExtractors(config.TokenLookup, "")
if err != nil {
panic(err)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@ -130,28 +130,58 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
return next(c)
}
req := c.Request()
k, err := c.Cookie(config.CookieName)
token := ""
// Generate token
if err != nil {
token = random.String(config.TokenLength)
if k, err := c.Cookie(config.CookieName); err != nil {
token = random.String(config.TokenLength) // Generate token
} else {
// Reuse token
token = k.Value
token = k.Value // Reuse token
}
switch req.Method {
switch c.Request().Method {
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
default:
// Validate token only for requests which are not defined as 'safe' by RFC7231
clientToken, err := extractor(c)
var lastExtractorErr error
var lastTokenErr error
outer:
for _, extractor := range extractors {
clientTokens, err := extractor(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
lastExtractorErr = err
continue
}
if !validateCSRFToken(token, clientToken) {
return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
for _, clientToken := range clientTokens {
if validateCSRFToken(token, clientToken) {
lastTokenErr = nil
lastExtractorErr = nil
break outer
}
lastTokenErr = ErrCSRFInvalid
}
}
var finalErr error
if lastTokenErr != nil {
finalErr = lastTokenErr
} else if lastExtractorErr != nil {
// ugly part to preserve backwards compatible errors. someone could rely on them
if lastExtractorErr == errQueryExtractorValueMissing {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the query string")
} else if lastExtractorErr == errFormExtractorValueMissing {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the form parameter")
} else if lastExtractorErr == errHeaderExtractorValueMissing {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in request header")
} else {
lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, lastExtractorErr.Error())
}
finalErr = lastExtractorErr
}
if finalErr != nil {
if config.ErrorHandler != nil {
return config.ErrorHandler(finalErr, c)
}
return finalErr
}
}
@ -184,38 +214,6 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
}
}
// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
// provided request header.
func csrfTokenFromHeader(header string) csrfTokenExtractor {
return func(c echo.Context) (string, error) {
return c.Request().Header.Get(header), nil
}
}
// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
// provided form parameter.
func csrfTokenFromForm(param string) csrfTokenExtractor {
return func(c echo.Context) (string, error) {
token := c.FormValue(param)
if token == "" {
return "", errors.New("missing csrf token in the form parameter")
}
return token, nil
}
}
// csrfTokenFromQuery returns a `csrfTokenExtractor` that extracts token from the
// provided query parameter.
func csrfTokenFromQuery(param string) csrfTokenExtractor {
return func(c echo.Context) (string, error) {
token := c.QueryParam(param)
if token == "" {
return "", errors.New("missing csrf token in the query string")
}
return token, nil
}
}
func validateCSRFToken(token, clientToken string) bool {
return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
}

View File

@ -1,10 +1,8 @@
package middleware
import (
"bytes"
"compress/gzip"
"io"
"io/ioutil"
"net/http"
"sync"
@ -43,26 +41,7 @@ type DefaultGzipDecompressPool struct {
}
func (d *DefaultGzipDecompressPool) gzipDecompressPool() sync.Pool {
return sync.Pool{
New: func() interface{} {
// create with an empty reader (but with GZIP header)
w, err := gzip.NewWriterLevel(ioutil.Discard, gzip.BestSpeed)
if err != nil {
return err
}
b := new(bytes.Buffer)
w.Reset(b)
w.Flush()
w.Close()
r, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
if err != nil {
return err
}
return r
},
}
return sync.Pool{New: func() interface{} { return new(gzip.Reader) }}
}
//Decompress decompresses request body based if content encoding type is set to "gzip" with default config
@ -82,38 +61,38 @@ func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
pool := config.GzipDecompressPool.gzipDecompressPool()
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
switch c.Request().Header.Get(echo.HeaderContentEncoding) {
case GZIPEncoding:
b := c.Request().Body
if c.Request().Header.Get(echo.HeaderContentEncoding) != GZIPEncoding {
return next(c)
}
i := pool.Get()
gr, ok := i.(*gzip.Reader)
if !ok {
if !ok || gr == nil {
return echo.NewHTTPError(http.StatusInternalServerError, i.(error).Error())
}
defer pool.Put(gr)
b := c.Request().Body
defer b.Close()
if err := gr.Reset(b); err != nil {
pool.Put(gr)
if err == io.EOF { //ignore if body is empty
return next(c)
}
return err
}
var buf bytes.Buffer
io.Copy(&buf, gr)
gr.Close()
pool.Put(gr)
// only Close gzip reader if it was set to a proper gzip source otherwise it will panic on close.
defer gr.Close()
b.Close() // http.Request.Body is closed by the Server, but because we are replacing it, it must be closed here
c.Request().Body = gr
r := ioutil.NopCloser(&buf)
c.Request().Body = r
}
return next(c)
}
}

View File

@ -0,0 +1,184 @@
package middleware
import (
"errors"
"fmt"
"github.com/labstack/echo/v4"
"net/textproto"
"strings"
)
const (
// extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion
// attack vector
extractorLimit = 20
)
var errHeaderExtractorValueMissing = errors.New("missing value in request header")
var errHeaderExtractorValueInvalid = errors.New("invalid value in request header")
var errQueryExtractorValueMissing = errors.New("missing value in the query string")
var errParamExtractorValueMissing = errors.New("missing value in path params")
var errCookieExtractorValueMissing = errors.New("missing value in cookies")
var errFormExtractorValueMissing = errors.New("missing value in the form")
// ValuesExtractor defines a function for extracting values (keys/tokens) from the given context.
type ValuesExtractor func(c echo.Context) ([]string, error)
func createExtractors(lookups string, authScheme string) ([]ValuesExtractor, error) {
if lookups == "" {
return nil, nil
}
sources := strings.Split(lookups, ",")
var extractors = make([]ValuesExtractor, 0)
for _, source := range sources {
parts := strings.Split(source, ":")
if len(parts) < 2 {
return nil, fmt.Errorf("extractor source for lookup could not be split into needed parts: %v", source)
}
switch parts[0] {
case "query":
extractors = append(extractors, valuesFromQuery(parts[1]))
case "param":
extractors = append(extractors, valuesFromParam(parts[1]))
case "cookie":
extractors = append(extractors, valuesFromCookie(parts[1]))
case "form":
extractors = append(extractors, valuesFromForm(parts[1]))
case "header":
prefix := ""
if len(parts) > 2 {
prefix = parts[2]
} else if authScheme != "" && parts[1] == echo.HeaderAuthorization {
// backwards compatibility for JWT and KeyAuth:
// * we only apply this fix to Authorization as header we use and uses prefixes like "Bearer <token-value>" etc
// * previously header extractor assumed that auth-scheme/prefix had a space as suffix we need to retain that
// behaviour for default values and Authorization header.
prefix = authScheme
if !strings.HasSuffix(prefix, " ") {
prefix += " "
}
}
extractors = append(extractors, valuesFromHeader(parts[1], prefix))
}
}
return extractors, nil
}
// valuesFromHeader returns a functions that extracts values from the request header.
// valuePrefix is parameter to remove first part (prefix) of the extracted value. This is useful if header value has static
// prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we want to remove is `<auth-scheme> `
// note the space at the end. In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove
// is `Basic `. In case of JWT tokens `Authorization: Bearer <token>` prefix is `Bearer `.
// If prefix is left empty the whole value is returned.
func valuesFromHeader(header string, valuePrefix string) ValuesExtractor {
prefixLen := len(valuePrefix)
// standard library parses http.Request header keys in canonical form but we may provide something else so fix this
header = textproto.CanonicalMIMEHeaderKey(header)
return func(c echo.Context) ([]string, error) {
values := c.Request().Header.Values(header)
if len(values) == 0 {
return nil, errHeaderExtractorValueMissing
}
result := make([]string, 0)
for i, value := range values {
if prefixLen == 0 {
result = append(result, value)
if i >= extractorLimit-1 {
break
}
continue
}
if len(value) > prefixLen && strings.EqualFold(value[:prefixLen], valuePrefix) {
result = append(result, value[prefixLen:])
if i >= extractorLimit-1 {
break
}
}
}
if len(result) == 0 {
if prefixLen > 0 {
return nil, errHeaderExtractorValueInvalid
}
return nil, errHeaderExtractorValueMissing
}
return result, nil
}
}
// valuesFromQuery returns a function that extracts values from the query string.
func valuesFromQuery(param string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
result := c.QueryParams()[param]
if len(result) == 0 {
return nil, errQueryExtractorValueMissing
} else if len(result) > extractorLimit-1 {
result = result[:extractorLimit]
}
return result, nil
}
}
// valuesFromParam returns a function that extracts values from the url param string.
func valuesFromParam(param string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
result := make([]string, 0)
paramVales := c.ParamValues()
for i, p := range c.ParamNames() {
if param == p {
result = append(result, paramVales[i])
if i >= extractorLimit-1 {
break
}
}
}
if len(result) == 0 {
return nil, errParamExtractorValueMissing
}
return result, nil
}
}
// valuesFromCookie returns a function that extracts values from the named cookie.
func valuesFromCookie(name string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
cookies := c.Cookies()
if len(cookies) == 0 {
return nil, errCookieExtractorValueMissing
}
result := make([]string, 0)
for i, cookie := range cookies {
if name == cookie.Name {
result = append(result, cookie.Value)
if i >= extractorLimit-1 {
break
}
}
}
if len(result) == 0 {
return nil, errCookieExtractorValueMissing
}
return result, nil
}
}
// valuesFromForm returns a function that extracts values from the form field.
func valuesFromForm(name string) ValuesExtractor {
return func(c echo.Context) ([]string, error) {
if c.Request().Form == nil {
_ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does
}
values := c.Request().Form[name]
if len(values) == 0 {
return nil, errFormExtractorValueMissing
}
if len(values) > extractorLimit-1 {
values = values[:extractorLimit]
}
result := append([]string{}, values...)
return result, nil
}
}

View File

@ -1,3 +1,4 @@
//go:build go1.15
// +build go1.15
package middleware
@ -5,12 +6,10 @@ package middleware
import (
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"net/http"
"reflect"
)
type (
@ -22,7 +21,8 @@ type (
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc BeforeFunc
// SuccessHandler defines a function which is executed for a valid token.
// SuccessHandler defines a function which is executed for a valid token before middleware chain continues with next
// middleware or handler.
SuccessHandler JWTSuccessHandler
// ErrorHandler defines a function which is executed for an invalid token.
@ -32,6 +32,13 @@ type (
// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
ErrorHandlerWithContext JWTErrorHandlerWithContext
// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to
// ignore the error (by returning `nil`).
// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
// In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context
// and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
ContinueOnIgnoredError bool
// Signing key to validate token.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
@ -61,16 +68,26 @@ type (
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "header:<name>" or "header:<name>:<cut-prefix>"
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
// want to cut is `<auth-scheme> ` note the space at the end.
// In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
// If prefix is left empty the whole value is returned.
// - "query:<name>"
// - "param:<name>"
// - "cookie:<name>"
// - "form:<name>"
// Multiply sources example:
// - "header: Authorization,cookie: myowncookie"
// Multiple sources example:
// - "header:Authorization,cookie:myowncookie"
TokenLookup string
// TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
// This is one of the two options to provide a token extractor.
// The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
// You can also provide both if you want.
TokenLookupFuncs []ValuesExtractor
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
@ -95,15 +112,13 @@ type (
}
// JWTSuccessHandler defines a function which is executed for a valid token.
JWTSuccessHandler func(echo.Context)
JWTSuccessHandler func(c echo.Context)
// JWTErrorHandler defines a function which is executed for an invalid token.
JWTErrorHandler func(error) error
JWTErrorHandler func(err error) error
// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context.
JWTErrorHandlerWithContext func(error, echo.Context) error
jwtExtractor func(echo.Context) (string, error)
JWTErrorHandlerWithContext func(err error, c echo.Context) error
)
// Algorithms
@ -124,6 +139,7 @@ var (
SigningMethod: AlgorithmHS256,
ContextKey: "user",
TokenLookup: "header:" + echo.HeaderAuthorization,
TokenLookupFuncs: nil,
AuthScheme: "Bearer",
Claims: jwt.MapClaims{},
KeyFunc: nil,
@ -163,7 +179,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
if config.Claims == nil {
config.Claims = DefaultJWTConfig.Claims
}
if config.TokenLookup == "" {
if config.TokenLookup == "" && len(config.TokenLookupFuncs) == 0 {
config.TokenLookup = DefaultJWTConfig.TokenLookup
}
if config.AuthScheme == "" {
@ -176,25 +192,12 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
config.ParseTokenFunc = config.defaultParseToken
}
// Initialize
// Split sources
sources := strings.Split(config.TokenLookup, ",")
var extractors []jwtExtractor
for _, source := range sources {
parts := strings.Split(source, ":")
switch parts[0] {
case "query":
extractors = append(extractors, jwtFromQuery(parts[1]))
case "param":
extractors = append(extractors, jwtFromParam(parts[1]))
case "cookie":
extractors = append(extractors, jwtFromCookie(parts[1]))
case "form":
extractors = append(extractors, jwtFromForm(parts[1]))
case "header":
extractors = append(extractors, jwtFromHeader(parts[1], config.AuthScheme))
extractors, err := createExtractors(config.TokenLookup, config.AuthScheme)
if err != nil {
panic(err)
}
if len(config.TokenLookupFuncs) > 0 {
extractors = append(config.TokenLookupFuncs, extractors...)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@ -206,30 +209,21 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
if config.BeforeFunc != nil {
config.BeforeFunc(c)
}
var auth string
var err error
var lastExtractorErr error
var lastTokenErr error
for _, extractor := range extractors {
// Extract token from extractor, if it's not fail break the loop and
// set auth
auth, err = extractor(c)
if err == nil {
break
}
}
// If none of extractor has a token, handle error
auths, err := extractor(c)
if err != nil {
if config.ErrorHandler != nil {
return config.ErrorHandler(err)
lastExtractorErr = ErrJWTMissing // backwards compatibility: all extraction errors are same (unlike KeyAuth)
continue
}
if config.ErrorHandlerWithContext != nil {
return config.ErrorHandlerWithContext(err, c)
}
return err
}
for _, auth := range auths {
token, err := config.ParseTokenFunc(auth, c)
if err == nil {
if err != nil {
lastTokenErr = err
continue
}
// Store user information from token into context.
c.Set(config.ContextKey, token)
if config.SuccessHandler != nil {
@ -237,18 +231,33 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
}
return next(c)
}
}
// we are here only when we did not successfully extract or parse any of the tokens
err := lastTokenErr
if err == nil { // prioritize token errors over extracting errors
err = lastExtractorErr
}
if config.ErrorHandler != nil {
return config.ErrorHandler(err)
}
if config.ErrorHandlerWithContext != nil {
return config.ErrorHandlerWithContext(err, c)
tmpErr := config.ErrorHandlerWithContext(err, c)
if config.ContinueOnIgnoredError && tmpErr == nil {
return next(c)
}
return tmpErr
}
// backwards compatible errors codes
if lastTokenErr != nil {
return &echo.HTTPError{
Code: ErrJWTInvalid.Code,
Message: ErrJWTInvalid.Message,
Internal: err,
}
}
return err // this is lastExtractorErr value
}
}
}
@ -289,59 +298,3 @@ func (config *JWTConfig) defaultKeyFunc(t *jwt.Token) (interface{}, error) {
return config.SigningKey, nil
}
// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
func jwtFromHeader(header string, authScheme string) jwtExtractor {
return func(c echo.Context) (string, error) {
auth := c.Request().Header.Get(header)
l := len(authScheme)
if len(auth) > l+1 && auth[:l] == authScheme {
return auth[l+1:], nil
}
return "", ErrJWTMissing
}
}
// jwtFromQuery returns a `jwtExtractor` that extracts token from the query string.
func jwtFromQuery(param string) jwtExtractor {
return func(c echo.Context) (string, error) {
token := c.QueryParam(param)
if token == "" {
return "", ErrJWTMissing
}
return token, nil
}
}
// jwtFromParam returns a `jwtExtractor` that extracts token from the url param string.
func jwtFromParam(param string) jwtExtractor {
return func(c echo.Context) (string, error) {
token := c.Param(param)
if token == "" {
return "", ErrJWTMissing
}
return token, nil
}
}
// jwtFromCookie returns a `jwtExtractor` that extracts token from the named cookie.
func jwtFromCookie(name string) jwtExtractor {
return func(c echo.Context) (string, error) {
cookie, err := c.Cookie(name)
if err != nil {
return "", ErrJWTMissing
}
return cookie.Value, nil
}
}
// jwtFromForm returns a `jwtExtractor` that extracts token from the form field.
func jwtFromForm(name string) jwtExtractor {
return func(c echo.Context) (string, error) {
field := c.FormValue(name)
if field == "" {
return "", ErrJWTMissing
}
return field, nil
}
}

View File

@ -2,10 +2,8 @@ package middleware
import (
"errors"
"net/http"
"strings"
"github.com/labstack/echo/v4"
"net/http"
)
type (
@ -14,14 +12,21 @@ type (
// Skipper defines a function to skip middleware.
Skipper Skipper
// KeyLookup is a string in the form of "<source>:<name>" that is used
// KeyLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract key from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "header:<name>" or "header:<name>:<cut-prefix>"
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
// want to cut is `<auth-scheme> ` note the space at the end.
// In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
// - "query:<name>"
// - "form:<name>"
KeyLookup string `yaml:"key_lookup"`
// - "cookie:<name>"
// Multiple sources example:
// - "header:Authorization,header:X-Api-Key"
KeyLookup string
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
@ -34,15 +39,20 @@ type (
// ErrorHandler defines a function which is executed for an invalid key.
// It may be used to define a custom error.
ErrorHandler KeyAuthErrorHandler
// ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
// ignore the error (by returning `nil`).
// This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
// In that case you can use ErrorHandler to set a default public key auth value in the request context
// and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then.
ContinueOnIgnoredError bool
}
// KeyAuthValidator defines a function to validate KeyAuth credentials.
KeyAuthValidator func(string, echo.Context) (bool, error)
keyExtractor func(echo.Context) (string, error)
KeyAuthValidator func(auth string, c echo.Context) (bool, error)
// KeyAuthErrorHandler defines a function which is executed for an invalid key.
KeyAuthErrorHandler func(error, echo.Context) error
KeyAuthErrorHandler func(err error, c echo.Context) error
)
var (
@ -54,6 +64,21 @@ var (
}
)
// ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups
type ErrKeyAuthMissing struct {
Err error
}
// Error returns errors text
func (e *ErrKeyAuthMissing) Error() string {
return e.Err.Error()
}
// Unwrap unwraps error
func (e *ErrKeyAuthMissing) Unwrap() error {
return e.Err
}
// KeyAuth returns an KeyAuth middleware.
//
// For valid key it calls the next handler.
@ -83,14 +108,9 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
panic("echo: key-auth middleware requires a validator function")
}
// Initialize
parts := strings.Split(config.KeyLookup, ":")
extractor := keyFromHeader(parts[1], config.AuthScheme)
switch parts[0] {
case "query":
extractor = keyFromQuery(parts[1])
case "form":
extractor = keyFromForm(parts[1])
extractors, err := createExtractors(config.KeyLookup, config.AuthScheme)
if err != nil {
panic(err)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@ -99,68 +119,62 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
return next(c)
}
// Extract and verify key
key, err := extractor(c)
var lastExtractorErr error
var lastValidatorErr error
for _, extractor := range extractors {
keys, err := extractor(c)
if err != nil {
lastExtractorErr = err
continue
}
for _, key := range keys {
valid, err := config.Validator(key, c)
if err != nil {
lastValidatorErr = err
continue
}
if valid {
return next(c)
}
lastValidatorErr = errors.New("invalid key")
}
}
// we are here only when we did not successfully extract and validate any of keys
err := lastValidatorErr
if err == nil { // prioritize validator errors over extracting errors
// ugly part to preserve backwards compatible errors. someone could rely on them
if lastExtractorErr == errQueryExtractorValueMissing {
err = errors.New("missing key in the query string")
} else if lastExtractorErr == errCookieExtractorValueMissing {
err = errors.New("missing key in cookies")
} else if lastExtractorErr == errFormExtractorValueMissing {
err = errors.New("missing key in the form")
} else if lastExtractorErr == errHeaderExtractorValueMissing {
err = errors.New("missing key in request header")
} else if lastExtractorErr == errHeaderExtractorValueInvalid {
err = errors.New("invalid key in the request header")
} else {
err = lastExtractorErr
}
err = &ErrKeyAuthMissing{Err: err}
}
if config.ErrorHandler != nil {
return config.ErrorHandler(err, c)
tmpErr := config.ErrorHandler(err, c)
if config.ContinueOnIgnoredError && tmpErr == nil {
return next(c)
}
return tmpErr
}
if lastValidatorErr != nil { // prioritize validator errors over extracting errors
return &echo.HTTPError{
Code: http.StatusUnauthorized,
Message: "Unauthorized",
Internal: lastValidatorErr,
}
}
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
valid, err := config.Validator(key, c)
if err != nil {
if config.ErrorHandler != nil {
return config.ErrorHandler(err, c)
}
return &echo.HTTPError{
Code: http.StatusUnauthorized,
Message: "invalid key",
Internal: err,
}
} else if valid {
return next(c)
}
return echo.ErrUnauthorized
}
}
}
// keyFromHeader returns a `keyExtractor` that extracts key from the request header.
func keyFromHeader(header string, authScheme string) keyExtractor {
return func(c echo.Context) (string, error) {
auth := c.Request().Header.Get(header)
if auth == "" {
return "", errors.New("missing key in request header")
}
if header == echo.HeaderAuthorization {
l := len(authScheme)
if len(auth) > l+1 && auth[:l] == authScheme {
return auth[l+1:], nil
}
return "", errors.New("invalid key in the request header")
}
return auth, nil
}
}
// keyFromQuery returns a `keyExtractor` that extracts key from the query string.
func keyFromQuery(param string) keyExtractor {
return func(c echo.Context) (string, error) {
key := c.QueryParam(param)
if key == "" {
return "", errors.New("missing key in the query string")
}
return key, nil
}
}
// keyFromForm returns a `keyExtractor` that extracts key from the form.
func keyFromForm(param string) keyExtractor {
return func(c echo.Context) (string, error) {
key := c.FormValue(param)
if key == "" {
return "", errors.New("missing key in the form")
}
return key, nil
}
}

View File

@ -23,6 +23,8 @@ type (
// Tags to construct the logger format.
//
// - time_unix
// - time_unix_milli
// - time_unix_micro
// - time_unix_nano
// - time_rfc3339
// - time_rfc3339_nano
@ -126,6 +128,12 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
switch tag {
case "time_unix":
return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
case "time_unix_milli":
// go 1.17 or later, it supports time#UnixMilli()
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000000, 10))
case "time_unix_micro":
// go 1.17 or later, it supports time#UnixMicro()
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000, 10))
case "time_unix_nano":
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
case "time_rfc3339":

View File

@ -12,10 +12,10 @@ import (
type (
// Skipper defines a function to skip middleware. Returning true skips processing
// the middleware.
Skipper func(echo.Context) bool
Skipper func(c echo.Context) bool
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc func(echo.Context)
BeforeFunc func(c echo.Context)
)
func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {

View File

@ -155,7 +155,8 @@ type (
RateLimiterMemoryStore struct {
visitors map[string]*Visitor
mutex sync.Mutex
rate rate.Limit
rate rate.Limit //for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
burst int
expiresIn time.Duration
lastCleanup time.Time
@ -170,6 +171,8 @@ type (
/*
NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with
the provided rate (as req/s). The provided rate less than 1 will be treated as zero.
for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
Burst and ExpiresIn will be set to default values.
Example (with 20 requests/sec):
@ -199,7 +202,7 @@ Characteristics:
Example:
limiterStore := middleware.NewRateLimiterMemoryStoreWithConfig(
middleware.RateLimiterMemoryStoreConfig{Rate: 50, Burst: 200, ExpiresIn: 5 * time.Minutes},
middleware.RateLimiterMemoryStoreConfig{Rate: 50, Burst: 200, ExpiresIn: 5 * time.Minute},
)
*/
func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (store *RateLimiterMemoryStore) {
@ -221,7 +224,7 @@ func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (s
// RateLimiterMemoryStoreConfig represents configuration for RateLimiterMemoryStore
type RateLimiterMemoryStoreConfig struct {
Rate rate.Limit // Rate of requests allowed to pass as req/s
Rate rate.Limit // Rate of requests allowed to pass as req/s. For more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
Burst int // Burst additionally allows a number of requests to pass when rate limit is reached
ExpiresIn time.Duration // ExpiresIn is the duration after that a rate limiter is cleaned up
}

Some files were not shown because too many files have changed in this diff Show More