2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2016-04-28 23:01:11 -04:00
2016-08-18 19:23:42 -04:00
package cmd
2016-04-28 23:01:11 -04:00
import (
2018-08-01 17:19:11 -04:00
"bytes"
2018-04-05 18:04:40 -04:00
"context"
2020-08-31 15:37:31 -04:00
"errors"
2019-11-04 12:30:59 -05:00
"fmt"
2016-04-28 23:01:11 -04:00
"io"
2018-08-01 17:19:11 -04:00
"io/ioutil"
2016-07-24 01:51:12 -04:00
"mime/multipart"
2017-11-14 19:56:24 -05:00
"net"
2016-07-19 00:20:17 -04:00
"net/http"
2021-02-03 23:41:33 -05:00
"net/textproto"
2017-03-13 17:41:13 -04:00
"net/url"
2019-11-04 12:30:59 -05:00
"regexp"
2016-07-22 23:31:45 -04:00
"strings"
2017-10-24 22:04:51 -04:00
2021-05-06 11:52:02 -04:00
"github.com/minio/madmin-go"
2021-06-01 17:59:40 -04:00
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/handlers"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
2020-01-20 11:45:59 -05:00
)
const (
copyDirective = "COPY"
replaceDirective = "REPLACE"
2016-04-28 23:01:11 -04:00
)
2017-04-03 17:50:09 -04:00
// Parses location constraint from the incoming reader.
func parseLocationConstraint ( r * http . Request ) ( location string , s3Error APIErrorCode ) {
2016-07-19 00:20:17 -04:00
// If the request has no body with content-length set to 0,
// we do not have to validate location constraint. Bucket will
// be created at default region.
locationConstraint := createBucketLocationConfiguration { }
2016-09-29 18:51:00 -04:00
err := xmlDecoder ( r . Body , & locationConstraint , r . ContentLength )
2019-01-31 10:19:09 -05:00
if err != nil && r . ContentLength != 0 {
2020-04-09 12:30:02 -04:00
logger . LogIf ( GlobalContext , err )
2017-04-03 17:50:09 -04:00
// Treat all other failures as XML parsing errors.
return "" , ErrMalformedXML
} // else for both err as nil or io.EOF
location = locationConstraint . Location
if location == "" {
2021-11-25 16:06:25 -05:00
location = globalSite . Region
2016-04-28 23:01:11 -04:00
}
2017-04-03 17:50:09 -04:00
return location , ErrNone
}
// Validates input location is same as configured region
2019-04-09 14:39:42 -04:00
// of MinIO server.
2017-04-03 17:50:09 -04:00
func isValidLocation ( location string ) bool {
2021-11-25 16:06:25 -05:00
return globalSite . Region == "" || globalSite . Region == location
2016-04-28 23:01:11 -04:00
}
2016-07-22 23:31:45 -04:00
// Supported headers that needs to be extracted.
var supportedHeaders = [ ] string {
"content-type" ,
"cache-control" ,
2018-03-14 05:57:32 -04:00
"content-language" ,
2016-07-22 23:31:45 -04:00
"content-encoding" ,
"content-disposition" ,
2021-02-03 23:41:33 -05:00
"x-amz-storage-class" ,
2019-10-07 01:50:24 -04:00
xhttp . AmzStorageClass ,
2020-01-20 11:45:59 -05:00
xhttp . AmzObjectTagging ,
2018-03-28 17:14:06 -04:00
"expires" ,
2020-07-21 20:49:56 -04:00
xhttp . AmzBucketReplicationStatus ,
2016-07-22 23:31:45 -04:00
// Add more supported headers here.
}
2020-01-20 11:45:59 -05:00
// isDirectiveValid - check if tagging-directive is valid.
func isDirectiveValid ( v string ) bool {
// Check if set metadata-directive is valid.
return isDirectiveCopy ( v ) || isDirectiveReplace ( v )
2016-12-26 19:29:26 -05:00
}
2020-01-20 11:45:59 -05:00
// Check if the directive COPY is requested.
func isDirectiveCopy ( value string ) bool {
// By default if directive is not set we
// treat it as 'COPY' this function returns true.
return value == copyDirective || value == ""
2016-12-26 19:29:26 -05:00
}
2020-01-20 11:45:59 -05:00
// Check if the directive REPLACE is requested.
func isDirectiveReplace ( value string ) bool {
return value == replaceDirective
2016-12-26 19:29:26 -05:00
}
2017-08-22 19:53:35 -04:00
// userMetadataKeyPrefixes contains the prefixes of used-defined metadata keys.
// All values stored with a key starting with one of the following prefixes
// must be extracted from the header.
var userMetadataKeyPrefixes = [ ] string {
2020-05-25 19:51:32 -04:00
"x-amz-meta-" ,
"x-minio-meta-" ,
2017-08-22 19:53:35 -04:00
}
2018-07-10 23:27:10 -04:00
// extractMetadata extracts metadata from HTTP header and HTTP queryString.
func extractMetadata ( ctx context . Context , r * http . Request ) ( metadata map [ string ] string , err error ) {
2021-08-08 01:43:01 -04:00
query := r . Form
2018-07-10 23:27:10 -04:00
header := r . Header
metadata = make ( map [ string ] string )
// Extract all query values.
2021-02-03 23:41:33 -05:00
err = extractMetadataFromMime ( ctx , textproto . MIMEHeader ( query ) , metadata )
2018-07-10 23:27:10 -04:00
if err != nil {
return nil , err
}
// Extract all header values.
2021-02-03 23:41:33 -05:00
err = extractMetadataFromMime ( ctx , textproto . MIMEHeader ( header ) , metadata )
2018-07-10 23:27:10 -04:00
if err != nil {
return nil , err
2017-03-13 17:41:13 -04:00
}
2017-12-22 06:28:13 -05:00
2018-12-19 17:31:45 -05:00
// Set content-type to default value if it is not set.
2020-07-08 20:36:56 -04:00
if _ , ok := metadata [ strings . ToLower ( xhttp . ContentType ) ] ; ! ok {
2021-02-10 11:56:37 -05:00
metadata [ strings . ToLower ( xhttp . ContentType ) ] = "binary/octet-stream"
2018-12-19 17:31:45 -05:00
}
2020-07-08 20:36:56 -04:00
2020-08-11 11:29:29 -04:00
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
for k := range metadata {
2021-02-03 23:41:33 -05:00
if equals ( k , xhttp . AmzMetaUnencryptedContentLength , xhttp . AmzMetaUnencryptedContentMD5 ) {
2020-08-11 11:29:29 -04:00
delete ( metadata , k )
}
}
2020-07-08 20:36:56 -04:00
if contentEncoding , ok := metadata [ strings . ToLower ( xhttp . ContentEncoding ) ] ; ok {
contentEncoding = trimAwsChunkedContentEncoding ( contentEncoding )
if contentEncoding != "" {
// Make sure to trim and save the content-encoding
// parameter for a streaming signature which is set
// to a custom value for example: "aws-chunked,gzip".
metadata [ strings . ToLower ( xhttp . ContentEncoding ) ] = contentEncoding
} else {
// Trimmed content encoding is empty when the header
// value is set to "aws-chunked" only.
// Make sure to delete the content-encoding parameter
// for a streaming signature which is set to value
// for example: "aws-chunked"
delete ( metadata , strings . ToLower ( xhttp . ContentEncoding ) )
}
}
2018-07-10 23:27:10 -04:00
// Success.
return metadata , nil
}
// extractMetadata extracts metadata from map values.
2021-02-03 23:41:33 -05:00
func extractMetadataFromMime ( ctx context . Context , v textproto . MIMEHeader , m map [ string ] string ) error {
2018-07-10 23:27:10 -04:00
if v == nil {
logger . LogIf ( ctx , errInvalidArgument )
return errInvalidArgument
}
2021-02-03 23:41:33 -05:00
nv := make ( textproto . MIMEHeader , len ( v ) )
for k , kv := range v {
// Canonicalize all headers, to remove any duplicates.
nv [ http . CanonicalHeaderKey ( k ) ] = kv
}
2017-12-22 06:28:13 -05:00
// Save all supported headers.
2016-07-22 23:31:45 -04:00
for _ , supportedHeader := range supportedHeaders {
2021-02-03 23:41:33 -05:00
value , ok := nv [ http . CanonicalHeaderKey ( supportedHeader ) ]
if ok {
m [ supportedHeader ] = strings . Join ( value , "," )
2016-07-22 23:31:45 -04:00
}
}
2021-02-03 23:41:33 -05:00
2018-07-10 23:27:10 -04:00
for key := range v {
2017-08-22 19:53:35 -04:00
for _ , prefix := range userMetadataKeyPrefixes {
2018-07-10 23:27:10 -04:00
if ! strings . HasPrefix ( strings . ToLower ( key ) , strings . ToLower ( prefix ) ) {
continue
}
2021-02-03 23:41:33 -05:00
value , ok := nv [ http . CanonicalHeaderKey ( key ) ]
2018-07-10 23:27:10 -04:00
if ok {
2018-07-12 12:40:14 -04:00
m [ key ] = strings . Join ( value , "," )
2017-08-22 19:53:35 -04:00
break
}
2016-07-22 23:31:45 -04:00
}
}
2018-07-10 23:27:10 -04:00
return nil
2016-12-19 19:14:04 -05:00
}
2017-03-13 17:41:13 -04:00
// The Query string for the redirect URL the client is
// redirected on successful upload.
func getRedirectPostRawQuery ( objInfo ObjectInfo ) string {
redirectValues := make ( url . Values )
redirectValues . Set ( "bucket" , objInfo . Bucket )
redirectValues . Set ( "key" , objInfo . Name )
2017-05-14 15:05:51 -04:00
redirectValues . Set ( "etag" , "\"" + objInfo . ETag + "\"" )
2017-03-13 17:41:13 -04:00
return redirectValues . Encode ( )
}
2018-11-29 20:35:11 -05:00
// Returns access credentials in the request Authorization header.
func getReqAccessCred ( r * http . Request , region string ) ( cred auth . Credentials ) {
2019-02-27 20:46:55 -05:00
cred , _ , _ = getReqAccessKeyV4 ( r , region , serviceS3 )
2018-11-07 09:40:03 -05:00
if cred . AccessKey == "" {
cred , _ , _ = getReqAccessKeyV2 ( r )
2018-11-02 21:40:08 -04:00
}
2018-11-29 20:35:11 -05:00
return cred
2018-11-02 21:40:08 -04:00
}
2017-03-13 17:41:13 -04:00
// Extract request params to be sent with event notifiation.
func extractReqParams ( r * http . Request ) map [ string ] string {
if r == nil {
return nil
2016-12-19 19:14:04 -05:00
}
2017-03-13 17:41:13 -04:00
2021-11-25 16:06:25 -05:00
region := globalSite . Region
2018-11-29 20:35:11 -05:00
cred := getReqAccessCred ( r , region )
2018-12-19 08:13:47 -05:00
2021-03-31 16:21:10 -04:00
principalID := cred . AccessKey
if cred . ParentUser != "" {
principalID = cred . ParentUser
}
2017-03-13 17:41:13 -04:00
// Success.
2021-03-03 14:13:31 -05:00
m := map [ string ] string {
2018-11-02 21:40:08 -04:00
"region" : region ,
2021-03-31 16:21:10 -04:00
"principalId" : principalID ,
2018-07-02 17:40:18 -04:00
"sourceIPAddress" : handlers . GetSourceIP ( r ) ,
2017-03-13 17:41:13 -04:00
// Add more fields here.
}
2021-03-03 14:13:31 -05:00
if _ , ok := r . Header [ xhttp . MinIOSourceReplicationRequest ] ; ok {
m [ xhttp . MinIOSourceReplicationRequest ] = ""
}
return m
2017-03-13 17:41:13 -04:00
}
2018-08-23 17:40:54 -04:00
// Extract response elements to be sent with event notifiation.
func extractRespElements ( w http . ResponseWriter ) map [ string ] string {
2020-10-02 16:36:13 -04:00
if w == nil {
return map [ string ] string { }
}
2018-08-23 17:40:54 -04:00
return map [ string ] string {
2019-07-03 01:34:32 -04:00
"requestId" : w . Header ( ) . Get ( xhttp . AmzRequestID ) ,
"content-length" : w . Header ( ) . Get ( xhttp . ContentLength ) ,
2018-08-23 17:40:54 -04:00
// Add more fields here.
}
}
2017-03-27 20:02:04 -04:00
// Trims away `aws-chunked` from the content-encoding header if present.
// Streaming signature clients can have custom content-encoding such as
// `aws-chunked,gzip` here we need to only save `gzip`.
// For more refer http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
func trimAwsChunkedContentEncoding ( contentEnc string ) ( trimmedContentEnc string ) {
if contentEnc == "" {
return contentEnc
}
var newEncs [ ] string
for _ , enc := range strings . Split ( contentEnc , "," ) {
if enc != streamingContentEncoding {
newEncs = append ( newEncs , enc )
}
}
return strings . Join ( newEncs , "," )
}
2017-03-13 17:41:13 -04:00
// Validate form field size for s3 specification requirement.
2018-04-05 18:04:40 -04:00
func validateFormFieldSize ( ctx context . Context , formValues http . Header ) error {
2017-03-13 17:41:13 -04:00
// Iterate over form values
for k := range formValues {
// Check if value's field exceeds S3 limit
if int64 ( len ( formValues . Get ( k ) ) ) > maxFormFieldSize {
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , errSizeUnexpected )
return errSizeUnexpected
2016-12-19 19:14:04 -05:00
}
}
2017-03-13 17:41:13 -04:00
// Success.
return nil
2016-07-22 23:31:45 -04:00
}
2016-07-24 01:51:12 -04:00
2016-07-28 15:02:22 -04:00
// Extract form fields and file data from a HTTP POST Policy
2018-04-05 18:04:40 -04:00
func extractPostPolicyFormValues ( ctx context . Context , form * multipart . Form ) ( filePart io . ReadCloser , fileName string , fileSize int64 , formValues http . Header , err error ) {
2021-11-16 12:28:29 -05:00
// HTML Form values
2016-07-28 15:02:22 -04:00
fileName = ""
2017-02-02 13:45:00 -05:00
2017-03-13 17:41:13 -04:00
// Canonicalize the form values into http.Header.
formValues = make ( http . Header )
2017-02-02 13:45:00 -05:00
for k , v := range form . Value {
2017-03-13 17:41:13 -04:00
formValues [ http . CanonicalHeaderKey ( k ) ] = v
}
// Validate form values.
2018-04-05 18:04:40 -04:00
if err = validateFormFieldSize ( ctx , formValues ) ; err != nil {
2017-03-13 17:41:13 -04:00
return nil , "" , 0 , nil , err
2017-02-02 13:45:00 -05:00
}
2018-08-01 17:19:11 -04:00
// this means that filename="" was not specified for file key and Go has
// an ugly way of handling this situation. Refer here
// https://golang.org/src/mime/multipart/formdata.go#L61
if len ( form . File ) == 0 {
var b = & bytes . Buffer { }
for _ , v := range formValues [ "File" ] {
b . WriteString ( v )
}
fileSize = int64 ( b . Len ( ) )
filePart = ioutil . NopCloser ( b )
return filePart , fileName , fileSize , formValues , nil
}
2017-02-02 13:45:00 -05:00
// Iterator until we find a valid File field and break
for k , v := range form . File {
canonicalFormName := http . CanonicalHeaderKey ( k )
if canonicalFormName == "File" {
if len ( v ) == 0 {
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , errInvalidArgument )
return nil , "" , 0 , nil , errInvalidArgument
2016-07-24 01:51:12 -04:00
}
2017-02-02 13:45:00 -05:00
// Fetch fileHeader which has the uploaded file information
fileHeader := v [ 0 ]
// Set filename
fileName = fileHeader . Filename
// Open the uploaded part
filePart , err = fileHeader . Open ( )
2017-02-09 15:37:32 -05:00
if err != nil {
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , err )
return nil , "" , 0 , nil , err
2017-02-09 15:37:32 -05:00
}
2017-02-02 13:45:00 -05:00
// Compute file size
fileSize , err = filePart . ( io . Seeker ) . Seek ( 0 , 2 )
if err != nil {
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , err )
return nil , "" , 0 , nil , err
2017-02-02 13:45:00 -05:00
}
// Reset Seek to the beginning
_ , err = filePart . ( io . Seeker ) . Seek ( 0 , 0 )
if err != nil {
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , err )
return nil , "" , 0 , nil , err
2017-02-02 13:45:00 -05:00
}
// File found and ready for reading
break
2016-07-24 01:51:12 -04:00
}
}
2017-02-02 13:45:00 -05:00
return filePart , fileName , fileSize , formValues , nil
2016-07-24 01:51:12 -04:00
}
2017-10-24 22:04:51 -04:00
// Log headers and body.
func httpTraceAll ( f http . HandlerFunc ) http . HandlerFunc {
2019-06-08 18:54:41 -04:00
return func ( w http . ResponseWriter , r * http . Request ) {
2021-03-27 02:24:07 -04:00
if globalTrace . NumSubscribers ( ) == 0 {
2019-06-08 18:54:41 -04:00
f . ServeHTTP ( w , r )
return
}
trace := Trace ( f , true , w , r )
2021-03-27 02:24:07 -04:00
globalTrace . Publish ( trace )
2017-10-24 22:04:51 -04:00
}
}
// Log only the headers.
func httpTraceHdrs ( f http . HandlerFunc ) http . HandlerFunc {
2019-06-08 18:54:41 -04:00
return func ( w http . ResponseWriter , r * http . Request ) {
2021-03-27 02:24:07 -04:00
if globalTrace . NumSubscribers ( ) == 0 {
2019-06-08 18:54:41 -04:00
f . ServeHTTP ( w , r )
return
}
trace := Trace ( f , false , w , r )
2021-03-27 02:24:07 -04:00
globalTrace . Publish ( trace )
2017-10-24 22:04:51 -04:00
}
}
2017-11-14 19:56:24 -05:00
2019-10-23 00:01:14 -04:00
func collectAPIStats ( api string , f http . HandlerFunc ) http . HandlerFunc {
return func ( w http . ResponseWriter , r * http . Request ) {
2020-04-30 01:17:36 -04:00
globalHTTPStats . currentS3Requests . Inc ( api )
defer globalHTTPStats . currentS3Requests . Dec ( api )
2019-10-23 00:01:14 -04:00
2020-04-30 14:27:19 -04:00
statsWriter := logger . NewResponseWriter ( w )
2019-10-23 00:01:14 -04:00
2020-04-30 01:17:36 -04:00
f . ServeHTTP ( statsWriter , r )
2019-10-23 00:01:14 -04:00
2020-12-11 02:02:25 -05:00
globalHTTPStats . updateStats ( api , r , statsWriter )
2019-10-23 00:01:14 -04:00
}
}
2017-11-14 19:56:24 -05:00
// Returns "/bucketName/objectName" for path-style or virtual-host-style requests.
2019-02-22 22:18:01 -05:00
func getResource ( path string , host string , domains [ ] string ) ( string , error ) {
if len ( domains ) == 0 {
2017-11-14 19:56:24 -05:00
return path , nil
}
// If virtual-host-style is enabled construct the "resource" properly.
if strings . Contains ( host , ":" ) {
// In bucket.mydomain.com:9000, strip out :9000
var err error
if host , _ , err = net . SplitHostPort ( host ) ; err != nil {
2018-04-05 18:04:40 -04:00
reqInfo := ( & logger . ReqInfo { } ) . AppendTags ( "host" , host )
reqInfo . AppendTags ( "path" , path )
2020-04-09 12:30:02 -04:00
ctx := logger . SetReqInfo ( GlobalContext , reqInfo )
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , err )
2017-11-14 19:56:24 -05:00
return "" , err
}
}
2019-02-22 22:18:01 -05:00
for _ , domain := range domains {
2020-09-09 12:57:37 -04:00
if host == minioReservedBucket + "." + domain {
continue
}
2019-02-22 22:18:01 -05:00
if ! strings . HasSuffix ( host , "." + domain ) {
continue
}
bucket := strings . TrimSuffix ( host , "." + domain )
2019-08-06 15:08:58 -04:00
return SlashSeparator + pathJoin ( bucket , path ) , nil
2017-11-14 19:56:24 -05:00
}
2019-02-22 22:18:01 -05:00
return path , nil
2017-11-14 19:56:24 -05:00
}
2021-02-08 13:15:12 -05:00
var regexVersion = regexp . MustCompile ( ` ^/minio.*/(v\d+)/.* ` )
2019-11-04 12:30:59 -05:00
func extractAPIVersion ( r * http . Request ) string {
2021-02-08 13:15:12 -05:00
if matches := regexVersion . FindStringSubmatch ( r . URL . Path ) ; len ( matches ) > 1 {
return matches [ 1 ]
}
return "unknown"
2017-11-14 19:56:24 -05:00
}
2019-07-05 23:41:35 -04:00
2021-12-06 12:44:48 -05:00
func generateUnexpectedRPCMsg ( rpcPath , subsystem string , expectedVersion , gotVersion string ) string {
var reason string
switch {
case expectedVersion != gotVersion :
reason = fmt . Sprintf ( "Server expects '%s' API version '%s', instead found '%s'" , subsystem , expectedVersion , gotVersion )
default :
reason = fmt . Sprintf ( "Unexpected RPC call at this path '%s'" , rpcPath )
}
return fmt . Sprintf ( "%s - *rolling upgrade is not allowed* - please make sure all servers are running the same MinIO version (%s)" , reason , ReleaseTag )
}
2020-10-28 12:18:35 -04:00
func methodNotAllowedHandler ( api string ) func ( w http . ResponseWriter , r * http . Request ) {
return func ( w http . ResponseWriter , r * http . Request ) {
2021-02-08 13:15:12 -05:00
if r . Method == http . MethodOptions {
return
}
version := extractAPIVersion ( r )
switch {
case strings . HasPrefix ( r . URL . Path , peerRESTPrefix ) :
2021-12-06 12:44:48 -05:00
desc := generateUnexpectedRPCMsg ( r . URL . Path , "peer" , peerRESTVersion , version )
2021-02-08 13:15:12 -05:00
writeErrorResponseString ( r . Context ( ) , w , APIError {
Code : "XMinioPeerVersionMismatch" ,
Description : desc ,
HTTPStatusCode : http . StatusUpgradeRequired ,
} , r . URL )
case strings . HasPrefix ( r . URL . Path , storageRESTPrefix ) :
2021-12-06 12:44:48 -05:00
desc := generateUnexpectedRPCMsg ( r . URL . Path , "storage" , storageRESTVersion , version )
2021-02-08 13:15:12 -05:00
writeErrorResponseString ( r . Context ( ) , w , APIError {
Code : "XMinioStorageVersionMismatch" ,
Description : desc ,
HTTPStatusCode : http . StatusUpgradeRequired ,
} , r . URL )
case strings . HasPrefix ( r . URL . Path , lockRESTPrefix ) :
2021-12-06 12:44:48 -05:00
desc := generateUnexpectedRPCMsg ( r . URL . Path , "lock" , lockRESTVersion , version )
2021-02-08 13:15:12 -05:00
writeErrorResponseString ( r . Context ( ) , w , APIError {
Code : "XMinioLockVersionMismatch" ,
Description : desc ,
HTTPStatusCode : http . StatusUpgradeRequired ,
} , r . URL )
case strings . HasPrefix ( r . URL . Path , adminPathPrefix ) :
var desc string
if version == "v1" {
desc = fmt . Sprintf ( "Server expects client requests with 'admin' API version '%s', found '%s', please upgrade the client to latest releases" , madmin . AdminAPIVersion , version )
} else if version == madmin . AdminAPIVersion {
desc = fmt . Sprintf ( "This 'admin' API is not supported by server in '%s'" , getMinioMode ( ) )
} else {
desc = fmt . Sprintf ( "Unexpected client 'admin' API version found '%s', expected '%s', please downgrade the client to older releases" , version , madmin . AdminAPIVersion )
}
writeErrorResponseJSON ( r . Context ( ) , w , APIError {
Code : "XMinioAdminVersionMismatch" ,
Description : desc ,
HTTPStatusCode : http . StatusUpgradeRequired ,
} , r . URL )
default :
writeErrorResponse ( r . Context ( ) , w , APIError {
2021-03-02 02:10:33 -05:00
Code : "BadRequest" ,
Description : fmt . Sprintf ( "An error occurred when parsing the HTTP request %s at '%s'" ,
r . Method , r . URL . Path ) ,
2021-02-08 13:15:12 -05:00
HTTPStatusCode : http . StatusBadRequest ,
2021-06-17 23:27:04 -04:00
} , r . URL )
2021-02-08 13:15:12 -05:00
}
2020-10-28 12:18:35 -04:00
}
}
2019-11-04 12:30:59 -05:00
// If none of the http routes match respond with appropriate errors
func errorResponseHandler ( w http . ResponseWriter , r * http . Request ) {
2020-07-27 12:03:38 -04:00
if r . Method == http . MethodOptions {
return
}
2019-11-04 12:30:59 -05:00
version := extractAPIVersion ( r )
2019-10-03 03:08:12 -04:00
switch {
2019-11-04 12:30:59 -05:00
case strings . HasPrefix ( r . URL . Path , peerRESTPrefix ) :
2021-02-08 13:15:12 -05:00
desc := fmt . Sprintf ( "Server expects 'peer' API version '%s', instead found '%s' - *rolling upgrade is not allowed* - please make sure all servers are running the same MinIO version (%s)" , peerRESTVersion , version , ReleaseTag )
2019-11-04 12:30:59 -05:00
writeErrorResponseString ( r . Context ( ) , w , APIError {
Code : "XMinioPeerVersionMismatch" ,
Description : desc ,
2020-06-17 17:49:26 -04:00
HTTPStatusCode : http . StatusUpgradeRequired ,
2019-11-04 12:30:59 -05:00
} , r . URL )
case strings . HasPrefix ( r . URL . Path , storageRESTPrefix ) :
2021-02-08 13:15:12 -05:00
desc := fmt . Sprintf ( "Server expects 'storage' API version '%s', instead found '%s' - *rolling upgrade is not allowed* - please make sure all servers are running the same MinIO version (%s)" , storageRESTVersion , version , ReleaseTag )
2019-11-04 12:30:59 -05:00
writeErrorResponseString ( r . Context ( ) , w , APIError {
Code : "XMinioStorageVersionMismatch" ,
Description : desc ,
2020-06-17 17:49:26 -04:00
HTTPStatusCode : http . StatusUpgradeRequired ,
2019-11-04 12:30:59 -05:00
} , r . URL )
case strings . HasPrefix ( r . URL . Path , lockRESTPrefix ) :
2021-02-08 13:15:12 -05:00
desc := fmt . Sprintf ( "Server expects 'lock' API version '%s', instead found '%s' - *rolling upgrade is not allowed* - please make sure all servers are running the same MinIO version (%s)" , lockRESTVersion , version , ReleaseTag )
2019-11-04 12:30:59 -05:00
writeErrorResponseString ( r . Context ( ) , w , APIError {
Code : "XMinioLockVersionMismatch" ,
Description : desc ,
2020-06-17 17:49:26 -04:00
HTTPStatusCode : http . StatusUpgradeRequired ,
2019-11-04 12:30:59 -05:00
} , r . URL )
case strings . HasPrefix ( r . URL . Path , adminPathPrefix ) :
var desc string
if version == "v1" {
desc = fmt . Sprintf ( "Server expects client requests with 'admin' API version '%s', found '%s', please upgrade the client to latest releases" , madmin . AdminAPIVersion , version )
} else if version == madmin . AdminAPIVersion {
desc = fmt . Sprintf ( "This 'admin' API is not supported by server in '%s'" , getMinioMode ( ) )
} else {
desc = fmt . Sprintf ( "Unexpected client 'admin' API version found '%s', expected '%s', please downgrade the client to older releases" , version , madmin . AdminAPIVersion )
}
writeErrorResponseJSON ( r . Context ( ) , w , APIError {
Code : "XMinioAdminVersionMismatch" ,
Description : desc ,
2020-06-17 17:49:26 -04:00
HTTPStatusCode : http . StatusUpgradeRequired ,
2019-11-04 12:30:59 -05:00
} , r . URL )
2019-10-03 03:08:12 -04:00
default :
2019-11-04 12:30:59 -05:00
writeErrorResponse ( r . Context ( ) , w , APIError {
2021-03-02 02:10:33 -05:00
Code : "BadRequest" ,
Description : fmt . Sprintf ( "An error occurred when parsing the HTTP request %s at '%s'" ,
r . Method , r . URL . Path ) ,
2019-11-04 12:30:59 -05:00
HTTPStatusCode : http . StatusBadRequest ,
2021-06-17 23:27:04 -04:00
} , r . URL )
2019-10-03 03:08:12 -04:00
}
2021-02-08 13:15:12 -05:00
2019-10-03 03:08:12 -04:00
}
2019-07-05 23:41:35 -04:00
// gets host name for current node
2019-07-18 12:58:37 -04:00
func getHostName ( r * http . Request ) ( hostName string ) {
2020-06-12 23:04:01 -04:00
if globalIsDistErasure {
2021-03-26 14:37:58 -04:00
hostName = globalLocalNodeName
2019-07-18 12:58:37 -04:00
} else {
hostName = r . Host
2019-07-05 23:41:35 -04:00
}
return
}
2020-07-03 14:53:03 -04:00
// Proxy any request to an endpoint.
func proxyRequest ( ctx context . Context , w http . ResponseWriter , r * http . Request , ep ProxyEndpoint ) ( success bool ) {
success = true
2020-08-07 07:10:16 -04:00
// Make sure we remove any existing headers before
// proxying the request to another node.
for k := range w . Header ( ) {
w . Header ( ) . Del ( k )
}
2020-07-03 14:53:03 -04:00
f := handlers . NewForwarder ( & handlers . Forwarder {
PassHost : true ,
RoundTripper : ep . Transport ,
ErrorHandler : func ( w http . ResponseWriter , r * http . Request , err error ) {
success = false
2020-08-31 15:37:31 -04:00
if err != nil && ! errors . Is ( err , context . Canceled ) {
logger . LogIf ( GlobalContext , err )
}
2020-07-03 14:53:03 -04:00
} ,
} )
r . URL . Scheme = "http"
2020-12-22 00:42:38 -05:00
if globalIsTLS {
2020-07-03 14:53:03 -04:00
r . URL . Scheme = "https"
}
r . URL . Host = ep . Host
f . ServeHTTP ( w , r )
return
}