forked from TrueCloudLab/frostfs-s3-gw
[#264] Remove minio code distributed with Apache 2.0 license
NeoFS S3 Gate based on Minio RELEASE.2020-07-02T00-15-09Z code which distributed with Apache 2.0 license. This commit removes remaining minio code with that license. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
parent
8f3c9c5783
commit
5d11d8f556
4 changed files with 0 additions and 2322 deletions
1916
api/errors/errors.go
1916
api/errors/errors.go
File diff suppressed because it is too large
Load diff
|
@ -1,111 +1 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type encoding int
|
|
||||||
|
|
||||||
const (
|
|
||||||
encodePathSegment encoding = iota
|
|
||||||
encodeQueryComponent
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
urlEncodingType = "url"
|
|
||||||
upperhex = "0123456789ABCDEF"
|
|
||||||
)
|
|
||||||
|
|
||||||
func shouldEscape(c byte) bool {
|
|
||||||
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case '-', '_', '.', '/', '*':
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// s3URLEncode is based on url.QueryEscape() code,
|
|
||||||
// while considering some S3 exceptions.
|
|
||||||
func s3URLEncode(s string, mode encoding) string {
|
|
||||||
spaceCount, hexCount := 0, 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
c := s[i]
|
|
||||||
if shouldEscape(c) {
|
|
||||||
if c == ' ' && mode == encodeQueryComponent {
|
|
||||||
spaceCount++
|
|
||||||
} else {
|
|
||||||
hexCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if spaceCount == 0 && hexCount == 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf [64]byte
|
|
||||||
var t []byte
|
|
||||||
|
|
||||||
required := len(s) + 2*hexCount
|
|
||||||
if required <= len(buf) {
|
|
||||||
t = buf[:required]
|
|
||||||
} else {
|
|
||||||
t = make([]byte, required)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hexCount == 0 {
|
|
||||||
copy(t, s)
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if s[i] == ' ' {
|
|
||||||
t[i] = '+'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
switch c := s[i]; {
|
|
||||||
case c == ' ' && mode == encodeQueryComponent:
|
|
||||||
t[j] = '+'
|
|
||||||
j++
|
|
||||||
case shouldEscape(c):
|
|
||||||
t[j] = '%'
|
|
||||||
t[j+1] = upperhex[c>>4]
|
|
||||||
t[j+2] = upperhex[c&15]
|
|
||||||
j += 3
|
|
||||||
default:
|
|
||||||
t[j] = s[i]
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func s3QueryEncode(name string, encodingType string) (result string) {
|
|
||||||
if encodingType == "" {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
encodingType = strings.ToLower(encodingType)
|
|
||||||
switch encodingType {
|
|
||||||
case urlEncodingType:
|
|
||||||
return s3URLEncode(name, encodeQueryComponent)
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
func s3PathEncode(name string, encodingType string) (result string) {
|
|
||||||
if encodingType == "" {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
encodingType = strings.ToLower(encodingType)
|
|
||||||
switch encodingType {
|
|
||||||
case urlEncodingType:
|
|
||||||
return s3URLEncode(name, encodePathSegment)
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
189
api/reqinfo.go
189
api/reqinfo.go
|
@ -1,39 +1,6 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// KeyVal - appended to ReqInfo.Tags.
|
|
||||||
KeyVal struct {
|
|
||||||
Key string
|
|
||||||
Val string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReqInfo stores the request info.
|
|
||||||
ReqInfo struct {
|
|
||||||
sync.RWMutex
|
|
||||||
RemoteHost string // Client Host/IP
|
|
||||||
Host string // Node Host/IP
|
|
||||||
UserAgent string // User Agent
|
|
||||||
DeploymentID string // x-minio-deployment-id
|
|
||||||
RequestID string // x-amz-request-id
|
|
||||||
API string // API name - GetObject PutObject NewMultipartUpload etc.
|
|
||||||
BucketName string // Bucket name
|
|
||||||
ObjectName string // Object name
|
|
||||||
URL *url.URL // Request url
|
|
||||||
tags []KeyVal // Any additional info not accommodated by above fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObjectRequest represents object request data.
|
// ObjectRequest represents object request data.
|
||||||
ObjectRequest struct {
|
ObjectRequest struct {
|
||||||
Bucket string
|
Bucket string
|
||||||
|
@ -46,159 +13,3 @@ type (
|
||||||
type contextKeyType string
|
type contextKeyType string
|
||||||
|
|
||||||
const ctxRequestInfo = contextKeyType("NeoFS-S3-GW")
|
const ctxRequestInfo = contextKeyType("NeoFS-S3-GW")
|
||||||
|
|
||||||
var (
|
|
||||||
// De-facto standard header keys.
|
|
||||||
xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
|
|
||||||
xRealIP = http.CanonicalHeaderKey("X-Real-IP")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// RFC7239 defines a new "Forwarded: " header designed to replace the
|
|
||||||
// existing use of X-Forwarded-* headers.
|
|
||||||
// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43.
|
|
||||||
forwarded = http.CanonicalHeaderKey("Forwarded")
|
|
||||||
// Allows for a sub-match of the first value after 'for=' to the next
|
|
||||||
// comma, semi-colon or space. The match is case-insensitive.
|
|
||||||
forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|, )]+)(.*)`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetSourceIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
|
|
||||||
// Forwarded headers (in that order), falls back to r.RemoteAddr when all
|
|
||||||
// else fails.
|
|
||||||
func GetSourceIP(r *http.Request) string {
|
|
||||||
var addr string
|
|
||||||
|
|
||||||
if fwd := r.Header.Get(xForwardedFor); fwd != "" {
|
|
||||||
// Only grab the first (client) address. Note that '192.168.0.1,
|
|
||||||
// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
|
|
||||||
// the first may represent forwarding proxies earlier in the chain.
|
|
||||||
s := strings.Index(fwd, ", ")
|
|
||||||
if s == -1 {
|
|
||||||
s = len(fwd)
|
|
||||||
}
|
|
||||||
addr = fwd[:s]
|
|
||||||
} else if fwd := r.Header.Get(xRealIP); fwd != "" {
|
|
||||||
// X-Real-IP should only contain one IP address (the client making the
|
|
||||||
// request).
|
|
||||||
addr = fwd
|
|
||||||
} else if fwd := r.Header.Get(forwarded); fwd != "" {
|
|
||||||
// match should contain at least two elements if the protocol was
|
|
||||||
// specified in the Forwarded header. The first element will always be
|
|
||||||
// the 'for=' capture, which we ignore. In the case of multiple IP
|
|
||||||
// addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only
|
|
||||||
// extract the first, which should be the client IP.
|
|
||||||
if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
|
|
||||||
// IPv6 addresses in Forwarded headers are quoted-strings. We strip
|
|
||||||
// these quotes.
|
|
||||||
addr = strings.Trim(match[1], `"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr != "" {
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to remote address if headers not set.
|
|
||||||
addr, _, _ = net.SplitHostPort(r.RemoteAddr)
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareContext(w http.ResponseWriter, r *http.Request) context.Context {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
bucket := vars["bucket"]
|
|
||||||
object, err := url.PathUnescape(vars["object"])
|
|
||||||
if err != nil {
|
|
||||||
object = vars["object"]
|
|
||||||
}
|
|
||||||
prefix, err := url.QueryUnescape(vars["prefix"])
|
|
||||||
if err != nil {
|
|
||||||
prefix = vars["prefix"]
|
|
||||||
}
|
|
||||||
if prefix != "" {
|
|
||||||
object = prefix
|
|
||||||
}
|
|
||||||
return SetReqInfo(r.Context(),
|
|
||||||
// prepare request info
|
|
||||||
NewReqInfo(w, r, ObjectRequest{
|
|
||||||
Bucket: bucket,
|
|
||||||
Object: object,
|
|
||||||
Method: mux.CurrentRoute(r).GetName(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReqInfo returns new ReqInfo based on parameters.
|
|
||||||
func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest) *ReqInfo {
|
|
||||||
return &ReqInfo{
|
|
||||||
API: req.Method,
|
|
||||||
BucketName: req.Bucket,
|
|
||||||
ObjectName: req.Object,
|
|
||||||
UserAgent: r.UserAgent(),
|
|
||||||
RemoteHost: GetSourceIP(r),
|
|
||||||
RequestID: GetRequestID(w),
|
|
||||||
DeploymentID: deploymentID.String(),
|
|
||||||
URL: r.URL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendTags - appends key/val to ReqInfo.tags.
|
|
||||||
func (r *ReqInfo) AppendTags(key string, val string) *ReqInfo {
|
|
||||||
if r == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
r.tags = append(r.tags, KeyVal{key, val})
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTags - sets key/val to ReqInfo.tags.
|
|
||||||
func (r *ReqInfo) SetTags(key string, val string) *ReqInfo {
|
|
||||||
if r == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r.Lock()
|
|
||||||
defer r.Unlock()
|
|
||||||
// Search of tag key already exists in tags
|
|
||||||
var updated bool
|
|
||||||
for _, tag := range r.tags {
|
|
||||||
if tag.Key == key {
|
|
||||||
tag.Val = val
|
|
||||||
updated = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !updated {
|
|
||||||
// Append to the end of tags list
|
|
||||||
r.tags = append(r.tags, KeyVal{key, val})
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTags - returns the user defined tags.
|
|
||||||
func (r *ReqInfo) GetTags() []KeyVal {
|
|
||||||
if r == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r.RLock()
|
|
||||||
defer r.RUnlock()
|
|
||||||
return append([]KeyVal(nil), r.tags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReqInfo sets ReqInfo in the context.
|
|
||||||
func SetReqInfo(ctx context.Context, req *ReqInfo) context.Context {
|
|
||||||
if ctx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return context.WithValue(ctx, ctxRequestInfo, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetReqInfo returns ReqInfo if set.
|
|
||||||
func GetReqInfo(ctx context.Context) *ReqInfo {
|
|
||||||
if ctx == nil {
|
|
||||||
return &ReqInfo{}
|
|
||||||
} else if r, ok := ctx.Value(ctxRequestInfo).(*ReqInfo); ok {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
return &ReqInfo{}
|
|
||||||
}
|
|
||||||
|
|
107
api/response.go
107
api/response.go
|
@ -1,15 +1,11 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/internal/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -109,109 +105,6 @@ var s3ErrorResponseMap = map[string]string{
|
||||||
// Add new API errors here.
|
// Add new API errors here.
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteErrorResponse writes error headers.
|
|
||||||
func WriteErrorResponse(w http.ResponseWriter, reqInfo *ReqInfo, err error) {
|
|
||||||
code := http.StatusInternalServerError
|
|
||||||
|
|
||||||
if e, ok := err.(errors.Error); ok {
|
|
||||||
code = e.HTTPStatusCode
|
|
||||||
|
|
||||||
switch e.Code {
|
|
||||||
case "SlowDown", "XNeoFSServerNotInitialized", "XNeoFSReadQuorum", "XNeoFSWriteQuorum":
|
|
||||||
// Set retry-after header to indicate user-agents to retry request after 120secs.
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
|
||||||
w.Header().Set(hdrRetryAfter, "120")
|
|
||||||
case "AccessDenied":
|
|
||||||
// TODO process when the request is from browser and also if browser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate error response.
|
|
||||||
errorResponse := getAPIErrorResponse(reqInfo, err)
|
|
||||||
encodedErrorResponse := EncodeResponse(errorResponse)
|
|
||||||
WriteResponse(w, code, encodedErrorResponse, MimeXML)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the http routes match respond with appropriate errors.
|
|
||||||
func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
desc := fmt.Sprintf("Unknown API request at %s", r.URL.Path)
|
|
||||||
WriteErrorResponse(w, GetReqInfo(r.Context()), errors.Error{
|
|
||||||
Code: "XMinioUnknownAPIRequest",
|
|
||||||
Description: desc,
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write http common headers.
|
|
||||||
func setCommonHeaders(w http.ResponseWriter) {
|
|
||||||
w.Header().Set(hdrServerInfo, version.Server)
|
|
||||||
w.Header().Set(hdrAcceptRanges, "bytes")
|
|
||||||
|
|
||||||
// Remove sensitive information
|
|
||||||
removeSensitiveHeaders(w.Header())
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeSensitiveHeaders removes confidential encryption
|
|
||||||
// information - e.g. the SSE-C key - from the HTTP headers.
|
|
||||||
// It has the same semantics as RemoveSensitiveEntries.
|
|
||||||
func removeSensitiveHeaders(h http.Header) {
|
|
||||||
h.Del(hdrSSECustomerKey)
|
|
||||||
h.Del(hdrSSECopyKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteResponse writes given statusCode and response into w (with mType header if set).
|
|
||||||
func WriteResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
|
|
||||||
setCommonHeaders(w)
|
|
||||||
if mType != MimeNone {
|
|
||||||
w.Header().Set(hdrContentType, string(mType))
|
|
||||||
}
|
|
||||||
w.Header().Set(hdrContentLength, strconv.Itoa(len(response)))
|
|
||||||
w.WriteHeader(statusCode)
|
|
||||||
if response == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = w.Write(response)
|
|
||||||
if flusher, ok := w.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeResponse encodes the response headers into XML format.
|
|
||||||
func EncodeResponse(response interface{}) []byte {
|
|
||||||
var bytesBuffer bytes.Buffer
|
|
||||||
bytesBuffer.WriteString(xml.Header)
|
|
||||||
_ = xml.
|
|
||||||
NewEncoder(&bytesBuffer).
|
|
||||||
Encode(response)
|
|
||||||
return bytesBuffer.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeToResponse encodes the response into ResponseWriter.
|
|
||||||
func EncodeToResponse(w http.ResponseWriter, response interface{}) error {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
if _, err := w.Write(xmlHeader); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err = xml.NewEncoder(w).Encode(response); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// // WriteSuccessResponseXML writes success headers and response if any,
|
|
||||||
// // with content-type set to `application/xml`.
|
|
||||||
// func WriteSuccessResponseXML(w http.ResponseWriter, response []byte) {
|
|
||||||
// WriteResponse(w, http.StatusOK, response, MimeXML)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// WriteSuccessResponseHeadersOnly writes HTTP (200) OK response with no data
|
|
||||||
// to the client.
|
|
||||||
func WriteSuccessResponseHeadersOnly(w http.ResponseWriter) {
|
|
||||||
WriteResponse(w, http.StatusOK, nil, MimeNone)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error - Returns S3 error string.
|
// Error - Returns S3 error string.
|
||||||
func (e ErrorResponse) Error() string {
|
func (e ErrorResponse) Error() string {
|
||||||
if e.Message == "" {
|
if e.Message == "" {
|
||||||
|
|
Loading…
Reference in a new issue