From 1d038e87c71d5a0d71dbee51b7485d673186a43b Mon Sep 17 00:00:00 2001 From: fschade Date: Tue, 30 Sep 2025 15:42:51 +0200 Subject: [PATCH] fix(antivirus): update icap-client library which fixes tcp socket reuse --- go.mod | 4 +- go.sum | 4 +- services/antivirus/pkg/scanners/icap.go | 2 +- services/antivirus/pkg/scanners/icap_test.go | 3 +- .../antivirus/pkg/scanners/mocks/scanner.go | 4 +- .../github.com/egirna/icap-client/.travis.yml | 19 --- .../egirna/icap-client/CONTRIBUTING.md | 9 -- .../github.com/egirna/icap-client/README.md | 84 ----------- vendor/github.com/egirna/icap-client/conn.go | 140 ------------------ .../opencloud-eu/icap-client/.editorconfig | 20 +++ .../opencloud-eu/icap-client/.gitattributes | 1 + .../icap-client/.gitignore | 4 - .../opencloud-eu/icap-client/.golangci.yaml | 76 ++++++++++ .../icap-client/LICENSE | 0 .../opencloud-eu/icap-client/Makefile | 19 +++ .../opencloud-eu/icap-client/README.md | 18 +++ .../icap-client/client.go | 44 +++--- .../icap-client/config.go | 8 +- .../opencloud-eu/icap-client/conn.go | 109 ++++++++++++++ .../icap-client/doc.go | 0 .../icap-client/icap_client.go | 83 +++++------ .../icap-client/request.go | 11 +- .../icap-client/test_server.go | 2 - vendor/modules.txt | 7 +- 24 files changed, 318 insertions(+), 353 deletions(-) delete mode 100644 vendor/github.com/egirna/icap-client/.travis.yml delete mode 100644 vendor/github.com/egirna/icap-client/CONTRIBUTING.md delete mode 100644 vendor/github.com/egirna/icap-client/README.md delete mode 100644 vendor/github.com/egirna/icap-client/conn.go create mode 100644 vendor/github.com/opencloud-eu/icap-client/.editorconfig create mode 100644 vendor/github.com/opencloud-eu/icap-client/.gitattributes rename vendor/github.com/{egirna => opencloud-eu}/icap-client/.gitignore (72%) create mode 100644 vendor/github.com/opencloud-eu/icap-client/.golangci.yaml rename vendor/github.com/{egirna => opencloud-eu}/icap-client/LICENSE (100%) create mode 100644 vendor/github.com/opencloud-eu/icap-client/Makefile create mode 100644 vendor/github.com/opencloud-eu/icap-client/README.md rename vendor/github.com/{egirna => opencloud-eu}/icap-client/client.go (58%) rename vendor/github.com/{egirna => opencloud-eu}/icap-client/config.go (85%) create mode 100644 vendor/github.com/opencloud-eu/icap-client/conn.go rename vendor/github.com/{egirna => opencloud-eu}/icap-client/doc.go (100%) rename vendor/github.com/{egirna => opencloud-eu}/icap-client/icap_client.go (88%) rename vendor/github.com/{egirna => opencloud-eu}/icap-client/request.go (94%) rename vendor/github.com/{egirna => opencloud-eu}/icap-client/test_server.go (99%) diff --git a/go.mod b/go.mod index 982d25d175..98349573c3 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,6 @@ require ( github.com/davidbyttow/govips/v2 v2.16.0 github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e - github.com/egirna/icap-client v0.1.1 github.com/gabriel-vasile/mimetype v1.4.10 github.com/ggwhite/go-masker v1.1.0 github.com/go-chi/chi/v5 v5.2.3 @@ -64,6 +63,7 @@ require ( github.com/onsi/ginkgo/v2 v2.25.3 github.com/onsi/gomega v1.38.2 github.com/open-policy-agent/opa v1.9.0 + github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 github.com/opencloud-eu/reva/v2 v2.38.1-0.20250924125540-eaa2437c36b2 github.com/opensearch-project/opensearch-go/v4 v4.5.0 @@ -390,8 +390,6 @@ require ( replace github.com/studio-b12/gowebdav => github.com/kobergj/gowebdav v0.0.0-20250102091030-aa65266db202 -replace github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387 - replace github.com/unrolled/secure => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c replace go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.20241115112658-b5d4de5ed9b3 diff --git a/go.sum b/go.sum index e5deba5ae5..98ccd3166b 100644 --- a/go.sum +++ b/go.sum @@ -345,8 +345,6 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= -github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387 h1:Y3wZgTr29sLxWSMz4KF91o0x87EaJF6FIPNJFepRIiw= -github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387/go.mod h1:HpntrRsQA6RKNXy2Nbr4kVj+NO3OYWpAQUVxeya+3sU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -932,6 +930,8 @@ github.com/open-policy-agent/opa v1.9.0 h1:QWFNwbcc29IRy0xwD3hRrMc/RtSersLY1Z6Ta github.com/open-policy-agent/opa v1.9.0/go.mod h1:72+lKmTda0O48m1VKAxxYl7MjP/EWFZu9fxHQK2xihs= github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M= github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY= +github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+lP5lUUIzjRGDg93WrQfZJZCaV1ZP3KeyXi8bzY= +github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI= github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI= github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q= github.com/opencloud-eu/reva/v2 v2.38.1-0.20250924125540-eaa2437c36b2 h1:e3B6KbWMjloKpqoTwTwvBLoCETRyyCDkQsqwRQMUdxc= diff --git a/services/antivirus/pkg/scanners/icap.go b/services/antivirus/pkg/scanners/icap.go index 30c2d32305..669efe148a 100644 --- a/services/antivirus/pkg/scanners/icap.go +++ b/services/antivirus/pkg/scanners/icap.go @@ -11,7 +11,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/mime" - ic "github.com/egirna/icap-client" + ic "github.com/opencloud-eu/icap-client" ) // Scanner is the interface that wraps the basic Do method diff --git a/services/antivirus/pkg/scanners/icap_test.go b/services/antivirus/pkg/scanners/icap_test.go index 220b14c954..068e26e4de 100644 --- a/services/antivirus/pkg/scanners/icap_test.go +++ b/services/antivirus/pkg/scanners/icap_test.go @@ -9,7 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - ic "github.com/egirna/icap-client" + ic "github.com/opencloud-eu/icap-client" + "github.com/opencloud-eu/opencloud/services/antivirus/pkg/scanners" "github.com/opencloud-eu/opencloud/services/antivirus/pkg/scanners/mocks" ) diff --git a/services/antivirus/pkg/scanners/mocks/scanner.go b/services/antivirus/pkg/scanners/mocks/scanner.go index d23befda2b..6e4719e1f1 100644 --- a/services/antivirus/pkg/scanners/mocks/scanner.go +++ b/services/antivirus/pkg/scanners/mocks/scanner.go @@ -5,8 +5,8 @@ package mocks import ( - "github.com/egirna/icap-client" - mock "github.com/stretchr/testify/mock" + "github.com/opencloud-eu/icap-client" + "github.com/stretchr/testify/mock" ) // NewScanner creates a new instance of Scanner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. diff --git a/vendor/github.com/egirna/icap-client/.travis.yml b/vendor/github.com/egirna/icap-client/.travis.yml deleted file mode 100644 index b96fe30e4d..0000000000 --- a/vendor/github.com/egirna/icap-client/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -dist: xenial - -language: go - -env: - - GO111MODULE=ON - -go: - - 1.21.x - -git: - depth: 1 - - -notifications: - email: true - -script: - - go test -v -count=1 . diff --git a/vendor/github.com/egirna/icap-client/CONTRIBUTING.md b/vendor/github.com/egirna/icap-client/CONTRIBUTING.md deleted file mode 100644 index bb2e836b24..0000000000 --- a/vendor/github.com/egirna/icap-client/CONTRIBUTING.md +++ /dev/null @@ -1,9 +0,0 @@ -# Contributing - -1. Fork the repo - -1. Make the change - -1. Update the README.md with details of the changes (if needed) - -1. Open your pull request against the dev branch diff --git a/vendor/github.com/egirna/icap-client/README.md b/vendor/github.com/egirna/icap-client/README.md deleted file mode 100644 index 388f6bb8bb..0000000000 --- a/vendor/github.com/egirna/icap-client/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# icap-client - -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) -[![Project status](https://img.shields.io/badge/version-0.1.0-green.svg)](https://github.com/egirna/icap-client/releases) -[![GoDoc](https://godoc.org/github.com/egirna/icap-client?status.svg)](https://godoc.org/github.com/egirna/icap-client) - - -Talk to the ICAP servers using probably the first ICAP client package in GO! - -### Installing -```console -go get -u github.com/egirna/icap-client - -``` - -### Usage - -**Import The Package** - -```go -import ic "github.com/egirna/icap-client" -``` - -**Making a simple RESPMOD call** - -```go - req, err := ic.NewRequest(context.Background(), MethodRESPMOD, "icap://:/", httpReq, httpResp) - if err != nil { - log.Fatal(err) - } - - client, err := ic.NewClient( - ic.WithICAPConnectionTimeout(5 * time.Second), - ) - if err != nil { - log.Fatal(err) - } - - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } -``` - -**Note**: `httpReq` & `httpResp` here are `*http.Response` & `*http.Request` respectively - -**Setting preview obtained from OPTIONS call** - -```go - req, err := ic.NewRequest(context.Background(), ic.MethodOPTIONS, "icap://:/", nil, nil) - if err != nil { - log.Fatal(err) - } - - client, err := ic.NewClient( - ic.WithICAPConnectionTimeout(5 * time.Second), - ) - if err != nil { - log.Fatal(err) - } - - req.SetPreview(optReq.PreviewBytes) - - // do something with req(ICAP *Request) -``` - -By default, the icap-client will dump the debugging logs to the standard output(stdout), -but you can always add your custom writer - -```go - f, _ := os.OpenFile("logs.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - ic.SetDebugOutput(f) -``` - -For more details, see the [docs](https://godoc.org/github.com/egirna/icap-client) and [examples](examples/). - - -### Contributing - -This package is still WIP, so totally open to suggestions. See the contribution guide [here](CONTRIBUTING.md). - -### License - -**icap-client** is licensed under the [Apache License](LICENSE). diff --git a/vendor/github.com/egirna/icap-client/conn.go b/vendor/github.com/egirna/icap-client/conn.go deleted file mode 100644 index 4c94fb6f27..0000000000 --- a/vendor/github.com/egirna/icap-client/conn.go +++ /dev/null @@ -1,140 +0,0 @@ -package icapclient - -import ( - "bytes" - "context" - "io" - "net" - "sync" - "syscall" - "time" -) - -// ICAPConnConfig is the configuration for the icap connection -type ICAPConnConfig struct { - // Timeout is the maximum amount of time a connection will be kept open - Timeout time.Duration -} - -// ICAPConn is the one responsible for driving the transport layer operations. We have to explicitly deal with the connection because the ICAP protocol is aware of keep alive and reconnects. -type ICAPConn struct { - tcp net.Conn - mu sync.Mutex - timeout time.Duration -} - -// NewICAPConn creates a new connection to the icap server -func NewICAPConn(conf ICAPConnConfig) (*ICAPConn, error) { - return &ICAPConn{ - timeout: conf.Timeout, - }, nil -} - -// Connect connects to the icap server -func (c *ICAPConn) Connect(ctx context.Context, address string) error { - dialer := net.Dialer{Timeout: c.timeout} - conn, err := dialer.DialContext(ctx, "tcp", address) - if err != nil { - return err - } - - c.tcp = conn - - if dialer.Timeout == 0 { - return nil - } - - deadline := time.Now().UTC().Add(dialer.Timeout) - - if err := c.tcp.SetReadDeadline(deadline); err != nil { - return err - } - - if err := c.tcp.SetWriteDeadline(deadline); err != nil { - return err - } - - return nil -} - -// Send sends a request to the icap server -func (c *ICAPConn) Send(in []byte) ([]byte, error) { - if !c.ok() { - return nil, syscall.EINVAL - } - - c.mu.Lock() - defer c.mu.Unlock() - - errChan := make(chan error) - resChan := make(chan []byte) - - go func() { - // send the message to the server - _, err := c.tcp.Write(in) - if err != nil { - errChan <- err - } - }() - - go func() { - data := make([]byte, 0) - - for { - tmp := make([]byte, 1096) - - // read the response from the server - n, err := c.tcp.Read(tmp) - - // something went wrong while reading from the server, - // send the error and exit the routine to prevent - // sending the response to resChan - if err != nil && err != io.EOF { - errChan <- err - return - } - - // EOF detected, an entire message is received - if err == io.EOF || n == 0 { - break - } - - data = append(data, tmp[:n]...) - - // explicitly breaking because the Read blocks for 100 continue message - if bytes.Equal(data, []byte(icap100ContinueMsg)) { - break - } - - // EOF detected, double crlf indicates the end of the message - if bytes.HasSuffix(data, []byte(doubleCRLF)) { - break - } - - // EOF detected, 204 no modifications and Double crlf indicate the end of the message - if bytes.Contains(data, []byte(icap204NoModsMsg)) { - break - } - } - - resChan <- data - }() - - select { - case err := <-errChan: - return nil, err - case res := <-resChan: - return res, nil - } -} - -// Close closes the tcp connection -func (c *ICAPConn) Close() error { - if !c.ok() { - return syscall.EINVAL - } - - return c.tcp.Close() -} - -func (c *ICAPConn) ok() bool { return c != nil && c.tcp != nil } diff --git a/vendor/github.com/opencloud-eu/icap-client/.editorconfig b/vendor/github.com/opencloud-eu/icap-client/.editorconfig new file mode 100644 index 0000000000..555a9414b8 --- /dev/null +++ b/vendor/github.com/opencloud-eu/icap-client/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false +indent_size = 1 + +[Makefile] +indent_style = tab \ No newline at end of file diff --git a/vendor/github.com/opencloud-eu/icap-client/.gitattributes b/vendor/github.com/opencloud-eu/icap-client/.gitattributes new file mode 100644 index 0000000000..6313b56c57 --- /dev/null +++ b/vendor/github.com/opencloud-eu/icap-client/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/vendor/github.com/egirna/icap-client/.gitignore b/vendor/github.com/opencloud-eu/icap-client/.gitignore similarity index 72% rename from vendor/github.com/egirna/icap-client/.gitignore rename to vendor/github.com/opencloud-eu/icap-client/.gitignore index a4e3acab18..5a17b3273a 100644 --- a/vendor/github.com/egirna/icap-client/.gitignore +++ b/vendor/github.com/opencloud-eu/icap-client/.gitignore @@ -11,10 +11,6 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Dependency directories (remove the comment below to include it) -vendor/ -go.sum - # Misc .DS_Store logs.txt diff --git a/vendor/github.com/opencloud-eu/icap-client/.golangci.yaml b/vendor/github.com/opencloud-eu/icap-client/.golangci.yaml new file mode 100644 index 0000000000..dfb6f28b83 --- /dev/null +++ b/vendor/github.com/opencloud-eu/icap-client/.golangci.yaml @@ -0,0 +1,76 @@ +linters-settings: + gofmt: + simplify: true + rewrite-rules: + - pattern: interface{} + replacement: any + misspell: + locale: US + gofumpt: + extra-rules: true + forbidigo: + forbid: + - context\.WithCancel$ + - ^print.*$ + - panic + - ^log.Fatal().*$ + errorlint: + errorf-multi: true + gci: + custom-order: true + sections: + - standard + - default + - prefix(github.com/opencloud-eu/woodpecker-ci-config-service) + godot: + scope: toplevel + period: true + +linters: + disable-all: true + enable: + - bidichk + - errcheck + - gofmt + - gosimple + - goimports + - govet + - ineffassign + - misspell + - revive + - staticcheck + - typecheck + - unused + - gofumpt + - errorlint + - forbidigo + - zerologlint + - asciicheck + - dogsled + - durationcheck + - errchkjson + - goheader + - gomoddirectives + - gomodguard + - goprintffuncname + - importas + - makezero + - rowserrcheck + - sqlclosecheck + - usetesting + - unconvert + - unparam + - wastedassign + - whitespace + - gocritic + - nolintlint + - stylecheck + - contextcheck + - forcetypeassert + - gci + - godot + +run: + timeout: 15m + build-tags: + - test diff --git a/vendor/github.com/egirna/icap-client/LICENSE b/vendor/github.com/opencloud-eu/icap-client/LICENSE similarity index 100% rename from vendor/github.com/egirna/icap-client/LICENSE rename to vendor/github.com/opencloud-eu/icap-client/LICENSE diff --git a/vendor/github.com/opencloud-eu/icap-client/Makefile b/vendor/github.com/opencloud-eu/icap-client/Makefile new file mode 100644 index 0000000000..070bc8bc1b --- /dev/null +++ b/vendor/github.com/opencloud-eu/icap-client/Makefile @@ -0,0 +1,19 @@ +.PHONY: lint +lint: + go tool golangci-lint run + +.PHONY: vendor +vendor: + go mod tidy + go mod vendor + +format: + go tool gofumpt -extra -w . + +.PHONY: clean +clean: + go clean -i ./... + +.PHONY: test +test: + go test -race -cover -coverprofile coverage.out -timeout 60s . diff --git a/vendor/github.com/opencloud-eu/icap-client/README.md b/vendor/github.com/opencloud-eu/icap-client/README.md new file mode 100644 index 0000000000..8c9129ab8e --- /dev/null +++ b/vendor/github.com/opencloud-eu/icap-client/README.md @@ -0,0 +1,18 @@ +# icap-client + +[![status-badge](https://ci.opencloud.eu/api/badges/21/status.svg)](https://ci.opencloud.eu/repos/21) +[![Matrix](https://img.shields.io/matrix/opencloud%3Amatrix.org?logo=matrix)](https://app.element.io/#/room/#opencloud:matrix.org) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +## Contributing + +Please follow the [OpenCloud Contribution Guidelines](https://github.com/opencloud-eu/opencloud/blob/main/CONTRIBUTING.md), which apply to all projects in the OpenCloud organization. + +## Acknowledgements + +This project is forked from [@egirna/icap-client](https://github.com/egirna/icap-client). +Special thanks to [egirna](https://github.com/egirna) for the original implementation and ongoing inspiration. + +### License + +**icap-client** is licensed under the [Apache License](LICENSE). diff --git a/vendor/github.com/egirna/icap-client/client.go b/vendor/github.com/opencloud-eu/icap-client/client.go similarity index 58% rename from vendor/github.com/egirna/icap-client/client.go rename to vendor/github.com/opencloud-eu/icap-client/client.go index 50943daadb..86631a3ecd 100644 --- a/vendor/github.com/egirna/icap-client/client.go +++ b/vendor/github.com/opencloud-eu/icap-client/client.go @@ -8,49 +8,43 @@ import ( "strings" ) -// Client represents the icap client who makes the icap server calls +// Client represents the ICAP client who makes the ICAP server calls. type Client struct { - conn Conn + config Config // Store config for connection parameters } -// NewClient creates a new icap client +// NewClient creates a new ICAP client (no persistent connection). func NewClient(options ...ConfigOption) (Client, error) { config := DefaultConfig() for _, option := range options { option(&config) } - - conn, err := NewICAPConn(config.ICAPConn) - if err != nil { - return Client{}, err - } - - return Client{ - conn: conn, - }, nil + return Client{config: config}, nil } -// Do is the main function of the client that makes the ICAP request -func (c *Client) Do(req Request) (res Response, err error) { - // establish connection to the icap server - err = c.conn.Connect(req.ctx, req.URL.Host) +// Do make the ICAP request, creating and dropping a connection each time. +func (c Client) Do(req Request) (res Response, err error) { + conn, err := NewICAPConn(c.config.ICAPConn) if err != nil { return Response{}, err } + + if err := conn.Connect(req.ctx, req.URL.Host); err != nil { + return Response{}, err + } defer func() { - err = errors.Join(err, c.conn.Close()) + err = errors.Join(err, conn.Close()) }() req.setDefaultRequestHeaders() - // convert the request to icap message message, err := toICAPRequest(req) if err != nil { return Response{}, err } - // send the icap message to the server - dataRes, err := c.conn.Send(message) + // send the ICAP message to the server + dataRes, err := conn.Send(message) if err != nil { return Response{}, err } @@ -60,25 +54,25 @@ func (c *Client) Do(req Request) (res Response, err error) { return Response{}, err } - // check if the message is fully done scanning or if it needs to be sent another chunk + // check if the message is fully done scanning or if it needs to be sent another chunk. done := !(res.StatusCode == http.StatusContinue && !req.bodyFittedInPreview && req.previewSet) if done { return res, nil } - // get the remaining body bytes + // get the remaining body bytes. data := req.remainingPreviewBytes if !bodyIsChunked(string(data)) { data = []byte(addHexBodyByteNotations(string(data))) } - // hydrate the icap message with closing doubleCRLF suffix + // hydrate the ICAP message with closing doubleCRLF suffix. if !bytes.HasSuffix(data, []byte(doubleCRLF)) { data = append(data, []byte(crlf)...) } - // send the remaining body bytes to the server - dataRes, err = c.conn.Send(data) + // send the remaining body bytes to the server. + dataRes, err = conn.Send(data) if err != nil { return Response{}, err } diff --git a/vendor/github.com/egirna/icap-client/config.go b/vendor/github.com/opencloud-eu/icap-client/config.go similarity index 85% rename from vendor/github.com/egirna/icap-client/config.go rename to vendor/github.com/opencloud-eu/icap-client/config.go index 94f023a6f6..4d4dcf8ef6 100644 --- a/vendor/github.com/egirna/icap-client/config.go +++ b/vendor/github.com/opencloud-eu/icap-client/config.go @@ -4,12 +4,12 @@ import ( "time" ) -// Config is the shared configuration for the icap client library +// Config is the shared configuration for the icap client library. type Config struct { ICAPConn ICAPConnConfig } -// DefaultConfig returns the default configuration for the icap client library +// DefaultConfig returns the default configuration for the icap client library. func DefaultConfig() Config { return Config{ ICAPConn: ICAPConnConfig{ @@ -18,10 +18,10 @@ func DefaultConfig() Config { } } -// ConfigOption is a function that configures the icap client +// ConfigOption is a function that configures the icap client. type ConfigOption func(*Config) -// WithICAPConnectionTimeout sets the timeout for the connection to the icap server +// WithICAPConnectionTimeout sets the timeout for the connection to the icap server. func WithICAPConnectionTimeout(timeout time.Duration) ConfigOption { return func(cfg *Config) { if timeout <= 0 { diff --git a/vendor/github.com/opencloud-eu/icap-client/conn.go b/vendor/github.com/opencloud-eu/icap-client/conn.go new file mode 100644 index 0000000000..e0538cd61c --- /dev/null +++ b/vendor/github.com/opencloud-eu/icap-client/conn.go @@ -0,0 +1,109 @@ +package icapclient + +import ( + "bytes" + "context" + "errors" + "io" + "net" + "sync" + "time" +) + +// ICAPConnConfig is the configuration for the icap connection. +type ICAPConnConfig struct { + // Timeout is the maximum amount of time a connection will be kept open + Timeout time.Duration +} + +// ICAPConn manages the transport layer for ICAP protocol. +type ICAPConn struct { + tcp net.Conn + mu sync.Mutex + timeout time.Duration +} + +// NewICAPConn creates a new connection configuration. +func NewICAPConn(conf ICAPConnConfig) (*ICAPConn, error) { + return &ICAPConn{ + timeout: conf.Timeout, + }, nil +} + +// Connect connects to the ICAP server. +func (c *ICAPConn) Connect(ctx context.Context, address string) error { + dialer := net.Dialer{Timeout: c.timeout} + conn, err := dialer.DialContext(ctx, "tcp", address) + if err != nil { + return err + } + c.tcp = conn + + if c.timeout > 0 { + deadline := time.Now().Add(c.timeout) + if err := c.tcp.SetDeadline(deadline); err != nil { + return err + } + } + + return nil +} + +// Send sends a request to the ICAP server and reads the response. +func (c *ICAPConn) Send(in []byte) ([]byte, error) { + if !c.ok() { + return nil, ErrInvalidConnection + } + + c.mu.Lock() + defer c.mu.Unlock() + + _, err := c.tcp.Write(in) + if err != nil { + return nil, err + } + + var data []byte + buf := make([]byte, 4096) + for { + n, err := c.tcp.Read(buf) + if err != nil && !errors.Is(err, io.EOF) { + return nil, err + } + + if errors.Is(err, io.EOF) || n == 0 { + break + } + + data = append(data, buf[:n]...) + + // Protocol checks for message termination + { + if bytes.Equal(data, []byte(icap100ContinueMsg)) { + break + } + + if bytes.HasSuffix(data, []byte(doubleCRLF)) { + break + } + + if bytes.Contains(data, []byte(icap204NoModsMsg)) { + break + } + } + } + + return data, nil +} + +// Close closes the TCP connection. +func (c *ICAPConn) Close() error { + if !c.ok() { + return ErrInvalidConnection + } + return c.tcp.Close() +} + +func (c *ICAPConn) ok() bool { + return c != nil && c.tcp != nil +} diff --git a/vendor/github.com/egirna/icap-client/doc.go b/vendor/github.com/opencloud-eu/icap-client/doc.go similarity index 100% rename from vendor/github.com/egirna/icap-client/doc.go rename to vendor/github.com/opencloud-eu/icap-client/doc.go diff --git a/vendor/github.com/egirna/icap-client/icap_client.go b/vendor/github.com/opencloud-eu/icap-client/icap_client.go similarity index 88% rename from vendor/github.com/egirna/icap-client/icap_client.go rename to vendor/github.com/opencloud-eu/icap-client/icap_client.go index 9f14b144da..e075e53a44 100644 --- a/vendor/github.com/egirna/icap-client/icap_client.go +++ b/vendor/github.com/opencloud-eu/icap-client/icap_client.go @@ -13,41 +13,44 @@ import ( "strings" ) -// the icap request methods +// the icap request methods. const ( MethodOPTIONS = "OPTIONS" MethodRESPMOD = "RESPMOD" MethodREQMOD = "REQMOD" ) -// shared errors +// shared errors. var ( - // ErrNoContext is used when no context is provided + // ErrNoContext is used when no context is provided. ErrNoContext = errors.New("no context provided") - // ErrInvalidScheme is used when the url scheme is not icap:// + // ErrInvalidScheme is used when the url scheme is not icap://. ErrInvalidScheme = errors.New("the url scheme must be icap://") - // ErrMethodNotAllowed is used when the method is not allowed + // ErrMethodNotAllowed is used when the method is not allowed. ErrMethodNotAllowed = errors.New("the requested method is not registered") - // ErrInvalidHost is used when the host is invalid + // ErrInvalidHost is used when the host is invalid. ErrInvalidHost = errors.New("the requested host is invalid") - // ErrInvalidTCPMsg is used when the tcp message is invalid - ErrInvalidTCPMsg = errors.New("invalid tcp message") + // ErrInvalidMsg is used when the tcp message is invalid. + ErrInvalidMsg = errors.New("invalid message") - // ErrREQMODWithoutReq is used when the request is nil for REQMOD method + // ErrInvalidConnection is used when the connection is invalid. + ErrInvalidConnection = errors.New("invalid connection") + + // ErrREQMODWithoutReq is used when the request is nil for REQMOD method. ErrREQMODWithoutReq = errors.New("http request cannot be nil for method REQMOD") - // ErrREQMODWithResp is used when the response is not nil for REQMOD method + // ErrREQMODWithResp is used when the response is not nil for REQMOD method. ErrREQMODWithResp = errors.New("http response must be nil for method REQMOD") - // ErrRESPMODWithoutResp is used when the response is nil for RESPMOD method + // ErrRESPMODWithoutResp is used when the response is nil for RESPMOD method. ErrRESPMODWithoutResp = errors.New("http response cannot be nil for method RESPMOD") ) -// general constants required for the package +// general constants required for the package. const ( schemeICAP = "icap" icapVersion = "ICAP/1.0" @@ -63,20 +66,20 @@ const ( icap204NoModsMsg = "ICAP/1.0 204 Unmodified" ) -// Common ICAP headers +// Common ICAP headers. const ( previewHeader = "Preview" encapsulatedHeader = "Encapsulated" ) -// Conn represents the connection to the icap server +// Conn represents the connection to the icap server. type Conn interface { io.Closer Connect(ctx context.Context, address string) error Send(in []byte) ([]byte, error) } -// Response represents the icap server response data +// Response represents the icap server response data. type Response struct { StatusCode int Status string @@ -86,10 +89,9 @@ type Response struct { ContentResponse *http.Response } -// getStatusWithCode prepares the status code and status text from two given strings +// getStatusWithCode prepares the status code and status text from two given strings. func getStatusWithCode(str1, str2 string) (int, string, error) { statusCode, err := strconv.Atoi(str1) - if err != nil { return 0, "", err } @@ -99,7 +101,7 @@ func getStatusWithCode(str1, str2 string) (int, string, error) { return statusCode, status, nil } -// getHeaderValue parses the header and its value from a tcp message string +// getHeaderValue parses the header and its value from a tcp message string. func getHeaderValue(str string) (string, string) { headerValues := strings.SplitN(str, ":", 2) header := headerValues[0] @@ -109,16 +111,15 @@ func getHeaderValue(str string) (string, string) { } return header, "" - } -// isRequestLine determines if the tcp message string is a request line, i.e., the first line of the message or not +// isRequestLine determines if the tcp message string is a request line, i.e., the first line of the message or not. func isRequestLine(str string) bool { return strings.Contains(str, icapVersion) || strings.Contains(str, httpVersion) } -// setEncapsulatedHeaderValue generates the Encapsulated values and assigns to the ICAP request string -func setEncapsulatedHeaderValue(icapReqStr string, httpReqStr, httpRespStr string) string { +// setEncapsulatedHeaderValue generates the Encapsulated values and assigns to the ICAP request string. +func setEncapsulatedHeaderValue(icapReqStr, httpReqStr, httpRespStr string) string { encVal := " " if strings.HasPrefix(icapReqStr, MethodOPTIONS) { @@ -173,15 +174,14 @@ func setEncapsulatedHeaderValue(icapReqStr string, httpReqStr, httpRespStr strin encVal += fmt.Sprintf(", null-body=%d", reqEndsAt+respIndices[0][1]) } } - } // formatting the ICAP request Encapsulated header with the value return fmt.Sprintf(icapReqStr, encVal) } -// replaceRequestURIWithActualURL replaces just the escaped portion of the url with the entire URL in the dumped request message -func replaceRequestURIWithActualURL(str string, uri, url string) string { +// replaceRequestURIWithActualURL replaces just the escaped portion of the url with the entire URL in the dumped request message. +func replaceRequestURIWithActualURL(str, uri, url string) string { if uri == "" { uri = "/" } @@ -189,12 +189,12 @@ func replaceRequestURIWithActualURL(str string, uri, url string) string { return strings.Replace(str, uri, url, 1) } -// addFullBodyInPreviewIndicator adds 0; ieof\r\n\r\n which indicates the entire body fitted in the preview +// addFullBodyInPreviewIndicator adds 0; ieof\r\n\r\n which indicates the entire body fitted in the preview. func addFullBodyInPreviewIndicator(str string) string { return strings.TrimSuffix(str, doubleCRLF) + fullBodyEndIndicatorPreviewMode } -// splitBodyAndHeader separates header and body from a http message +// splitBodyAndHeader separates header and body from a http message. func splitBodyAndHeader(str string) (string, string, bool) { ss := strings.SplitN(str, doubleCRLF, 2) @@ -208,7 +208,7 @@ func splitBodyAndHeader(str string) (string, string, bool) { return headerStr, bodyStr, true } -// bodyIsChunked determines if the http body is already chunked from the origin server or not +// bodyIsChunked determines if the http body is already chunked from the origin server or not. func bodyIsChunked(str string) bool { _, bodyStr, ok := splitBodyAndHeader(str) @@ -219,7 +219,7 @@ func bodyIsChunked(str string) bool { return regexp.MustCompile(`\r\n0(\r\n)+$`).MatchString(bodyStr) } -// parsePreviewBodyBytes parses the preview portion of the body and only keeps that in the message +// parsePreviewBodyBytes parses the preview portion of the body and only keeps that in the message. func parsePreviewBodyBytes(str string, pb int) string { headerStr, bodyStr, ok := splitBodyAndHeader(str) if !ok { @@ -229,21 +229,17 @@ func parsePreviewBodyBytes(str string, pb int) string { return headerStr + doubleCRLF + bodyStr[:pb] } -// addHexBodyByteNotations adds the hexadecimal byte notations to the string, -// for example, Hello World, becomes -// b -// Hello World -// 0 +// 0. func addHexBodyByteNotations(str string) string { return fmt.Sprintf("%x%s%s%s", len([]byte(str)), crlf, str, bodyEndIndicator) } -// addHeaderAndBody merges the header and body of the http message +// addHeaderAndBody merges the header and body of the http message. func addHeaderAndBody(headerStr, bodyStr string) string { return headerStr + doubleCRLF + bodyStr } -// toICAPRequest returns the given request in its ICAP/1.x wire +// toICAPRequest returns the given request in its ICAP/1.x wire. func toICAPRequest(req Request) ([]byte, error) { // Making the ICAP message block reqStr := fmt.Sprintf("%s %s %s%s", req.Method, req.URL.String(), icapVersion, crlf) @@ -262,7 +258,6 @@ func toICAPRequest(req Request) ([]byte, error) { httpReqStr := "" if req.HTTPRequest != nil { b, err := httputil.DumpRequestOut(req.HTTPRequest, true) - if err != nil { return nil, err } @@ -282,7 +277,6 @@ func toICAPRequest(req Request) ([]byte, error) { httpReqStr = addHeaderAndBody(headerStr, bodyStr) } } - } // if the HTTP Request message block doesn't end with a \r\n\r\n, @@ -292,14 +286,12 @@ func toICAPRequest(req Request) ([]byte, error) { httpReqStr += crlf } } - } // build the HTTP Response message block httpRespStr := "" if req.HTTPResponse != nil { b, err := httputil.DumpResponse(req.HTTPResponse, true) - if err != nil { return nil, err } @@ -321,13 +313,12 @@ func toICAPRequest(req Request) ([]byte, error) { if httpRespStr != "" && !strings.HasSuffix(httpRespStr, doubleCRLF) { // if the HTTP Response message block doesn't end with a \r\n\r\n, then going to add one by force for better calculation of byte offsets httpRespStr += crlf } - } if encVal := req.Header.Get(encapsulatedHeader); encVal != "" { reqStr = fmt.Sprintf(reqStr, encVal) } else { - //populating the Encapsulated header of the ICAP message portion + // populating the Encapsulated header of the ICAP message portion reqStr = setEncapsulatedHeaderValue(reqStr, httpReqStr, httpRespStr) } @@ -345,7 +336,7 @@ func toICAPRequest(req Request) ([]byte, error) { return data, nil } -// toClientResponse reads an ICAP message and returns a Response +// toClientResponse reads an ICAP message and returns a Response. func toClientResponse(b *bufio.Reader) (Response, error) { resp := Response{ Header: make(map[string][]string), @@ -354,14 +345,13 @@ func toClientResponse(b *bufio.Reader) (Response, error) { scheme := "" httpMsg := "" for currentMsg, err := b.ReadString('\n'); err == nil || currentMsg != ""; currentMsg, err = b.ReadString('\n') { // keep reading the buffer message which is the http response message - // if the current message line if the first line of the message portion(request line) if isRequestLine(currentMsg) { ss := strings.Split(currentMsg, " ") // must contain 3 words, for example, "ICAP/1.0 200 OK" or "GET /something HTTP/1.1" if len(ss) < 3 { - return Response{}, fmt.Errorf("%w: %s", ErrInvalidTCPMsg, currentMsg) + return Response{}, fmt.Errorf("%w: %s", ErrInvalidMsg, currentMsg) } // preparing the scheme below @@ -409,7 +399,8 @@ func toClientResponse(b *bufio.Reader) (Response, error) { httpMsg += strings.TrimSpace(currentMsg) + crlf bufferEmpty := b.Buffered() == 0 - // a crlf indicates the end of the HTTP message and the buffer check is just in case the buffer ended with one last message instead of a crlf + // a crlf indicates the end of the HTTP message, + // and the buffer check is just in case the buffer ended with one last message instead of a crlf if currentMsg == crlf || bufferEmpty { request, err := http.ReadRequest(bufio.NewReader(strings.NewReader(httpMsg))) if err != nil { diff --git a/vendor/github.com/egirna/icap-client/request.go b/vendor/github.com/opencloud-eu/icap-client/request.go similarity index 94% rename from vendor/github.com/egirna/icap-client/request.go rename to vendor/github.com/opencloud-eu/icap-client/request.go index 82907c9877..0ce6573a62 100644 --- a/vendor/github.com/egirna/icap-client/request.go +++ b/vendor/github.com/opencloud-eu/icap-client/request.go @@ -13,7 +13,7 @@ import ( "strings" ) -// Request represents the icap client request data +// Request represents the icap client request data. type Request struct { Method string URL *url.URL @@ -32,7 +32,6 @@ type Request struct { // todo: method iota func NewRequest(ctx context.Context, method, urlStr string, httpReq *http.Request, httpResp *http.Response) (Request, error) { u, err := url.Parse(urlStr) - if err != nil { return Request{}, err } @@ -128,8 +127,7 @@ func (r *Request) SetPreview(maxBytes int) (err error) { return err } -// setDefaultRequestHeaders is called by the client before sending the request -// to the ICAP server to ensure all required headers are set +// to the ICAP server to ensure all required headers are set. func (r *Request) setDefaultRequestHeaders() { if _, exists := r.Header["Allow"]; !exists { r.Header.Add("Allow", "204") // assigning 204 by default if Allow not provided @@ -141,10 +139,9 @@ func (r *Request) setDefaultRequestHeaders() { } } -// extendHeader extends the current ICAP Request header with a new header +// extendHeader extends the current ICAP Request header with a new header. func (r *Request) extendHeader(hdr http.Header) error { for header, values := range hdr { - if header == previewHeader && r.previewSet { continue } @@ -174,7 +171,7 @@ func (r *Request) extendHeader(hdr http.Header) error { return nil } -// validate checks if the ICAP request is valid or not +// validate checks if the ICAP request is valid or not. func (r *Request) validate() error { var err error diff --git a/vendor/github.com/egirna/icap-client/test_server.go b/vendor/github.com/opencloud-eu/icap-client/test_server.go similarity index 99% rename from vendor/github.com/egirna/icap-client/test_server.go rename to vendor/github.com/opencloud-eu/icap-client/test_server.go index fae4aa474a..04ff284759 100644 --- a/vendor/github.com/egirna/icap-client/test_server.go +++ b/vendor/github.com/opencloud-eu/icap-client/test_server.go @@ -97,7 +97,6 @@ func respmodHandler(w icap.ResponseWriter, req *icap.Request) { } w.WriteHeader(status, nil, false) - } } @@ -134,7 +133,6 @@ func reqmodHandler(w icap.ResponseWriter, req *icap.Request) { } w.WriteHeader(status, nil, false) - } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 42a0c98773..cf8ee94ac3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -419,9 +419,6 @@ github.com/ebitengine/purego/internal/strings # github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc ## explicit github.com/egirna/icap -# github.com/egirna/icap-client v0.1.1 => github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387 -## explicit; go 1.21 -github.com/egirna/icap-client # github.com/emirpasic/gods v1.18.1 ## explicit; go 1.2 github.com/emirpasic/gods/containers @@ -1324,6 +1321,9 @@ github.com/open-policy-agent/opa/v1/types github.com/open-policy-agent/opa/v1/util github.com/open-policy-agent/opa/v1/util/decoding github.com/open-policy-agent/opa/v1/version +# github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 +## explicit; go 1.24.6 +github.com/opencloud-eu/icap-client # github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 ## explicit; go 1.18 github.com/opencloud-eu/libre-graph-api-go @@ -2660,7 +2660,6 @@ sigs.k8s.io/yaml ## explicit; go 1.13 stash.kopano.io/kgol/rndm # github.com/studio-b12/gowebdav => github.com/kobergj/gowebdav v0.0.0-20250102091030-aa65266db202 -# github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387 # github.com/unrolled/secure => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c # go-micro.dev/v4 => github.com/butonic/go-micro/v4 v4.11.1-0.20241115112658-b5d4de5ed9b3 # github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a