[#135] Add fuzzing tests for handlers
Some checks failed
/ DCO (pull_request) Failing after 1m15s
/ Builds (1.21) (pull_request) Successful in 1m20s
/ Builds (1.22) (pull_request) Successful in 1m25s
/ Vulncheck (pull_request) Successful in 1m24s
/ Lint (pull_request) Successful in 2m38s
/ Tests (1.21) (pull_request) Successful in 1m35s
/ Tests (1.22) (pull_request) Successful in 1m34s
Some checks failed
/ DCO (pull_request) Failing after 1m15s
/ Builds (1.21) (pull_request) Successful in 1m20s
/ Builds (1.22) (pull_request) Successful in 1m25s
/ Vulncheck (pull_request) Successful in 1m24s
/ Lint (pull_request) Successful in 2m38s
/ Tests (1.21) (pull_request) Successful in 1m35s
/ Tests (1.22) (pull_request) Successful in 1m34s
This commit is contained in:
parent
5ee09790f0
commit
4bc3159f26
6 changed files with 1419 additions and 39 deletions
40
Makefile
40
Makefile
|
@ -30,6 +30,15 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
||||||
sed "s/-/~/")-${OS_RELEASE}
|
sed "s/-/~/")-${OS_RELEASE}
|
||||||
.PHONY: debpackage debclean
|
.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_TIMEOUT ?= 30
|
||||||
|
FUZZ_FUNCTIONS ?= "all"
|
||||||
|
FUZZ_AUX ?= ""
|
||||||
|
|
||||||
# Make all binaries
|
# Make all binaries
|
||||||
all: $(BINS)
|
all: $(BINS)
|
||||||
$(BINS): $(DIRS) dep
|
$(BINS): $(DIRS) dep
|
||||||
|
@ -78,6 +87,36 @@ cover:
|
||||||
@go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
|
@go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
|
||||||
@go tool cover -html=coverage.txt -o coverage.html
|
@go tool cover -html=coverage.txt -o coverage.html
|
||||||
|
|
||||||
|
CLANG := $(shell which clang-17 2>/dev/null)
|
||||||
|
|
||||||
|
# Run fuzzing tests
|
||||||
|
.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: install-ngfuzz
|
||||||
|
install-ngfuzz:
|
||||||
|
ifeq (,$(wildcard $(FUZZING_DIR)/ngfuzz))
|
||||||
|
@rm -rf $(FUZZING_DIR)/ngfuzz
|
||||||
|
@git clone -b $(NGFUZZ_BRANCH) $(NGFUZZ_REPO) $(FUZZING_DIR)/ngfuzz
|
||||||
|
@cd $(FUZZING_DIR)/ngfuzz && make
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: install-fuzzing-deps
|
||||||
|
install-fuzzing-deps: check-clang install-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
|
||||||
|
cd $(FUZZING_DIR)/ngfuzz && \
|
||||||
|
./ngfuzz -fuzz $(FUZZ_FUNCTIONS) -rootdir ../../../.. -timeout $(FUZZ_TIMEOUT) $(FUZZ_AUX) && \
|
||||||
|
./ngfuzz -report
|
||||||
|
|
||||||
# Reformat code
|
# Reformat code
|
||||||
fmt:
|
fmt:
|
||||||
@echo "⇒ Processing gofmt check"
|
@echo "⇒ Processing gofmt check"
|
||||||
|
@ -150,6 +189,7 @@ version:
|
||||||
clean:
|
clean:
|
||||||
rm -rf vendor
|
rm -rf vendor
|
||||||
rm -rf $(BINDIR)
|
rm -rf $(BINDIR)
|
||||||
|
rm -rf $(FUZZING_DIR)
|
||||||
|
|
||||||
# Package for Debian
|
# Package for Debian
|
||||||
debpackage:
|
debpackage:
|
||||||
|
|
90
README.md
90
README.md
|
@ -6,6 +6,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[![Report](https://goreportcard.com/badge/git.frostfs.info/TrueCloudLab/frostfs-http-gw)](https://goreportcard.com/report/git.frostfs.info/TrueCloudLab/frostfs-http-gw)
|
[![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)
|
![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)
|
![License](https://img.shields.io/badge/license-GPL--3.0-orange.svg)
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
# FrostFS HTTP Gateway
|
# FrostFS HTTP Gateway
|
||||||
|
|
||||||
FrostFS HTTP Gateway bridges FrostFS internal protocol and HTTP standard.
|
FrostFS HTTP Gateway bridges FrostFS internal protocol and HTTP standard.
|
||||||
|
|
||||||
- you can download one file per request from the FrostFS Network
|
- you can download one file per request from the FrostFS Network
|
||||||
- you can upload one file per request into 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
|
These two commands are functionally equivalent, they run the gate with one
|
||||||
backend node (and otherwise default settings):
|
backend node (and otherwise default settings):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ frostfs-http-gw -p 192.168.130.72:8080
|
$ frostfs-http-gw -p 192.168.130.72:8080
|
||||||
$ HTTP_GW_PEERS_0_ADDRESS=192.168.130.72:8080 frostfs-http-gw
|
$ 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`:
|
It's also possible to specify uri scheme (grpc or grpcs) when using `-p`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ frostfs-http-gw -p grpc://192.168.130.72:8080
|
$ frostfs-http-gw -p grpc://192.168.130.72:8080
|
||||||
$ HTTP_GW_PEERS_0_ADDRESS=grpcs://192.168.130.72:8080 frostfs-http-gw
|
$ 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
|
$ 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:
|
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 \
|
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
|
frostfs-http-gw
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will make gateway use 192.168.130.71 while it is healthy. Otherwise, it will make the gateway use
|
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%.
|
192.168.130.72 for 90% of requests and 192.168.130.73 for remaining 10%.
|
||||||
|
|
||||||
### Keys
|
### Keys
|
||||||
|
|
||||||
You can provide a wallet via `--wallet` or `-w` flag. You can also specify the account address using `--address`
|
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 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.
|
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
|
$ frostfs-http-gw -p $FROSTFS_NODE -w $WALLET_PATH --address $ACCOUNT_ADDRESS
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ frostfs-http-gw -p 192.168.130.72:8080 -w wallet.json --address NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP
|
$ 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.
|
"2m" is 2 minutes.
|
||||||
|
|
||||||
### Zip streaming
|
### Zip streaming
|
||||||
|
|
||||||
The gateway supports downloading files by common prefix (like dir) in zip format. You can enable compression
|
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.
|
using config or `HTTP_GW_ZIP_COMPRESSION=true` environment variable.
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
|
|
||||||
You can specify logging level using variable:
|
You can specify logging level using variable:
|
||||||
|
|
||||||
```
|
```
|
||||||
HTTP_GW_LOGGER_LEVEL=debug
|
HTTP_GW_LOGGER_LEVEL=debug
|
||||||
```
|
```
|
||||||
|
|
||||||
### Yaml file
|
### Yaml file
|
||||||
|
|
||||||
Configuration file is optional and can be used instead of environment variables/other parameters.
|
Configuration file is optional and can be used instead of environment variables/other parameters.
|
||||||
It can be specified with `--config` parameter:
|
It can be specified with `--config` parameter:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ frostfs-http-gw --config your-config.yaml
|
$ 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)
|
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
|
```shell
|
||||||
$ curl -s --data '{"id":1,"jsonrpc":"2.0","method":"getcontractstate","params":[1]}' \
|
$ 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
|
#### Create a container
|
||||||
|
|
||||||
You can create a container via [frostfs-cli](https://git.frostfs.info/TrueCloudLab/frostfs-node/releases):
|
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
|
$ frostfs-cli -r $FROSTFS_NODE -w $WALLET container create --policy $POLICY --basic-acl $ACL
|
||||||
```
|
```
|
||||||
|
|
||||||
where `$WALLET` is a path to user wallet,
|
where `$WALLET` is a path to user wallet,
|
||||||
`$ACL` -- hex encoded basic ACL value or keywords 'private, 'public-read', 'public-read-write' and
|
`$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
|
`$POLICY` -- QL-encoded or JSON-encoded placement policy or path to file with it
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ frostfs-cli -r 192.168.130.72:8080 -w ./wallet.json container create --policy "REP 3" --basic-acl public --await
|
$ 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
|
#### 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:
|
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
|
$ frostfs-cli -r $FROSTFS_NODE -k $KEY object put --file $FILENAME --cid $CID
|
||||||
```
|
```
|
||||||
|
|
||||||
where
|
where
|
||||||
`$KEY` -- the key, please read the information [above](#create-a-container),
|
`$KEY` -- the key, please read the information [above](#create-a-container),
|
||||||
`$CID` -- container ID.
|
`$CID` -- container ID.
|
||||||
|
|
||||||
For example:
|
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
|
$ 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
|
### Downloading
|
||||||
|
|
||||||
#### Requests
|
#### Requests
|
||||||
|
@ -313,6 +334,7 @@ $ wget http://localhost:8082/get/container-name/2m8PtaoricLouCn5zE8hAFr3gZEBDCZF
|
||||||
```
|
```
|
||||||
|
|
||||||
##### By attributes
|
##### By attributes
|
||||||
|
|
||||||
There is also more complex interface provided for attribute-based downloads,
|
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
|
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:
|
can be used as well. The generic syntax for it looks like this:
|
||||||
|
@ -336,7 +358,9 @@ Example for file name attribute:
|
||||||
```
|
```
|
||||||
$ wget http://localhost:8082/get_by_attribute/88GdaZFTcYJn1dqiSECss8kKPmmun6d6BfvC4zhwfLYM/FileName/cat.jpeg
|
$ wget http://localhost:8082/get_by_attribute/88GdaZFTcYJn1dqiSECss8kKPmmun6d6BfvC4zhwfLYM/FileName/cat.jpeg
|
||||||
```
|
```
|
||||||
|
|
||||||
Or when the filename includes special symbols:
|
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+jpeg # means 'cat jpeg'
|
||||||
$ wget http://localhost:8082/get_by_attribute/88GdaZFTcYJn1dqiSECss8kKPmmun6d6BfvC4zhwfLYM/FileName/cat%25jpeg # 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:
|
Or when the attribute includes special symbols:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ wget http://localhost:8082/get_by_attribute/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ/Olo%2Blo/100500 # means Olo+lo
|
$ 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
|
##### Zip
|
||||||
|
|
||||||
You can download some dir (files with the same prefix) in zip (it will be compressed if config contains appropriate param):
|
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
|
$ wget http://localhost:8082/zip/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ/common/prefix
|
||||||
```
|
```
|
||||||
|
@ -374,22 +401,22 @@ 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
|
$ curl -F 'file=@cat.jpeg;filename=cat.jpeg' -H 'X-Attribute-FilePath: common/prefix/cat.jpeg' http://localhost:8082/upload/Dxhf4PNprrJHWWTG5RGLdfLkJiSQ3AQqit1MSnEPRkDZ
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Replies
|
#### Replies
|
||||||
|
|
||||||
You get object contents in the reply body (if GET method was used), but at the same time you also get a
|
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:
|
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-Length` is set to the length of the object
|
||||||
* `Content-Disposition` is `inline` for regular requests and `attachment` for
|
* `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
|
requests with `download=true` argument, `filename` is also added if there
|
||||||
is `FileName` attribute set for this object
|
is `FileName` attribute set for this object
|
||||||
* `Last-Modified` header is set to `Timestamp` attribute value if it's
|
* `Last-Modified` header is set to `Timestamp` attribute value if it's
|
||||||
present for the object
|
present for the object
|
||||||
* `x-container-id` contains container ID
|
* `x-container-id` contains container ID
|
||||||
* `x-object-id` contains object ID
|
* `x-object-id` contains object ID
|
||||||
* `x-owner-id` contains owner address
|
* `x-owner-id` contains owner address
|
||||||
* all the other FrostFS attributes are converted to `X-Attribute-*` headers (but only
|
* 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`
|
if they can be safely represented in HTTP header), for example `FileName`
|
||||||
attribute becomes `X-Attribute-FileName` header
|
attribute becomes `X-Attribute-FileName` header
|
||||||
|
|
||||||
|
@ -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:
|
You can also add some attributes to your file using the following rules:
|
||||||
* all "X-Attribute-*" headers get converted to object attributes with
|
|
||||||
|
* all "X-Attribute-*" headers get converted to object attributes with
|
||||||
"X-Attribute-" prefix stripped, that is if you add "X-Attribute-Ololo:
|
"X-Attribute-" prefix stripped, that is if you add "X-Attribute-Ololo:
|
||||||
100500" header to your request the resulting object will get "Ololo:
|
100500" header to your request the resulting object will get "Ololo:
|
||||||
100500" attribute
|
100500" attribute
|
||||||
* "X-Attribute-SYSTEM-*" headers are special
|
* "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
|
(`-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
|
FrostFS attributes starting with `__SYSTEM__` prefix, for these attributes all
|
||||||
dashes get converted to underscores and all letters are capitalized. For
|
dashes get converted to underscores and all letters are capitalized. For
|
||||||
example, you can use "X-Attribute-SYSTEM-Expiration-Epoch" header to set
|
example, you can use "X-Attribute-SYSTEM-Expiration-Epoch" header to set
|
||||||
`__SYSTEM__EXPIRATION_EPOCH` attribute
|
`__SYSTEM__EXPIRATION_EPOCH` attribute
|
||||||
* `FileName` attribute is set from multipart's `filename` if not set
|
* `FileName` attribute is set from multipart's `filename` if not set
|
||||||
explicitly via `X-Attribute-FileName` header
|
explicitly via `X-Attribute-FileName` header
|
||||||
* `Timestamp` attribute can be set using gateway local time if using
|
* `Timestamp` attribute can be set using gateway local time if using
|
||||||
HTTP_GW_UPLOAD_HEADER_USE_DEFAULT_TIMESTAMP option and if request doesn't
|
HTTP_GW_UPLOAD_HEADER_USE_DEFAULT_TIMESTAMP option and if request doesn't
|
||||||
provide `X-Attribute-Timestamp` header of its own
|
provide `X-Attribute-Timestamp` header of its own
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**NOTE**
|
**NOTE**
|
||||||
|
|
||||||
There are some reserved headers type of `X-Attribute-SYSTEM-*` (headers are arranged in descending order of priority):
|
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`
|
1. `X-Attribute-System-Expiration-Epoch: 100`
|
||||||
2. `X-Attribute-System-Expiration-Duration: 24h30m`
|
2. `X-Attribute-System-Expiration-Duration: 24h30m`
|
||||||
3. `X-Attribute-System-Expiration-Timestamp: 1637574797`
|
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
|
For successful uploads you get JSON data in reply body with a container and
|
||||||
object ID, like this:
|
object ID, like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"object_id": "9ANhbry2ryjJY1NZbcjryJMRXG5uGNKd73kD3V1sVFsX",
|
"object_id": "9ANhbry2ryjJY1NZbcjryJMRXG5uGNKd73kD3V1sVFsX",
|
||||||
|
@ -474,9 +505,10 @@ to grant access.
|
||||||
|
|
||||||
FrostFS Bearer Token basically is a container owner-signed policy (refer to FrostFS
|
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:
|
documentation for more details). There are two options to pass them to gateway:
|
||||||
* "Authorization" header with "Bearer" type and base64-encoded token in
|
|
||||||
|
* "Authorization" header with "Bearer" type and base64-encoded token in
|
||||||
credentials field
|
credentials field
|
||||||
* "Bearer" cookie with base64-encoded token contents
|
* "Bearer" cookie with base64-encoded token contents
|
||||||
|
|
||||||
For example, you have a mobile application frontend with a backend part storing
|
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
|
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.
|
works the same way.
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
In order to generate a bearer token, you need to have wallet (which will be used to sign the token)
|
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
|
1. Suppose you have a container with private policy for wallet key
|
||||||
|
@ -500,9 +533,9 @@ $ frostfs-cli ape-manager add -r <endpoint> --wallet <wallet> \
|
||||||
--chain-id <chainID>
|
--chain-id <chainID>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
2. Form a Bearer token (10000 is lifetime expiration in epoch) to impersonate
|
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**:
|
HTTP Gateway request as wallet signed request and save it to **bearer.json**:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"body": {
|
"body": {
|
||||||
|
@ -518,11 +551,13 @@ $ frostfs-cli ape-manager add -r <endpoint> --wallet <wallet> \
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Sign it with the wallet:
|
3. Sign it with the wallet:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ frostfs-cli util sign bearer-token --from bearer.json --to signed.json -w <wallet>
|
$ frostfs-cli util sign bearer-token --from bearer.json --to signed.json -w <wallet>
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Encode to base64 to use in header:
|
4. Encode to base64 to use in header:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ base64 -w 0 signed.json
|
$ base64 -w 0 signed.json
|
||||||
# output: Ck4KKgoECAIQBhIiCiCZGdlbN7DPGPMg9rsWqV+p2XdMzUqknRiexewSFp8kmBIbChk17MUri6OJ0X5ftsHzy7NERDNFB4C92PcaGgMIkE4SZgohAxpsb7vfAso1F0X6hrm6WpRS14WsT3/Ct1SMoqRsT89KEkEEGxKi8GjKSf52YqhppgaOTQHbUsL3jn7SHLqS3ndAQ7NtAATnmRHleZw2V2xRRSRBQdjDC05KK83LhdSax72Fsw==
|
# output: Ck4KKgoECAIQBhIiCiCZGdlbN7DPGPMg9rsWqV+p2XdMzUqknRiexewSFp8kmBIbChk17MUri6OJ0X5ftsHzy7NERDNFB4C92PcaGgMIkE4SZgohAxpsb7vfAso1F0X6hrm6WpRS14WsT3/Ct1SMoqRsT89KEkEEGxKi8GjKSf52YqhppgaOTQHbUsL3jn7SHLqS3ndAQ7NtAATnmRHleZw2V2xRRSRBQdjDC05KK83LhdSax72Fsw==
|
||||||
|
@ -551,6 +586,7 @@ $ echo 'NhVtreTTCoqsMQV5Wp55fqnriiUCpEaKm3' | base58 --decode | base64
|
||||||
```
|
```
|
||||||
|
|
||||||
Then specify this value in Bearer Token Json
|
Then specify this value in Bearer Token Json
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"body": {
|
"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.
|
and Pprof at `localhost:8083/debug/pprof` by default. Host and port can be configured.
|
||||||
See [configuration](./docs/gate-configuration.md).
|
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
|
## Credits
|
||||||
|
|
||||||
Please see [CREDITS](CREDITS.md) for details.
|
Please see [CREDITS](CREDITS.md) for details.
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -17,6 +17,7 @@ require (
|
||||||
github.com/ssgreg/journald v1.0.0
|
github.com/ssgreg/journald v1.0.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/testcontainers/testcontainers-go v0.13.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
|
github.com/valyala/fasthttp v1.34.0
|
||||||
go.opentelemetry.io/otel v1.16.0
|
go.opentelemetry.io/otel v1.16.0
|
||||||
go.opentelemetry.io/otel/trace v1.16.0
|
go.opentelemetry.io/otel/trace v1.16.0
|
||||||
|
|
2
go.sum
2
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/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-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/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 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
|
||||||
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
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=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
|
|
574
internal/handler/handler_fuzz_test.go
Normal file
574
internal/handler/handler_fuzz_test.go
Normal file
|
@ -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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fuzzSuccessExitCode = 0
|
||||||
|
fuzzFailExitCode = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
709
tests/fuzzing/dict.txt
Normal file
709
tests/fuzzing/dict.txt
Normal file
|
@ -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"
|
Loading…
Reference in a new issue