Esempio n. 1
0
// getBucketLocationRequest - Wrapper creates a new getBucketLocation request.
func (c Client) getBucketLocationRequest(bucketName string) (*http.Request, error) {
	// Set location query.
	urlValues := make(url.Values)
	urlValues.Set("location", "")

	// Set get bucket location always as path style.
	targetURL := c.endpointURL
	targetURL.Path = path.Join(bucketName, "") + "/"
	targetURL.RawQuery = urlValues.Encode()

	// Get a new HTTP request for the method.
	req, err := http.NewRequest("GET", targetURL.String(), nil)
	if err != nil {
		return nil, err
	}

	// Set UserAgent for the request.
	c.setUserAgent(req)

	// Set sha256 sum for signature calculation only with signature version '4'.
	if c.signature.isV4() {
		var contentSha256 string
		if c.secure {
			contentSha256 = unsignedPayload
		} else {
			contentSha256 = hex.EncodeToString(sum256([]byte{}))
		}
		req.Header.Set("X-Amz-Content-Sha256", contentSha256)
	}

	// Sign the request.
	if c.signature.isV4() {
		req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
	} else if c.signature.isV2() {
		req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey)
	}
	return req, nil
}
Esempio n. 2
0
// Tests validate http request generation for 'getBucketLocation'.
func TestGetBucketLocationRequest(t *testing.T) {
	// Generates expected http request for getBucketLocation.
	// Used for asserting with the actual request generated.
	createExpectedRequest := func(c *Client, bucketName string, req *http.Request) (*http.Request, error) {
		// Set location query.
		urlValues := make(url.Values)
		urlValues.Set("location", "")

		// Set get bucket location always as path style.
		targetURL := c.endpointURL
		targetURL.Path = path.Join(bucketName, "") + "/"
		targetURL.RawQuery = urlValues.Encode()

		// Get a new HTTP request for the method.
		var err error
		req, err = http.NewRequest("GET", targetURL.String(), nil)
		if err != nil {
			return nil, err
		}

		// Set UserAgent for the request.
		c.setUserAgent(req)

		// Set sha256 sum for signature calculation only with signature version '4'.
		if c.signature.isV4() {
			req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256([]byte{})))
		}

		// Sign the request.
		if c.signature.isV4() {
			req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
		} else if c.signature.isV2() {
			req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey)
		}
		return req, nil

	}
	// Info for 'Client' creation.
	// Will be used as arguments for 'NewClient'.
	type infoForClient struct {
		endPoint       string
		accessKey      string
		secretKey      string
		enableInsecure bool
	}
	// dataset for 'NewClient' call.
	info := []infoForClient{
		// endpoint localhost.
		// both access-key and secret-key are empty.
		{"localhost:9000", "", "", false},
		// both access-key are secret-key exists.
		{"localhost:9000", "my-access-key", "my-secret-key", false},
		// one of acess-key and secret-key are empty.
		{"localhost:9000", "", "my-secret-key", false},

		// endpoint amazon s3.
		{"s3.amazonaws.com", "", "", false},
		{"s3.amazonaws.com", "my-access-key", "my-secret-key", false},
		{"s3.amazonaws.com", "my-acess-key", "", false},

		// endpoint google cloud storage.
		{"storage.googleapis.com", "", "", false},
		{"storage.googleapis.com", "my-access-key", "my-secret-key", false},
		{"storage.googleapis.com", "", "my-secret-key", false},

		// endpoint custom domain running Minio server.
		{"play.minio.io", "", "", false},
		{"play.minio.io", "my-access-key", "my-secret-key", false},
		{"play.minio.io", "my-acess-key", "", false},
	}
	testCases := []struct {
		bucketName string
		// data for new client creation.
		info infoForClient
		// error in the output.
		err error
		// flag indicating whether tests should pass.
		shouldPass bool
	}{
		// Client is constructed using the info struct.
		// case with empty location.
		{"my-bucket", info[0], nil, true},
		// case with location set to standard 'us-east-1'.
		{"my-bucket", info[0], nil, true},
		// case with location set to a value different from 'us-east-1'.
		{"my-bucket", info[0], nil, true},

		{"my-bucket", info[1], nil, true},
		{"my-bucket", info[1], nil, true},
		{"my-bucket", info[1], nil, true},

		{"my-bucket", info[2], nil, true},
		{"my-bucket", info[2], nil, true},
		{"my-bucket", info[2], nil, true},

		{"my-bucket", info[3], nil, true},
		{"my-bucket", info[3], nil, true},
		{"my-bucket", info[3], nil, true},

		{"my-bucket", info[4], nil, true},
		{"my-bucket", info[4], nil, true},
		{"my-bucket", info[4], nil, true},

		{"my-bucket", info[5], nil, true},
		{"my-bucket", info[5], nil, true},
		{"my-bucket", info[5], nil, true},

		{"my-bucket", info[6], nil, true},
		{"my-bucket", info[6], nil, true},
		{"my-bucket", info[6], nil, true},

		{"my-bucket", info[7], nil, true},
		{"my-bucket", info[7], nil, true},
		{"my-bucket", info[7], nil, true},

		{"my-bucket", info[8], nil, true},
		{"my-bucket", info[8], nil, true},
		{"my-bucket", info[8], nil, true},

		{"my-bucket", info[9], nil, true},
		{"my-bucket", info[9], nil, true},
		{"my-bucket", info[9], nil, true},

		{"my-bucket", info[10], nil, true},
		{"my-bucket", info[10], nil, true},
		{"my-bucket", info[10], nil, true},

		{"my-bucket", info[11], nil, true},
		{"my-bucket", info[11], nil, true},
		{"my-bucket", info[11], nil, true},
	}
	for i, testCase := range testCases {
		// cannot create a newclient with empty endPoint value.
		// validates and creates a new client only if the endPoint value is not empty.
		client := &Client{}
		var err error
		if testCase.info.endPoint != "" {

			client, err = New(testCase.info.endPoint, testCase.info.accessKey, testCase.info.secretKey, testCase.info.enableInsecure)
			if err != nil {
				t.Fatalf("Test %d: Failed to create new Client: %s", i+1, err.Error())
			}
		}

		actualReq, err := client.getBucketLocationRequest(testCase.bucketName)
		if err != nil && testCase.shouldPass {
			t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
		}
		if err == nil && !testCase.shouldPass {
			t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
		}
		// Failed as expected, but does it fail for the expected reason.
		if err != nil && !testCase.shouldPass {
			if err.Error() != testCase.err.Error() {
				t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error())
			}
		}

		// Test passes as expected, but the output values are verified for correctness here.
		if err == nil && testCase.shouldPass {
			expectedReq := &http.Request{}
			expectedReq, err = createExpectedRequest(client, testCase.bucketName, expectedReq)
			if err != nil {
				t.Fatalf("Test %d: Expected request Creation failed", i+1)
			}
			if expectedReq.Method != actualReq.Method {
				t.Errorf("Test %d: The expected Request method doesn't match with the actual one", i+1)
			}
			if expectedReq.URL.String() != actualReq.URL.String() {
				t.Errorf("Test %d: Expected the request URL to be '%s', but instead found '%s'", i+1, expectedReq.URL.String(), actualReq.URL.String())
			}
			if expectedReq.ContentLength != actualReq.ContentLength {
				t.Errorf("Test %d: Expected the request body Content-Length to be '%d', but found '%d' instead", i+1, expectedReq.ContentLength, actualReq.ContentLength)
			}

			if expectedReq.Header.Get("X-Amz-Content-Sha256") != actualReq.Header.Get("X-Amz-Content-Sha256") {
				t.Errorf("Test %d: 'X-Amz-Content-Sha256' header of the expected request doesn't match with that of the actual request", i+1)
			}
			if expectedReq.Header.Get("User-Agent") != actualReq.Header.Get("User-Agent") {
				t.Errorf("Test %d: Expected 'User-Agent' header to be \"%s\",but found \"%s\" instead", i+1, expectedReq.Header.Get("User-Agent"), actualReq.Header.Get("User-Agent"))
			}
		}
	}
}
Esempio n. 3
0
// makeBucketRequest constructs request for makeBucket.
func (c Client) makeBucketRequest(bucketName string, location string) (*http.Request, error) {
	// Validate input arguments.
	if err := isValidBucketName(bucketName); err != nil {
		return nil, err
	}

	// In case of Amazon S3.  The make bucket issued on already
	// existing bucket would fail with 'AuthorizationMalformed' error
	// if virtual style is used. So we default to 'path style' as that
	// is the preferred method here. The final location of the
	// 'bucket' is provided through XML LocationConstraint data with
	// the request.
	targetURL := c.endpointURL
	targetURL.Path = path.Join(bucketName, "") + "/"

	// get a new HTTP request for the method.
	req, err := http.NewRequest("PUT", targetURL.String(), nil)
	if err != nil {
		return nil, err
	}

	// set UserAgent for the request.
	c.setUserAgent(req)

	// set sha256 sum for signature calculation only with signature version '4'.
	if c.signature.isV4() {
		req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256([]byte{})))
	}

	// If location is not 'us-east-1' create bucket location config.
	if location != "us-east-1" && location != "" {
		createBucketConfig := createBucketConfiguration{}
		createBucketConfig.Location = location
		var createBucketConfigBytes []byte
		createBucketConfigBytes, err = xml.Marshal(createBucketConfig)
		if err != nil {
			return nil, err
		}
		createBucketConfigBuffer := bytes.NewBuffer(createBucketConfigBytes)
		req.Body = ioutil.NopCloser(createBucketConfigBuffer)
		req.ContentLength = int64(len(createBucketConfigBytes))
		// Set content-md5.
		req.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(sumMD5(createBucketConfigBytes)))
		if c.signature.isV4() {
			// Set sha256.
			req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(createBucketConfigBytes)))
		}
	}

	// Sign the request.
	if c.signature.isV4() {
		// Signature calculated for MakeBucket request should be for 'us-east-1',
		// regardless of the bucket's location constraint.
		req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
	} else if c.signature.isV2() {
		req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey)
	}

	// Return signed request.
	return req, nil
}
// Tests validate http request formulated for creation of bucket.
func TestMakeBucketRequest(t *testing.T) {
	// Generates expected http request for bucket creation.
	// Used for asserting with the actual request generated.
	createExpectedRequest := func(c *Client, bucketName string, location string, req *http.Request) (*http.Request, error) {
		targetURL := c.endpointURL
		targetURL.Path = path.Join(bucketName, "") + "/"

		// get a new HTTP request for the method.
		var err error
		req, err = http.NewRequest("PUT", targetURL.String(), nil)
		if err != nil {
			return nil, err
		}

		// set UserAgent for the request.
		c.setUserAgent(req)

		// set sha256 sum for signature calculation only with signature version '4'.
		if c.signature.isV4() {
			req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256([]byte{})))
		}

		// If location is not 'us-east-1' create bucket location config.
		if location != "us-east-1" && location != "" {
			createBucketConfig := createBucketConfiguration{}
			createBucketConfig.Location = location
			var createBucketConfigBytes []byte
			createBucketConfigBytes, err = xml.Marshal(createBucketConfig)
			if err != nil {
				return nil, err
			}
			createBucketConfigBuffer := bytes.NewBuffer(createBucketConfigBytes)
			req.Body = ioutil.NopCloser(createBucketConfigBuffer)
			req.ContentLength = int64(len(createBucketConfigBytes))
			// Set content-md5.
			req.Header.Set("Content-Md5", base64.StdEncoding.EncodeToString(sumMD5(createBucketConfigBytes)))
			if c.signature.isV4() {
				// Set sha256.
				req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum256(createBucketConfigBytes)))
			}
		}

		// Sign the request.
		if c.signature.isV4() {
			// Signature calculated for MakeBucket request should be for 'us-east-1',
			// regardless of the bucket's location constraint.
			req = s3signer.SignV4(*req, c.accessKeyID, c.secretAccessKey, "us-east-1")
		} else if c.signature.isV2() {
			req = s3signer.SignV2(*req, c.accessKeyID, c.secretAccessKey)
		}

		// Return signed request.
		return req, nil
	}

	// Get Request body.
	getReqBody := func(reqBody io.ReadCloser) (string, error) {
		contents, err := ioutil.ReadAll(reqBody)
		if err != nil {
			return "", err
		}
		return string(contents), nil
	}

	// Info for 'Client' creation.
	// Will be used as arguments for 'NewClient'.
	type infoForClient struct {
		endPoint       string
		accessKey      string
		secretKey      string
		enableInsecure bool
	}
	// dataset for 'NewClient' call.
	info := []infoForClient{
		// endpoint localhost.
		// both access-key and secret-key are empty.
		{"localhost:9000", "", "", false},
		// both access-key are secret-key exists.
		{"localhost:9000", "my-access-key", "my-secret-key", false},
		// one of acess-key and secret-key are empty.
		{"localhost:9000", "", "my-secret-key", false},

		// endpoint amazon s3.
		{"s3.amazonaws.com", "", "", false},
		{"s3.amazonaws.com", "my-access-key", "my-secret-key", false},
		{"s3.amazonaws.com", "my-acess-key", "", false},

		// endpoint google cloud storage.
		{"storage.googleapis.com", "", "", false},
		{"storage.googleapis.com", "my-access-key", "my-secret-key", false},
		{"storage.googleapis.com", "", "my-secret-key", false},

		// endpoint custom domain running Minio server.
		{"play.minio.io", "", "", false},
		{"play.minio.io", "my-access-key", "my-secret-key", false},
		{"play.minio.io", "my-acess-key", "", false},
	}

	testCases := []struct {
		bucketName string
		location   string
		// data for new client creation.
		info infoForClient
		// error in the output.
		err error
		// flag indicating whether tests should pass.
		shouldPass bool
	}{
		// Test cases with Invalid bucket name.
		{".mybucket", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot."), false},
		{"mybucket.", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot start or end with a '.' dot."), false},
		{"mybucket-", "", infoForClient{}, ErrInvalidBucketName("Bucket name contains invalid characters."), false},
		{"my", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot be smaller than 3 characters."), false},
		{"", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot be empty."), false},
		{"my..bucket", "", infoForClient{}, ErrInvalidBucketName("Bucket name cannot have successive periods."), false},

		// Test case with all valid values for S3 bucket location.
		// Client is constructed using the info struct.
		// case with empty location.
		{"my-bucket", "", info[0], nil, true},
		// case with location set to standard 'us-east-1'.
		{"my-bucket", "us-east-1", info[0], nil, true},
		// case with location set to a value different from 'us-east-1'.
		{"my-bucket", "eu-central-1", info[0], nil, true},

		{"my-bucket", "", info[1], nil, true},
		{"my-bucket", "us-east-1", info[1], nil, true},
		{"my-bucket", "eu-central-1", info[1], nil, true},

		{"my-bucket", "", info[2], nil, true},
		{"my-bucket", "us-east-1", info[2], nil, true},
		{"my-bucket", "eu-central-1", info[2], nil, true},

		{"my-bucket", "", info[3], nil, true},
		{"my-bucket", "us-east-1", info[3], nil, true},
		{"my-bucket", "eu-central-1", info[3], nil, true},

		{"my-bucket", "", info[4], nil, true},
		{"my-bucket", "us-east-1", info[4], nil, true},
		{"my-bucket", "eu-central-1", info[4], nil, true},

		{"my-bucket", "", info[5], nil, true},
		{"my-bucket", "us-east-1", info[5], nil, true},
		{"my-bucket", "eu-central-1", info[5], nil, true},

		{"my-bucket", "", info[6], nil, true},
		{"my-bucket", "us-east-1", info[6], nil, true},
		{"my-bucket", "eu-central-1", info[6], nil, true},

		{"my-bucket", "", info[7], nil, true},
		{"my-bucket", "us-east-1", info[7], nil, true},
		{"my-bucket", "eu-central-1", info[7], nil, true},

		{"my-bucket", "", info[8], nil, true},
		{"my-bucket", "us-east-1", info[8], nil, true},
		{"my-bucket", "eu-central-1", info[8], nil, true},

		{"my-bucket", "", info[9], nil, true},
		{"my-bucket", "us-east-1", info[9], nil, true},
		{"my-bucket", "eu-central-1", info[9], nil, true},

		{"my-bucket", "", info[10], nil, true},
		{"my-bucket", "us-east-1", info[10], nil, true},
		{"my-bucket", "eu-central-1", info[10], nil, true},

		{"my-bucket", "", info[11], nil, true},
		{"my-bucket", "us-east-1", info[11], nil, true},
		{"my-bucket", "eu-central-1", info[11], nil, true},
	}

	for i, testCase := range testCases {
		// cannot create a newclient with empty endPoint value.
		// validates and creates a new client only if the endPoint value is not empty.
		client := &Client{}
		var err error
		if testCase.info.endPoint != "" {

			client, err = New(testCase.info.endPoint, testCase.info.accessKey, testCase.info.secretKey, testCase.info.enableInsecure)
			if err != nil {
				t.Fatalf("Test %d: Failed to create new Client: %s", i+1, err.Error())
			}
		}

		actualReq, err := client.makeBucketRequest(testCase.bucketName, testCase.location)
		if err != nil && testCase.shouldPass {
			t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
		}
		if err == nil && !testCase.shouldPass {
			t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
		}
		// Failed as expected, but does it fail for the expected reason.
		if err != nil && !testCase.shouldPass {
			if err.Error() != testCase.err.Error() {
				t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, testCase.err.Error(), err.Error())
			}
		}

		// Test passes as expected, but the output values are verified for correctness here.
		if err == nil && testCase.shouldPass {
			expectedReq := &http.Request{}
			expectedReq, err = createExpectedRequest(client, testCase.bucketName, testCase.location, expectedReq)
			if err != nil {
				t.Fatalf("Test %d: Expected request Creation failed", i+1)
			}
			if expectedReq.Method != actualReq.Method {
				t.Errorf("Test %d: The expected Request method doesn't match with the actual one", i+1)
			}
			if expectedReq.URL.String() != actualReq.URL.String() {
				t.Errorf("Test %d: Expected the request URL to be '%s', but instead found '%s'", i+1, expectedReq.URL.String(), actualReq.URL.String())
			}
			if expectedReq.ContentLength != actualReq.ContentLength {
				t.Errorf("Test %d: Expected the request body Content-Length to be '%d', but found '%d' instead", i+1, expectedReq.ContentLength, actualReq.ContentLength)
			}

			if expectedReq.Header.Get("X-Amz-Content-Sha256") != actualReq.Header.Get("X-Amz-Content-Sha256") {
				t.Errorf("Test %d: 'X-Amz-Content-Sha256' header of the expected request doesn't match with that of the actual request", i+1)
			}
			if expectedReq.Header.Get("User-Agent") != actualReq.Header.Get("User-Agent") {
				t.Errorf("Test %d: Expected 'User-Agent' header to be \"%s\",but found \"%s\" instead", i+1, expectedReq.Header.Get("User-Agent"), actualReq.Header.Get("User-Agent"))
			}

			if testCase.location != "us-east-1" && testCase.location != "" {
				expectedContent, err := getReqBody(expectedReq.Body)
				if err != nil {
					t.Fatalf("Test %d: Coudln't parse request body", i+1)
				}
				actualContent, err := getReqBody(actualReq.Body)
				if err != nil {
					t.Fatalf("Test %d: Coudln't parse request body", i+1)
				}
				if expectedContent != actualContent {
					t.Errorf("Test %d: Expected request body doesn't match actual content body", i+1)
				}
				if expectedReq.Header.Get("Content-Md5") != actualReq.Header.Get("Content-Md5") {
					t.Errorf("Test %d: Request body Md5 differs from the expected result", i+1)
				}
			}
		}
	}
}