diff --git a/go.mod b/go.mod
index 0bf0480..2fd9d01 100644
--- a/go.mod
+++ b/go.mod
@@ -1,25 +1,25 @@
module git.paulbsd.com/paulbsd/qrz
-go 1.21
+go 1.22
require (
- github.com/antchfx/htmlquery v1.3.0
- github.com/antchfx/xpath v1.2.5 // indirect
+ github.com/antchfx/htmlquery v1.3.1
+ github.com/antchfx/xpath v1.3.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
- github.com/labstack/echo/v4 v4.11.4
+ github.com/labstack/echo/v4 v4.12.0
github.com/lib/pq v1.10.9
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.23.0 // indirect
github.com/robfig/cron v1.2.0
- golang.org/x/crypto v0.21.0 // indirect
- golang.org/x/net v0.22.0
- golang.org/x/sys v0.18.0 // indirect
+ golang.org/x/crypto v0.22.0 // indirect
+ golang.org/x/net v0.24.0
+ golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0
xorm.io/builder v0.3.13 // indirect
- xorm.io/xorm v1.3.8
+ xorm.io/xorm v1.3.9
)
require (
diff --git a/go.sum b/go.sum
index 69c4a52..accca32 100644
--- a/go.sum
+++ b/go.sum
@@ -19,11 +19,15 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E=
github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8=
+github.com/antchfx/htmlquery v1.3.1 h1:wm0LxjLMsZhRHfQKKZscDf2COyH4vDYA3wyH+qZ+Ylc=
+github.com/antchfx/htmlquery v1.3.1/go.mod h1:PTj+f1V2zksPlwNt7uVvZPsxpKNa7mlVliCRxLX6Nx8=
github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY=
github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.2.5 h1:hqZ+wtQ+KIOV/S3bGZcIhpgYC26um2bZYP2KVGcR7VY=
github.com/antchfx/xpath v1.2.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
+github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc=
+github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -257,6 +261,8 @@ github.com/labstack/echo/v4 v4.11.2 h1:T+cTLQxWCDfqDEoydYm5kCobjmHwOwcv4OJAPHilm
github.com/labstack/echo/v4 v4.11.2/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
+github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
+github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
@@ -494,6 +500,8 @@ golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -529,6 +537,7 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
@@ -543,6 +552,8 @@ golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -609,6 +620,8 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -863,3 +876,5 @@ xorm.io/xorm v1.3.4 h1:vWFKzR3DhGUDl5b4srhUjhDwjxkZAc4C7BFszpu0swI=
xorm.io/xorm v1.3.4/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
xorm.io/xorm v1.3.8 h1:CJmplmWqfSRpLWSPMmqz+so8toBp3m7ehuRehIWedZo=
xorm.io/xorm v1.3.8/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
+xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
+xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
diff --git a/vendor/github.com/antchfx/htmlquery/.travis.yml b/vendor/github.com/antchfx/htmlquery/.travis.yml
deleted file mode 100644
index 86da84a..0000000
--- a/vendor/github.com/antchfx/htmlquery/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: go
-
-go:
- - 1.9.x
- - 1.12.x
- - 1.13.x
-
-install:
- - go get golang.org/x/net/html/charset
- - go get golang.org/x/net/html
- - go get github.com/antchfx/xpath
- - go get github.com/mattn/goveralls
- - go get github.com/golang/groupcache
-
-script:
- - $HOME/gopath/bin/goveralls -service=travis-ci
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/htmlquery/README.md b/vendor/github.com/antchfx/htmlquery/README.md
index 5eb290d..ee4d473 100644
--- a/vendor/github.com/antchfx/htmlquery/README.md
+++ b/vendor/github.com/antchfx/htmlquery/README.md
@@ -1,36 +1,32 @@
-htmlquery
-====
-[![Build Status](https://travis-ci.org/antchfx/htmlquery.svg?branch=master)](https://travis-ci.org/antchfx/htmlquery)
-[![Coverage Status](https://coveralls.io/repos/github/antchfx/htmlquery/badge.svg?branch=master)](https://coveralls.io/github/antchfx/htmlquery?branch=master)
+# htmlquery
+
+[![Build Status](https://github.com/antchfx/htmlquery/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/htmlquery/actions/workflows/testing.yml)
[![GoDoc](https://godoc.org/github.com/antchfx/htmlquery?status.svg)](https://godoc.org/github.com/antchfx/htmlquery)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/htmlquery)](https://goreportcard.com/report/github.com/antchfx/htmlquery)
-Overview
-====
+# Overview
`htmlquery` is an XPath query package for HTML, lets you extract data or evaluate from HTML documents by an XPath expression.
-`htmlquery` built-in the query object caching feature based on [LRU](https://godoc.org/github.com/golang/groupcache/lru), this feature will caching the recently used XPATH query string. Enable query caching can avoid re-compile XPath expression each query.
+`htmlquery` built-in the query object caching feature based on [LRU](https://godoc.org/github.com/golang/groupcache/lru), this feature will caching the recently used XPATH query string. Enable query caching can avoid re-compile XPath expression each query.
You can visit this page to learn about the supported XPath(1.0/2.0) syntax. https://github.com/antchfx/xpath
-XPath query packages for Go
-===
+# XPath query packages for Go
+
| Name | Description |
| ------------------------------------------------- | ----------------------------------------- |
| [htmlquery](https://github.com/antchfx/htmlquery) | XPath query package for the HTML document |
| [xmlquery](https://github.com/antchfx/xmlquery) | XPath query package for the XML document |
| [jsonquery](https://github.com/antchfx/jsonquery) | XPath query package for the JSON document |
-Installation
-====
+# Installation
```
go get github.com/antchfx/htmlquery
```
-Getting Started
-====
+# Getting Started
#### Query, returns matched elements or error.
@@ -70,15 +66,15 @@ list := htmlquery.Find(doc, "//a")
#### Find all A elements that have `href` attribute.
```go
-list := htmlquery.Find(doc, "//a[@href]")
+list := htmlquery.Find(doc, "//a[@href]")
```
#### Find all A elements with `href` attribute and only return `href` value.
```go
-list := htmlquery.Find(doc, "//a/@href")
+list := htmlquery.Find(doc, "//a/@href")
for _ , n := range list{
- fmt.Println(htmlquery.SelectAttr(n, "href")) // output @href value
+ fmt.Println(htmlquery.InnerText(n)) // output @href value
}
```
@@ -89,6 +85,7 @@ a := htmlquery.FindOne(doc, "//a[3]")
```
### Find children element (img) under A `href` and print the source
+
```go
a := htmlquery.FindOne(doc, "//a")
img := htmlquery.FindOne(a, "//img")
@@ -103,9 +100,7 @@ v := expr.Evaluate(htmlquery.CreateXPathNavigator(doc)).(float64)
fmt.Printf("total count is %f", v)
```
-
-Quick Starts
-===
+# Quick Starts
```go
func main() {
@@ -127,9 +122,7 @@ func main() {
}
```
-
-FAQ
-====
+# FAQ
#### `Find()` vs `QueryAll()`, which is better?
@@ -158,6 +151,6 @@ BenchmarkDisableSelectorCache-4 500000 3162 ns/op
htmlquery.DisableSelectorCache = true
```
-Questions
-===
+# Questions
+
Please let me know if you have any questions.
diff --git a/vendor/github.com/antchfx/htmlquery/query.go b/vendor/github.com/antchfx/htmlquery/query.go
index f90f31c..c1c6457 100644
--- a/vendor/github.com/antchfx/htmlquery/query.go
+++ b/vendor/github.com/antchfx/htmlquery/query.go
@@ -5,6 +5,8 @@ package htmlquery
import (
"bufio"
+ "compress/gzip"
+ "compress/zlib"
"fmt"
"io"
"net/http"
@@ -88,15 +90,44 @@ func QuerySelectorAll(top *html.Node, selector *xpath.Expr) []*html.Node {
return elems
}
-// LoadURL loads the HTML document from the specified URL.
+// LoadURL loads the HTML document from the specified URL. Default enabling gzip on a HTTP request.
func LoadURL(url string) (*html.Node, error) {
- resp, err := http.Get(url)
+ req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
- defer resp.Body.Close()
+ // Enable gzip compression.
+ req.Header.Add("Accept-Encoding", "gzip")
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ var reader io.ReadCloser
- r, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
+ defer func() {
+ if reader != nil {
+ reader.Close()
+ }
+ }()
+ encoding := resp.Header.Get("Content-Encoding")
+ switch encoding {
+ case "gzip":
+ reader, err = gzip.NewReader(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ case "deflate":
+ reader, err = zlib.NewReader(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ case "":
+ reader = resp.Body
+ default:
+ return nil, fmt.Errorf("%s compression is not support", encoding)
+ }
+
+ r, err := charset.NewReader(reader, resp.Header.Get("Content-Type"))
if err != nil {
return nil, err
}
diff --git a/vendor/github.com/antchfx/xpath/.travis.yml b/vendor/github.com/antchfx/xpath/.travis.yml
deleted file mode 100644
index 6b63957..0000000
--- a/vendor/github.com/antchfx/xpath/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-language: go
-
-go:
- - 1.6
- - 1.9
- - '1.10'
-
-install:
- - go get github.com/mattn/goveralls
-
-script:
- - $HOME/gopath/bin/goveralls -service=travis-ci
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/xpath/README.md b/vendor/github.com/antchfx/xpath/README.md
index d1e3a3c..733c4c8 100644
--- a/vendor/github.com/antchfx/xpath/README.md
+++ b/vendor/github.com/antchfx/xpath/README.md
@@ -1,14 +1,13 @@
-XPath
-====
+# XPath
+
[![GoDoc](https://godoc.org/github.com/antchfx/xpath?status.svg)](https://godoc.org/github.com/antchfx/xpath)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/xpath/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xpath?branch=master)
-[![Build Status](https://travis-ci.org/antchfx/xpath.svg?branch=master)](https://travis-ci.org/antchfx/xpath)
+[![Build Status](https://github.com/antchfx/xpath/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/xpath/actions/workflows/testing.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xpath)](https://goreportcard.com/report/github.com/antchfx/xpath)
XPath is Go package provides selecting nodes from XML, HTML or other documents using XPath expression.
-Implementation
-===
+# Implementation
- [htmlquery](https://github.com/antchfx/htmlquery) - an XPath query package for HTML document
@@ -16,8 +15,7 @@ Implementation
- [jsonquery](https://github.com/antchfx/jsonquery) - an XPath query package for JSON document
-Supported Features
-===
+# Supported Features
#### The basic XPath patterns.
@@ -63,11 +61,14 @@ Supported Features
- `child::*` : The child axis selects children of the current node.
+ - `child::node()`: Selects all the children of the context node.
+ - `child::text()`: Selects all text node children of the context node.
+
- `descendant::*` : The descendant axis selects descendants of the current node. It is equivalent to '//'.
- `descendant-or-self::*` : Selects descendants including the current node.
-- `attribute::*` : Selects attributes of the current element. It is equivalent to @*
+- `attribute::*` : Selects attributes of the current element. It is equivalent to @\*
- `following-sibling::*` : Selects nodes after the current node.
@@ -87,27 +88,27 @@ Supported Features
#### Expressions
- The gxpath supported three types: number, boolean, string.
+The gxpath supported three types: number, boolean, string.
- `path` : Selects nodes based on the path.
- `a = b` : Standard comparisons.
- * a = b True if a equals b.
- * a != b True if a is not equal to b.
- * a < b True if a is less than b.
- * a <= b True if a is less than or equal to b.
- * a > b True if a is greater than b.
- * a >= b True if a is greater than or equal to b.
+ - `a = b` : True if a equals b.
+ - `a != b` : True if a is not equal to b.
+ - `a < b` : True if a is less than b.
+ - `a <= b` : True if a is less than or equal to b.
+ - `a > b` : True if a is greater than b.
+ - `a >= b` : True if a is greater than or equal to b.
- `a + b` : Arithmetic expressions.
- * `- a` Unary minus
- * a + b Add
- * a - b Substract
- * a * b Multiply
- * a div b Divide
- * a mod b Floating point mod, like Java.
+ - `- a` Unary minus
+ - `a + b` : Addition
+ - `a - b` : Subtraction
+ - `a * b` : Multiplication
+ - `a div b` : Division
+ - `a mod b` : Modulus (division remainder)
- `a or b` : Boolean `or` operation.
@@ -117,49 +118,50 @@ Supported Features
- `fun(arg1, ..., argn)` : Function calls:
-| Function | Supported |
-| --- | --- |
-`boolean()`| ✓ |
-`ceiling()`| ✓ |
-`choose()`| ✗ |
-`concat()`| ✓ |
-`contains()`| ✓ |
-`count()`| ✓ |
-`current()`| ✗ |
-`document()`| ✗ |
-`element-available()`| ✗ |
-`ends-with()`| ✓ |
-`false()`| ✓ |
-`floor()`| ✓ |
-`format-number()`| ✗ |
-`function-available()`| ✗ |
-`generate-id()`| ✗ |
-`id()`| ✗ |
-`key()`| ✗ |
-`lang()`| ✗ |
-`last()`| ✓ |
-`local-name()`| ✓ |
-`matches()`| ✓ |
-`name()`| ✓ |
-`namespace-uri()`| ✓ |
-`normalize-space()`| ✓ |
-`not()`| ✓ |
-`number()`| ✓ |
-`position()`| ✓ |
-`replace()`| ✓ |
-`reverse()`| ✓ |
-`round()`| ✓ |
-`starts-with()`| ✓ |
-`string()`| ✓ |
-`string-join()`[^1]| ✓ |
-`string-length()`| ✓ |
-`substring()`| ✓ |
-`substring-after()`| ✓ |
-`substring-before()`| ✓ |
-`sum()`| ✓ |
-`system-property()`| ✗ |
-`translate()`| ✓ |
-`true()`| ✓ |
-`unparsed-entity-url()` | ✗ |
+| Function | Supported |
+| ----------------------- | --------- |
+| `boolean()` | ✓ |
+| `ceiling()` | ✓ |
+| `choose()` | ✗ |
+| `concat()` | ✓ |
+| `contains()` | ✓ |
+| `count()` | ✓ |
+| `current()` | ✗ |
+| `document()` | ✗ |
+| `element-available()` | ✗ |
+| `ends-with()` | ✓ |
+| `false()` | ✓ |
+| `floor()` | ✓ |
+| `format-number()` | ✗ |
+| `function-available()` | ✗ |
+| `generate-id()` | ✗ |
+| `id()` | ✗ |
+| `key()` | ✗ |
+| `lang()` | ✗ |
+| `last()` | ✓ |
+| `local-name()` | ✓ |
+| `lower-case()`[^1] | ✓ |
+| `matches()` | ✓ |
+| `name()` | ✓ |
+| `namespace-uri()` | ✓ |
+| `normalize-space()` | ✓ |
+| `not()` | ✓ |
+| `number()` | ✓ |
+| `position()` | ✓ |
+| `replace()` | ✓ |
+| `reverse()` | ✓ |
+| `round()` | ✓ |
+| `starts-with()` | ✓ |
+| `string()` | ✓ |
+| `string-join()`[^1] | ✓ |
+| `string-length()` | ✓ |
+| `substring()` | ✓ |
+| `substring-after()` | ✓ |
+| `substring-before()` | ✓ |
+| `sum()` | ✓ |
+| `system-property()` | ✗ |
+| `translate()` | ✓ |
+| `true()` | ✓ |
+| `unparsed-entity-url()` | ✗ |
-[^1]: XPath-2.0 expression
\ No newline at end of file
+[^1]: XPath-2.0 expression
diff --git a/vendor/github.com/antchfx/xpath/build.go b/vendor/github.com/antchfx/xpath/build.go
index 2977bbc..e079e96 100644
--- a/vendor/github.com/antchfx/xpath/build.go
+++ b/vendor/github.com/antchfx/xpath/build.go
@@ -7,15 +7,39 @@ import (
type flag int
-const (
- noneFlag flag = iota
- filterFlag
-)
+var flagsEnum = struct {
+ None flag
+ SmartDesc flag
+ PosFilter flag
+ Filter flag
+ Condition flag
+}{
+ None: 0,
+ SmartDesc: 1,
+ PosFilter: 2,
+ Filter: 4,
+ Condition: 8,
+}
+
+type builderProp int
+
+var builderProps = struct {
+ None builderProp
+ PosFilter builderProp
+ HasPosition builderProp
+ HasLast builderProp
+ NonFlat builderProp
+}{
+ None: 0,
+ PosFilter: 1,
+ HasPosition: 2,
+ HasLast: 4,
+ NonFlat: 8,
+}
// builder provides building an XPath expressions.
type builder struct {
- depth int
- flag flag
+ parseDepth int
firstInput query
}
@@ -63,23 +87,26 @@ func axisPredicate(root *axisNode) func(NodeNavigator) bool {
return predicate
}
-// processAxisNode processes a query for the XPath axis node.
-func (b *builder) processAxisNode(root *axisNode) (query, error) {
+// processAxis processes a query for the XPath axis node.
+func (b *builder) processAxis(root *axisNode, flags flag, props *builderProp) (query, error) {
var (
- err error
- qyInput query
- qyOutput query
- predicate = axisPredicate(root)
+ err error
+ qyInput query
+ qyOutput query
)
+ b.firstInput = nil
+ predicate := axisPredicate(root)
if root.Input == nil {
qyInput = &contextQuery{}
+ *props = builderProps.None
} else {
+ inputFlags := flagsEnum.None
if root.AxeType == "child" && (root.Input.Type() == nodeAxis) {
if input := root.Input.(*axisNode); input.AxeType == "descendant-or-self" {
var qyGrandInput query
if input.Input != nil {
- qyGrandInput, _ = b.processNode(input.Input)
+ qyGrandInput, _ = b.processNode(input.Input, flagsEnum.SmartDesc, props)
} else {
qyGrandInput = &contextQuery{}
}
@@ -94,14 +121,14 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
}
return v
}
- // fix `//*[contains(@id,"food")]//*[contains(@id,"food")]`, see https://github.com/antchfx/htmlquery/issues/52
- // Skip the current node(Self:false) for the next descendants nodes.
- _, ok := qyGrandInput.(*contextQuery)
- qyOutput = &descendantQuery{Input: qyGrandInput, Predicate: filter, Self: ok}
+ qyOutput = &descendantQuery{name: root.LocalName, Input: qyGrandInput, Predicate: filter, Self: false}
+ *props |= builderProps.NonFlat
return qyOutput, nil
}
+ } else if ((flags & flagsEnum.Filter) == 0) && (root.AxeType == "descendant" || root.AxeType == "descendant-or-self") {
+ inputFlags |= flagsEnum.SmartDesc
}
- qyInput, err = b.processNode(root.Input)
+ qyInput, err = b.processNode(root.Input, inputFlags, props)
if err != nil {
return nil, err
}
@@ -109,11 +136,13 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
switch root.AxeType {
case "ancestor":
- qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate}
+ qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
+ *props |= builderProps.NonFlat
case "ancestor-or-self":
- qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate, Self: true}
+ qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true}
+ *props |= builderProps.NonFlat
case "attribute":
- qyOutput = &attributeQuery{Input: qyInput, Predicate: predicate}
+ qyOutput = &attributeQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
case "child":
filter := func(n NodeNavigator) bool {
v := predicate(n)
@@ -127,19 +156,35 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
}
return v
}
- qyOutput = &childQuery{Input: qyInput, Predicate: filter}
+ if (*props & builderProps.NonFlat) == 0 {
+ qyOutput = &childQuery{name: root.LocalName, Input: qyInput, Predicate: filter}
+ } else {
+ qyOutput = &cachedChildQuery{name: root.LocalName, Input: qyInput, Predicate: filter}
+ }
case "descendant":
- qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate}
+ if (flags & flagsEnum.SmartDesc) != flagsEnum.None {
+ qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: false, Predicate: predicate}
+ } else {
+ qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
+ }
+ *props |= builderProps.NonFlat
case "descendant-or-self":
- qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate, Self: true}
+ if (flags & flagsEnum.SmartDesc) != flagsEnum.None {
+ qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: true, Predicate: predicate}
+ } else {
+ qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true}
+ }
+ *props |= builderProps.NonFlat
case "following":
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate}
+ *props |= builderProps.NonFlat
case "following-sibling":
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
case "parent":
qyOutput = &parentQuery{Input: qyInput, Predicate: predicate}
case "preceding":
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate}
+ *props |= builderProps.NonFlat
case "preceding-sibling":
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
case "self":
@@ -153,56 +198,182 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
return qyOutput, nil
}
-// processFilterNode builds query for the XPath filter predicate.
-func (b *builder) processFilterNode(root *filterNode) (query, error) {
- b.flag |= filterFlag
+func canBeNumber(q query) bool {
+ if q.ValueType() != xpathResultType.Any {
+ return q.ValueType() == xpathResultType.Number
+ }
+ return true
+}
- qyInput, err := b.processNode(root.Input)
+// processFilterNode builds query for the XPath filter predicate.
+func (b *builder) processFilter(root *filterNode, flags flag, props *builderProp) (query, error) {
+ first := (flags & flagsEnum.Filter) == 0
+
+ qyInput, err := b.processNode(root.Input, (flags | flagsEnum.Filter), props)
if err != nil {
return nil, err
}
- qyCond, err := b.processNode(root.Condition)
+ firstInput := b.firstInput
+
+ var propsCond builderProp
+ cond, err := b.processNode(root.Condition, flags, &propsCond)
if err != nil {
return nil, err
}
- qyOutput := &filterQuery{Input: qyInput, Predicate: qyCond}
- return qyOutput, nil
+
+ // Checking whether is number
+ if canBeNumber(cond) || ((propsCond & (builderProps.HasPosition | builderProps.HasLast)) != 0) {
+ propsCond |= builderProps.HasPosition
+ flags |= flagsEnum.PosFilter
+ }
+
+ if root.Input.Type() != nodeFilter {
+ *props &= ^builderProps.PosFilter
+ }
+
+ if (propsCond & builderProps.HasPosition) != 0 {
+ *props |= builderProps.PosFilter
+ }
+
+ merge := (qyInput.Properties() & queryProps.Merge) != 0
+ if (propsCond & builderProps.HasPosition) != builderProps.None {
+ if (propsCond & builderProps.HasLast) != 0 {
+ // https://github.com/antchfx/xpath/issues/76
+ // https://github.com/antchfx/xpath/issues/78
+ if qyFunc, ok := cond.(*functionQuery); ok {
+ switch qyFunc.Input.(type) {
+ case *filterQuery:
+ cond = &lastQuery{Input: qyFunc.Input}
+ }
+ }
+ }
+ }
+
+ if first && firstInput != nil {
+ if merge && ((*props & builderProps.PosFilter) != 0) {
+ qyInput = &filterQuery{Input: qyInput, Predicate: cond, NoPosition: false}
+
+ var (
+ rootQuery = &contextQuery{}
+ parent query
+ )
+ switch axisQuery := firstInput.(type) {
+ case *ancestorQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *attributeQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *childQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *cachedChildQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *descendantQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *followingQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *precedingQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *parentQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *selfQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *groupQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ case *descendantOverDescendantQuery:
+ if _, ok := axisQuery.Input.(*contextQuery); !ok {
+ parent = axisQuery.Input
+ axisQuery.Input = rootQuery
+ }
+ }
+ b.firstInput = nil
+ if parent != nil {
+ return &mergeQuery{Input: parent, Child: qyInput}, nil
+ }
+ return qyInput, nil
+ }
+ b.firstInput = nil
+ }
+
+ resultQuery := &filterQuery{
+ Input: qyInput,
+ Predicate: cond,
+ NoPosition: (propsCond & builderProps.HasPosition) == 0,
+ }
+ return resultQuery, nil
}
// processFunctionNode processes query for the XPath function node.
-func (b *builder) processFunctionNode(root *functionNode) (query, error) {
+func (b *builder) processFunction(root *functionNode, props *builderProp) (query, error) {
+ // Reset builder props
+ *props = builderProps.None
+
var qyOutput query
switch root.FuncName {
+ case "lower-case":
+ arg, err := b.processNode(root.Args[0], flagsEnum.None, props)
+ if err != nil {
+ return nil, err
+ }
+ qyOutput = &functionQuery{Input: arg, Func: lowerCaseFunc}
case "starts-with":
- arg1, err := b.processNode(root.Args[0])
+ arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
- arg2, err := b.processNode(root.Args[1])
+ arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: startwithFunc(arg1, arg2)}
+ qyOutput = &functionQuery{Func: startwithFunc(arg1, arg2)}
case "ends-with":
- arg1, err := b.processNode(root.Args[0])
+ arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
- arg2, err := b.processNode(root.Args[1])
+ arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: endwithFunc(arg1, arg2)}
+ qyOutput = &functionQuery{Func: endwithFunc(arg1, arg2)}
case "contains":
- arg1, err := b.processNode(root.Args[0])
+ arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
- arg2, err := b.processNode(root.Args[1])
+ arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: containsFunc(arg1, arg2)}
+ qyOutput = &functionQuery{Func: containsFunc(arg1, arg2)}
case "matches":
//matches(string , pattern)
if len(root.Args) != 2 {
@@ -212,10 +383,10 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2 query
err error
)
- if arg1, err = b.processNode(root.Args[0]); err != nil {
+ if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
- if arg2, err = b.processNode(root.Args[1]); err != nil {
+ if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
// Issue #92, testing the regular expression before.
@@ -224,7 +395,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
return nil, fmt.Errorf("matches() got error. %v", err)
}
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: matchesFunc(arg1, arg2)}
+ qyOutput = &functionQuery{Func: matchesFunc(arg1, arg2)}
case "substring":
//substring( string , start [, length] )
if len(root.Args) < 2 {
@@ -234,18 +405,18 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query
err error
)
- if arg1, err = b.processNode(root.Args[0]); err != nil {
+ if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
- if arg2, err = b.processNode(root.Args[1]); err != nil {
+ if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
if len(root.Args) == 3 {
- if arg3, err = b.processNode(root.Args[2]); err != nil {
+ if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err
}
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: substringFunc(arg1, arg2, arg3)}
+ qyOutput = &functionQuery{Func: substringFunc(arg1, arg2, arg3)}
case "substring-before", "substring-after":
//substring-xxxx( haystack, needle )
if len(root.Args) != 2 {
@@ -255,31 +426,30 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2 query
err error
)
- if arg1, err = b.processNode(root.Args[0]); err != nil {
+ if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
- if arg2, err = b.processNode(root.Args[1]); err != nil {
+ if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
qyOutput = &functionQuery{
- Input: b.firstInput,
- Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"),
+ Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"),
}
case "string-length":
// string-length( [string] )
if len(root.Args) < 1 {
return nil, errors.New("xpath: string-length function must have at least one parameter")
}
- arg1, err := b.processNode(root.Args[0])
+ arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: stringLengthFunc(arg1)}
+ qyOutput = &functionQuery{Func: stringLengthFunc(arg1)}
case "normalize-space":
if len(root.Args) == 0 {
return nil, errors.New("xpath: normalize-space function must have at least one parameter")
}
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -293,16 +463,16 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query
err error
)
- if arg1, err = b.processNode(root.Args[0]); err != nil {
+ if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
- if arg2, err = b.processNode(root.Args[1]); err != nil {
+ if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
- if arg3, err = b.processNode(root.Args[2]); err != nil {
+ if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: replaceFunc(arg1, arg2, arg3)}
+ qyOutput = &functionQuery{Func: replaceFunc(arg1, arg2, arg3)}
case "translate":
//translate( string , string, string )
if len(root.Args) != 3 {
@@ -312,21 +482,21 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query
err error
)
- if arg1, err = b.processNode(root.Args[0]); err != nil {
+ if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
- if arg2, err = b.processNode(root.Args[1]); err != nil {
+ if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
- if arg3, err = b.processNode(root.Args[2]); err != nil {
+ if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: translateFunc(arg1, arg2, arg3)}
+ qyOutput = &functionQuery{Func: translateFunc(arg1, arg2, arg3)}
case "not":
if len(root.Args) == 0 {
return nil, errors.New("xpath: not function must have at least one parameter")
}
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -340,46 +510,46 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
err error
)
if len(root.Args) == 1 {
- arg, err = b.processNode(root.Args[0])
+ arg, err = b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
}
switch root.FuncName {
case "name":
- qyOutput = &functionQuery{Input: b.firstInput, Func: nameFunc(arg)}
+ qyOutput = &functionQuery{Func: nameFunc(arg)}
case "local-name":
- qyOutput = &functionQuery{Input: b.firstInput, Func: localNameFunc(arg)}
+ qyOutput = &functionQuery{Func: localNameFunc(arg)}
case "namespace-uri":
- qyOutput = &functionQuery{Input: b.firstInput, Func: namespaceFunc(arg)}
+ qyOutput = &functionQuery{Func: namespaceFunc(arg)}
}
case "true", "false":
val := root.FuncName == "true"
qyOutput = &functionQuery{
- Input: b.firstInput,
Func: func(_ query, _ iterator) interface{} {
return val
},
}
case "last":
- switch typ := b.firstInput.(type) {
- case *groupQuery, *filterQuery:
- // https://github.com/antchfx/xpath/issues/76
- // https://github.com/antchfx/xpath/issues/78
- qyOutput = &lastQuery{Input: typ}
- default:
- qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc}
- }
-
+ //switch typ := b.firstInput.(type) {
+ //case *groupQuery, *filterQuery:
+ // https://github.com/antchfx/xpath/issues/76
+ // https://github.com/antchfx/xpath/issues/78
+ //qyOutput = &lastQuery{Input: typ}
+ //default:
+ qyOutput = &functionQuery{Func: lastFunc}
+ //}
+ *props |= builderProps.HasLast
case "position":
- qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc}
+ qyOutput = &functionQuery{Func: positionFunc}
+ *props |= builderProps.HasPosition
case "boolean", "number", "string":
- inp := b.firstInput
+ var inp query
if len(root.Args) > 1 {
return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName)
}
if len(root.Args) == 1 {
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -396,13 +566,10 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
}
qyOutput = f
case "count":
- //if b.firstInput == nil {
- // return nil, errors.New("xpath: expression must evaluate to node-set")
- //}
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets")
}
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -411,7 +578,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: sum(node-sets) function must with have parameters node-sets")
}
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -420,7 +587,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: ceiling(node-sets) function must with have parameters node-sets")
}
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -440,18 +607,18 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
}
var args []query
for _, v := range root.Args {
- q, err := b.processNode(v)
+ q, err := b.processNode(v, flagsEnum.None, props)
if err != nil {
return nil, err
}
args = append(args, q)
}
- qyOutput = &functionQuery{Input: b.firstInput, Func: concatFunc(args...)}
+ qyOutput = &functionQuery{Func: concatFunc(args...)}
case "reverse":
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: reverse(node-sets) function must with have parameters node-sets")
}
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -460,11 +627,11 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
if len(root.Args) != 2 {
return nil, fmt.Errorf("xpath: string-join(node-sets, separator) function requires node-set and argument")
}
- argQuery, err := b.processNode(root.Args[0])
+ argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
- arg1, err := b.processNode(root.Args[1])
+ arg1, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
@@ -472,18 +639,29 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
default:
return nil, fmt.Errorf("not yet support this function %s()", root.FuncName)
}
+
+ if funcQuery, ok := qyOutput.(*functionQuery); ok && funcQuery.Input == nil {
+ funcQuery.Input = b.firstInput
+ }
return qyOutput, nil
}
-func (b *builder) processOperatorNode(root *operatorNode) (query, error) {
- left, err := b.processNode(root.Left)
+func (b *builder) processOperator(root *operatorNode, props *builderProp) (query, error) {
+ var (
+ leftProp builderProp
+ rightProp builderProp
+ )
+
+ left, err := b.processNode(root.Left, flagsEnum.None, &leftProp)
if err != nil {
return nil, err
}
- right, err := b.processNode(root.Right)
+ right, err := b.processNode(root.Right, flagsEnum.None, &rightProp)
if err != nil {
return nil, err
}
+ *props = leftProp | rightProp
+
var qyOutput query
switch root.Op {
case "+", "-", "*", "div", "mod": // Numeric operator
@@ -525,41 +703,45 @@ func (b *builder) processOperatorNode(root *operatorNode) (query, error) {
}
qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr}
case "|":
+ *props |= builderProps.NonFlat
qyOutput = &unionQuery{Left: left, Right: right}
}
return qyOutput, nil
}
-func (b *builder) processNode(root node) (q query, err error) {
- if b.depth = b.depth + 1; b.depth > 1024 {
+func (b *builder) processNode(root node, flags flag, props *builderProp) (q query, err error) {
+ if b.parseDepth = b.parseDepth + 1; b.parseDepth > 1024 {
err = errors.New("the xpath expressions is too complex")
return
}
-
+ *props = builderProps.None
switch root.Type() {
case nodeConstantOperand:
n := root.(*operandNode)
q = &constantQuery{Val: n.Val}
case nodeRoot:
- q = &contextQuery{Root: true}
+ q = &absoluteQuery{}
case nodeAxis:
- q, err = b.processAxisNode(root.(*axisNode))
+ q, err = b.processAxis(root.(*axisNode), flags, props)
b.firstInput = q
case nodeFilter:
- q, err = b.processFilterNode(root.(*filterNode))
+ q, err = b.processFilter(root.(*filterNode), flags, props)
b.firstInput = q
case nodeFunction:
- q, err = b.processFunctionNode(root.(*functionNode))
+ q, err = b.processFunction(root.(*functionNode), props)
case nodeOperator:
- q, err = b.processOperatorNode(root.(*operatorNode))
+ q, err = b.processOperator(root.(*operatorNode), props)
case nodeGroup:
- q, err = b.processNode(root.(*groupNode).Input)
+ q, err = b.processNode(root.(*groupNode).Input, flagsEnum.None, props)
if err != nil {
return
}
q = &groupQuery{Input: q}
- b.firstInput = q
+ if b.firstInput == nil {
+ b.firstInput = q
+ }
}
+ b.parseDepth--
return
}
@@ -579,5 +761,6 @@ func build(expr string, namespaces map[string]string) (q query, err error) {
}()
root := parse(expr, namespaces)
b := &builder{}
- return b.processNode(root)
+ props := builderProps.None
+ return b.processNode(root, flagsEnum.None, &props)
}
diff --git a/vendor/github.com/antchfx/xpath/func.go b/vendor/github.com/antchfx/xpath/func.go
index 4131bfd..65386f0 100644
--- a/vendor/github.com/antchfx/xpath/func.go
+++ b/vendor/github.com/antchfx/xpath/func.go
@@ -645,3 +645,9 @@ func stringJoinFunc(arg1 query) func(query, iterator) interface{} {
return strings.Join(parts, separator)
}
}
+
+// lower-case is XPATH function that converts a string to lower case.
+func lowerCaseFunc(q query, t iterator) interface{} {
+ v := functionArgs(q).Evaluate(t)
+ return strings.ToLower(asString(t, v))
+}
diff --git a/vendor/github.com/antchfx/xpath/operator.go b/vendor/github.com/antchfx/xpath/operator.go
index eb38ac6..12aadc1 100644
--- a/vendor/github.com/antchfx/xpath/operator.go
+++ b/vendor/github.com/antchfx/xpath/operator.go
@@ -1,40 +1,12 @@
package xpath
import (
- "fmt"
"reflect"
"strconv"
)
// The XPath number operator function list.
-// valueType is a return value type.
-type valueType int
-
-const (
- booleanType valueType = iota
- numberType
- stringType
- nodeSetType
-)
-
-func getValueType(i interface{}) valueType {
- v := reflect.ValueOf(i)
- switch v.Kind() {
- case reflect.Float64:
- return numberType
- case reflect.String:
- return stringType
- case reflect.Bool:
- return booleanType
- default:
- if _, ok := i.(query); ok {
- return nodeSetType
- }
- }
- panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
-}
-
type logical func(iterator, string, interface{}, interface{}) bool
var logicalFuncs = [][]logical{
@@ -228,50 +200,50 @@ func cmpBooleanBoolean(t iterator, op string, m, n interface{}) bool {
// eqFunc is an `=` operator.
func eqFunc(t iterator, m, n interface{}) interface{} {
- t1 := getValueType(m)
- t2 := getValueType(n)
+ t1 := getXPathType(m)
+ t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "=", m, n)
}
// gtFunc is an `>` operator.
func gtFunc(t iterator, m, n interface{}) interface{} {
- t1 := getValueType(m)
- t2 := getValueType(n)
+ t1 := getXPathType(m)
+ t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, ">", m, n)
}
// geFunc is an `>=` operator.
func geFunc(t iterator, m, n interface{}) interface{} {
- t1 := getValueType(m)
- t2 := getValueType(n)
+ t1 := getXPathType(m)
+ t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, ">=", m, n)
}
// ltFunc is an `<` operator.
func ltFunc(t iterator, m, n interface{}) interface{} {
- t1 := getValueType(m)
- t2 := getValueType(n)
+ t1 := getXPathType(m)
+ t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "<", m, n)
}
// leFunc is an `<=` operator.
func leFunc(t iterator, m, n interface{}) interface{} {
- t1 := getValueType(m)
- t2 := getValueType(n)
+ t1 := getXPathType(m)
+ t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "<=", m, n)
}
// neFunc is an `!=` operator.
func neFunc(t iterator, m, n interface{}) interface{} {
- t1 := getValueType(m)
- t2 := getValueType(n)
+ t1 := getXPathType(m)
+ t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "!=", m, n)
}
// orFunc is an `or` operator.
var orFunc = func(t iterator, m, n interface{}) interface{} {
- t1 := getValueType(m)
- t2 := getValueType(n)
+ t1 := getXPathType(m)
+ t2 := getXPathType(n)
return logicalFuncs[t1][t2](t, "or", m, n)
}
diff --git a/vendor/github.com/antchfx/xpath/query.go b/vendor/github.com/antchfx/xpath/query.go
index 4e6c634..fe6f488 100644
--- a/vendor/github.com/antchfx/xpath/query.go
+++ b/vendor/github.com/antchfx/xpath/query.go
@@ -7,6 +7,44 @@ import (
"reflect"
)
+// The return type of the XPath expression.
+type resultType int
+
+var xpathResultType = struct {
+ Boolean resultType
+ // A numeric value
+ Number resultType
+ String resultType
+ // A node collection.
+ NodeSet resultType
+ // Any of the XPath node types.
+ Any resultType
+}{
+ Boolean: 0,
+ Number: 1,
+ String: 2,
+ NodeSet: 3,
+ Any: 4,
+}
+
+type queryProp int
+
+var queryProps = struct {
+ None queryProp
+ Position queryProp
+ Count queryProp
+ Cached queryProp
+ Reverse queryProp
+ Merge queryProp
+}{
+ None: 0,
+ Position: 1,
+ Count: 2,
+ Cached: 4,
+ Reverse: 8,
+ Merge: 16,
+}
+
type iterator interface {
Current() NodeNavigator
}
@@ -20,12 +58,15 @@ type query interface {
Evaluate(iterator) interface{}
Clone() query
+
+ // ValueType returns the value type of the current query.
+ ValueType() resultType
+
+ Properties() queryProp
}
// nopQuery is an empty query that always return nil for any query.
-type nopQuery struct {
- query
-}
+type nopQuery struct{}
func (nopQuery) Select(iterator) NodeNavigator { return nil }
@@ -33,21 +74,23 @@ func (nopQuery) Evaluate(iterator) interface{} { return nil }
func (nopQuery) Clone() query { return nopQuery{} }
+func (nopQuery) ValueType() resultType { return xpathResultType.NodeSet }
+
+func (nopQuery) Properties() queryProp {
+ return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
+}
+
// contextQuery is returns current node on the iterator object query.
type contextQuery struct {
count int
- Root bool // Moving to root-level node in the current context iterator.
}
-func (c *contextQuery) Select(t iterator) (n NodeNavigator) {
- if c.count == 0 {
- c.count++
- n = t.Current().Copy()
- if c.Root {
- n.MoveToRoot()
- }
+func (c *contextQuery) Select(t iterator) NodeNavigator {
+ if c.count > 0 {
+ return nil
}
- return n
+ c.count++
+ return t.Current().Copy()
}
func (c *contextQuery) Evaluate(iterator) interface{} {
@@ -56,12 +99,53 @@ func (c *contextQuery) Evaluate(iterator) interface{} {
}
func (c *contextQuery) Clone() query {
- return &contextQuery{Root: c.Root}
+ return &contextQuery{}
+}
+
+func (c *contextQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (c *contextQuery) Properties() queryProp {
+ return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
+}
+
+type absoluteQuery struct {
+ count int
+}
+
+func (a *absoluteQuery) Select(t iterator) (n NodeNavigator) {
+ if a.count > 0 {
+ return
+ }
+ a.count++
+ n = t.Current().Copy()
+ n.MoveToRoot()
+ return
+}
+
+func (a *absoluteQuery) Evaluate(t iterator) interface{} {
+ a.count = 0
+ return a
+}
+
+func (a *absoluteQuery) Clone() query {
+ return &absoluteQuery{}
+}
+
+func (a *absoluteQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (a *absoluteQuery) Properties() queryProp {
+ return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}
// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
type ancestorQuery struct {
+ name string
iterator func() NodeNavigator
+ table map[uint64]bool
Self bool
Input query
@@ -69,6 +153,10 @@ type ancestorQuery struct {
}
func (a *ancestorQuery) Select(t iterator) NodeNavigator {
+ if a.table == nil {
+ a.table = make(map[uint64]bool)
+ }
+
for {
if a.iterator == nil {
node := a.Input.Select(t)
@@ -78,24 +166,27 @@ func (a *ancestorQuery) Select(t iterator) NodeNavigator {
first := true
node = node.Copy()
a.iterator = func() NodeNavigator {
- if first && a.Self {
+ if first {
first = false
- if a.Predicate(node) {
+ if a.Self && a.Predicate(node) {
return node
}
}
for node.MoveToParent() {
- if !a.Predicate(node) {
- continue
+ if a.Predicate(node) {
+ return node
}
- return node
}
return nil
}
}
- if node := a.iterator(); node != nil {
- return node
+ for node := a.iterator(); node != nil; node = a.iterator() {
+ node_id := getHashCode(node.Copy())
+ if _, ok := a.table[node_id]; !ok {
+ a.table[node_id] = true
+ return node
+ }
}
a.iterator = nil
}
@@ -112,11 +203,20 @@ func (a *ancestorQuery) Test(n NodeNavigator) bool {
}
func (a *ancestorQuery) Clone() query {
- return &ancestorQuery{Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
+ return &ancestorQuery{name: a.name, Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
+}
+
+func (a *ancestorQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (a *ancestorQuery) Properties() queryProp {
+ return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge | queryProps.Reverse
}
// attributeQuery is an XPath attribute node query.(@*)
type attributeQuery struct {
+ name string
iterator func() NodeNavigator
Input query
@@ -162,11 +262,20 @@ func (a *attributeQuery) Test(n NodeNavigator) bool {
}
func (a *attributeQuery) Clone() query {
- return &attributeQuery{Input: a.Input.Clone(), Predicate: a.Predicate}
+ return &attributeQuery{name: a.name, Input: a.Input.Clone(), Predicate: a.Predicate}
+}
+
+func (a *attributeQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (a *attributeQuery) Properties() queryProp {
+ return queryProps.Merge
}
// childQuery is an XPath child node query.(child::*)
type childQuery struct {
+ name string
posit int
iterator func() NodeNavigator
@@ -216,7 +325,15 @@ func (c *childQuery) Test(n NodeNavigator) bool {
}
func (c *childQuery) Clone() query {
- return &childQuery{Input: c.Input.Clone(), Predicate: c.Predicate}
+ return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
+}
+
+func (c *childQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (c *childQuery) Properties() queryProp {
+ return queryProps.Merge
}
// position returns a position of current NodeNavigator.
@@ -224,8 +341,75 @@ func (c *childQuery) position() int {
return c.posit
}
+type cachedChildQuery struct {
+ name string
+ posit int
+ iterator func() NodeNavigator
+
+ Input query
+ Predicate func(NodeNavigator) bool
+}
+
+func (c *cachedChildQuery) Select(t iterator) NodeNavigator {
+ for {
+ if c.iterator == nil {
+ c.posit = 0
+ node := c.Input.Select(t)
+ if node == nil {
+ return nil
+ }
+ node = node.Copy()
+ first := true
+ c.iterator = func() NodeNavigator {
+ for {
+ if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
+ return nil
+ }
+ first = false
+ if c.Predicate(node) {
+ return node
+ }
+ }
+ }
+ }
+
+ if node := c.iterator(); node != nil {
+ c.posit++
+ return node
+ }
+ c.iterator = nil
+ }
+}
+
+func (c *cachedChildQuery) Evaluate(t iterator) interface{} {
+ c.Input.Evaluate(t)
+ c.iterator = nil
+ return c
+}
+
+func (c *cachedChildQuery) position() int {
+ return c.posit
+}
+
+func (c *cachedChildQuery) Test(n NodeNavigator) bool {
+ return c.Predicate(n)
+}
+
+func (c *cachedChildQuery) Clone() query {
+ return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
+}
+
+func (c *cachedChildQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (c *cachedChildQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
// descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*)
type descendantQuery struct {
+ name string
iterator func() NodeNavigator
posit int
level int
@@ -245,14 +429,11 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
}
node = node.Copy()
d.level = 0
- positmap := make(map[int]int)
first := true
d.iterator = func() NodeNavigator {
- if first && d.Self {
+ if first {
first = false
- if d.Predicate(node) {
- d.posit = 1
- positmap[d.level] = 1
+ if d.Self && d.Predicate(node) {
return node
}
}
@@ -260,7 +441,6 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
for {
if node.MoveToChild() {
d.level = d.level + 1
- positmap[d.level] = 0
} else {
for {
if d.level == 0 {
@@ -274,8 +454,6 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
}
}
if d.Predicate(node) {
- positmap[d.level]++
- d.posit = positmap[d.level]
return node
}
}
@@ -283,6 +461,7 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
}
if node := d.iterator(); node != nil {
+ d.posit++
return node
}
d.iterator = nil
@@ -309,7 +488,15 @@ func (d *descendantQuery) depth() int {
}
func (d *descendantQuery) Clone() query {
- return &descendantQuery{Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
+ return &descendantQuery{name: d.name, Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
+}
+
+func (d *descendantQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (d *descendantQuery) Properties() queryProp {
+ return queryProps.Merge
}
// followingQuery is an XPath following node query.(following::*|following-sibling::*)
@@ -390,6 +577,14 @@ func (f *followingQuery) Clone() query {
return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate}
}
+func (f *followingQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (f *followingQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
func (f *followingQuery) position() int {
return f.posit
}
@@ -471,6 +666,14 @@ func (p *precedingQuery) Clone() query {
return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate}
}
+func (p *precedingQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (p *precedingQuery) Properties() queryProp {
+ return queryProps.Merge | queryProps.Reverse
+}
+
func (p *precedingQuery) position() int {
return p.posit
}
@@ -503,6 +706,14 @@ func (p *parentQuery) Clone() query {
return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate}
}
+func (p *parentQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (p *parentQuery) Properties() queryProp {
+ return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
+}
+
func (p *parentQuery) Test(n NodeNavigator) bool {
return p.Predicate(n)
}
@@ -539,12 +750,22 @@ func (s *selfQuery) Clone() query {
return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate}
}
+func (s *selfQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (s *selfQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
// filterQuery is an XPath query for predicate filter.
type filterQuery struct {
- Input query
- Predicate query
- posit int
- positmap map[int]int
+ Input query
+ Predicate query
+ NoPosition bool
+
+ posit int
+ positmap map[int]int
}
func (f *filterQuery) do(t iterator) bool {
@@ -602,6 +823,14 @@ func (f *filterQuery) Clone() query {
return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()}
}
+func (f *filterQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (f *filterQuery) Properties() queryProp {
+ return (queryProps.Position | f.Input.Properties()) & (queryProps.Reverse | queryProps.Merge)
+}
+
// functionQuery is an XPath function that returns a computed value for
// the Evaluate call of the current NodeNavigator node. Select call isn't
// applicable for functionQuery.
@@ -624,6 +853,14 @@ func (f *functionQuery) Clone() query {
return &functionQuery{Input: f.Input.Clone(), Func: f.Func}
}
+func (f *functionQuery) ValueType() resultType {
+ return xpathResultType.Any
+}
+
+func (f *functionQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
// transformFunctionQuery diffs from functionQuery where the latter computes a scalar
// value (number,string,boolean) for the current NodeNavigator node while the former
// (transformFunctionQuery) performs a mapping or transform of the current NodeNavigator
@@ -652,6 +889,14 @@ func (f *transformFunctionQuery) Clone() query {
return &transformFunctionQuery{Input: f.Input.Clone(), Func: f.Func}
}
+func (f *transformFunctionQuery) ValueType() resultType {
+ return xpathResultType.Any
+}
+
+func (f *transformFunctionQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
// constantQuery is an XPath constant operand.
type constantQuery struct {
Val interface{}
@@ -669,6 +914,14 @@ func (c *constantQuery) Clone() query {
return c
}
+func (c *constantQuery) ValueType() resultType {
+ return getXPathType(c.Val)
+}
+
+func (c *constantQuery) Properties() queryProp {
+ return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
+}
+
type groupQuery struct {
posit int
@@ -692,6 +945,14 @@ func (g *groupQuery) Clone() query {
return &groupQuery{Input: g.Input.Clone()}
}
+func (g *groupQuery) ValueType() resultType {
+ return g.Input.ValueType()
+}
+
+func (g *groupQuery) Properties() queryProp {
+ return queryProps.Position
+}
+
func (g *groupQuery) position() int {
return g.posit
}
@@ -726,6 +987,14 @@ func (l *logicalQuery) Clone() query {
return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do}
}
+func (l *logicalQuery) ValueType() resultType {
+ return xpathResultType.Boolean
+}
+
+func (l *logicalQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
// numericQuery is an XPath numeric operator expression.
type numericQuery struct {
Left, Right query
@@ -747,6 +1016,14 @@ func (n *numericQuery) Clone() query {
return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do}
}
+func (n *numericQuery) ValueType() resultType {
+ return xpathResultType.Number
+}
+
+func (n *numericQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
type booleanQuery struct {
IsOr bool
Left, Right query
@@ -837,6 +1114,14 @@ func (b *booleanQuery) Clone() query {
return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()}
}
+func (b *booleanQuery) ValueType() resultType {
+ return xpathResultType.Boolean
+}
+
+func (b *booleanQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
type unionQuery struct {
Left, Right query
iterator func() NodeNavigator
@@ -894,6 +1179,14 @@ func (u *unionQuery) Clone() query {
return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
}
+func (u *unionQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (u *unionQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
type lastQuery struct {
buffer []NodeNavigator
counted bool
@@ -923,6 +1216,147 @@ func (q *lastQuery) Clone() query {
return &lastQuery{Input: q.Input.Clone()}
}
+func (q *lastQuery) ValueType() resultType {
+ return xpathResultType.Number
+}
+
+func (q *lastQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
+type descendantOverDescendantQuery struct {
+ name string
+ level int
+ posit int
+ currentNode NodeNavigator
+
+ Input query
+ MatchSelf bool
+ Predicate func(NodeNavigator) bool
+}
+
+func (d *descendantOverDescendantQuery) moveToFirstChild() bool {
+ if d.currentNode.MoveToChild() {
+ d.level++
+ return true
+ }
+ return false
+}
+
+func (d *descendantOverDescendantQuery) moveUpUntilNext() bool {
+ for !d.currentNode.MoveToNext() {
+ d.level--
+ if d.level == 0 {
+ return false
+ }
+ d.currentNode.MoveToParent()
+ }
+ return true
+}
+
+func (d *descendantOverDescendantQuery) Select(t iterator) NodeNavigator {
+ for {
+ if d.level == 0 {
+ node := d.Input.Select(t)
+ if node == nil {
+ return nil
+ }
+ d.currentNode = node.Copy()
+ d.posit = 0
+ if d.MatchSelf && d.Predicate(d.currentNode) {
+ d.posit = 1
+ return d.currentNode
+ }
+ d.moveToFirstChild()
+ } else if !d.moveUpUntilNext() {
+ continue
+ }
+ for ok := true; ok; ok = d.moveToFirstChild() {
+ if d.Predicate(d.currentNode) {
+ d.posit++
+ return d.currentNode
+ }
+ }
+ }
+}
+
+func (d *descendantOverDescendantQuery) Evaluate(t iterator) interface{} {
+ d.Input.Evaluate(t)
+ return d
+}
+
+func (d *descendantOverDescendantQuery) Clone() query {
+ return &descendantOverDescendantQuery{Input: d.Input.Clone(), Predicate: d.Predicate, MatchSelf: d.MatchSelf}
+}
+
+func (d *descendantOverDescendantQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (d *descendantOverDescendantQuery) Properties() queryProp {
+ return queryProps.Merge
+}
+
+func (d *descendantOverDescendantQuery) position() int {
+ return d.posit
+}
+
+type mergeQuery struct {
+ Input query
+ Child query
+
+ iterator func() NodeNavigator
+}
+
+func (m *mergeQuery) Select(t iterator) NodeNavigator {
+ for {
+ if m.iterator == nil {
+ root := m.Input.Select(t)
+ if root == nil {
+ return nil
+ }
+ m.Child.Evaluate(t)
+ root = root.Copy()
+ t.Current().MoveTo(root)
+ var list []NodeNavigator
+ for node := m.Child.Select(t); node != nil; node = m.Child.Select(t) {
+ list = append(list, node.Copy())
+ }
+ i := 0
+ m.iterator = func() NodeNavigator {
+ if i >= len(list) {
+ return nil
+ }
+ result := list[i]
+ i++
+ return result
+ }
+ }
+
+ if node := m.iterator(); node != nil {
+ return node
+ }
+ m.iterator = nil
+ }
+}
+
+func (m *mergeQuery) Evaluate(t iterator) interface{} {
+ m.Input.Evaluate(t)
+ return m
+}
+
+func (m *mergeQuery) Clone() query {
+ return &mergeQuery{Input: m.Input.Clone(), Child: m.Child.Clone()}
+}
+
+func (m *mergeQuery) ValueType() resultType {
+ return xpathResultType.NodeSet
+}
+
+func (m *mergeQuery) Properties() queryProp {
+ return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
+}
+
func getHashCode(n NodeNavigator) uint64 {
var sb bytes.Buffer
switch n.NodeType() {
@@ -958,7 +1392,7 @@ func getHashCode(n NodeNavigator) uint64 {
}
}
h := fnv.New64a()
- h.Write([]byte(sb.String()))
+ h.Write(sb.Bytes())
return h.Sum64()
}
@@ -981,3 +1415,20 @@ func getNodeDepth(q query) int {
}
return 0
}
+
+func getXPathType(i interface{}) resultType {
+ v := reflect.ValueOf(i)
+ switch v.Kind() {
+ case reflect.Float64:
+ return xpathResultType.Number
+ case reflect.String:
+ return xpathResultType.String
+ case reflect.Bool:
+ return xpathResultType.Boolean
+ default:
+ if _, ok := i.(query); ok {
+ return xpathResultType.NodeSet
+ }
+ }
+ panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
+}
diff --git a/vendor/github.com/antchfx/xpath/xpath.go b/vendor/github.com/antchfx/xpath/xpath.go
index 1c0a5a2..aa27370 100644
--- a/vendor/github.com/antchfx/xpath/xpath.go
+++ b/vendor/github.com/antchfx/xpath/xpath.go
@@ -74,6 +74,7 @@ type NodeNavigator interface {
type NodeIterator struct {
node NodeNavigator
query query
+ table map[uint64]bool
}
// Current returns current node which matched.
@@ -83,14 +84,22 @@ func (t *NodeIterator) Current() NodeNavigator {
// MoveNext moves Navigator to the next match node.
func (t *NodeIterator) MoveNext() bool {
- n := t.query.Select(t)
- if n != nil {
+ for {
+ n := t.query.Select(t)
+ if n == nil {
+ return false
+ }
if !t.node.MoveTo(n) {
t.node = n.Copy()
}
+ // https://github.com/antchfx/xpath/issues/94
+ id := getHashCode(n.Copy())
+ if _, ok := t.table[id]; ok {
+ continue
+ }
+ t.table[id] = true
return true
}
- return false
}
// Select selects a node set using the specified XPath expression.
@@ -121,14 +130,14 @@ func (expr *Expr) Evaluate(root NodeNavigator) interface{} {
val := expr.q.Evaluate(iteratorFunc(func() NodeNavigator { return root }))
switch val.(type) {
case query:
- return &NodeIterator{query: expr.q.Clone(), node: root}
+ return &NodeIterator{query: expr.q.Clone(), node: root, table: make(map[uint64]bool)}
}
return val
}
// Select selects a node set using the specified XPath expression.
func (expr *Expr) Select(root NodeNavigator) *NodeIterator {
- return &NodeIterator{query: expr.q.Clone(), node: root}
+ return &NodeIterator{query: expr.q.Clone(), node: root, table: make(map[uint64]bool)}
}
// String returns XPath expression string.
diff --git a/vendor/github.com/labstack/echo/v4/CHANGELOG.md b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
index cc17e28..de3857f 100644
--- a/vendor/github.com/labstack/echo/v4/CHANGELOG.md
+++ b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## v4.12.0 - 2024-04-15
+
+**Security**
+
+* Update golang.org/x/net dep because of [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687) by @aldas in https://github.com/labstack/echo/pull/2625
+
+
+**Enhancements**
+
+* binder: make binding to Map work better with string destinations by @aldas in https://github.com/labstack/echo/pull/2554
+* README.md: add Encore as sponsor by @marcuskohlberg in https://github.com/labstack/echo/pull/2579
+* Reorder paragraphs in README.md by @aldas in https://github.com/labstack/echo/pull/2581
+* CI: upgrade actions/checkout to v4 by @aldas in https://github.com/labstack/echo/pull/2584
+* Remove default charset from 'application/json' Content-Type header by @doortts in https://github.com/labstack/echo/pull/2568
+* CI: Use Go 1.22 by @aldas in https://github.com/labstack/echo/pull/2588
+* binder: allow binding to a nil map by @georgmu in https://github.com/labstack/echo/pull/2574
+* Add Skipper Unit Test In BasicBasicAuthConfig and Add More Detail Explanation regarding BasicAuthValidator by @RyoKusnadi in https://github.com/labstack/echo/pull/2461
+* fix some typos by @teslaedison in https://github.com/labstack/echo/pull/2603
+* fix: some typos by @pomadev in https://github.com/labstack/echo/pull/2596
+* Allow ResponseWriters to unwrap writers when flushing/hijacking by @aldas in https://github.com/labstack/echo/pull/2595
+* Add SPDX licence comments to files. by @aldas in https://github.com/labstack/echo/pull/2604
+* Upgrade deps by @aldas in https://github.com/labstack/echo/pull/2605
+* Change type definition blocks to single declarations. This helps copy… by @aldas in https://github.com/labstack/echo/pull/2606
+* Fix Real IP logic by @cl-bvl in https://github.com/labstack/echo/pull/2550
+* Default binder can use `UnmarshalParams(params []string) error` inter… by @aldas in https://github.com/labstack/echo/pull/2607
+* Default binder can bind pointer to slice as struct field. For example `*[]string` by @aldas in https://github.com/labstack/echo/pull/2608
+* Remove maxparam dependence from Context by @aldas in https://github.com/labstack/echo/pull/2611
+* When route is registered with empty path it is normalized to `/`. by @aldas in https://github.com/labstack/echo/pull/2616
+* proxy middleware should use httputil.ReverseProxy for SSE requests by @aldas in https://github.com/labstack/echo/pull/2624
+
+
## v4.11.4 - 2023-12-20
**Security**
diff --git a/vendor/github.com/labstack/echo/v4/Makefile b/vendor/github.com/labstack/echo/v4/Makefile
index 6aff6a8..f9e5afb 100644
--- a/vendor/github.com/labstack/echo/v4/Makefile
+++ b/vendor/github.com/labstack/echo/v4/Makefile
@@ -31,6 +31,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.17"
-test_version: ## Run tests inside Docker with given version (defaults to 1.17 oldest supported). Example: make test_version goversion=1.17
+goversion ?= "1.19"
+test_version: ## Run tests inside Docker with given version (defaults to 1.19 oldest supported). Example: make test_version goversion=1.19
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"
diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md
index 18accea..351ba3c 100644
--- a/vendor/github.com/labstack/echo/v4/README.md
+++ b/vendor/github.com/labstack/echo/v4/README.md
@@ -9,20 +9,18 @@
[![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
+## Echo
-Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with
-older versions.
+High performance, extensible, minimalist Go web framework.
-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:
+* [Official website](https://echo.labstack.com)
+* [Quick start](https://echo.labstack.com/docs/quick-start)
+* [Middlewares](https://echo.labstack.com/docs/category/middleware)
-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.
+Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions)
-For older versions, please use the latest v3 tag.
-## Feature Overview
+### Feature Overview
- Optimized HTTP router which smartly prioritize routes
- Build robust and scalable RESTful APIs
@@ -38,6 +36,18 @@ For older versions, please use the latest v3 tag.
- Automatic TLS via Let’s Encrypt
- HTTP/2 support
+## Sponsors
+
+
+
+
+Click [here](https://github.com/sponsors/labstack) for more information on sponsorship.
+
## Benchmarks
Date: 2020/11/11
@@ -57,6 +67,7 @@ The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
// go get github.com/labstack/echo/{version}
go get github.com/labstack/echo/v4
```
+Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
### Example
@@ -117,10 +128,6 @@ of middlewares in this list.
Please send a PR to add your own library here.
-## Help
-
-- [Forum](https://github.com/labstack/echo/discussions)
-
## Contribute
**Use issues for everything**
diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go
index 374a2ae..507def3 100644
--- a/vendor/github.com/labstack/echo/v4/bind.go
+++ b/vendor/github.com/labstack/echo/v4/bind.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
@@ -11,23 +14,28 @@ import (
"strings"
)
-type (
- // Binder is the interface that wraps the Bind method.
- Binder interface {
- Bind(i interface{}, c Context) error
- }
+// Binder is the interface that wraps the Bind method.
+type Binder interface {
+ Bind(i interface{}, c Context) error
+}
- // DefaultBinder is the default implementation of the Binder interface.
- DefaultBinder struct{}
+// DefaultBinder is the default implementation of the Binder interface.
+type DefaultBinder struct{}
- // BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
- // Types that don't implement this, but do implement encoding.TextUnmarshaler
- // will use that interface instead.
- BindUnmarshaler interface {
- // UnmarshalParam decodes and assigns a value from an form or query param.
- UnmarshalParam(param string) error
- }
-)
+// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
+// Types that don't implement this, but do implement encoding.TextUnmarshaler
+// will use that interface instead.
+type BindUnmarshaler interface {
+ // UnmarshalParam decodes and assigns a value from an form or query param.
+ UnmarshalParam(param string) error
+}
+
+// bindMultipleUnmarshaler is used by binder to unmarshal multiple values from request at once to
+// type implementing this interface. For example request could have multiple query fields `?a=1&a=2&b=test` in that case
+// for `a` following slice `["1", "2"] will be passed to unmarshaller.
+type bindMultipleUnmarshaler interface {
+ UnmarshalParams(params []string) error
+}
// BindPathParams binds path params to bindable object
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
@@ -131,10 +139,29 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
typ := reflect.TypeOf(destination).Elem()
val := reflect.ValueOf(destination).Elem()
- // Map
- if typ.Kind() == reflect.Map {
+ // Support binding to limited Map destinations:
+ // - map[string][]string,
+ // - map[string]string <-- (binds first value from data slice)
+ // - map[string]interface{}
+ // You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
+ // params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
+ if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
+ k := typ.Elem().Kind()
+ isElemInterface := k == reflect.Interface
+ isElemString := k == reflect.String
+ isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
+ if !(isElemSliceOfStrings || isElemString || isElemInterface) {
+ return nil
+ }
+ if val.IsNil() {
+ val.Set(reflect.MakeMap(typ))
+ }
for k, v := range data {
- val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
+ if isElemString {
+ val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
+ } else {
+ val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
+ }
}
return nil
}
@@ -161,14 +188,14 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
}
structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag)
- if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" {
+ if typeField.Anonymous && structFieldKind == reflect.Struct && inputFieldName != "" {
// if anonymous struct with query/param/form tags, report an error
return errors.New("query/param/form tags are not allowed with anonymous struct field")
}
if inputFieldName == "" {
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
- // structs that implement BindUnmarshaler are binded only when they have explicit tag
+ // structs that implement BindUnmarshaler are bound only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
@@ -197,27 +224,46 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
continue
}
- // Call this first, in case we're dealing with an alias to an array type
- if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
+ // NOTE: algorithm here is not particularly sophisticated. It probably does not work with absurd types like `**[]*int`
+ // but it is smart enough to handle niche cases like `*int`,`*[]string`,`[]*int` .
+
+ // try unmarshalling first, in case we're dealing with an alias to an array type
+ if ok, err := unmarshalInputsToField(typeField.Type.Kind(), inputValue, structField); ok {
if err != nil {
return err
}
continue
}
- numElems := len(inputValue)
- if structFieldKind == reflect.Slice && numElems > 0 {
+ if ok, err := unmarshalInputToField(typeField.Type.Kind(), inputValue[0], structField); ok {
+ if err != nil {
+ return err
+ }
+ continue
+ }
+
+ // we could be dealing with pointer to slice `*[]string` so dereference it. There are wierd OpenAPI generators
+ // that could create struct fields like that.
+ if structFieldKind == reflect.Pointer {
+ structFieldKind = structField.Elem().Kind()
+ structField = structField.Elem()
+ }
+
+ if structFieldKind == reflect.Slice {
sliceOf := structField.Type().Elem().Kind()
+ numElems := len(inputValue)
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := 0; j < numElems; j++ {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err
}
}
- val.Field(i).Set(slice)
- } else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
- return err
+ structField.Set(slice)
+ continue
+ }
+ if err := setWithProperType(structFieldKind, inputValue[0], structField); err != nil {
+ return err
}
}
return nil
@@ -225,7 +271,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
// But also call it here, in case we're dealing with an array of BindUnmarshalers
- if ok, err := unmarshalField(valueKind, val, structField); ok {
+ if ok, err := unmarshalInputToField(valueKind, val, structField); ok {
return err
}
@@ -266,35 +312,41 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
return nil
}
-func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
- switch valueKind {
- case reflect.Ptr:
- return unmarshalFieldPtr(val, field)
- default:
- return unmarshalFieldNonPtr(val, field)
+func unmarshalInputsToField(valueKind reflect.Kind, values []string, field reflect.Value) (bool, error) {
+ if valueKind == reflect.Ptr {
+ if field.IsNil() {
+ field.Set(reflect.New(field.Type().Elem()))
+ }
+ field = field.Elem()
}
+
+ fieldIValue := field.Addr().Interface()
+ unmarshaler, ok := fieldIValue.(bindMultipleUnmarshaler)
+ if !ok {
+ return false, nil
+ }
+ return true, unmarshaler.UnmarshalParams(values)
}
-func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
- fieldIValue := field.Addr().Interface()
- if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
- return true, unmarshaler.UnmarshalParam(value)
+func unmarshalInputToField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
+ if valueKind == reflect.Ptr {
+ if field.IsNil() {
+ field.Set(reflect.New(field.Type().Elem()))
+ }
+ field = field.Elem()
}
- if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
- return true, unmarshaler.UnmarshalText([]byte(value))
+
+ fieldIValue := field.Addr().Interface()
+ switch unmarshaler := fieldIValue.(type) {
+ case BindUnmarshaler:
+ return true, unmarshaler.UnmarshalParam(val)
+ case encoding.TextUnmarshaler:
+ return true, unmarshaler.UnmarshalText([]byte(val))
}
return false, nil
}
-func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
- if field.IsNil() {
- // Initialize the pointer to a nil value
- field.Set(reflect.New(field.Type().Elem()))
- }
- return unmarshalFieldNonPtr(value, field.Elem())
-}
-
func setIntField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
diff --git a/vendor/github.com/labstack/echo/v4/binder.go b/vendor/github.com/labstack/echo/v4/binder.go
index 8e7b814..ebabeaf 100644
--- a/vendor/github.com/labstack/echo/v4/binder.go
+++ b/vendor/github.com/labstack/echo/v4/binder.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
diff --git a/vendor/github.com/labstack/echo/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go
index 6a18116..4edaa2e 100644
--- a/vendor/github.com/labstack/echo/v4/context.go
+++ b/vendor/github.com/labstack/echo/v4/context.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
@@ -13,204 +16,216 @@ import (
"sync"
)
-type (
- // Context represents the context of the current HTTP request. It holds request and
- // response objects, path, path parameters, data and registered handler.
- Context interface {
- // Request returns `*http.Request`.
- Request() *http.Request
+// Context represents the context of the current HTTP request. It holds request and
+// response objects, path, path parameters, data and registered handler.
+type Context interface {
+ // Request returns `*http.Request`.
+ Request() *http.Request
- // SetRequest sets `*http.Request`.
- SetRequest(r *http.Request)
+ // SetRequest sets `*http.Request`.
+ SetRequest(r *http.Request)
- // SetResponse sets `*Response`.
- SetResponse(r *Response)
+ // SetResponse sets `*Response`.
+ SetResponse(r *Response)
- // Response returns `*Response`.
- Response() *Response
+ // Response returns `*Response`.
+ Response() *Response
- // IsTLS returns true if HTTP connection is TLS otherwise false.
- IsTLS() bool
+ // IsTLS returns true if HTTP connection is TLS otherwise false.
+ IsTLS() bool
- // IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
- IsWebSocket() bool
+ // IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
+ IsWebSocket() bool
- // Scheme returns the HTTP protocol scheme, `http` or `https`.
- Scheme() string
+ // Scheme returns the HTTP protocol scheme, `http` or `https`.
+ Scheme() string
- // RealIP returns the client's network address based on `X-Forwarded-For`
- // or `X-Real-IP` request header.
- // The behavior can be configured using `Echo#IPExtractor`.
- RealIP() string
+ // RealIP returns the client's network address based on `X-Forwarded-For`
+ // or `X-Real-IP` request header.
+ // The behavior can be configured using `Echo#IPExtractor`.
+ RealIP() string
- // Path returns the registered path for the handler.
- Path() string
+ // Path returns the registered path for the handler.
+ Path() string
- // SetPath sets the registered path for the handler.
- SetPath(p string)
+ // SetPath sets the registered path for the handler.
+ SetPath(p string)
- // Param returns path parameter by name.
- Param(name string) string
+ // Param returns path parameter by name.
+ Param(name string) string
- // ParamNames returns path parameter names.
- ParamNames() []string
+ // ParamNames returns path parameter names.
+ ParamNames() []string
- // SetParamNames sets path parameter names.
- SetParamNames(names ...string)
+ // SetParamNames sets path parameter names.
+ SetParamNames(names ...string)
- // ParamValues returns path parameter values.
- ParamValues() []string
+ // ParamValues returns path parameter values.
+ ParamValues() []string
- // SetParamValues sets path parameter values.
- SetParamValues(values ...string)
+ // SetParamValues sets path parameter values.
+ SetParamValues(values ...string)
- // QueryParam returns the query param for the provided name.
- QueryParam(name string) string
+ // QueryParam returns the query param for the provided name.
+ QueryParam(name string) string
- // QueryParams returns the query parameters as `url.Values`.
- QueryParams() url.Values
+ // QueryParams returns the query parameters as `url.Values`.
+ QueryParams() url.Values
- // QueryString returns the URL query string.
- QueryString() string
+ // QueryString returns the URL query string.
+ QueryString() string
- // FormValue returns the form field value for the provided name.
- FormValue(name string) string
+ // FormValue returns the form field value for the provided name.
+ FormValue(name string) string
- // FormParams returns the form parameters as `url.Values`.
- FormParams() (url.Values, error)
+ // FormParams returns the form parameters as `url.Values`.
+ FormParams() (url.Values, error)
- // FormFile returns the multipart form file for the provided name.
- FormFile(name string) (*multipart.FileHeader, error)
+ // FormFile returns the multipart form file for the provided name.
+ FormFile(name string) (*multipart.FileHeader, error)
- // MultipartForm returns the multipart form.
- MultipartForm() (*multipart.Form, error)
+ // MultipartForm returns the multipart form.
+ MultipartForm() (*multipart.Form, error)
- // Cookie returns the named cookie provided in the request.
- Cookie(name string) (*http.Cookie, error)
+ // Cookie returns the named cookie provided in the request.
+ Cookie(name string) (*http.Cookie, error)
- // SetCookie adds a `Set-Cookie` header in HTTP response.
- SetCookie(cookie *http.Cookie)
+ // SetCookie adds a `Set-Cookie` header in HTTP response.
+ SetCookie(cookie *http.Cookie)
- // Cookies returns the HTTP cookies sent with the request.
- Cookies() []*http.Cookie
+ // Cookies returns the HTTP cookies sent with the request.
+ Cookies() []*http.Cookie
- // Get retrieves data from the context.
- Get(key string) interface{}
+ // Get retrieves data from the context.
+ Get(key string) interface{}
- // Set saves data in the context.
- Set(key string, val interface{})
+ // Set saves data in the context.
+ Set(key string, val interface{})
- // Bind binds path params, query params and the request body into provided type `i`. The default binder
- // binds body based on Content-Type header.
- Bind(i interface{}) error
+ // Bind binds path params, query params and the request body into provided type `i`. The default binder
+ // binds body based on Content-Type header.
+ Bind(i interface{}) error
- // Validate validates provided `i`. It is usually called after `Context#Bind()`.
- // Validator must be registered using `Echo#Validator`.
- Validate(i interface{}) error
+ // Validate validates provided `i`. It is usually called after `Context#Bind()`.
+ // Validator must be registered using `Echo#Validator`.
+ Validate(i interface{}) error
- // Render renders a template with data and sends a text/html response with status
- // code. Renderer must be registered using `Echo.Renderer`.
- Render(code int, name string, data interface{}) error
+ // Render renders a template with data and sends a text/html response with status
+ // code. Renderer must be registered using `Echo.Renderer`.
+ Render(code int, name string, data interface{}) error
- // HTML sends an HTTP response with status code.
- HTML(code int, html string) error
+ // HTML sends an HTTP response with status code.
+ HTML(code int, html string) error
- // HTMLBlob sends an HTTP blob response with status code.
- HTMLBlob(code int, b []byte) error
+ // HTMLBlob sends an HTTP blob response with status code.
+ HTMLBlob(code int, b []byte) error
- // String sends a string response with status code.
- String(code int, s string) error
+ // String sends a string response with status code.
+ String(code int, s string) error
- // JSON sends a JSON response with status code.
- JSON(code int, i interface{}) error
+ // JSON sends a JSON response with status code.
+ JSON(code int, i interface{}) error
- // JSONPretty sends a pretty-print JSON with status code.
- JSONPretty(code int, i interface{}, indent string) error
+ // JSONPretty sends a pretty-print JSON with status code.
+ JSONPretty(code int, i interface{}, indent string) error
- // JSONBlob sends a JSON blob response with status code.
- JSONBlob(code int, b []byte) error
+ // JSONBlob sends a JSON blob response with status code.
+ JSONBlob(code int, b []byte) error
- // JSONP sends a JSONP response with status code. It uses `callback` to construct
- // the JSONP payload.
- JSONP(code int, callback string, i interface{}) error
+ // JSONP sends a JSONP response with status code. It uses `callback` to construct
+ // the JSONP payload.
+ JSONP(code int, callback string, i interface{}) error
- // JSONPBlob sends a JSONP blob response with status code. It uses `callback`
- // to construct the JSONP payload.
- JSONPBlob(code int, callback string, b []byte) error
+ // JSONPBlob sends a JSONP blob response with status code. It uses `callback`
+ // to construct the JSONP payload.
+ JSONPBlob(code int, callback string, b []byte) error
- // XML sends an XML response with status code.
- XML(code int, i interface{}) error
+ // XML sends an XML response with status code.
+ XML(code int, i interface{}) error
- // XMLPretty sends a pretty-print XML with status code.
- XMLPretty(code int, i interface{}, indent string) error
+ // XMLPretty sends a pretty-print XML with status code.
+ XMLPretty(code int, i interface{}, indent string) error
- // XMLBlob sends an XML blob response with status code.
- XMLBlob(code int, b []byte) error
+ // XMLBlob sends an XML blob response with status code.
+ XMLBlob(code int, b []byte) error
- // Blob sends a blob response with status code and content type.
- Blob(code int, contentType string, b []byte) error
+ // Blob sends a blob response with status code and content type.
+ Blob(code int, contentType string, b []byte) error
- // Stream sends a streaming response with status code and content type.
- Stream(code int, contentType string, r io.Reader) error
+ // Stream sends a streaming response with status code and content type.
+ Stream(code int, contentType string, r io.Reader) error
- // File sends a response with the content of the file.
- File(file string) error
+ // File sends a response with the content of the file.
+ File(file string) error
- // Attachment sends a response as attachment, prompting client to save the
- // file.
- Attachment(file string, name string) error
+ // Attachment sends a response as attachment, prompting client to save the
+ // file.
+ Attachment(file string, name string) error
- // Inline sends a response as inline, opening the file in the browser.
- Inline(file string, name string) error
+ // Inline sends a response as inline, opening the file in the browser.
+ Inline(file string, name string) error
- // NoContent sends a response with no body and a status code.
- NoContent(code int) error
+ // NoContent sends a response with no body and a status code.
+ NoContent(code int) error
- // Redirect redirects the request to a provided URL with status code.
- Redirect(code int, url string) error
+ // Redirect redirects the request to a provided URL with status code.
+ Redirect(code int, url string) error
- // Error invokes the registered global HTTP error handler. Generally used by middleware.
- // A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
- // middlewares up in chain can not change Response status code or Response body anymore.
- //
- // Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
- Error(err error)
+ // Error invokes the registered global HTTP error handler. Generally used by middleware.
+ // A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
+ // middlewares up in chain can not change Response status code or Response body anymore.
+ //
+ // Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
+ Error(err error)
- // Handler returns the matched handler by router.
- Handler() HandlerFunc
+ // Handler returns the matched handler by router.
+ Handler() HandlerFunc
- // SetHandler sets the matched handler by router.
- SetHandler(h HandlerFunc)
+ // SetHandler sets the matched handler by router.
+ SetHandler(h HandlerFunc)
- // Logger returns the `Logger` instance.
- Logger() Logger
+ // Logger returns the `Logger` instance.
+ Logger() Logger
- // SetLogger Set the logger
- SetLogger(l Logger)
+ // SetLogger Set the logger
+ SetLogger(l Logger)
- // Echo returns the `Echo` instance.
- Echo() *Echo
+ // Echo returns the `Echo` instance.
+ Echo() *Echo
- // Reset resets the context after request completes. It must be called along
- // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
- // See `Echo#ServeHTTP()`
- Reset(r *http.Request, w http.ResponseWriter)
- }
+ // Reset resets the context after request completes. It must be called along
+ // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
+ // See `Echo#ServeHTTP()`
+ Reset(r *http.Request, w http.ResponseWriter)
+}
- context struct {
- request *http.Request
- response *Response
- path string
- pnames []string
- pvalues []string
- query url.Values
- handler HandlerFunc
- store Map
- echo *Echo
- logger Logger
- lock sync.RWMutex
- }
-)
+type context struct {
+ request *http.Request
+ response *Response
+ query url.Values
+ echo *Echo
+ logger Logger
+
+ store Map
+ lock sync.RWMutex
+
+ // following fields are set by Router
+
+ // path is route path that Router matched. It is empty string where there is no route match.
+ // Route registered with RouteNotFound is considered as a match and path therefore is not empty.
+ path string
+
+ // pnames length is tied to param count for the matched route
+ pnames []string
+
+ // Usually echo.Echo is sizing pvalues but there could be user created middlewares that decide to
+ // overwrite parameter by calling SetParamNames + SetParamValues.
+ // When echo.Echo allocated that slice it length/capacity is tied to echo.Echo.maxParam value.
+ //
+ // It is important that pvalues size is always equal or bigger to pnames length.
+ pvalues []string
+ handler HandlerFunc
+}
const (
// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
@@ -329,13 +344,9 @@ func (c *context) SetParamNames(names ...string) {
c.pnames = names
l := len(names)
- if *c.echo.maxParam < l {
- *c.echo.maxParam = l
- }
-
if len(c.pvalues) < l {
// Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them,
- // probably those values will be overriden in a Context#SetParamValues
+ // probably those values will be overridden in a Context#SetParamValues
newPvalues := make([]string, l)
copy(newPvalues, c.pvalues)
c.pvalues = newPvalues
@@ -347,11 +358,11 @@ func (c *context) ParamValues() []string {
}
func (c *context) SetParamValues(values ...string) {
- // NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times
+ // NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam (or bigger) at all times
// It will brake the Router#Find code
limit := len(values)
- if limit > *c.echo.maxParam {
- limit = *c.echo.maxParam
+ if limit > len(c.pvalues) {
+ c.pvalues = make([]string, limit)
}
for i := 0; i < limit; i++ {
c.pvalues[i] = values[i]
@@ -489,7 +500,7 @@ func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error
}
func (c *context) json(code int, i interface{}, indent string) error {
- c.writeContentType(MIMEApplicationJSONCharsetUTF8)
+ c.writeContentType(MIMEApplicationJSON)
c.response.Status = code
return c.echo.JSONSerializer.Serialize(c, i, indent)
}
@@ -507,7 +518,7 @@ func (c *context) JSONPretty(code int, i interface{}, indent string) (err error)
}
func (c *context) JSONBlob(code int, b []byte) (err error) {
- return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
+ return c.Blob(code, MIMEApplicationJSON, b)
}
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
@@ -642,8 +653,8 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.path = ""
c.pnames = nil
c.logger = nil
- // NOTE: Don't reset because it has to have length c.echo.maxParam at all times
- for i := 0; i < *c.echo.maxParam; i++ {
+ // NOTE: Don't reset because it has to have length c.echo.maxParam (or bigger) at all times
+ for i := 0; i < len(c.pvalues); i++ {
c.pvalues[i] = ""
}
}
diff --git a/vendor/github.com/labstack/echo/v4/context_fs.go b/vendor/github.com/labstack/echo/v4/context_fs.go
index 1038f89..1c25baf 100644
--- a/vendor/github.com/labstack/echo/v4/context_fs.go
+++ b/vendor/github.com/labstack/echo/v4/context_fs.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go
index 9924ac8..ab66b0d 100644
--- a/vendor/github.com/labstack/echo/v4/echo.go
+++ b/vendor/github.com/labstack/echo/v4/echo.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
/*
Package echo implements high performance, minimalist Go web framework.
@@ -60,97 +63,95 @@ import (
"golang.org/x/net/http2/h2c"
)
-type (
- // Echo is the top-level framework instance.
- //
- // Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
- // fields from handlers/middlewares and changing field values at the same time leads to data-races.
- // Adding new routes after the server has been started is also not safe!
- 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.
- startupMutex sync.RWMutex
- colorer *color.Color
+// Echo is the top-level framework instance.
+//
+// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
+// fields from handlers/middlewares and changing field values at the same time leads to data-races.
+// Adding new routes after the server has been started is also not safe!
+type 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 bound) without having data races.
+ startupMutex sync.RWMutex
+ colorer *color.Color
- // premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns
- // an error the router is not executed and the request will end up in the global error handler.
- premiddleware []MiddlewareFunc
- middleware []MiddlewareFunc
- maxParam *int
- router *Router
- routers map[string]*Router
- pool sync.Pool
+ // premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns
+ // an error the router is not executed and the request will end up in the global error handler.
+ premiddleware []MiddlewareFunc
+ middleware []MiddlewareFunc
+ maxParam *int
+ router *Router
+ routers map[string]*Router
+ pool sync.Pool
- StdLogger *stdLog.Logger
- Server *http.Server
- TLSServer *http.Server
- Listener net.Listener
- TLSListener net.Listener
- AutoTLSManager autocert.Manager
- DisableHTTP2 bool
- Debug bool
- HideBanner bool
- HidePort bool
- HTTPErrorHandler HTTPErrorHandler
- Binder Binder
- JSONSerializer JSONSerializer
- Validator Validator
- Renderer Renderer
- Logger Logger
- IPExtractor IPExtractor
- ListenerNetwork string
+ StdLogger *stdLog.Logger
+ Server *http.Server
+ TLSServer *http.Server
+ Listener net.Listener
+ TLSListener net.Listener
+ AutoTLSManager autocert.Manager
+ DisableHTTP2 bool
+ Debug bool
+ HideBanner bool
+ HidePort bool
+ HTTPErrorHandler HTTPErrorHandler
+ Binder Binder
+ JSONSerializer JSONSerializer
+ Validator Validator
+ Renderer Renderer
+ Logger Logger
+ IPExtractor IPExtractor
+ ListenerNetwork string
- // OnAddRouteHandler is called when Echo adds new route to specific host router.
- OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc)
- }
+ // OnAddRouteHandler is called when Echo adds new route to specific host router.
+ OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc)
+}
- // Route contains a handler and information for matching against requests.
- Route struct {
- Method string `json:"method"`
- Path string `json:"path"`
- Name string `json:"name"`
- }
+// Route contains a handler and information for matching against requests.
+type Route struct {
+ Method string `json:"method"`
+ Path string `json:"path"`
+ Name string `json:"name"`
+}
- // HTTPError represents an error that occurred while handling a request.
- HTTPError struct {
- Code int `json:"-"`
- Message interface{} `json:"message"`
- Internal error `json:"-"` // Stores the error returned by an external dependency
- }
+// HTTPError represents an error that occurred while handling a request.
+type HTTPError struct {
+ Code int `json:"-"`
+ Message interface{} `json:"message"`
+ Internal error `json:"-"` // Stores the error returned by an external dependency
+}
- // MiddlewareFunc defines a function to process middleware.
- MiddlewareFunc func(next HandlerFunc) HandlerFunc
+// MiddlewareFunc defines a function to process middleware.
+type MiddlewareFunc func(next HandlerFunc) HandlerFunc
- // HandlerFunc defines a function to serve HTTP requests.
- HandlerFunc func(c Context) error
+// HandlerFunc defines a function to serve HTTP requests.
+type HandlerFunc func(c Context) error
- // HTTPErrorHandler is a centralized HTTP error handler.
- HTTPErrorHandler func(err error, c Context)
+// HTTPErrorHandler is a centralized HTTP error handler.
+type HTTPErrorHandler func(err error, c Context)
- // Validator is the interface that wraps the Validate function.
- Validator interface {
- Validate(i interface{}) error
- }
+// Validator is the interface that wraps the Validate function.
+type Validator interface {
+ Validate(i interface{}) error
+}
- // JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
- JSONSerializer interface {
- Serialize(c Context, i interface{}, indent string) error
- Deserialize(c Context, i interface{}) error
- }
+// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
+type JSONSerializer interface {
+ Serialize(c Context, i interface{}, indent string) error
+ Deserialize(c Context, i interface{}) error
+}
- // Renderer is the interface that wraps the Render function.
- Renderer interface {
- Render(io.Writer, string, interface{}, Context) error
- }
+// Renderer is the interface that wraps the Render function.
+type Renderer interface {
+ Render(io.Writer, string, interface{}, Context) error
+}
- // Map defines a generic map of type `map[string]interface{}`.
- Map map[string]interface{}
+// Map defines a generic map of type `map[string]interface{}`.
+type Map map[string]interface{}
- // Common struct for Echo & Group.
- common struct{}
-)
+// Common struct for Echo & Group.
+type common struct{}
// HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead.
@@ -169,7 +170,12 @@ const (
// MIME types
const (
- MIMEApplicationJSON = "application/json"
+ // MIMEApplicationJSON JavaScript Object Notation (JSON) https://www.rfc-editor.org/rfc/rfc8259
+ MIMEApplicationJSON = "application/json"
+ // Deprecated: Please use MIMEApplicationJSON instead. JSON should be encoded using UTF-8 by default.
+ // No "charset" parameter is defined for this registration.
+ // Adding one really has no effect on compliant recipients.
+ // See RFC 8259, section 8.1. https://datatracker.ietf.org/doc/html/rfc8259#section-8.1
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
@@ -259,7 +265,7 @@ const (
const (
// Version of Echo
- Version = "4.11.4"
+ Version = "4.12.0"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
@@ -274,21 +280,19 @@ ____________________________________O/_______
`
)
-var (
- methods = [...]string{
- http.MethodConnect,
- http.MethodDelete,
- http.MethodGet,
- http.MethodHead,
- http.MethodOptions,
- http.MethodPatch,
- http.MethodPost,
- PROPFIND,
- http.MethodPut,
- http.MethodTrace,
- REPORT,
- }
-)
+var methods = [...]string{
+ http.MethodConnect,
+ http.MethodDelete,
+ http.MethodGet,
+ http.MethodHead,
+ http.MethodOptions,
+ http.MethodPatch,
+ http.MethodPost,
+ PROPFIND,
+ http.MethodPut,
+ http.MethodTrace,
+ REPORT,
+}
// Errors
var (
@@ -341,22 +345,23 @@ var (
ErrInvalidListenerNetwork = errors.New("invalid listener network")
)
-// Error handlers
-var (
- NotFoundHandler = func(c Context) error {
- return ErrNotFound
- }
+// NotFoundHandler is the handler that router uses in case there was no matching route found. Returns an error that results
+// HTTP 404 status code.
+var NotFoundHandler = func(c Context) error {
+ return ErrNotFound
+}
- 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
+// MethodNotAllowedHandler is the handler thar router uses in case there was no matching route found but there was
+// another matching routes for that requested URL. Returns an error that results HTTP 405 Method Not Allowed status code.
+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
+}
// New creates an instance of Echo.
func New() (e *Echo) {
@@ -414,7 +419,7 @@ func (e *Echo) Routers() map[string]*Router {
//
// 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
+// handler. Then the error that global error handler received will be ignored because we have already "committed" the
// response and status code header has been sent to the client.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
diff --git a/vendor/github.com/labstack/echo/v4/echo_fs.go b/vendor/github.com/labstack/echo/v4/echo_fs.go
index 9f83a03..a7b231f 100644
--- a/vendor/github.com/labstack/echo/v4/echo_fs.go
+++ b/vendor/github.com/labstack/echo/v4/echo_fs.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
diff --git a/vendor/github.com/labstack/echo/v4/group.go b/vendor/github.com/labstack/echo/v4/group.go
index 749a5ca..eca25c9 100644
--- a/vendor/github.com/labstack/echo/v4/group.go
+++ b/vendor/github.com/labstack/echo/v4/group.go
@@ -1,21 +1,22 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
"net/http"
)
-type (
- // Group is a set of sub-routes for a specified route. It can be used for inner
- // routes that share a common middleware or functionality that should be separate
- // from the parent echo instance while still inheriting from it.
- Group struct {
- common
- host string
- prefix string
- middleware []MiddlewareFunc
- echo *Echo
- }
-)
+// Group is a set of sub-routes for a specified route. It can be used for inner
+// routes that share a common middleware or functionality that should be separate
+// from the parent echo instance while still inheriting from it.
+type Group struct {
+ common
+ host string
+ prefix string
+ middleware []MiddlewareFunc
+ echo *Echo
+}
// Use implements `Echo#Use()` for sub-routes within the Group.
func (g *Group) Use(middleware ...MiddlewareFunc) {
diff --git a/vendor/github.com/labstack/echo/v4/group_fs.go b/vendor/github.com/labstack/echo/v4/group_fs.go
index aedc4c6..c1b7ec2 100644
--- a/vendor/github.com/labstack/echo/v4/group_fs.go
+++ b/vendor/github.com/labstack/echo/v4/group_fs.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
diff --git a/vendor/github.com/labstack/echo/v4/ip.go b/vendor/github.com/labstack/echo/v4/ip.go
index 1bcd756..6aed8d6 100644
--- a/vendor/github.com/labstack/echo/v4/ip.go
+++ b/vendor/github.com/labstack/echo/v4/ip.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
@@ -64,7 +67,7 @@ XFF: "x" "x, a" "x, a, b"
```
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".
+configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure".
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)`.
@@ -225,15 +228,21 @@ func extractIP(req *http.Request) string {
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
+ directIP := extractIP(req)
realIP := req.Header.Get(HeaderXRealIP)
- if realIP != "" {
+ if realIP == "" {
+ return directIP
+ }
+
+ if checker.trust(net.ParseIP(directIP)) {
realIP = strings.TrimPrefix(realIP, "[")
realIP = strings.TrimSuffix(realIP, "]")
- if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) {
+ if rIP := net.ParseIP(realIP); rIP != nil {
return realIP
}
}
- return extractIP(req)
+
+ return directIP
}
}
diff --git a/vendor/github.com/labstack/echo/v4/json.go b/vendor/github.com/labstack/echo/v4/json.go
index 16b2d05..6da0aaf 100644
--- a/vendor/github.com/labstack/echo/v4/json.go
+++ b/vendor/github.com/labstack/echo/v4/json.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
diff --git a/vendor/github.com/labstack/echo/v4/log.go b/vendor/github.com/labstack/echo/v4/log.go
index 3f8de59..0acd9ff 100644
--- a/vendor/github.com/labstack/echo/v4/log.go
+++ b/vendor/github.com/labstack/echo/v4/log.go
@@ -1,41 +1,41 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
- "io"
-
"github.com/labstack/gommon/log"
+ "io"
)
-type (
- // Logger defines the logging interface.
- Logger interface {
- Output() io.Writer
- SetOutput(w io.Writer)
- Prefix() string
- SetPrefix(p string)
- Level() log.Lvl
- SetLevel(v log.Lvl)
- SetHeader(h string)
- Print(i ...interface{})
- Printf(format string, args ...interface{})
- Printj(j log.JSON)
- Debug(i ...interface{})
- Debugf(format string, args ...interface{})
- Debugj(j log.JSON)
- Info(i ...interface{})
- Infof(format string, args ...interface{})
- Infoj(j log.JSON)
- Warn(i ...interface{})
- Warnf(format string, args ...interface{})
- Warnj(j log.JSON)
- Error(i ...interface{})
- Errorf(format string, args ...interface{})
- Errorj(j log.JSON)
- Fatal(i ...interface{})
- Fatalj(j log.JSON)
- Fatalf(format string, args ...interface{})
- Panic(i ...interface{})
- Panicj(j log.JSON)
- Panicf(format string, args ...interface{})
- }
-)
+// Logger defines the logging interface.
+type Logger interface {
+ Output() io.Writer
+ SetOutput(w io.Writer)
+ Prefix() string
+ SetPrefix(p string)
+ Level() log.Lvl
+ SetLevel(v log.Lvl)
+ SetHeader(h string)
+ Print(i ...interface{})
+ Printf(format string, args ...interface{})
+ Printj(j log.JSON)
+ Debug(i ...interface{})
+ Debugf(format string, args ...interface{})
+ Debugj(j log.JSON)
+ Info(i ...interface{})
+ Infof(format string, args ...interface{})
+ Infoj(j log.JSON)
+ Warn(i ...interface{})
+ Warnf(format string, args ...interface{})
+ Warnj(j log.JSON)
+ Error(i ...interface{})
+ Errorf(format string, args ...interface{})
+ Errorj(j log.JSON)
+ Fatal(i ...interface{})
+ Fatalj(j log.JSON)
+ Fatalf(format string, args ...interface{})
+ Panic(i ...interface{})
+ Panicj(j log.JSON)
+ Panicf(format string, args ...interface{})
+}
diff --git a/vendor/github.com/labstack/echo/v4/response.go b/vendor/github.com/labstack/echo/v4/response.go
index d9c9aa6..a795ce3 100644
--- a/vendor/github.com/labstack/echo/v4/response.go
+++ b/vendor/github.com/labstack/echo/v4/response.go
@@ -1,25 +1,27 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
"bufio"
+ "errors"
"net"
"net/http"
)
-type (
- // Response wraps an http.ResponseWriter and implements its interface to be used
- // by an HTTP handler to construct an HTTP response.
- // See: https://golang.org/pkg/net/http/#ResponseWriter
- Response struct {
- echo *Echo
- beforeFuncs []func()
- afterFuncs []func()
- Writer http.ResponseWriter
- Status int
- Size int64
- Committed bool
- }
-)
+// Response wraps an http.ResponseWriter and implements its interface to be used
+// by an HTTP handler to construct an HTTP response.
+// See: https://golang.org/pkg/net/http/#ResponseWriter
+type Response struct {
+ echo *Echo
+ beforeFuncs []func()
+ afterFuncs []func()
+ Writer http.ResponseWriter
+ Status int
+ Size int64
+ Committed bool
+}
// NewResponse creates a new instance of Response.
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
@@ -84,14 +86,17 @@ func (r *Response) Write(b []byte) (n int, err error) {
// buffered data to the client.
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
func (r *Response) Flush() {
- r.Writer.(http.Flusher).Flush()
+ err := responseControllerFlush(r.Writer)
+ if err != nil && errors.Is(err, http.ErrNotSupported) {
+ panic(errors.New("response writer flushing is not supported"))
+ }
}
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
// take over the connection.
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
- return r.Writer.(http.Hijacker).Hijack()
+ return responseControllerHijack(r.Writer)
}
// Unwrap returns the original http.ResponseWriter.
diff --git a/vendor/github.com/labstack/echo/v4/responsecontroller_1.19.go b/vendor/github.com/labstack/echo/v4/responsecontroller_1.19.go
new file mode 100644
index 0000000..782dab3
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/responsecontroller_1.19.go
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
+//go:build !go1.20
+
+package echo
+
+import (
+ "bufio"
+ "fmt"
+ "net"
+ "net/http"
+)
+
+// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
+func responseControllerFlush(rw http.ResponseWriter) error {
+ for {
+ switch t := rw.(type) {
+ case interface{ FlushError() error }:
+ return t.FlushError()
+ case http.Flusher:
+ t.Flush()
+ return nil
+ case interface{ Unwrap() http.ResponseWriter }:
+ rw = t.Unwrap()
+ default:
+ return fmt.Errorf("%w", http.ErrNotSupported)
+ }
+ }
+}
+
+// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
+func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
+ for {
+ switch t := rw.(type) {
+ case http.Hijacker:
+ return t.Hijack()
+ case interface{ Unwrap() http.ResponseWriter }:
+ rw = t.Unwrap()
+ default:
+ return nil, nil, fmt.Errorf("%w", http.ErrNotSupported)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/responsecontroller_1.20.go b/vendor/github.com/labstack/echo/v4/responsecontroller_1.20.go
new file mode 100644
index 0000000..6d77c07
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/responsecontroller_1.20.go
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
+//go:build go1.20
+
+package echo
+
+import (
+ "bufio"
+ "net"
+ "net/http"
+)
+
+func responseControllerFlush(rw http.ResponseWriter) error {
+ return http.NewResponseController(rw).Flush()
+}
+
+func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
+ return http.NewResponseController(rw).Hijack()
+}
diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go
index ee6f3fa..0326731 100644
--- a/vendor/github.com/labstack/echo/v4/router.go
+++ b/vendor/github.com/labstack/echo/v4/router.go
@@ -1,3 +1,6 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
+
package echo
import (
@@ -6,56 +9,58 @@ import (
"net/http"
)
-type (
- // Router is the registry of all registered routes for an `Echo` instance for
- // request matching and URL path parameter parsing.
- Router struct {
- tree *node
- routes map[string]*Route
- echo *Echo
- }
- node struct {
- kind kind
- label byte
- prefix string
- parent *node
- staticChildren children
- originalPath string
- methods *routeMethods
- paramChild *node
- anyChild *node
- paramsCount int
- // isLeaf indicates that node does not have child routes
- isLeaf bool
- // isHandler indicates that node has at least one handler registered to it
- isHandler bool
+// Router is the registry of all registered routes for an `Echo` instance for
+// request matching and URL path parameter parsing.
+type Router struct {
+ tree *node
+ routes map[string]*Route
+ echo *Echo
+}
- // notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
- notFoundHandler *routeMethod
- }
- kind uint8
- children []*node
- routeMethod struct {
- ppath string
- pnames []string
- handler HandlerFunc
- }
- routeMethods struct {
- connect *routeMethod
- delete *routeMethod
- get *routeMethod
- head *routeMethod
- options *routeMethod
- patch *routeMethod
- post *routeMethod
- propfind *routeMethod
- put *routeMethod
- trace *routeMethod
- report *routeMethod
- anyOther map[string]*routeMethod
- allowHeader string
- }
-)
+type node struct {
+ kind kind
+ label byte
+ prefix string
+ parent *node
+ staticChildren children
+ originalPath string
+ methods *routeMethods
+ paramChild *node
+ anyChild *node
+ paramsCount int
+ // isLeaf indicates that node does not have child routes
+ isLeaf bool
+ // isHandler indicates that node has at least one handler registered to it
+ isHandler bool
+
+ // notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
+ notFoundHandler *routeMethod
+}
+
+type kind uint8
+type children []*node
+
+type routeMethod struct {
+ ppath string
+ pnames []string
+ handler HandlerFunc
+}
+
+type routeMethods struct {
+ connect *routeMethod
+ delete *routeMethod
+ get *routeMethod
+ head *routeMethod
+ options *routeMethod
+ patch *routeMethod
+ post *routeMethod
+ propfind *routeMethod
+ put *routeMethod
+ trace *routeMethod
+ report *routeMethod
+ anyOther map[string]*routeMethod
+ allowHeader string
+}
const (
staticKind kind = iota
@@ -180,8 +185,18 @@ func (r *Router) Reverse(name string, params ...interface{}) string {
return uri.String()
}
+func normalizePathSlash(path string) string {
+ if path == "" {
+ path = "/"
+ } else if path[0] != '/' {
+ path = "/" + path
+ }
+ return path
+}
+
func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
- r.Add(method, path, h)
+ path = normalizePathSlash(path)
+ r.insert(method, path, h)
route := &Route{
Method: method,
@@ -194,13 +209,11 @@ func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
// Add registers a new route for method and path with matching handler.
func (r *Router) Add(method, path string, h HandlerFunc) {
- // Validate path
- if path == "" {
- path = "/"
- }
- if path[0] != '/' {
- path = "/" + path
- }
+ r.insert(method, normalizePathSlash(path), h)
+}
+
+func (r *Router) insert(method, path string, h HandlerFunc) {
+ path = normalizePathSlash(path)
pnames := []string{} // Param names
ppath := path // Pristine path
@@ -219,7 +232,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
}
j := i + 1
- r.insert(method, path[:i], staticKind, routeMethod{})
+ r.insertNode(method, path[:i], staticKind, routeMethod{})
for ; i < lcpIndex && path[i] != '/'; i++ {
}
@@ -229,21 +242,21 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
if i == lcpIndex {
// path node is last fragment of route path. ie. `/users/:id`
- r.insert(method, path[:i], paramKind, routeMethod{ppath, pnames, h})
+ r.insertNode(method, path[:i], paramKind, routeMethod{ppath, pnames, h})
} else {
- r.insert(method, path[:i], paramKind, routeMethod{})
+ r.insertNode(method, path[:i], paramKind, routeMethod{})
}
} else if path[i] == '*' {
- r.insert(method, path[:i], staticKind, routeMethod{})
+ r.insertNode(method, path[:i], staticKind, routeMethod{})
pnames = append(pnames, "*")
- r.insert(method, path[:i+1], anyKind, routeMethod{ppath, pnames, h})
+ r.insertNode(method, path[:i+1], anyKind, routeMethod{ppath, pnames, h})
}
}
- r.insert(method, path, staticKind, routeMethod{ppath, pnames, h})
+ r.insertNode(method, path, staticKind, routeMethod{ppath, pnames, h})
}
-func (r *Router) insert(method, path string, t kind, rm routeMethod) {
+func (r *Router) insertNode(method, path string, t kind, rm routeMethod) {
// Adjust max param
paramLen := len(rm.pnames)
if *r.echo.maxParam < paramLen {
diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go
index e2b298d..43557ab 100644
--- a/vendor/golang.org/x/net/http2/frame.go
+++ b/vendor/golang.org/x/net/http2/frame.go
@@ -1564,6 +1564,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
if size > remainSize {
hdec.SetEmitEnabled(false)
mh.Truncated = true
+ remainSize = 0
return
}
remainSize -= size
@@ -1576,6 +1577,36 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
var hc headersOrContinuation = hf
for {
frag := hc.HeaderBlockFragment()
+
+ // Avoid parsing large amounts of headers that we will then discard.
+ // If the sender exceeds the max header list size by too much,
+ // skip parsing the fragment and close the connection.
+ //
+ // "Too much" is either any CONTINUATION frame after we've already
+ // exceeded the max header list size (in which case remainSize is 0),
+ // or a frame whose encoded size is more than twice the remaining
+ // header list bytes we're willing to accept.
+ if int64(len(frag)) > int64(2*remainSize) {
+ if VerboseLogs {
+ log.Printf("http2: header list too large")
+ }
+ // It would be nice to send a RST_STREAM before sending the GOAWAY,
+ // but the structure of the server's frame writer makes this difficult.
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+
+ // Also close the connection after any CONTINUATION frame following an
+ // invalid header, since we stop tracking the size of the headers after
+ // an invalid one.
+ if invalid != nil {
+ if VerboseLogs {
+ log.Printf("http2: invalid header: %v", invalid)
+ }
+ // It would be nice to send a RST_STREAM before sending the GOAWAY,
+ // but the structure of the server's frame writer makes this difficult.
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+
if _, err := hdec.Write(frag); err != nil {
return nil, ConnectionError(ErrCodeCompression)
}
diff --git a/vendor/golang.org/x/net/http2/pipe.go b/vendor/golang.org/x/net/http2/pipe.go
index 684d984..3b9f06b 100644
--- a/vendor/golang.org/x/net/http2/pipe.go
+++ b/vendor/golang.org/x/net/http2/pipe.go
@@ -77,7 +77,10 @@ func (p *pipe) Read(d []byte) (n int, err error) {
}
}
-var errClosedPipeWrite = errors.New("write on closed buffer")
+var (
+ errClosedPipeWrite = errors.New("write on closed buffer")
+ errUninitializedPipeWrite = errors.New("write on uninitialized buffer")
+)
// Write copies bytes from p into the buffer and wakes a reader.
// It is an error to write more data than the buffer can hold.
@@ -91,6 +94,12 @@ func (p *pipe) Write(d []byte) (n int, err error) {
if p.err != nil || p.breakErr != nil {
return 0, errClosedPipeWrite
}
+ // pipe.setBuffer is never invoked, leaving the buffer uninitialized.
+ // We shouldn't try to write to an uninitialized pipe,
+ // but returning an error is better than panicking.
+ if p.b == nil {
+ return 0, errUninitializedPipeWrite
+ }
return p.b.Write(d)
}
diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go
index ae94c64..ce2e8b4 100644
--- a/vendor/golang.org/x/net/http2/server.go
+++ b/vendor/golang.org/x/net/http2/server.go
@@ -124,6 +124,7 @@ type Server struct {
// IdleTimeout specifies how long until idle clients should be
// closed with a GOAWAY frame. PING frames are not considered
// activity for the purposes of IdleTimeout.
+ // If zero or negative, there is no timeout.
IdleTimeout time.Duration
// MaxUploadBufferPerConnection is the size of the initial flow
@@ -434,7 +435,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
// passes the connection off to us with the deadline already set.
// Write deadlines are set per stream in serverConn.newStream.
// Disarm the net.Conn write deadline here.
- if sc.hs.WriteTimeout != 0 {
+ if sc.hs.WriteTimeout > 0 {
sc.conn.SetWriteDeadline(time.Time{})
}
@@ -924,7 +925,7 @@ func (sc *serverConn) serve() {
sc.setConnState(http.StateActive)
sc.setConnState(http.StateIdle)
- if sc.srv.IdleTimeout != 0 {
+ if sc.srv.IdleTimeout > 0 {
sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
defer sc.idleTimer.Stop()
}
@@ -1637,7 +1638,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
delete(sc.streams, st.id)
if len(sc.streams) == 0 {
sc.setConnState(http.StateIdle)
- if sc.srv.IdleTimeout != 0 {
+ if sc.srv.IdleTimeout > 0 {
sc.idleTimer.Reset(sc.srv.IdleTimeout)
}
if h1ServerKeepAlivesDisabled(sc.hs) {
@@ -2017,7 +2018,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
// similar to how the http1 server works. Here it's
// technically more like the http1 Server's ReadHeaderTimeout
// (in Go 1.8), though. That's a more sane option anyway.
- if sc.hs.ReadTimeout != 0 {
+ if sc.hs.ReadTimeout > 0 {
sc.conn.SetReadDeadline(time.Time{})
st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
}
@@ -2038,7 +2039,7 @@ func (sc *serverConn) upgradeRequest(req *http.Request) {
// Disable any read deadline set by the net/http package
// prior to the upgrade.
- if sc.hs.ReadTimeout != 0 {
+ if sc.hs.ReadTimeout > 0 {
sc.conn.SetReadDeadline(time.Time{})
}
@@ -2116,7 +2117,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
st.flow.conn = &sc.flow // link to conn-level counter
st.flow.add(sc.initialStreamSendWindowSize)
st.inflow.init(sc.srv.initialStreamRecvWindowSize())
- if sc.hs.WriteTimeout != 0 {
+ if sc.hs.WriteTimeout > 0 {
st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
}
diff --git a/vendor/golang.org/x/net/http2/testsync.go b/vendor/golang.org/x/net/http2/testsync.go
new file mode 100644
index 0000000..61075bd
--- /dev/null
+++ b/vendor/golang.org/x/net/http2/testsync.go
@@ -0,0 +1,331 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package http2
+
+import (
+ "context"
+ "sync"
+ "time"
+)
+
+// testSyncHooks coordinates goroutines in tests.
+//
+// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
+// - the goroutine running RoundTrip;
+// - the clientStream.doRequest goroutine, which writes the request; and
+// - the clientStream.readLoop goroutine, which reads the response.
+//
+// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
+// are blocked waiting for some condition such as reading the Request.Body or waiting for
+// flow control to become available.
+//
+// The testSyncHooks also manage timers and synthetic time in tests.
+// This permits us to, for example, start a request and cause it to time out waiting for
+// response headers without resorting to time.Sleep calls.
+type testSyncHooks struct {
+ // active/inactive act as a mutex and condition variable.
+ //
+ // - neither chan contains a value: testSyncHooks is locked.
+ // - active contains a value: unlocked, and at least one goroutine is not blocked
+ // - inactive contains a value: unlocked, and all goroutines are blocked
+ active chan struct{}
+ inactive chan struct{}
+
+ // goroutine counts
+ total int // total goroutines
+ condwait map[*sync.Cond]int // blocked in sync.Cond.Wait
+ blocked []*testBlockedGoroutine // otherwise blocked
+
+ // fake time
+ now time.Time
+ timers []*fakeTimer
+
+ // Transport testing: Report various events.
+ newclientconn func(*ClientConn)
+ newstream func(*clientStream)
+}
+
+// testBlockedGoroutine is a blocked goroutine.
+type testBlockedGoroutine struct {
+ f func() bool // blocked until f returns true
+ ch chan struct{} // closed when unblocked
+}
+
+func newTestSyncHooks() *testSyncHooks {
+ h := &testSyncHooks{
+ active: make(chan struct{}, 1),
+ inactive: make(chan struct{}, 1),
+ condwait: map[*sync.Cond]int{},
+ }
+ h.inactive <- struct{}{}
+ h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
+ return h
+}
+
+// lock acquires the testSyncHooks mutex.
+func (h *testSyncHooks) lock() {
+ select {
+ case <-h.active:
+ case <-h.inactive:
+ }
+}
+
+// waitInactive waits for all goroutines to become inactive.
+func (h *testSyncHooks) waitInactive() {
+ for {
+ <-h.inactive
+ if !h.unlock() {
+ break
+ }
+ }
+}
+
+// unlock releases the testSyncHooks mutex.
+// It reports whether any goroutines are active.
+func (h *testSyncHooks) unlock() (active bool) {
+ // Look for a blocked goroutine which can be unblocked.
+ blocked := h.blocked[:0]
+ unblocked := false
+ for _, b := range h.blocked {
+ if !unblocked && b.f() {
+ unblocked = true
+ close(b.ch)
+ } else {
+ blocked = append(blocked, b)
+ }
+ }
+ h.blocked = blocked
+
+ // Count goroutines blocked on condition variables.
+ condwait := 0
+ for _, count := range h.condwait {
+ condwait += count
+ }
+
+ if h.total > condwait+len(blocked) {
+ h.active <- struct{}{}
+ return true
+ } else {
+ h.inactive <- struct{}{}
+ return false
+ }
+}
+
+// goRun starts a new goroutine.
+func (h *testSyncHooks) goRun(f func()) {
+ h.lock()
+ h.total++
+ h.unlock()
+ go func() {
+ defer func() {
+ h.lock()
+ h.total--
+ h.unlock()
+ }()
+ f()
+ }()
+}
+
+// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
+// It waits until f returns true before proceeding.
+//
+// Example usage:
+//
+// h.blockUntil(func() bool {
+// // Is the context done yet?
+// select {
+// case <-ctx.Done():
+// default:
+// return false
+// }
+// return true
+// })
+// // Wait for the context to become done.
+// <-ctx.Done()
+//
+// The function f passed to blockUntil must be non-blocking and idempotent.
+func (h *testSyncHooks) blockUntil(f func() bool) {
+ if f() {
+ return
+ }
+ ch := make(chan struct{})
+ h.lock()
+ h.blocked = append(h.blocked, &testBlockedGoroutine{
+ f: f,
+ ch: ch,
+ })
+ h.unlock()
+ <-ch
+}
+
+// broadcast is sync.Cond.Broadcast.
+func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
+ h.lock()
+ delete(h.condwait, cond)
+ h.unlock()
+ cond.Broadcast()
+}
+
+// broadcast is sync.Cond.Wait.
+func (h *testSyncHooks) condWait(cond *sync.Cond) {
+ h.lock()
+ h.condwait[cond]++
+ h.unlock()
+}
+
+// newTimer creates a new fake timer.
+func (h *testSyncHooks) newTimer(d time.Duration) timer {
+ h.lock()
+ defer h.unlock()
+ t := &fakeTimer{
+ hooks: h,
+ when: h.now.Add(d),
+ c: make(chan time.Time),
+ }
+ h.timers = append(h.timers, t)
+ return t
+}
+
+// afterFunc creates a new fake AfterFunc timer.
+func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
+ h.lock()
+ defer h.unlock()
+ t := &fakeTimer{
+ hooks: h,
+ when: h.now.Add(d),
+ f: f,
+ }
+ h.timers = append(h.timers, t)
+ return t
+}
+
+func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
+ ctx, cancel := context.WithCancel(ctx)
+ t := h.afterFunc(d, cancel)
+ return ctx, func() {
+ t.Stop()
+ cancel()
+ }
+}
+
+func (h *testSyncHooks) timeUntilEvent() time.Duration {
+ h.lock()
+ defer h.unlock()
+ var next time.Time
+ for _, t := range h.timers {
+ if next.IsZero() || t.when.Before(next) {
+ next = t.when
+ }
+ }
+ if d := next.Sub(h.now); d > 0 {
+ return d
+ }
+ return 0
+}
+
+// advance advances time and causes synthetic timers to fire.
+func (h *testSyncHooks) advance(d time.Duration) {
+ h.lock()
+ defer h.unlock()
+ h.now = h.now.Add(d)
+ timers := h.timers[:0]
+ for _, t := range h.timers {
+ t := t // remove after go.mod depends on go1.22
+ t.mu.Lock()
+ switch {
+ case t.when.After(h.now):
+ timers = append(timers, t)
+ case t.when.IsZero():
+ // stopped timer
+ default:
+ t.when = time.Time{}
+ if t.c != nil {
+ close(t.c)
+ }
+ if t.f != nil {
+ h.total++
+ go func() {
+ defer func() {
+ h.lock()
+ h.total--
+ h.unlock()
+ }()
+ t.f()
+ }()
+ }
+ }
+ t.mu.Unlock()
+ }
+ h.timers = timers
+}
+
+// A timer wraps a time.Timer, or a synthetic equivalent in tests.
+// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
+type timer interface {
+ C() <-chan time.Time
+ Stop() bool
+ Reset(d time.Duration) bool
+}
+
+// timeTimer implements timer using real time.
+type timeTimer struct {
+ t *time.Timer
+ c chan time.Time
+}
+
+// newTimeTimer creates a new timer using real time.
+func newTimeTimer(d time.Duration) timer {
+ ch := make(chan time.Time)
+ t := time.AfterFunc(d, func() {
+ close(ch)
+ })
+ return &timeTimer{t, ch}
+}
+
+// newTimeAfterFunc creates an AfterFunc timer using real time.
+func newTimeAfterFunc(d time.Duration, f func()) timer {
+ return &timeTimer{
+ t: time.AfterFunc(d, f),
+ }
+}
+
+func (t timeTimer) C() <-chan time.Time { return t.c }
+func (t timeTimer) Stop() bool { return t.t.Stop() }
+func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
+
+// fakeTimer implements timer using fake time.
+type fakeTimer struct {
+ hooks *testSyncHooks
+
+ mu sync.Mutex
+ when time.Time // when the timer will fire
+ c chan time.Time // closed when the timer fires; mutually exclusive with f
+ f func() // called when the timer fires; mutually exclusive with c
+}
+
+func (t *fakeTimer) C() <-chan time.Time { return t.c }
+
+func (t *fakeTimer) Stop() bool {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ stopped := t.when.IsZero()
+ t.when = time.Time{}
+ return stopped
+}
+
+func (t *fakeTimer) Reset(d time.Duration) bool {
+ if t.c != nil || t.f == nil {
+ panic("fakeTimer only supports Reset on AfterFunc timers")
+ }
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.hooks.lock()
+ defer t.hooks.unlock()
+ active := !t.when.IsZero()
+ t.when = t.hooks.now.Add(d)
+ if !active {
+ t.hooks.timers = append(t.hooks.timers, t)
+ }
+ return active
+}
diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go
index c2a5b44..ce375c8 100644
--- a/vendor/golang.org/x/net/http2/transport.go
+++ b/vendor/golang.org/x/net/http2/transport.go
@@ -147,6 +147,12 @@ type Transport struct {
// waiting for their turn.
StrictMaxConcurrentStreams bool
+ // IdleConnTimeout is the maximum amount of time an idle
+ // (keep-alive) connection will remain idle before closing
+ // itself.
+ // Zero means no limit.
+ IdleConnTimeout time.Duration
+
// ReadIdleTimeout is the timeout after which a health check using ping
// frame will be carried out if no frame is received on the connection.
// Note that a ping response will is considered a received frame, so if
@@ -178,6 +184,8 @@ type Transport struct {
connPoolOnce sync.Once
connPoolOrDef ClientConnPool // non-nil version of ConnPool
+
+ syncHooks *testSyncHooks
}
func (t *Transport) maxHeaderListSize() uint32 {
@@ -302,7 +310,7 @@ type ClientConn struct {
readerErr error // set before readerDone is closed
idleTimeout time.Duration // or 0 for never
- idleTimer *time.Timer
+ idleTimer timer
mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes
@@ -344,6 +352,60 @@ type ClientConn struct {
werr error // first write error that has occurred
hbuf bytes.Buffer // HPACK encoder writes into this
henc *hpack.Encoder
+
+ syncHooks *testSyncHooks // can be nil
+}
+
+// Hook points used for testing.
+// Outside of tests, cc.syncHooks is nil and these all have minimal implementations.
+// Inside tests, see the testSyncHooks function docs.
+
+// goRun starts a new goroutine.
+func (cc *ClientConn) goRun(f func()) {
+ if cc.syncHooks != nil {
+ cc.syncHooks.goRun(f)
+ return
+ }
+ go f()
+}
+
+// condBroadcast is cc.cond.Broadcast.
+func (cc *ClientConn) condBroadcast() {
+ if cc.syncHooks != nil {
+ cc.syncHooks.condBroadcast(cc.cond)
+ }
+ cc.cond.Broadcast()
+}
+
+// condWait is cc.cond.Wait.
+func (cc *ClientConn) condWait() {
+ if cc.syncHooks != nil {
+ cc.syncHooks.condWait(cc.cond)
+ }
+ cc.cond.Wait()
+}
+
+// newTimer creates a new time.Timer, or a synthetic timer in tests.
+func (cc *ClientConn) newTimer(d time.Duration) timer {
+ if cc.syncHooks != nil {
+ return cc.syncHooks.newTimer(d)
+ }
+ return newTimeTimer(d)
+}
+
+// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
+func (cc *ClientConn) afterFunc(d time.Duration, f func()) timer {
+ if cc.syncHooks != nil {
+ return cc.syncHooks.afterFunc(d, f)
+ }
+ return newTimeAfterFunc(d, f)
+}
+
+func (cc *ClientConn) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
+ if cc.syncHooks != nil {
+ return cc.syncHooks.contextWithTimeout(ctx, d)
+ }
+ return context.WithTimeout(ctx, d)
}
// clientStream is the state for a single HTTP/2 stream. One of these
@@ -425,7 +487,7 @@ func (cs *clientStream) abortStreamLocked(err error) {
// TODO(dneil): Clean up tests where cs.cc.cond is nil.
if cs.cc.cond != nil {
// Wake up writeRequestBody if it is waiting on flow control.
- cs.cc.cond.Broadcast()
+ cs.cc.condBroadcast()
}
}
@@ -435,7 +497,7 @@ func (cs *clientStream) abortRequestBodyWrite() {
defer cc.mu.Unlock()
if cs.reqBody != nil && cs.reqBodyClosed == nil {
cs.closeReqBodyLocked()
- cc.cond.Broadcast()
+ cc.condBroadcast()
}
}
@@ -445,10 +507,10 @@ func (cs *clientStream) closeReqBodyLocked() {
}
cs.reqBodyClosed = make(chan struct{})
reqBodyClosed := cs.reqBodyClosed
- go func() {
+ cs.cc.goRun(func() {
cs.reqBody.Close()
close(reqBodyClosed)
- }()
+ })
}
type stickyErrWriter struct {
@@ -537,15 +599,6 @@ func authorityAddr(scheme string, authority string) (addr string) {
return net.JoinHostPort(host, port)
}
-var retryBackoffHook func(time.Duration) *time.Timer
-
-func backoffNewTimer(d time.Duration) *time.Timer {
- if retryBackoffHook != nil {
- return retryBackoffHook(d)
- }
- return time.NewTimer(d)
-}
-
// RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
@@ -573,13 +626,27 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
backoff := float64(uint(1) << (uint(retry) - 1))
backoff += backoff * (0.1 * mathrand.Float64())
d := time.Second * time.Duration(backoff)
- timer := backoffNewTimer(d)
+ var tm timer
+ if t.syncHooks != nil {
+ tm = t.syncHooks.newTimer(d)
+ t.syncHooks.blockUntil(func() bool {
+ select {
+ case <-tm.C():
+ case <-req.Context().Done():
+ default:
+ return false
+ }
+ return true
+ })
+ } else {
+ tm = newTimeTimer(d)
+ }
select {
- case <-timer.C:
+ case <-tm.C():
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
continue
case <-req.Context().Done():
- timer.Stop()
+ tm.Stop()
err = req.Context().Err()
}
}
@@ -658,6 +725,9 @@ func canRetryError(err error) bool {
}
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
+ if t.syncHooks != nil {
+ return t.newClientConn(nil, singleUse, t.syncHooks)
+ }
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
@@ -666,7 +736,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
if err != nil {
return nil, err
}
- return t.newClientConn(tconn, singleUse)
+ return t.newClientConn(tconn, singleUse, nil)
}
func (t *Transport) newTLSConfig(host string) *tls.Config {
@@ -732,10 +802,10 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 {
}
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
- return t.newClientConn(c, t.disableKeepAlives())
+ return t.newClientConn(c, t.disableKeepAlives(), nil)
}
-func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
+func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) {
cc := &ClientConn{
t: t,
tconn: c,
@@ -750,10 +820,15 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
wantSettingsAck: true,
pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1),
+ syncHooks: hooks,
+ }
+ if hooks != nil {
+ hooks.newclientconn(cc)
+ c = cc.tconn
}
if d := t.idleConnTimeout(); d != 0 {
cc.idleTimeout = d
- cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
+ cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
}
if VerboseLogs {
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
@@ -818,7 +893,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
return nil, cc.werr
}
- go cc.readLoop()
+ cc.goRun(cc.readLoop)
return cc, nil
}
@@ -826,7 +901,7 @@ func (cc *ClientConn) healthCheck() {
pingTimeout := cc.t.pingTimeout()
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
// trigger the healthCheck again if there is no frame received.
- ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
+ ctx, cancel := cc.contextWithTimeout(context.Background(), pingTimeout)
defer cancel()
cc.vlogf("http2: Transport sending health check")
err := cc.Ping(ctx)
@@ -1056,7 +1131,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
// Wait for all in-flight streams to complete or connection to close
done := make(chan struct{})
cancelled := false // guarded by cc.mu
- go func() {
+ cc.goRun(func() {
cc.mu.Lock()
defer cc.mu.Unlock()
for {
@@ -1068,9 +1143,9 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
if cancelled {
break
}
- cc.cond.Wait()
+ cc.condWait()
}
- }()
+ })
shutdownEnterWaitStateHook()
select {
case <-done:
@@ -1080,7 +1155,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
cc.mu.Lock()
// Free the goroutine above
cancelled = true
- cc.cond.Broadcast()
+ cc.condBroadcast()
cc.mu.Unlock()
return ctx.Err()
}
@@ -1118,7 +1193,7 @@ func (cc *ClientConn) closeForError(err error) {
for _, cs := range cc.streams {
cs.abortStreamLocked(err)
}
- cc.cond.Broadcast()
+ cc.condBroadcast()
cc.mu.Unlock()
cc.closeConn()
}
@@ -1215,6 +1290,10 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
}
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
+ return cc.roundTrip(req, nil)
+}
+
+func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
ctx := req.Context()
cs := &clientStream{
cc: cc,
@@ -1229,9 +1308,23 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
respHeaderRecv: make(chan struct{}),
donec: make(chan struct{}),
}
- go cs.doRequest(req)
+ cc.goRun(func() {
+ cs.doRequest(req)
+ })
waitDone := func() error {
+ if cc.syncHooks != nil {
+ cc.syncHooks.blockUntil(func() bool {
+ select {
+ case <-cs.donec:
+ case <-ctx.Done():
+ case <-cs.reqCancel:
+ default:
+ return false
+ }
+ return true
+ })
+ }
select {
case <-cs.donec:
return nil
@@ -1292,7 +1385,24 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
return err
}
+ if streamf != nil {
+ streamf(cs)
+ }
+
for {
+ if cc.syncHooks != nil {
+ cc.syncHooks.blockUntil(func() bool {
+ select {
+ case <-cs.respHeaderRecv:
+ case <-cs.abort:
+ case <-ctx.Done():
+ case <-cs.reqCancel:
+ default:
+ return false
+ }
+ return true
+ })
+ }
select {
case <-cs.respHeaderRecv:
return handleResponseHeaders()
@@ -1348,6 +1458,21 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
if cc.reqHeaderMu == nil {
panic("RoundTrip on uninitialized ClientConn") // for tests
}
+ var newStreamHook func(*clientStream)
+ if cc.syncHooks != nil {
+ newStreamHook = cc.syncHooks.newstream
+ cc.syncHooks.blockUntil(func() bool {
+ select {
+ case cc.reqHeaderMu <- struct{}{}:
+ <-cc.reqHeaderMu
+ case <-cs.reqCancel:
+ case <-ctx.Done():
+ default:
+ return false
+ }
+ return true
+ })
+ }
select {
case cc.reqHeaderMu <- struct{}{}:
case <-cs.reqCancel:
@@ -1372,6 +1497,10 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
}
cc.mu.Unlock()
+ if newStreamHook != nil {
+ newStreamHook(cs)
+ }
+
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
if !cc.t.disableCompression() &&
req.Header.Get("Accept-Encoding") == "" &&
@@ -1452,15 +1581,30 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
var respHeaderTimer <-chan time.Time
var respHeaderRecv chan struct{}
if d := cc.responseHeaderTimeout(); d != 0 {
- timer := time.NewTimer(d)
+ timer := cc.newTimer(d)
defer timer.Stop()
- respHeaderTimer = timer.C
+ respHeaderTimer = timer.C()
respHeaderRecv = cs.respHeaderRecv
}
// Wait until the peer half-closes its end of the stream,
// or until the request is aborted (via context, error, or otherwise),
// whichever comes first.
for {
+ if cc.syncHooks != nil {
+ cc.syncHooks.blockUntil(func() bool {
+ select {
+ case <-cs.peerClosed:
+ case <-respHeaderTimer:
+ case <-respHeaderRecv:
+ case <-cs.abort:
+ case <-ctx.Done():
+ case <-cs.reqCancel:
+ default:
+ return false
+ }
+ return true
+ })
+ }
select {
case <-cs.peerClosed:
return nil
@@ -1609,7 +1753,7 @@ func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
return nil
}
cc.pendingRequests++
- cc.cond.Wait()
+ cc.condWait()
cc.pendingRequests--
select {
case <-cs.abort:
@@ -1871,10 +2015,26 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
cs.flow.take(take)
return take, nil
}
- cc.cond.Wait()
+ cc.condWait()
}
}
+func validateHeaders(hdrs http.Header) string {
+ for k, vv := range hdrs {
+ if !httpguts.ValidHeaderFieldName(k) {
+ return fmt.Sprintf("name %q", k)
+ }
+ for _, v := range vv {
+ if !httpguts.ValidHeaderFieldValue(v) {
+ // Don't include the value in the error,
+ // because it may be sensitive.
+ return fmt.Sprintf("value for header %q", k)
+ }
+ }
+ }
+ return ""
+}
+
var errNilRequestURL = errors.New("http2: Request.URI is nil")
// requires cc.wmu be held.
@@ -1912,19 +2072,14 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
}
}
- // Check for any invalid headers and return an error before we
+ // Check for any invalid headers+trailers and return an error before we
// potentially pollute our hpack state. (We want to be able to
// continue to reuse the hpack encoder for future requests)
- for k, vv := range req.Header {
- if !httpguts.ValidHeaderFieldName(k) {
- return nil, fmt.Errorf("invalid HTTP header name %q", k)
- }
- for _, v := range vv {
- if !httpguts.ValidHeaderFieldValue(v) {
- // Don't include the value in the error, because it may be sensitive.
- return nil, fmt.Errorf("invalid HTTP header value for header %q", k)
- }
- }
+ if err := validateHeaders(req.Header); err != "" {
+ return nil, fmt.Errorf("invalid HTTP header %s", err)
+ }
+ if err := validateHeaders(req.Trailer); err != "" {
+ return nil, fmt.Errorf("invalid HTTP trailer %s", err)
}
enumerateHeaders := func(f func(name, value string)) {
@@ -2143,7 +2298,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
}
// Wake up writeRequestBody via clientStream.awaitFlowControl and
// wake up RoundTrip if there is a pending request.
- cc.cond.Broadcast()
+ cc.condBroadcast()
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
@@ -2231,7 +2386,7 @@ func (rl *clientConnReadLoop) cleanup() {
cs.abortStreamLocked(err)
}
}
- cc.cond.Broadcast()
+ cc.condBroadcast()
cc.mu.Unlock()
}
@@ -2266,10 +2421,9 @@ func (rl *clientConnReadLoop) run() error {
cc := rl.cc
gotSettings := false
readIdleTimeout := cc.t.ReadIdleTimeout
- var t *time.Timer
+ var t timer
if readIdleTimeout != 0 {
- t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
- defer t.Stop()
+ t = cc.afterFunc(readIdleTimeout, cc.healthCheck)
}
for {
f, err := cc.fr.ReadFrame()
@@ -2684,7 +2838,7 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
})
return nil
}
- if !cs.firstByte {
+ if !cs.pastHeaders {
cc.logf("protocol error: received DATA before a HEADERS frame")
rl.endStreamError(cs, StreamError{
StreamID: f.StreamID,
@@ -2867,7 +3021,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
for _, cs := range cc.streams {
cs.flow.add(delta)
}
- cc.cond.Broadcast()
+ cc.condBroadcast()
cc.initialWindowSize = s.Val
case SettingHeaderTableSize:
@@ -2922,7 +3076,7 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
return ConnectionError(ErrCodeFlowControl)
}
- cc.cond.Broadcast()
+ cc.condBroadcast()
return nil
}
@@ -2964,24 +3118,38 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
}
cc.mu.Unlock()
}
- errc := make(chan error, 1)
- go func() {
+ var pingError error
+ errc := make(chan struct{})
+ cc.goRun(func() {
cc.wmu.Lock()
defer cc.wmu.Unlock()
- if err := cc.fr.WritePing(false, p); err != nil {
- errc <- err
+ if pingError = cc.fr.WritePing(false, p); pingError != nil {
+ close(errc)
return
}
- if err := cc.bw.Flush(); err != nil {
- errc <- err
+ if pingError = cc.bw.Flush(); pingError != nil {
+ close(errc)
return
}
- }()
+ })
+ if cc.syncHooks != nil {
+ cc.syncHooks.blockUntil(func() bool {
+ select {
+ case <-c:
+ case <-errc:
+ case <-ctx.Done():
+ case <-cc.readerDone:
+ default:
+ return false
+ }
+ return true
+ })
+ }
select {
case <-c:
return nil
- case err := <-errc:
- return err
+ case <-errc:
+ return pingError
case <-ctx.Done():
return ctx.Err()
case <-cc.readerDone:
@@ -3150,9 +3318,17 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
}
func (t *Transport) idleConnTimeout() time.Duration {
+ // to keep things backwards compatible, we use non-zero values of
+ // IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
+ // http1 transport, followed by 0
+ if t.IdleConnTimeout != 0 {
+ return t.IdleConnTimeout
+ }
+
if t.t1 != nil {
return t.t1.IdleConnTimeout
}
+
return 0
}
diff --git a/vendor/golang.org/x/sys/unix/mmap_nomremap.go b/vendor/golang.org/x/sys/unix/mmap_nomremap.go
index 4b68e59..7f602ff 100644
--- a/vendor/golang.org/x/sys/unix/mmap_nomremap.go
+++ b/vendor/golang.org/x/sys/unix/mmap_nomremap.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris
+//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris || zos
package unix
diff --git a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
index b473038..27c41b6 100644
--- a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
+++ b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
@@ -1520,6 +1520,14 @@ func (m *mmapper) Munmap(data []byte) (err error) {
return nil
}
+func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) {
+ return mapper.Mmap(fd, offset, length, prot, flags)
+}
+
+func Munmap(b []byte) (err error) {
+ return mapper.Munmap(b)
+}
+
func Read(fd int, p []byte) (n int, err error) {
n, err = read(fd, p)
if raceenabled {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index b775b9a..fe099f7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,7 +1,7 @@
-# github.com/antchfx/htmlquery v1.3.0
+# github.com/antchfx/htmlquery v1.3.1
## explicit; go 1.14
github.com/antchfx/htmlquery
-# github.com/antchfx/xpath v1.2.5
+# github.com/antchfx/xpath v1.3.0
## explicit; go 1.14
github.com/antchfx/xpath
# github.com/goccy/go-json v0.10.2
@@ -24,7 +24,7 @@ github.com/golang/snappy
# github.com/json-iterator/go v1.1.12
## explicit; go 1.12
github.com/json-iterator/go
-# github.com/labstack/echo/v4 v4.11.4
+# github.com/labstack/echo/v4 v4.12.0
## explicit; go 1.18
github.com/labstack/echo/v4
# github.com/labstack/gommon v0.4.2
@@ -77,11 +77,11 @@ github.com/valyala/bytebufferpool
# github.com/valyala/fasttemplate v1.2.2
## explicit; go 1.12
github.com/valyala/fasttemplate
-# golang.org/x/crypto v0.21.0
+# golang.org/x/crypto v0.22.0
## explicit; go 1.18
golang.org/x/crypto/acme
golang.org/x/crypto/acme/autocert
-# golang.org/x/net v0.22.0
+# golang.org/x/net v0.24.0
## explicit; go 1.18
golang.org/x/net/html
golang.org/x/net/html/atom
@@ -91,7 +91,7 @@ golang.org/x/net/http2
golang.org/x/net/http2/h2c
golang.org/x/net/http2/hpack
golang.org/x/net/idna
-# golang.org/x/sys v0.18.0
+# golang.org/x/sys v0.19.0
## explicit; go 1.18
golang.org/x/sys/unix
# golang.org/x/text v0.14.0
@@ -122,7 +122,7 @@ gopkg.in/ini.v1
# xorm.io/builder v0.3.13
## explicit; go 1.11
xorm.io/builder
-# xorm.io/xorm v1.3.8
+# xorm.io/xorm v1.3.9
## explicit; go 1.18
xorm.io/xorm
xorm.io/xorm/caches
diff --git a/vendor/xorm.io/xorm/convert/time.go b/vendor/xorm.io/xorm/convert/time.go
index c923e95..8447214 100644
--- a/vendor/xorm.io/xorm/convert/time.go
+++ b/vendor/xorm.io/xorm/convert/time.go
@@ -28,14 +28,19 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' {
+ if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") {
+ return &time.Time{}, nil
+ }
dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation)
if err != nil {
return nil, err
}
dt = dt.In(convertedLocation)
- dt.IsZero()
return &dt, nil
} else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' {
+ if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") {
+ return &time.Time{}, nil
+ }
dt, err := time.Parse(time.RFC3339, s)
if err != nil {
return nil, err
@@ -43,6 +48,10 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) >= 21 && s[10] == 'T' && s[19] == '.' {
+ if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) ||
+ strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) {
+ return &time.Time{}, nil
+ }
dt, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
return nil, err
@@ -50,6 +59,10 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) >= 21 && s[19] == '.' {
+ if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) ||
+ strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) {
+ return &time.Time{}, nil
+ }
layout := "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20)
dt, err := time.ParseInLocation(layout, s, originalLocation)
if err != nil {
@@ -68,20 +81,20 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) == 8 && s[2] == ':' && s[5] == ':' {
- currentDate := time.Now()
dt, err := time.ParseInLocation("15:04:05", s, originalLocation)
if err != nil {
return nil, err
}
- // add current date for correct time locations
- dt = dt.AddDate(currentDate.Year(), int(currentDate.Month()), currentDate.Day())
- dt = dt.In(convertedLocation)
+ dt = dt.AddDate(2006, 01, 02).In(convertedLocation)
// back to zero year
- dt = dt.AddDate(-currentDate.Year(), int(-currentDate.Month()), -currentDate.Day())
+ dt = dt.AddDate(-2006, -01, -02)
return &dt, nil
} else {
i, err := strconv.ParseInt(s, 10, 64)
if err == nil {
+ if i == 0 {
+ return &time.Time{}, nil
+ }
tm := time.Unix(i, 0).In(convertedLocation)
return &tm, nil
}
@@ -108,6 +121,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
if !t.Valid {
return nil, nil
}
+ if utils.IsTimeZero(t.Time) {
+ return &time.Time{}, nil
+ }
z, _ := t.Time.Zone()
if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() {
tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(),
@@ -117,6 +133,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
tm := t.Time.In(uiLoc)
return &tm, nil
case *time.Time:
+ if utils.IsTimeZero(*t) {
+ return &time.Time{}, nil
+ }
z, _ := t.Zone()
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
@@ -126,6 +145,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
tm := t.In(uiLoc)
return &tm, nil
case time.Time:
+ if utils.IsTimeZero(t) {
+ return &time.Time{}, nil
+ }
z, _ := t.Zone()
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
@@ -135,12 +157,21 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
tm := t.In(uiLoc)
return &tm, nil
case int:
+ if t == 0 {
+ return &time.Time{}, nil
+ }
tm := time.Unix(int64(t), 0).In(uiLoc)
return &tm, nil
case int64:
+ if t == 0 {
+ return &time.Time{}, nil
+ }
tm := time.Unix(t, 0).In(uiLoc)
return &tm, nil
case *sql.NullInt64:
+ if t.Int64 == 0 {
+ return &time.Time{}, nil
+ }
tm := time.Unix(t.Int64, 0).In(uiLoc)
return &tm, nil
}
diff --git a/vendor/xorm.io/xorm/dialects/dameng.go b/vendor/xorm.io/xorm/dialects/dameng.go
index 907b881..d1120fe 100644
--- a/vendor/xorm.io/xorm/dialects/dameng.go
+++ b/vendor/xorm.io/xorm/dialects/dameng.go
@@ -618,8 +618,8 @@ func (db *dameng) SQLType(c *schemas.Column) string {
res = t
}
- hasLen1 := (c.Length > 0)
- hasLen2 := (c.Length2 > 0)
+ hasLen1 := c.Length > 0
+ hasLen2 := c.Length2 > 0
if hasLen2 {
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
diff --git a/vendor/xorm.io/xorm/dialects/mssql.go b/vendor/xorm.io/xorm/dialects/mssql.go
index 13399ed..a7a8da1 100644
--- a/vendor/xorm.io/xorm/dialects/mssql.go
+++ b/vendor/xorm.io/xorm/dialects/mssql.go
@@ -330,15 +330,11 @@ func (db *mssql) SQLType(c *schemas.Column) string {
res += "(MAX)"
}
case schemas.TimeStamp, schemas.DateTime:
- if c.Length > 3 {
- res = "DATETIME2"
- } else {
- return schemas.DateTime
- }
+ return "DATETIME2"
case schemas.TimeStampz:
res = "DATETIMEOFFSET"
c.Length = 7
- case schemas.MediumInt, schemas.TinyInt, schemas.SmallInt, schemas.UnsignedMediumInt, schemas.UnsignedTinyInt, schemas.UnsignedSmallInt:
+ case schemas.MediumInt, schemas.SmallInt, schemas.UnsignedMediumInt, schemas.UnsignedTinyInt, schemas.UnsignedSmallInt:
res = schemas.Int
case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json:
res = db.defaultVarchar + "(MAX)"
@@ -381,8 +377,8 @@ func (db *mssql) SQLType(c *schemas.Column) string {
return res
}
- hasLen1 := (c.Length > 0)
- hasLen2 := (c.Length2 > 0)
+ hasLen1 := c.Length > 0
+ hasLen2 := c.Length2 > 0
if hasLen2 {
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
diff --git a/vendor/xorm.io/xorm/dialects/mysql.go b/vendor/xorm.io/xorm/dialects/mysql.go
index 2c061a1..d11c728 100644
--- a/vendor/xorm.io/xorm/dialects/mysql.go
+++ b/vendor/xorm.io/xorm/dialects/mysql.go
@@ -326,8 +326,8 @@ func (db *mysql) SQLType(c *schemas.Column) string {
res = t
}
- hasLen1 := (c.Length > 0)
- hasLen2 := (c.Length2 > 0)
+ hasLen1 := c.Length > 0
+ hasLen2 := c.Length2 > 0
if res == schemas.BigInt && !hasLen1 && !hasLen2 {
c.Length = 20
diff --git a/vendor/xorm.io/xorm/dialects/oracle.go b/vendor/xorm.io/xorm/dialects/oracle.go
index ac0fb94..5f614b1 100644
--- a/vendor/xorm.io/xorm/dialects/oracle.go
+++ b/vendor/xorm.io/xorm/dialects/oracle.go
@@ -585,8 +585,8 @@ func (db *oracle) SQLType(c *schemas.Column) string {
res = t
}
- hasLen1 := (c.Length > 0)
- hasLen2 := (c.Length2 > 0)
+ hasLen1 := c.Length > 0
+ hasLen2 := c.Length2 > 0
if hasLen2 {
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
diff --git a/vendor/xorm.io/xorm/dialects/postgres.go b/vendor/xorm.io/xorm/dialects/postgres.go
index 9957445..6e37958 100644
--- a/vendor/xorm.io/xorm/dialects/postgres.go
+++ b/vendor/xorm.io/xorm/dialects/postgres.go
@@ -957,8 +957,8 @@ func (db *postgres) SQLType(c *schemas.Column) string {
// for bool, we don't need length information
return res
}
- hasLen1 := (c.Length > 0)
- hasLen2 := (c.Length2 > 0)
+ hasLen1 := c.Length > 0
+ hasLen2 := c.Length2 > 0
if hasLen2 {
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
@@ -1185,7 +1185,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r' AND c.relname = $1%s AND f.a
col.IsPrimaryKey = true
}
- col.Nullable = (isNullable == "YES")
+ col.Nullable = isNullable == "YES"
switch strings.ToLower(dataType) {
case "character varying", "string":
diff --git a/vendor/xorm.io/xorm/dialects/time.go b/vendor/xorm.io/xorm/dialects/time.go
index cdc896b..4a6beb7 100644
--- a/vendor/xorm.io/xorm/dialects/time.go
+++ b/vendor/xorm.io/xorm/dialects/time.go
@@ -7,20 +7,23 @@ package dialects
import (
"strings"
"time"
+ "xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
)
// FormatColumnTime format column time
func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.Column, t time.Time) (interface{}, error) {
- if t.IsZero() {
+ if utils.IsTimeZero(t) {
if col.Nullable {
return nil, nil
}
-
if col.SQLType.IsNumeric() {
return 0, nil
}
+ if col.SQLType.Name == schemas.TimeStamp || col.SQLType.Name == schemas.TimeStampz {
+ t = time.Unix(0, 0)
+ }
}
tmZone := dbLocation
diff --git a/vendor/xorm.io/xorm/engine.go b/vendor/xorm.io/xorm/engine.go
index e16bb3e..bb7d232 100644
--- a/vendor/xorm.io/xorm/engine.go
+++ b/vendor/xorm.io/xorm/engine.go
@@ -1212,7 +1212,7 @@ func (engine *Engine) Insert(beans ...interface{}) (int64, error) {
func (engine *Engine) InsertOne(bean interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
- return session.InsertOne(bean)
+ return session.Insert(bean)
}
// Update records, bean's non-empty fields are updated contents,
diff --git a/vendor/xorm.io/xorm/internal/statements/order_by.go b/vendor/xorm.io/xorm/internal/statements/order_by.go
index 54a3c6e..04197fb 100644
--- a/vendor/xorm.io/xorm/internal/statements/order_by.go
+++ b/vendor/xorm.io/xorm/internal/statements/order_by.go
@@ -50,7 +50,7 @@ var ErrNoColumnName = errors.New("no column name")
func (statement *Statement) writeOrderBy(w *builder.BytesWriter, orderBy orderBy) error {
switch t := orderBy.orderStr.(type) {
- case (*builder.Expression):
+ case *builder.Expression:
if _, err := fmt.Fprint(w.Builder, statement.dialect.Quoter().Replace(t.Content())); err != nil {
return err
}
diff --git a/vendor/xorm.io/xorm/internal/statements/statement.go b/vendor/xorm.io/xorm/internal/statements/statement.go
index dd4024b..734cc19 100644
--- a/vendor/xorm.io/xorm/internal/statements/statement.go
+++ b/vendor/xorm.io/xorm/internal/statements/statement.go
@@ -170,7 +170,7 @@ func (statement *Statement) Reset() {
// SQL adds raw sql statement
func (statement *Statement) SQL(query interface{}, args ...interface{}) *Statement {
switch t := query.(type) {
- case (*builder.Builder):
+ case *builder.Builder:
var err error
statement.RawSQL, statement.RawParams, err = t.ToSQL()
if err != nil {
@@ -616,7 +616,7 @@ func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, i
// MergeConds merge conditions from bean and id
func (statement *Statement) MergeConds(bean interface{}) error {
if !statement.NoAutoCondition && statement.RefTable != nil {
- addedTableName := (len(statement.joins) > 0)
+ addedTableName := len(statement.joins) > 0
autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName)
if err != nil {
return err
@@ -713,11 +713,14 @@ func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond {
cond := builder.NewCond()
if col.SQLType.IsNumeric() {
cond = builder.Eq{colName: 0}
- } else {
- // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value.
- if statement.dialect.URI().DBType != schemas.MSSQL {
- cond = builder.Eq{colName: utils.ZeroTime1}
+ } else if col.SQLType.Name == schemas.TimeStamp || col.SQLType.Name == schemas.TimeStampz {
+ tmZone := statement.defaultTimeZone
+ if col.TimeZone != nil {
+ tmZone = col.TimeZone
}
+ cond = builder.Eq{colName: time.Unix(0, 0).In(tmZone).Format("2006-01-02 15:04:05.999999999")}
+ } else {
+ cond = builder.Eq{colName: utils.ZeroTime1}
}
if col.Nullable {
diff --git a/vendor/xorm.io/xorm/internal/statements/update.go b/vendor/xorm.io/xorm/internal/statements/update.go
index 61342e3..34c6111 100644
--- a/vendor/xorm.io/xorm/internal/statements/update.go
+++ b/vendor/xorm.io/xorm/internal/statements/update.go
@@ -126,6 +126,9 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
if fieldValue.CanAddr() {
if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok {
+ if utils.IsZero(fieldValue.Interface()) {
+ continue
+ }
data, err := structConvert.ToDB()
if err != nil {
return nil, nil, err
diff --git a/vendor/xorm.io/xorm/internal/utils/zero.go b/vendor/xorm.io/xorm/internal/utils/zero.go
index 007e3c3..22c2407 100644
--- a/vendor/xorm.io/xorm/internal/utils/zero.go
+++ b/vendor/xorm.io/xorm/internal/utils/zero.go
@@ -146,6 +146,6 @@ const (
// IsTimeZero return true if a time is zero
func IsTimeZero(t time.Time) bool {
- return t.IsZero() || t.Format("2006-01-02 15:04:05") == ZeroTime0 ||
- t.Format("2006-01-02 15:04:05") == ZeroTime1
+ return t.IsZero() || t.Format("2006-01-02 15:04:05.999999999") == ZeroTime0 ||
+ t.Format("2006-01-02 15:04:05.999999999") == ZeroTime1
}
diff --git a/vendor/xorm.io/xorm/names/mapper.go b/vendor/xorm.io/xorm/names/mapper.go
index 69f6717..4f5910e 100644
--- a/vendor/xorm.io/xorm/names/mapper.go
+++ b/vendor/xorm.io/xorm/names/mapper.go
@@ -149,7 +149,7 @@ func isASCIIUpper(r rune) bool {
func toASCIIUpper(r rune) rune {
if 'a' <= r && r <= 'z' {
- r -= ('a' - 'A')
+ r -= 'a' - 'A'
}
return r
}
diff --git a/vendor/xorm.io/xorm/session_insert.go b/vendor/xorm.io/xorm/session_insert.go
index 7003e0f..7cc1524 100644
--- a/vendor/xorm.io/xorm/session_insert.go
+++ b/vendor/xorm.io/xorm/session_insert.go
@@ -471,7 +471,8 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac
}
if col.IsDeleted {
- arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, time.Time{})
+ zeroTime := time.Date(1, 1, 1, 0, 0, 0, 0, session.engine.DatabaseTZ)
+ arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, zeroTime)
if err != nil {
return nil, nil, err
}
diff --git a/vendor/xorm.io/xorm/sync.go b/vendor/xorm.io/xorm/sync.go
index adc2d85..f1b5e59 100644
--- a/vendor/xorm.io/xorm/sync.go
+++ b/vendor/xorm.io/xorm/sync.go
@@ -17,6 +17,8 @@ type SyncOptions struct {
IgnoreConstrains bool
// IgnoreIndices will not add or delete indices
IgnoreIndices bool
+ // IgnoreDropIndices will not delete indices
+ IgnoreDropIndices bool
}
type SyncResult struct{}
@@ -55,6 +57,7 @@ func (session *Session) Sync(beans ...interface{}) error {
WarnIfDatabaseColumnMissed: false,
IgnoreConstrains: false,
IgnoreIndices: false,
+ IgnoreDropIndices: false,
}, beans...)
return err
}
@@ -244,7 +247,7 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
for name2, index2 := range oriTable.Indexes {
if _, ok := foundIndexNames[name2]; !ok {
// ignore based on there type
- if (index2.Type == schemas.IndexType && opts.IgnoreIndices) ||
+ if (index2.Type == schemas.IndexType && (opts.IgnoreIndices || opts.IgnoreDropIndices)) ||
(index2.Type == schemas.UniqueType && opts.IgnoreConstrains) {
// make sure we do not add a index with same name later
delete(addedNames, name2)
diff --git a/vendor/xorm.io/xorm/tags/tag.go b/vendor/xorm.io/xorm/tags/tag.go
index 024c9c1..55f0b7c 100644
--- a/vendor/xorm.io/xorm/tags/tag.go
+++ b/vendor/xorm.io/xorm/tags/tag.go
@@ -163,7 +163,7 @@ func PKTagHandler(ctx *Context) error {
// NULLTagHandler describes null tag handler
func NULLTagHandler(ctx *Context) error {
- ctx.col.Nullable = (strings.ToUpper(ctx.preTag) != "NOT")
+ ctx.col.Nullable = strings.ToUpper(ctx.preTag) != "NOT"
return nil
}