diff --git a/Makefile b/Makefile
index 04cfea4..e8c87f7 100755
--- a/Makefile
+++ b/Makefile
@@ -30,6 +30,16 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
sed "s/-/~/")-${OS_RELEASE}
.PHONY: debpackage debclean
+FUZZING_DIR = $(shell pwd)/tests/fuzzing/files
+NGFUZZ_REPO = https://b.yadro.com/scm/sdl/ngfuzz.git
+NGFUZZ_BRANCH = feature/go-118-fuzz-build
+GO_118_FUZZ_BUILD_REPO = b.yadro.com/obj/go-118-fuzz-build
+
+FUZZ_NGFUZZ_DIR ?= ""
+FUZZ_TIMEOUT ?= 30
+FUZZ_FUNCTIONS ?= "all"
+FUZZ_AUX ?= ""
+
# Make all binaries
all: $(BINS)
$(BINS): $(DIRS) dep
@@ -78,6 +88,39 @@ cover:
@go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
@go tool cover -html=coverage.txt -o coverage.html
+# Run fuzzing
+CLANG := $(shell which clang-17 2>/dev/null)
+
+.PHONY: check-clang all
+check-clang:
+ifeq ($(CLANG),)
+ @echo "clang-17 is not installed. Please install it before proceeding - https://apt.llvm.org/llvm.sh "
+ @exit 1
+endif
+
+.PHONY: check-ngfuzz all
+check-ngfuzz:
+ @if [ -z "$(FUZZ_NGFUZZ_DIR)" ]; then \
+ echo "Please set a variable FUZZ_NGFUZZ_DIR to specify path to the ngfuzz"; \
+ exit 1; \
+ fi
+
+.PHONY: install-fuzzing-deps
+install-fuzzing-deps: check-clang check-ngfuzz
+ifeq (,$(wildcard $(FUZZING_DIR)/go-118-fuzz-build))
+ GOBIN=$(FUZZING_DIR) go install $(GO_118_FUZZ_BUILD_REPO)
+endif
+
+.PHONY: fuzz
+fuzz: install-fuzzing-deps
+ @START_PATH=$$(pwd); \
+ ROOT_PATH=$$(realpath --relative-to=$(FUZZ_NGFUZZ_DIR) $$START_PATH) ; \
+ cd $(FUZZ_NGFUZZ_DIR) && \
+ ./ngfuzz -clean && \
+ ./ngfuzz -fuzz $(FUZZ_FUNCTIONS) -rootdir $$ROOT_PATH -timeout $(FUZZ_TIMEOUT) $(FUZZ_AUX) && \
+ ./ngfuzz -report
+
+
# Reformat code
fmt:
@echo "⇒ Processing gofmt check"
@@ -149,7 +192,8 @@ version:
# Clean up
clean:
rm -rf vendor
- rm -rf $(BINDIR)
+ rm -rf $(BINDIR)
+ rm -rf $(FUZZING_DIR)
# Package for Debian
debpackage:
diff --git a/README.md b/README.md
index aa982db..9f8d995 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
---
+
[![Report](https://goreportcard.com/badge/git.frostfs.info/TrueCloudLab/frostfs-http-gw)](https://goreportcard.com/report/git.frostfs.info/TrueCloudLab/frostfs-http-gw)
![Release](https://img.shields.io/badge/dynamic/json.svg?label=release&url=https://git.frostfs.info/api/v1/repos/TrueCloudLab/frostfs-http-gw/releases&query=$[0].tag_name&color=orange)
![License](https://img.shields.io/badge/license-GPL--3.0-orange.svg)
@@ -13,6 +14,7 @@
# FrostFS HTTP Gateway
FrostFS HTTP Gateway bridges FrostFS internal protocol and HTTP standard.
+
- you can download one file per request from the FrostFS Network
- you can upload one file per request into the FrostFS Network
@@ -56,11 +58,14 @@ you can get the IP address of the node in the output of `make hosts` command
These two commands are functionally equivalent, they run the gate with one
backend node (and otherwise default settings):
+
```
$ frostfs-http-gw -p 192.168.130.72:8080
$ HTTP_GW_PEERS_0_ADDRESS=192.168.130.72:8080 frostfs-http-gw
```
+
It's also possible to specify uri scheme (grpc or grpcs) when using `-p`:
+
```
$ frostfs-http-gw -p grpc://192.168.130.72:8080
$ HTTP_GW_PEERS_0_ADDRESS=grpcs://192.168.130.72:8080 frostfs-http-gw
@@ -80,6 +85,7 @@ gateway spread requests equally among them (using weight 1 and priority 1 for ev
```
$ frostfs-http-gw -p 192.168.130.72:8080 -p 192.168.130.71:8080
```
+
If you want some specific load distribution proportions, use weights and priorities:
```
@@ -88,17 +94,22 @@ $ HTTP_GW_PEERS_0_ADDRESS=192.168.130.71:8080 HTTP_GW_PEERS_0_WEIGHT=1 HTTP_GW_P
HTTP_GW_PEERS_2_ADDRESS=192.168.130.73:8080 HTTP_GW_PEERS_2_WEIGHT=1 HTTP_GW_PEERS_2_PRIORITY=2 \
frostfs-http-gw
```
+
This command will make gateway use 192.168.130.71 while it is healthy. Otherwise, it will make the gateway use
192.168.130.72 for 90% of requests and 192.168.130.73 for remaining 10%.
### Keys
+
You can provide a wallet via `--wallet` or `-w` flag. You can also specify the account address using `--address`
(if no address provided default one will be used). If wallet is used, you need to set `HTTP_GW_WALLET_PASSPHRASE` variable to decrypt the wallet.
If no wallet provided, the gateway autogenerates a key pair it will use for FrostFS requests.
+
```
$ frostfs-http-gw -p $FROSTFS_NODE -w $WALLET_PATH --address $ACCOUNT_ADDRESS
```
+
Example:
+
```
$ frostfs-http-gw -p 192.168.130.72:8080 -w wallet.json --address NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP
```
@@ -164,18 +175,23 @@ All timing options accept values with suffixes, so "15s" is 15 seconds and
"2m" is 2 minutes.
### Zip streaming
+
The gateway supports downloading files by common prefix (like dir) in zip format. You can enable compression
using config or `HTTP_GW_ZIP_COMPRESSION=true` environment variable.
### Logging
+
You can specify logging level using variable:
+
```
HTTP_GW_LOGGER_LEVEL=debug
```
### Yaml file
+
Configuration file is optional and can be used instead of environment variables/other parameters.
It can be specified with `--config` parameter:
+
```
$ frostfs-http-gw --config your-config.yaml
```
@@ -229,7 +245,7 @@ resolve_order:
```
2. Make sure your container is registered in NNS contract. If you use [frostfs-dev-env](https://git.frostfs.info/TrueCloudLab/frostfs-dev-env)
-you can check if your container (e.g. with `container-name` name) is registered in NNS:
+ you can check if your container (e.g. with `container-name` name) is registered in NNS:
```shell
$ curl -s --data '{"id":1,"jsonrpc":"2.0","method":"getcontractstate","params":[1]}' \
@@ -256,14 +272,17 @@ $ curl http://localhost:8082/get_by_attribute/container-name/FileName/object-nam
#### Create a container
You can create a container via [frostfs-cli](https://git.frostfs.info/TrueCloudLab/frostfs-node/releases):
+
```
$ frostfs-cli -r $FROSTFS_NODE -w $WALLET container create --policy $POLICY --basic-acl $ACL
```
-where `$WALLET` is a path to user wallet,
-`$ACL` -- hex encoded basic ACL value or keywords 'private, 'public-read', 'public-read-write' and
+
+where `$WALLET` is a path to user wallet,
+`$ACL` -- hex encoded basic ACL value or keywords 'private, 'public-read', 'public-read-write' and
`$POLICY` -- QL-encoded or JSON-encoded placement policy or path to file with it
For example:
+
```
$ frostfs-cli -r 192.168.130.72:8080 -w ./wallet.json container create --policy "REP 3" --basic-acl public --await
```
@@ -275,19 +294,21 @@ the file `wallets/wallet.key`.
#### Prepare a file in a container
To create a file via [frostfs-cli](https://git.frostfs.info/TrueCloudLab/frostfs-node/releases), run a command below:
+
```
$ frostfs-cli -r $FROSTFS_NODE -k $KEY object put --file $FILENAME --cid $CID
```
+
where
`$KEY` -- the key, please read the information [above](#create-a-container),
`$CID` -- container ID.
For example:
+
```
$ frostfs-cli -r 192.168.130.72:8080 -w ./wallet.json object put --file cat.png --cid Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ --attributes img_type=cat,my_attr=cute
```
-
### Downloading
#### Requests
@@ -313,6 +334,7 @@ $ wget http://localhost:8082/get/container-name/2m8PtaoricLouCn5zE8hAFr3gZEBDCZF
```
##### By attributes
+
There is also more complex interface provided for attribute-based downloads,
it's usually used to retrieve files by their names, but any other attribute
can be used as well. The generic syntax for it looks like this:
@@ -326,7 +348,7 @@ where
**NB!** The attribute key and value should be url encoded, i.e., if you want to download an object with the attribute value
`a cat`, the value in the request must be `a+cat`. In the same way with the attribute key. If you don't escape such values
-everything can still work (for example you can use `d@ta` without encoding) but it's HIGHLY RECOMMENDED to encode all your attributes.
+everything can still work (for example you can use `d@ta` without encoding) but it's HIGHLY RECOMMENDED to encode all your attributes.
If multiple objects have specified attribute with specified value, then the
first one of them is returned (and you can't get others via this interface).
@@ -336,7 +358,9 @@ Example for file name attribute:
```
$ wget http://localhost:8082/get_by_attribute/88GdaZFTcYJn1dqiSECss8kKPmmun6d6BfvC4zhwfLYM/FileName/cat.jpeg
```
+
Or when the filename includes special symbols:
+
```
$ wget http://localhost:8082/get_by_attribute/88GdaZFTcYJn1dqiSECss8kKPmmun6d6BfvC4zhwfLYM/FileName/cat+jpeg # means 'cat jpeg'
$ wget http://localhost:8082/get_by_attribute/88GdaZFTcYJn1dqiSECss8kKPmmun6d6BfvC4zhwfLYM/FileName/cat%25jpeg # means 'cat%jpeg'
@@ -349,6 +373,7 @@ $ wget http://localhost:8082/get_by_attribute/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit
```
Or when the attribute includes special symbols:
+
```
$ wget http://localhost:8082/get_by_attribute/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ/Olo%2Blo/100500 # means Olo+lo
```
@@ -362,7 +387,9 @@ $ wget http://localhost:8082/get/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ/2m
```
##### Zip
+
You can download some dir (files with the same prefix) in zip (it will be compressed if config contains appropriate param):
+
```
$ wget http://localhost:8082/zip/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ/common/prefix
```
@@ -374,24 +401,24 @@ otherwise they will not be in the zip archive. You can upload file with this att
$ curl -F 'file=@cat.jpeg;filename=cat.jpeg' -H 'X-Attribute-FilePath: common/prefix/cat.jpeg' http://localhost:8082/upload/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ
```
-
#### Replies
You get object contents in the reply body (if GET method was used), but at the same time you also get a
set of reply headers generated using the following rules:
- * `Content-Length` is set to the length of the object
- * `Content-Type` is autodetected dynamically by gateway
- * `Content-Disposition` is `inline` for regular requests and `attachment` for
- requests with `download=true` argument, `filename` is also added if there
- is `FileName` attribute set for this object
- * `Last-Modified` header is set to `Timestamp` attribute value if it's
- present for the object
- * `x-container-id` contains container ID
- * `x-object-id` contains object ID
- * `x-owner-id` contains owner address
- * all the other FrostFS attributes are converted to `X-Attribute-*` headers (but only
- if they can be safely represented in HTTP header), for example `FileName`
- attribute becomes `X-Attribute-FileName` header
+
+* `Content-Length` is set to the length of the object
+* `Content-Type` is autodetected dynamically by gateway
+* `Content-Disposition` is `inline` for regular requests and `attachment` for
+ requests with `download=true` argument, `filename` is also added if there
+ is `FileName` attribute set for this object
+* `Last-Modified` header is set to `Timestamp` attribute value if it's
+ present for the object
+* `x-container-id` contains container ID
+* `x-object-id` contains object ID
+* `x-owner-id` contains owner address
+* all the other FrostFS attributes are converted to `X-Attribute-*` headers (but only
+ if they can be safely represented in HTTP header), for example `FileName`
+ attribute becomes `X-Attribute-FileName` header
##### Caching strategy
@@ -422,26 +449,29 @@ $ curl --no-buffer -F 'file=@pipe;filename=catvideo.mp4' http://localhost:8082/u
```
You can also add some attributes to your file using the following rules:
- * all "X-Attribute-*" headers get converted to object attributes with
- "X-Attribute-" prefix stripped, that is if you add "X-Attribute-Ololo:
- 100500" header to your request the resulting object will get "Ololo:
- 100500" attribute
- * "X-Attribute-SYSTEM-*" headers are special
- (`-SYSTEM-` part can also be `-system-` or`-System-` (and even legacy `-Neofs-` for some next releases)), they're used to set internal
- FrostFS attributes starting with `__SYSTEM__` prefix, for these attributes all
- dashes get converted to underscores and all letters are capitalized. For
- example, you can use "X-Attribute-SYSTEM-Expiration-Epoch" header to set
- `__SYSTEM__EXPIRATION_EPOCH` attribute
- * `FileName` attribute is set from multipart's `filename` if not set
- explicitly via `X-Attribute-FileName` header
- * `Timestamp` attribute can be set using gateway local time if using
- HTTP_GW_UPLOAD_HEADER_USE_DEFAULT_TIMESTAMP option and if request doesn't
- provide `X-Attribute-Timestamp` header of its own
+
+* all "X-Attribute-*" headers get converted to object attributes with
+ "X-Attribute-" prefix stripped, that is if you add "X-Attribute-Ololo:
+ 100500" header to your request the resulting object will get "Ololo:
+ 100500" attribute
+* "X-Attribute-SYSTEM-*" headers are special
+ (`-SYSTEM-` part can also be `-system-` or`-System-` (and even legacy `-Neofs-` for some next releases)), they're used to set internal
+ FrostFS attributes starting with `__SYSTEM__` prefix, for these attributes all
+ dashes get converted to underscores and all letters are capitalized. For
+ example, you can use "X-Attribute-SYSTEM-Expiration-Epoch" header to set
+ `__SYSTEM__EXPIRATION_EPOCH` attribute
+* `FileName` attribute is set from multipart's `filename` if not set
+ explicitly via `X-Attribute-FileName` header
+* `Timestamp` attribute can be set using gateway local time if using
+ HTTP_GW_UPLOAD_HEADER_USE_DEFAULT_TIMESTAMP option and if request doesn't
+ provide `X-Attribute-Timestamp` header of its own
---
+
**NOTE**
There are some reserved headers type of `X-Attribute-SYSTEM-*` (headers are arranged in descending order of priority):
+
1. `X-Attribute-System-Expiration-Epoch: 100`
2. `X-Attribute-System-Expiration-Duration: 24h30m`
3. `X-Attribute-System-Expiration-Timestamp: 1637574797`
@@ -453,6 +483,7 @@ which transforms to `X-Attribute-System-Expiration-Epoch`. So you can provide ex
For successful uploads you get JSON data in reply body with a container and
object ID, like this:
+
```
{
"object_id": "9ANhbry2ryjJY1NZbcjryJMRXG5uGNKd73kD3V1sVFsX",
@@ -474,9 +505,10 @@ to grant access.
FrostFS Bearer Token basically is a container owner-signed policy (refer to FrostFS
documentation for more details). There are two options to pass them to gateway:
- * "Authorization" header with "Bearer" type and base64-encoded token in
- credentials field
- * "Bearer" cookie with base64-encoded token contents
+
+* "Authorization" header with "Bearer" type and base64-encoded token in
+ credentials field
+* "Bearer" cookie with base64-encoded token contents
For example, you have a mobile application frontend with a backend part storing
data in FrostFS. When a user authorizes in the mobile app, the backend issues a FrostFS
@@ -486,6 +518,7 @@ the corresponding header to the upload request. Accessing policy protected data
works the same way.
##### Example
+
In order to generate a bearer token, you need to have wallet (which will be used to sign the token)
1. Suppose you have a container with private policy for wallet key
@@ -500,9 +533,9 @@ $ frostfs-cli ape-manager add -r --wallet \
--chain-id
```
-
2. Form a Bearer token (10000 is lifetime expiration in epoch) to impersonate
HTTP Gateway request as wallet signed request and save it to **bearer.json**:
+
```
{
"body": {
@@ -518,11 +551,13 @@ $ frostfs-cli ape-manager add -r --wallet \
```
3. Sign it with the wallet:
+
```
$ frostfs-cli util sign bearer-token --from bearer.json --to signed.json -w
```
4. Encode to base64 to use in header:
+
```
$ base64 -w 0 signed.json
# output: Ck4KKgoECAIQBhIiCiCZGdlbN7DPGPMg9rsWqV+p2XdMzUqknRiexewSFp8kmBIbChk17MUri6OJ0X5ftsHzy7NERDNFB4C92PcaGgMIkE4SZgohAxpsb7vfAso1F0X6hrm6WpRS14WsT3/Ct1SMoqRsT89KEkEEGxKi8GjKSf52YqhppgaOTQHbUsL3jn7SHLqS3ndAQ7NtAATnmRHleZw2V2xRRSRBQdjDC05KK83LhdSax72Fsw==
@@ -551,6 +586,7 @@ $ echo 'NhVtreTTCoqsMQV5Wp55fqnriiUCpEaKm3' | base58 --decode | base64
```
Then specify this value in Bearer Token Json
+
```
{
"body": {
@@ -572,6 +608,24 @@ If enabled, Prometheus metrics are available at `localhost:8084` endpoint
and Pprof at `localhost:8083/debug/pprof` by default. Host and port can be configured.
See [configuration](./docs/gate-configuration.md).
+## Fuzzing
+
+To run fuzzing tests use the following command:
+
+```shell
+$ make fuzz
+```
+
+This command will install dependencies for the fuzzing process and run existing fuzzing tests.
+
+You can also use the following arguments:
+
+```
+FUZZ_TIMEOUT - time to run each fuzzing test (default 30)
+FUZZ_FUNCTIONS - fuzzing tests that will be started (default "all")
+FUZZ_AUX - additional parameters for the fuzzer (for example, "-debug")
+````
+
## Credits
Please see [CREDITS](CREDITS.md) for details.
diff --git a/go.mod b/go.mod
index 493a003..dde26f5 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
github.com/ssgreg/journald v1.0.0
github.com/stretchr/testify v1.9.0
github.com/testcontainers/testcontainers-go v0.13.0
+ github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4
github.com/valyala/fasthttp v1.34.0
go.opentelemetry.io/otel v1.16.0
go.opentelemetry.io/otel/trace v1.16.0
diff --git a/go.sum b/go.sum
index 73b2798..07eccfa 100644
--- a/go.sum
+++ b/go.sum
@@ -820,6 +820,8 @@ github.com/testcontainers/testcontainers-go v0.13.0 h1:OUujSlEGsXVo/ykPVZk3KanBN
github.com/testcontainers/testcontainers-go v0.13.0/go.mod h1:z1abufU633Eb/FmSBTzV6ntZAC1eZBYPtaFsn4nPuDk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4 h1:GpfJ7OdNjS7BFTVwNCUI9L4aCJOFRbr5fdHqjdhoYE8=
+github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4/go.mod h1:f3jBhpWvuZmue0HZK52GzRHJOYHYSILs/c8+K2S/J+o=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
diff --git a/internal/handler/handler_fuzz_test.go b/internal/handler/handler_fuzz_test.go
new file mode 100644
index 0000000..8d44fbf
--- /dev/null
+++ b/internal/handler/handler_fuzz_test.go
@@ -0,0 +1,574 @@
+//go:build gofuzz
+// +build gofuzz
+
+package handler
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ syserrors "errors"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "testing"
+
+ "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
+ "git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
+ "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
+ "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
+ cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
+ "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
+ go_fuzz_utils "github.com/trailofbits/go-fuzz-utils"
+ "github.com/valyala/fasthttp"
+)
+
+const (
+ fuzzSuccessExitCode = 1
+ fuzzFailExitCode = 0
+)
+
+func prepareStrings(tp *go_fuzz_utils.TypeProvider, count int) ([]string, error) {
+ array := make([]string, count)
+ var err error
+
+ for i := 0; i < count; i++ {
+ err = tp.Reset()
+ if err != nil {
+ return nil, err
+ }
+
+ array[i], err = tp.GetString()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return array, nil
+}
+
+func prepareBools(tp *go_fuzz_utils.TypeProvider, count int) ([]bool, error) {
+ array := make([]bool, count)
+ var err error
+
+ for i := 0; i < count; i++ {
+ err = tp.Reset()
+ if err != nil {
+ return nil, err
+ }
+
+ array[i], err = tp.GetBool()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return array, nil
+}
+
+func getRandomDeterministicPositiveIntInRange(tp *go_fuzz_utils.TypeProvider, max int) (int, error) {
+ count, err := tp.GetInt()
+ if err != nil {
+ return -1, err
+ }
+ count = count % max
+ if count < 0 {
+ count += max
+ }
+ return count, nil
+}
+
+func generateHeaders(tp *go_fuzz_utils.TypeProvider, r *fasthttp.Request, params []string) error {
+ count, err := tp.GetInt()
+ if err != nil {
+ return err
+ }
+ count = count % len(params)
+ if count < 0 {
+ count += len(params)
+ }
+
+ for i := 0; i < count; i++ {
+ position, err := tp.GetInt()
+ if err != nil {
+ return err
+ }
+ position = position % len(params)
+ if position < 0 {
+ position += len(params)
+ }
+
+ v, err := tp.GetString()
+ if err != nil {
+ return err
+ }
+
+ r.Header.Set(params[position], v)
+
+ }
+
+ return nil
+}
+
+func maybeFillRandom(tp *go_fuzz_utils.TypeProvider, initValue string) (error, string) {
+ rnd, err := tp.GetBool()
+ if err != nil {
+ return err, ""
+ }
+ if rnd == true {
+ initValue, err = tp.GetString()
+ if err != nil {
+ return err, ""
+ }
+ }
+ return nil, initValue
+}
+
+func upload(tp *go_fuzz_utils.TypeProvider) (error, context.Context, *handlerContext, cid.ID, *fasthttp.RequestCtx, string, string, string) {
+ hc, err := prepareHandlerContext()
+ if err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ aclList := []acl.Basic{
+ acl.Private,
+ acl.PrivateExtended,
+ acl.PublicRO,
+ acl.PublicROExtended,
+ acl.PublicRW,
+ acl.PublicRWExtended,
+ acl.PublicAppend,
+ acl.PublicAppendExtended,
+ }
+
+ pos, err := getRandomDeterministicPositiveIntInRange(tp, len(aclList))
+ if err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+ acl := aclList[pos]
+
+ strings, err := prepareStrings(tp, 6)
+ if err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+ bktName := strings[0]
+ objFileName := strings[1]
+ valAttr := strings[2]
+ keyAttr := strings[3]
+
+ if len(bktName) == 0 {
+ return syserrors.New("not enought buckets"), nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ cnrID, cnr, err := hc.prepareContainer(bktName, acl)
+ if err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ hc.frostfs.SetContainer(cnrID, cnr)
+
+ ctx := context.Background()
+ ctx = middleware.SetNamespace(ctx, "")
+
+ r := new(fasthttp.RequestCtx)
+ utils.SetContextToRequest(ctx, r)
+ r.SetUserValue("cid", cnrID.EncodeToString())
+
+ attributes := map[string]string{
+ object.AttributeFileName: objFileName,
+ keyAttr: valAttr,
+ }
+
+ var buff bytes.Buffer
+ w := multipart.NewWriter(&buff)
+ fw, err := w.CreateFormFile("file", attributes[object.AttributeFileName])
+ if err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ content, err := tp.GetBytes()
+ if err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ if _, err = io.Copy(fw, bytes.NewReader(content)); err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ if err = w.Close(); err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ r.Request.SetBodyStream(&buff, buff.Len())
+ r.Request.Header.Set("Content-Type", w.FormDataContentType())
+ r.Request.Header.Set("X-Attribute-"+keyAttr, valAttr)
+
+ err = generateHeaders(tp, &r.Request, []string{"X-Attribute-", "X-Attribute-DupKey", "X-Attribute-MyAttribute", "X-Attribute-System-DupKey", "X-Attribute-System-Expiration-Epoch1", "X-Attribute-SYSTEM-Expiration-Epoch2", "X-Attribute-system-Expiration-Epoch3", "X-Attribute-User-Attribute", "X-Attribute-", "X-Attribute-FileName", "X-Attribute-FROSTFS", "X-Attribute-neofs", "X-Attribute-SYSTEM", "X-Attribute-System-Expiration-Duration", "X-Attribute-System-Expiration-Epoch", "X-Attribute-System-Expiration-RFC3339", "X-Attribute-System-Expiration-Timestamp", "X-Attribute-Timestamp", "X-Attribute-" + strings[4], "X-Attribute-System-" + strings[5]})
+ if err != nil {
+ return err, nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ hc.Handler().Upload(r)
+
+ if r.Response.StatusCode() != http.StatusOK {
+ return syserrors.New("error on upload"), nil, nil, cid.ID{}, nil, "", "", ""
+ }
+
+ return nil, ctx, hc, cnrID, r, objFileName, keyAttr, valAttr
+}
+
+func InitFuzzUpload() {
+
+}
+
+func DoFuzzUpload(input []byte) int {
+ // FUZZER INIT
+ if len(input) < 100 {
+ return fuzzFailExitCode
+ }
+
+ tp, err := go_fuzz_utils.NewTypeProvider(input)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, _, _, _, _, _, _, _ = upload(tp)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ return fuzzSuccessExitCode
+}
+
+func FuzzUpload(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ DoFuzzUpload(data)
+ })
+}
+
+func downloadOrHead(tp *go_fuzz_utils.TypeProvider, ctx context.Context, hc *handlerContext, cnrID cid.ID, resp *fasthttp.RequestCtx, filename string) (error, *fasthttp.RequestCtx) {
+
+ var putRes putResponse
+
+ defer func() {
+ if r := recover(); r != nil {
+ panic(resp)
+ }
+ }()
+
+ data := resp.Response.Body()
+ err := json.Unmarshal(data, &putRes)
+
+ if err != nil {
+ return err, nil
+ }
+
+ obj := hc.frostfs.objects[putRes.ContainerID+"/"+putRes.ObjectID]
+ attr := object.NewAttribute()
+ attr.SetKey(object.AttributeFilePath)
+
+ err, filename = maybeFillRandom(tp, filename)
+ if err != nil {
+ return err, nil
+ }
+
+ attr.SetValue(filename)
+ obj.SetAttributes(append(obj.Attributes(), *attr)...)
+
+ r := new(fasthttp.RequestCtx)
+ utils.SetContextToRequest(ctx, r)
+
+ cid := cnrID.EncodeToString()
+ err, cid = maybeFillRandom(tp, cid)
+ if err != nil {
+ return err, nil
+ }
+ oid := putRes.ObjectID
+ err, oid = maybeFillRandom(tp, oid)
+ if err != nil {
+ return err, nil
+ }
+ r.SetUserValue("cid", cid)
+ r.SetUserValue("oid", oid)
+
+ rnd, err := tp.GetBool()
+ if err != nil {
+ return err, nil
+ }
+ if rnd == true {
+ r.SetUserValue("download", "true")
+ }
+
+ return nil, r
+}
+
+func InitFuzzGet() {
+}
+
+func DoFuzzGet(input []byte) int {
+ // FUZZER INIT
+ if len(input) < 100 {
+ return fuzzFailExitCode
+ }
+
+ tp, err := go_fuzz_utils.NewTypeProvider(input)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, ctx, hc, cnrID, resp, filename, _, _ := upload(tp)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, r := downloadOrHead(tp, ctx, hc, cnrID, resp, filename)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ hc.Handler().DownloadByAddressOrBucketName(r)
+
+ return fuzzSuccessExitCode
+}
+
+func FuzzGet(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ DoFuzzUpload(data)
+ })
+}
+
+func InitFuzzHead() {
+}
+
+func DoFuzzHead(input []byte) int {
+ // FUZZER INIT
+ if len(input) < 100 {
+ return fuzzFailExitCode
+ }
+
+ tp, err := go_fuzz_utils.NewTypeProvider(input)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, ctx, hc, cnrID, resp, filename, _, _ := upload(tp)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, r := downloadOrHead(tp, ctx, hc, cnrID, resp, filename)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ hc.Handler().HeadByAddressOrBucketName(r)
+
+ return fuzzSuccessExitCode
+}
+
+func FuzzHead(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ DoFuzzHead(data)
+ })
+}
+
+func InitFuzzDownloadByAttribute() {
+}
+
+func DoFuzzDownloadByAttribute(input []byte) int {
+ // FUZZER INIT
+ if len(input) < 100 {
+ return fuzzFailExitCode
+ }
+
+ tp, err := go_fuzz_utils.NewTypeProvider(input)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, ctx, hc, cnrID, _, _, attrKey, attrVal := upload(tp)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ cid := cnrID.EncodeToString()
+ err, cid = maybeFillRandom(tp, cid)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, attrKey = maybeFillRandom(tp, attrKey)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, attrVal = maybeFillRandom(tp, attrVal)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ r := new(fasthttp.RequestCtx)
+ utils.SetContextToRequest(ctx, r)
+ r.SetUserValue("cid", cid)
+ r.SetUserValue("attr_key", attrKey)
+ r.SetUserValue("attr_val", attrVal)
+
+ hc.Handler().DownloadByAttribute(r)
+
+ return fuzzSuccessExitCode
+}
+
+func FuzzDownloadByAttribute(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ DoFuzzDownloadByAttribute(data)
+ })
+}
+
+func InitFuzzHeadByAttribute() {
+}
+
+func DoFuzzHeadByAttribute(input []byte) int {
+ // FUZZER INIT
+ if len(input) < 100 {
+ return fuzzFailExitCode
+ }
+
+ tp, err := go_fuzz_utils.NewTypeProvider(input)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, ctx, hc, cnrID, _, _, attrKey, attrVal := upload(tp)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ cid := cnrID.EncodeToString()
+ err, cid = maybeFillRandom(tp, cid)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, attrKey = maybeFillRandom(tp, attrKey)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, attrVal = maybeFillRandom(tp, attrVal)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ r := new(fasthttp.RequestCtx)
+ utils.SetContextToRequest(ctx, r)
+ r.SetUserValue("cid", cid)
+ r.SetUserValue("attr_key", attrKey)
+ r.SetUserValue("attr_val", attrVal)
+
+ hc.Handler().HeadByAttribute(r)
+
+ return fuzzSuccessExitCode
+}
+
+func FuzzHeadByAttribute(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ DoFuzzHeadByAttribute(data)
+ })
+}
+
+func InitFuzzDownloadZipped() {
+}
+
+func DoFuzzDownloadZipped(input []byte) int {
+ // FUZZER INIT
+ if len(input) < 100 {
+ return fuzzFailExitCode
+ }
+
+ tp, err := go_fuzz_utils.NewTypeProvider(input)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ err, ctx, hc, cnrID, _, _, _, _ := upload(tp)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ cid := cnrID.EncodeToString()
+ err, cid = maybeFillRandom(tp, cid)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ prefix := ""
+ err, prefix = maybeFillRandom(tp, prefix)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ r := new(fasthttp.RequestCtx)
+ utils.SetContextToRequest(ctx, r)
+ r.SetUserValue("cid", cid)
+ r.SetUserValue("prefix", prefix)
+
+ hc.Handler().DownloadZipped(r)
+
+ return fuzzSuccessExitCode
+}
+
+func FuzzDownloadZipped(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ DoFuzzDownloadZipped(data)
+ })
+}
+
+func InitFuzzStoreBearerTokenAppCtx() {
+}
+
+func DoFuzzStoreBearerTokenAppCtx(input []byte) int {
+ // FUZZER INIT
+ if len(input) < 100 {
+ return fuzzFailExitCode
+ }
+
+ tp, err := go_fuzz_utils.NewTypeProvider(input)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ prefix := ""
+ err, prefix = maybeFillRandom(tp, prefix)
+ if err != nil {
+ return fuzzFailExitCode
+ }
+
+ ctx := context.Background()
+ ctx = middleware.SetNamespace(ctx, "")
+
+ r := new(fasthttp.RequestCtx)
+ utils.SetContextToRequest(ctx, r)
+
+ strings, err := prepareStrings(tp, 3)
+
+ rand, err := prepareBools(tp, 2)
+
+ if rand[0] == true {
+ r.Request.Header.Set(fasthttp.HeaderAuthorization, "Bearer"+strings[0])
+ } else if rand[1] == true {
+ r.Request.Header.SetCookie(fasthttp.HeaderAuthorization, "Bearer"+strings[1])
+ } else {
+ r.Request.Header.Set(fasthttp.HeaderAuthorization, "Bearer"+strings[0])
+ r.Request.Header.SetCookie(fasthttp.HeaderAuthorization, "Bearer"+strings[1])
+ }
+
+ tokens.StoreBearerTokenAppCtx(ctx, r)
+
+ return fuzzSuccessExitCode
+}
+
+func FuzzStoreBearerTokenAppCtx(f *testing.F) {
+ f.Fuzz(func(t *testing.T, data []byte) {
+ DoFuzzStoreBearerTokenAppCtx(data)
+ })
+}
diff --git a/tests/fuzzing/dict.txt b/tests/fuzzing/dict.txt
new file mode 100644
index 0000000..3e4b799
--- /dev/null
+++ b/tests/fuzzing/dict.txt
@@ -0,0 +1,709 @@
+" "
+"()<>@,;:\\\\\\"
+"-"
+"--"
+"."
+"/"
+"\\"
+"_"
+".0."
+":0"
+"0"
+"0.0.0.0:8080"
+".0.address"
+".0.priority"
+".0.weight"
+"100"
+"101"
+"102"
+"103"
+"1.2.7"
+"1.3.0"
+"1d"
+"1dd37fba80fec4e6a6f13fd708d8dcb3b29def768017052f6c930fa1c5d90bbb"
+"24h"
+"-5h"
+"abc"
+"access denied"
+"Access Denied"
+"actual"
+"add attribute to result object"
+"added path /get_by_attribute/{cid}/{attr_key}/{attr_val:*}"
+"added path /get/{cid}/{oid}"
+"added path /upload/{cid}"
+"added path /zip/{cid}/{prefix}"
+"added storage peer"
+".address"
+"address"
+"addresses to listen"
+"add server"
+"aio"
+"aio container started"
+"all nodes represent a secondary object"
+"allocs"
+"application/json; charset=UTF-8"
+"application/zip"
+"app_name"
+"archive/zip"
+"attachment"
+"attachment; filename=\\"
+"attr_key"
+"attr_val"
+"Authorization"
+"Average request duration (in milliseconds) for specific method on node in pool"
+"avg_request_duration"
+"bad base64 cookie"
+"bad base64 header"
+"bad cookie, but good header"
+"bad header and cookie"
+"bad header, but good cookie"
+"Bar"
+"bare"
+"Bearer "
+"Bearer"
+"block"
+"both deprecated and new system attributes formats are used, please use only one"
+"--boundary"
+"--boundary--"
+"boundary"
+"bucket"
+"bucket cid"
+"bucket name"
+"bucketname"
+"bufio"
+"build zap logger instance: %v"
+"b.yadro.com/obj/go-118-fuzz-build/testing"
+"bytes"
+"cache.buckets.lifetime"
+"cache.buckets.size"
+"cannot load TLS key pair from certFile '%s' and keyFile '%s': %w"
+"can't base64-decode bearer token"
+"can't base64-decode bearer token: %w"
+"can't gracefully shut down service, force stop"
+"can't shut down service"
+"can't unmarshal bearer token"
+"can't unmarshal bearer token: %w"
+"CERTIFICATE"
+"cert.pem"
+"cert provider: disabled"
+"cid"
+"close temporary multipart/form file"
+"close zip writer"
+"commandline"
+"config"
+"config-dir"
+"config dir path"
+"config paths"
+"connect_timeout"
+"console"
+"constant_labels,omitempty"
+"container_id"
+"container not found %s"
+"container resolver will be disabled because of resolvers 'resolver_order' is empty"
+"Content-Disposition"
+"content of file"
+"content of file1"
+"content of file2"
+"Content-Transfer-Encoding"
+"Content-Transfer-Encoding: quoted-printable"
+"Content-Type"
+"context"
+"__context_bearer_token_key"
+"cookie token unmarshal error"
+"copy object payload to zip file: %v"
+"could not detect Content-Type from payload"
+"could not detect Content-Type from payload: "
+"could not dial nns: %w"
+"could not encode response"
+"could not fetch and store bearer token"
+"could not fetch and store bearer token: "
+"could not fetch bearer token"
+"could not get bucket"
+"could not get bucket: "
+"could not load FrostFS private key"
+"could not parse client time"
+"could not prepare expiration header"
+"could not prepare expiration header: "
+"could not prepare listener: %w"
+"could not process headers"
+"could not receive multipart/form"
+"could not receive multipart/form: "
+"could not receive object"
+"could not search for objects"
+"could not search for objects: "
+"could not store file in frostfs"
+"could not unescape attr_key: "
+"could not unescape attr_val: "
+"could not unescape prefix: "
+"couldn't decrypt account: %w"
+"couldn't find wallet account for %s"
+"couldn't get epoch durations from network info: %w"
+"couldn't get namespace from context"
+"couldn't parse creation date"
+"couldn't parse value %s of header %s"
+"couldn't put bucket info into cache"
+"couldn't read password"
+"couldn't resolve container '%s' as '%s': %w"
+"couldn't resolve container '%s': %w"
+"create_session"
+"crypto/rand"
+"crypto/rsa"
+"crypto/sha256"
+"crypto/tls"
+"crypto/x509"
+"crypto/x509/pkix"
+"current_errors"
+"Current HTTP gateway state"
+"custom"
+"data"
+"data to return"
+"Date header"
+"debug"
+"/debug/pprof/"
+"/debug/pprof/cmdline"
+"/debug/pprof/profile"
+"/debug/pprof/symbol"
+"/debug/pprof/trace"
+"default"
+"Default environments:"
+"delete_container"
+"delete_object"
+"dev"
+"dGVzdAo="
+"dns"
+"download"
+"duplicate address"
+"duplicate keys error"
+"duplicate system keys error"
+"elapsed"
+"empty"
+"enable pprof"
+"enable prometheus"
+"encoding/base64"
+"encoding/hex"
+"encoding/json"
+"encoding/pem"
+"endpoint"
+"endpoint_info"
+"Enter password > "
+"EpochDuration is empty"
+"error_detail"
+"errors"
+"expected"
+"EXPIRATION_DURATION"
+"EXPIRATION_EPOCH"
+"expiration epoch '%d' must be greater than current epoch '%d'"
+"EXPIRATION_RFC3339"
+"EXPIRATION_TIMESTAMP"
+"failed to add object to archive"
+"failed to add server"
+"failed to create connection pool"
+"failed to create resolver"
+"failed to create tree pool"
+"failed to dial connection pool"
+"failed to dial tree pool"
+"failed to initialize tracing"
+"failed to reconnect server"
+"failed to reload config"
+"failed to reload config because it's missed"
+"failed to reload server parameters"
+"failed to shutdown tracing"
+"failed to unescape query"
+"failed to update cert (listener close: %v): %w"
+"failed to update resolvers"
+"failed to update tls certs: %w"
+"file"
+"; filename="
+"filename"
+"FileName"
+"File to export http gateway metrics to."
+"first-value"
+"first_value"
+"flag"
+"flag 'out' must be provided to dump metrics description"
+"flush zip writer: %v"
+"fmt"
+"--foo"
+"Foo"
+"--foobar"
+"foo-bar"
+"Foo-Bar"
+"foo.txt"
+"form"
+"form-data"
+"found empty bearer token"
+"friendly"
+"FrostFS"
+"frostfs.buffer_max_size_for_put"
+"frostfs.client_cut"
+"FrostFS HTTP Gateway\\nVersion: %s\\nGoVersion: %s\\n"
+"FrostFS HTTP Gateway %s\\n"
+"frostfs-http-gw"
+"frostfs_http_gw"
+"FrostFS nodes"
+"frostfs.tree_pool_max_attempts"
+"frost-http-gw"
+"gateway timeout"
+"/get/"
+"get"
+"GET"
+"get_balance"
+"/get_by_attribute/"
+"get by attribute "
+"get by attribute"
+"/get_by_attribute/{cid}/{attr_key}/{attr_val:*}"
+"/get/{cid}/{oid:*}"
+"get_container"
+"get_container_eacl"
+"get frostfs container '%s': %w"
+"get FrostFS object: %v"
+"get latest object version"
+"get_object"
+"get zip "
+"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/api"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/api/layer"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/cache"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/frostfs"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/frostfs/services"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/multipart"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
+"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
+"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
+"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
+"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree/service"
+"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
+"git.frostfs.info/TrueCloudLab/zapjournald"
+"github.com/bluele/gcache"
+"github.com/fasthttp/router"
+"github.com/nspcc-dev/neo-go/cli/flags"
+"github.com/nspcc-dev/neo-go/cli/input"
+"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
+"github.com/nspcc-dev/neo-go/pkg/util"
+"github.com/nspcc-dev/neo-go/pkg/wallet"
+"github.com/prometheus/client_golang/prometheus"
+"github.com/prometheus/client_golang/prometheus/promhttp"
+"github.com/prometheus/client_model/go"
+"github.com/spf13/pflag"
+"github.com/spf13/viper"
+"github.com/ssgreg/journald"
+"github.com/stretchr/testify/require"
+"github.com/testcontainers/testcontainers-go"
+"github.com/testcontainers/testcontainers-go/wait"
+"github.com/trailofbits/go-fuzz-utils"
+"github.com/valyala/fasthttp"
+"golang.org/x/exp/slices"
+"golang.org/x/net/http2"
+"GOMEMLIMIT"
+"google.golang.org/grpc"
+"google.golang.org/grpc/codes"
+"google.golang.org/grpc/status"
+"go.opentelemetry.io/otel/attribute"
+"go.opentelemetry.io/otel/codes"
+"go.opentelemetry.io/otel/semconv/v1.17.0"
+"go.opentelemetry.io/otel/trace"
+"goroutine"
+"go.uber.org/zap"
+"go.uber.org/zap/zapcore"
+"gRPC connection rebalance timer"
+"gRPC connect timeout"
+"gRPC individual message timeout"
+"gRPC request timeout"
+"h"
+"h2"
+"head"
+"head by attribute"
+"header token unmarshal error"
+"head_object"
+"health"
+"heap"
+"hello"
+"help"
+"host"
+"http://"
+"http.client_address"
+"HTTP_GW"
+"http://localhost:30333"
+"http.path"
+"http.query"
+"HTTP Server endpoint health"
+"https://localhost:%d"
+"id"
+"ignore part, empty filename"
+"ignore part, empty form name"
+"incorrect logger level configuration %s (%v), "
+"init full object reading via connection pool"
+"init full payload range reading via connection pool"
+"init object search via connection pool"
+"init payload range reading via connection pool"
+"inline"
+"invalid address"
+"invalid cache entry type"
+"invalid cache size, using default value"
+"invalid duration"
+"invalid duration negative"
+"invalid filepath '%s'"
+"invalid lifetime, using default value (in seconds)"
+"invalid metric type"
+"invalid rfc3339"
+"invalid rfc3339 zero"
+"invalid servers configuration: no known server found"
+"invalid timestamp sec"
+"invalid timestamp sec zero"
+"invalid token"
+"invalid-token"
+"invalid tree node: %w"
+"io"
+"IsDeleteMarker"
+"iterating over selected objects failed"
+"journald"
+"key"
+"key duplication error: "
+"key duplication error: %s"
+"key.pem"
+"less than 512b"
+"list_container"
+"listen_address"
+"listen and serve"
+"localhost"
+"localhost:8080"
+"localhost:8082"
+"localhost:8083"
+"localhost:8084"
+"logger.destination"
+"logger.level"
+"log level won't be updated"
+"math"
+"math/big"
+"method"
+"Method Not Allowed"
+"metrics"
+"metrics are disabled"
+"mime"
+"mime/multipart"
+"mime/quotedprintable"
+"more than 512b"
+"multipart: boundary is empty"
+"multipart: expecting a new Part; got line %q"
+"multipart: NextPart: %v"
+"multipart: unexpected line in Next(): %q"
+"mutex"
+"MyAttribute"
+"myFile"
+"\\n"
+"name"
+"namespace"
+"\\n--boundary"
+"neofs-"
+"Neofs-"
+"__NEOFS__"
+"NEOFS-"
+"Neofs-Expiration-Epoch"
+"__NEOFS__EXPIRATION_EPOCH"
+"Neofs-Random-Attr"
+"__NEOFS__RANDOM_ATTR"
+"net"
+"net/http"
+"net/http/pprof"
+"net/textproto"
+"net/url"
+"network_info"
+"newFile.txt"
+"new_value"
+"nil context"
+"nns"
+"node"
+"no healthy servers"
+"no resolvers"
+"normal"
+"not found"
+"Not found"
+"Not Found"
+"no wallet path specified, creating ephemeral key automatically for this run"
+".ns"
+"Number of errors on current connections that will be reset after the threshold"
+"object body close error: %w"
+"object deleted"
+"object_id"
+"object not found"
+"objects not found"
+"object was deleted"
+"object wasn't found"
+"oid"
+"OID"
+"oid1"
+"oid2"
+"ok for cookie"
+"ok for header"
+"old_value"
+"one node of the object version"
+"one node of the object version and one node of the secondary object"
+"os"
+"os/signal"
+"out"
+"overall_errors"
+"overall_node_errors"
+"overall_node_requests"
+"p"
+"parameter"
+"parse expiration epoch '%s': %w"
+"path"
+"peers"
+"Peers preset:"
+"pool"
+"pool_error_threshold"
+"pool must not be nil for DNS resolver"
+"pprof"
+"Pprof"
+"pprof.address"
+"pprof.enabled"
+"prefix"
+".priority"
+"priority"
+"Prometheus"
+"prometheus.address"
+"prometheus.enabled"
+"put_container"
+"put_object"
+"put with bearer token in cookie"
+"put with bearer token in header"
+"put with duplicate keys "
+"query"
+"quoted-printable"
+"range_object"
+"read container via connection pool"
+"read full object payload"
+"read network info via client"
+"read network info via client: %w"
+"read object header via connection pool"
+"read object list failed"
+"read object list failed: "
+"read payload"
+"read system DNS parameter of the FrostFS: %w"
+"rebalance_timer"
+"reconnecting server..."
+"reconnect_interval"
+"remote"
+"REP 1"
+"request"
+"REQUEST"
+"request_timeout"
+"resolve_bucket.default_namespaces"
+"resolve_bucket.namespace_header"
+"resolve_order"
+"resolver nns won't be used since rpc_endpoint isn't provided"
+"resolver settings must not be nil for DNS resolver"
+"resolver settings must not be nil for NNS resolver"
+"\\r\\n"
+"\\r\\n--"
+"\\r\\n--boundary"
+"root"
+"root2"
+"rpc address must not be empty for NNS resolver"
+"rpc_endpoint"
+"RSA PRIVATE KEY"
+"runtime"
+"runtime/debug"
+"runtime.soft_memory_limit"
+"s3-client-cut"
+"save object via connection pool"
+"second-value"
+"second_value"
+"server"
+"server.0.address"
+"server reconnected successfully"
+"service"
+"service couldn't start on configured port"
+"service hasn't started since it's disabled"
+"service is running"
+"set_container_eacl"
+"set container name resolve order"
+"several nodes of different types and with different timestamp"
+"show help"
+"show version"
+"shutting down service"
+"shutting down web server"
+"SIGHUP config reload completed"
+"SIGHUP config reload started"
+"simple get "
+"simple put "
+"soft runtime memory defined with GOMEMLIMIT environment variable, config value skipped"
+"soft runtime memory limit value updated"
+"some-attr"
+"some-get-by-attr-value"
+"some-get-value"
+"Some txt content. Content-Type must be detected properly by detector."
+"sort"
+"%s_%s_[N]_ADDRESS = string\\n"
+"%s_%s_[N]_WEIGHT = float\\n"
+"%s_%s = %s\\n"
+"%s/%s/%s/%s"
+"starting application"
+"starting server"
+"state"
+"stdout"
+"strconv"
+"stream_timeout"
+"strings"
+"%s: %v"
+"%s: %v: %s"
+"%s: %w"
+"%s: %w: %s"
+"sync"
+"syscall"
+"system"
+"system-"
+"System-"
+"__SYSTEM__"
+"SYSTEM-"
+"SystemDNS"
+"system DNS parameter not found or empty"
+"System-Expiration-Epoch"
+"__SYSTEM__EXPIRATION_EPOCH"
+"__SYSTEM__EXPIRATION_EPOCH1"
+"__SYSTEM__EXPIRATION_EPOCH2"
+"__SYSTEM__EXPIRATION_EPOCH3"
+"System-Random-Attr"
+"__SYSTEM__RANDOM_ATTR"
+"\\t"
+"%T"
+"tcp"
+"testing"
+"test namespaces "
+"test_resolver"
+"text/plain; charset=utf-8"
+"threadcreate"
+"time"
+"timeout"
+"tls cert"
+"tls.cert_file"
+"TLS certificate path"
+"tls disabled"
+"tls enabled"
+"tls.enabled"
+"tls key"
+"tls.key_file"
+"TLS key path"
+"token"
+"TOKEN"
+"token is missing in the context"
+"token without payload"
+"token without the bearer prefix"
+"Total number of errors for connection in pool"
+"Total number of errors in pool"
+"Total number of requests to specific node in pool"
+"tracing config updated"
+"tracing.enabled"
+"tracing.endpoint"
+"tracing.exporter"
+"true"
+"truecloudlab/frostfs-aio:"
+"type"
+"unicode"
+"unicode/utf8"
+"unknown resolver: %s"
+"/upload/"
+"/upload/{cid}"
+"upload_header.use_default_timestamp"
+"User-Attribute"
+"user value"
+"using credentials"
+"usupported filters"
+"v"
+"val"
+"valid duration"
+"valid epoch"
+"valid epoch, valid duration"
+"valid epoch, valid rfc3339"
+"valid epoch, valid timestamp milli"
+"valid epoch, valid timestamp nano"
+"valid epoch, valid timestamp sec"
+"valid max uint 64"
+"valid rfc3339"
+"valid timestamp sec"
+"value"
+"value in config"
+"value should be one of %v"
+"value %s of header %s must be in the future"
+"value %s of header %s must be positive"
+"variable_labels,omitempty"
+"version"
+"version_info"
+"Version of current FrostFS HTTP Gate instance"
+"w"
+"wallet"
+"wallet.address"
+"wallet.passphrase"
+"wallet.path"
+"web.max_request_body_size"
+"web.read_buffer_size"
+"web.read_timeout"
+"web.stream_request_body"
+"web.write_buffer_size"
+"web.write_timeout"
+".weight"
+"weight"
+"WRONG BASE64"
+"wrong destination for logger: %s"
+"wrong object id"
+"%w: %s"
+"X-Attribute-"
+"X-Attribute-DupKey"
+"X-Attribute-FileName"
+"X-Attribute-FROSTFS"
+"X-Attribute-MyAttribute"
+"X-Attribute-neofs"
+"X-Attribute-System-"
+"X-Attribute-SYSTEM"
+"X-Attribute-System-DupKey"
+"X-Attribute-System-Expiration-Duration"
+"X-Attribute-System-Expiration-Epoch"
+"X-Attribute-System-Expiration-Epoch1"
+"X-Attribute-SYSTEM-Expiration-Epoch2"
+"X-Attribute-system-Expiration-Epoch3"
+"X-Attribute-System-Expiration-RFC3339"
+"X-Attribute-System-Expiration-Timestamp"
+"X-Attribute-Timestamp"
+"X-Attribute-User-Attribute"
+"x-container-id"
+"X-Container-Id"
+"X-Frostfs-Namespace"
+"x-object-id"
+"X-Object-Id"
+"X-Owner-Id"
+".yaml"
+"yaml"
+".yml"
+"/zip/"
+"zip"
+"/zip/{cid}/{prefix:*}"
+"zip.compression"
+"zip create header: %v"
+"/zipfolder"
+"/zipfolder/dir"
+"zipfolder/dir/name1.txt"
+"zipfolder/name2.txt"