github.com/ethersphere/bee/v2@v2.2.0/pkg/api/dirs_test.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package api_test
     6  
     7  import (
     8  	"archive/tar"
     9  	"bytes"
    10  	"context"
    11  	"fmt"
    12  	"io"
    13  	"mime/multipart"
    14  	"net/http"
    15  	"net/textproto"
    16  	"path"
    17  	"strconv"
    18  	"testing"
    19  
    20  	"github.com/ethersphere/bee/v2/pkg/api"
    21  	"github.com/ethersphere/bee/v2/pkg/file/loadsave"
    22  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    23  	"github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest"
    24  	"github.com/ethersphere/bee/v2/pkg/manifest"
    25  	mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock"
    26  	mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock"
    27  	"github.com/ethersphere/bee/v2/pkg/swarm"
    28  )
    29  
    30  // nolint:paralleltest
    31  func TestDirs(t *testing.T) {
    32  	var (
    33  		dirUploadResource   = "/bzz"
    34  		bzzDownloadResource = func(addr, path string) string { return "/bzz/" + addr + "/" + path }
    35  		ctx                 = context.Background()
    36  		storer              = mockstorer.New()
    37  		client, _, _, _     = newTestServer(t, testServerOptions{
    38  			Storer:          storer,
    39  			PreventRedirect: true,
    40  			Post:            mockpost.New(mockpost.WithAcceptAll()),
    41  		})
    42  	)
    43  
    44  	t.Run("empty request body", func(t *testing.T) {
    45  		jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource,
    46  			http.StatusBadRequest,
    47  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
    48  			jsonhttptest.WithRequestBody(bytes.NewReader(nil)),
    49  			jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
    50  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
    51  				Message: api.InvalidRequest.Error(),
    52  				Code:    http.StatusBadRequest,
    53  			}),
    54  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
    55  		)
    56  	})
    57  
    58  	t.Run("non tar file", func(t *testing.T) {
    59  		file := bytes.NewReader([]byte("some data"))
    60  
    61  		jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource,
    62  			http.StatusInternalServerError,
    63  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
    64  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
    65  			jsonhttptest.WithRequestBody(file),
    66  			jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
    67  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
    68  				Message: api.DirectoryStoreError.Error(),
    69  				Code:    http.StatusInternalServerError,
    70  			}),
    71  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
    72  		)
    73  	})
    74  
    75  	t.Run("wrong content type", func(t *testing.T) {
    76  		tarReader := tarFiles(t, []f{{
    77  			data: []byte("some data"),
    78  			name: "binary-file",
    79  		}})
    80  
    81  		// submit valid tar, but with wrong content-type
    82  		jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource,
    83  			http.StatusBadRequest,
    84  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
    85  			jsonhttptest.WithRequestBody(tarReader),
    86  			jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
    87  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
    88  				Message: api.InvalidContentType.Error(),
    89  				Code:    http.StatusBadRequest,
    90  			}),
    91  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "other"),
    92  		)
    93  	})
    94  
    95  	// valid tars
    96  	for _, tc := range []struct {
    97  		name                string
    98  		expectedReference   swarm.Address
    99  		encrypt             bool
   100  		wantIndexFilename   string
   101  		wantErrorFilename   string
   102  		indexFilenameOption jsonhttptest.Option
   103  		errorFilenameOption jsonhttptest.Option
   104  		doMultipart         bool
   105  		files               []f // files in dir for test case
   106  	}{
   107  		{
   108  			name:              "non-nested files without extension",
   109  			expectedReference: swarm.MustParseHexAddress("f3312af64715d26b5e1a3dc90f012d2c9cc74a167899dab1d07cdee8c107f939"),
   110  			files: []f{
   111  				{
   112  					data: []byte("first file data"),
   113  					name: "file1",
   114  					dir:  "",
   115  					header: http.Header{
   116  						api.ContentTypeHeader: {""},
   117  					},
   118  				},
   119  				{
   120  					data: []byte("second file data"),
   121  					name: "file2",
   122  					dir:  "",
   123  					header: http.Header{
   124  						api.ContentTypeHeader: {""},
   125  					},
   126  				},
   127  			},
   128  		},
   129  		{
   130  			name:              "nested files with extension",
   131  			doMultipart:       true,
   132  			expectedReference: swarm.MustParseHexAddress("4c9c76d63856102e54092c38a7cd227d769752d768b7adc8c3542e3dd9fcf295"),
   133  			files: []f{
   134  				{
   135  					data: []byte("robots text"),
   136  					name: "robots.txt",
   137  					dir:  "",
   138  					header: http.Header{
   139  						api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   140  					},
   141  				},
   142  				{
   143  					data: []byte("image 1"),
   144  					name: "1.png",
   145  					dir:  "img",
   146  					header: http.Header{
   147  						api.ContentTypeHeader: {"image/png"},
   148  					},
   149  				},
   150  				{
   151  					data: []byte("image 2"),
   152  					name: "2.png",
   153  					dir:  "img",
   154  					header: http.Header{
   155  						api.ContentTypeHeader: {"image/png"},
   156  					},
   157  				},
   158  			},
   159  		},
   160  		{
   161  			name:              "no index filename",
   162  			expectedReference: swarm.MustParseHexAddress("9e178dbd1ed4b748379e25144e28dfb29c07a4b5114896ef454480115a56b237"),
   163  			doMultipart:       true,
   164  			files: []f{
   165  				{
   166  					data: []byte("<h1>Swarm"),
   167  					name: "index.html",
   168  					dir:  "",
   169  					header: http.Header{
   170  						api.ContentTypeHeader: {"text/html; charset=utf-8"},
   171  					},
   172  				},
   173  			},
   174  		},
   175  		{
   176  			name:                "explicit index filename",
   177  			expectedReference:   swarm.MustParseHexAddress("a58484e3d77bbdb40323ddc9020c6e96e5eb5deb52015d3e0f63cce629ac1aa6"),
   178  			wantIndexFilename:   "index.html",
   179  			indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
   180  			doMultipart:         true,
   181  			files: []f{
   182  				{
   183  					data: []byte("<h1>Swarm"),
   184  					name: "index.html",
   185  					dir:  "",
   186  					header: http.Header{
   187  						api.ContentTypeHeader: {"text/html; charset=utf-8"},
   188  					},
   189  				},
   190  			},
   191  		},
   192  		{
   193  			name:                "nested index filename",
   194  			expectedReference:   swarm.MustParseHexAddress("3e2f008a578c435efa7a1fce146e21c4ae8c20b80fbb4c4e0c1c87ca08fef414"),
   195  			wantIndexFilename:   "index.html",
   196  			indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
   197  			files: []f{
   198  				{
   199  					data: []byte("<h1>Swarm"),
   200  					name: "index.html",
   201  					dir:  "dir",
   202  					header: http.Header{
   203  						api.ContentTypeHeader: {"text/html; charset=utf-8"},
   204  					},
   205  				},
   206  			},
   207  		},
   208  		{
   209  			name:                "explicit index and error filename",
   210  			expectedReference:   swarm.MustParseHexAddress("2cd9a6ac11eefbb71b372fb97c3ef64109c409955964a294fdc183c1014b3844"),
   211  			wantIndexFilename:   "index.html",
   212  			wantErrorFilename:   "error.html",
   213  			indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
   214  			errorFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmErrorDocumentHeader, "error.html"),
   215  			doMultipart:         true,
   216  			files: []f{
   217  				{
   218  					data: []byte("<h1>Swarm"),
   219  					name: "index.html",
   220  					dir:  "",
   221  					header: http.Header{
   222  						api.ContentTypeHeader: {"text/html; charset=utf-8"},
   223  					},
   224  				},
   225  				{
   226  					data: []byte("<h2>404"),
   227  					name: "error.html",
   228  					dir:  "",
   229  					header: http.Header{
   230  						api.ContentTypeHeader: {"text/html; charset=utf-8"},
   231  					},
   232  				},
   233  			},
   234  		},
   235  		{
   236  			name:              "invalid archive paths",
   237  			expectedReference: swarm.MustParseHexAddress("133c92414c047708f3d6a8561571a0cc96512899ff0edbd9690c857f01ab6883"),
   238  			files: []f{
   239  				{
   240  					data:     []byte("<h1>Swarm"),
   241  					name:     "index.html",
   242  					dir:      "",
   243  					filePath: "./index.html",
   244  				},
   245  				{
   246  					data:     []byte("body {}"),
   247  					name:     "app.css",
   248  					dir:      "",
   249  					filePath: "./app.css",
   250  				},
   251  				{
   252  					data: []byte(`User-agent: *
   253  		Disallow: /`),
   254  					name:     "robots.txt",
   255  					dir:      "",
   256  					filePath: "./robots.txt",
   257  				},
   258  			},
   259  		},
   260  		{
   261  			name:    "encrypted",
   262  			encrypt: true,
   263  			files: []f{
   264  				{
   265  					data:     []byte("<h1>Swarm"),
   266  					name:     "index.html",
   267  					dir:      "",
   268  					filePath: "./index.html",
   269  				},
   270  			},
   271  		},
   272  	} {
   273  		verify := func(t *testing.T, resp api.BzzUploadResponse) {
   274  			t.Helper()
   275  			// NOTE: reference will be different each time when encryption is enabled
   276  			if !tc.encrypt {
   277  				if !resp.Reference.Equal(tc.expectedReference) {
   278  					t.Fatalf("expected root reference to match %s, got %s", tc.expectedReference, resp.Reference)
   279  				}
   280  			}
   281  
   282  			// verify manifest content
   283  			verifyManifest, err := manifest.NewDefaultManifestReference(
   284  				resp.Reference,
   285  				loadsave.NewReadonly(storer.ChunkStore()),
   286  			)
   287  			if err != nil {
   288  				t.Fatal(err)
   289  			}
   290  
   291  			validateFile := func(t *testing.T, file f, filePath string) {
   292  				t.Helper()
   293  
   294  				jsonhttptest.Request(t, client, http.MethodGet,
   295  					bzzDownloadResource(resp.Reference.String(), filePath),
   296  					http.StatusOK,
   297  					jsonhttptest.WithExpectedResponse(file.data),
   298  					jsonhttptest.WithRequestHeader(api.ContentTypeHeader, file.header.Get(api.ContentTypeHeader)),
   299  				)
   300  			}
   301  
   302  			validateIsPermanentRedirect := func(t *testing.T, fromPath, toPath string) {
   303  				t.Helper()
   304  
   305  				expectedResponse := fmt.Sprintf("<a href=\"%s\">Permanent Redirect</a>.\n\n",
   306  					bzzDownloadResource(resp.Reference.String(), toPath))
   307  
   308  				jsonhttptest.Request(t, client, http.MethodGet,
   309  					bzzDownloadResource(resp.Reference.String(), fromPath),
   310  					http.StatusPermanentRedirect,
   311  					jsonhttptest.WithExpectedResponse([]byte(expectedResponse)),
   312  				)
   313  			}
   314  
   315  			validateAltPath := func(t *testing.T, fromPath, toPath string) {
   316  				t.Helper()
   317  
   318  				var respBytes []byte
   319  
   320  				jsonhttptest.Request(t, client, http.MethodGet,
   321  					bzzDownloadResource(resp.Reference.String(), toPath), http.StatusOK,
   322  					jsonhttptest.WithPutResponseBody(&respBytes),
   323  				)
   324  
   325  				jsonhttptest.Request(t, client, http.MethodGet,
   326  					bzzDownloadResource(resp.Reference.String(), fromPath), http.StatusOK,
   327  					jsonhttptest.WithExpectedResponse(respBytes),
   328  				)
   329  			}
   330  
   331  			// check if each file can be located and read
   332  			for _, file := range tc.files {
   333  				validateFile(t, file, path.Join(file.dir, file.name))
   334  			}
   335  
   336  			// check index filename
   337  			if tc.wantIndexFilename != "" {
   338  				entry, err := verifyManifest.Lookup(ctx, manifest.RootPath)
   339  				if err != nil {
   340  					t.Fatal(err)
   341  				}
   342  
   343  				manifestRootMetadata := entry.Metadata()
   344  				indexDocumentSuffixPath, ok := manifestRootMetadata[manifest.WebsiteIndexDocumentSuffixKey]
   345  				if !ok {
   346  					t.Fatalf("expected index filename '%s', did not find any", tc.wantIndexFilename)
   347  				}
   348  
   349  				// check index suffix for each dir
   350  				for _, file := range tc.files {
   351  					if file.dir != "" {
   352  						validateIsPermanentRedirect(t, file.dir, file.dir+"/")
   353  						validateAltPath(t, file.dir+"/", path.Join(file.dir, indexDocumentSuffixPath))
   354  					}
   355  				}
   356  			}
   357  
   358  			// check error filename
   359  			if tc.wantErrorFilename != "" {
   360  				entry, err := verifyManifest.Lookup(ctx, manifest.RootPath)
   361  				if err != nil {
   362  					t.Fatal(err)
   363  				}
   364  
   365  				manifestRootMetadata := entry.Metadata()
   366  				errorDocumentPath, ok := manifestRootMetadata[manifest.WebsiteErrorDocumentPathKey]
   367  				if !ok {
   368  					t.Fatalf("expected error filename '%s', did not find any", tc.wantErrorFilename)
   369  				}
   370  
   371  				// check error document
   372  				validateAltPath(t, "_non_existent_file_path_", errorDocumentPath)
   373  			}
   374  
   375  		}
   376  		t.Run(tc.name, func(t *testing.T) {
   377  			t.Run("tar_upload", func(t *testing.T) {
   378  				// tar all the test case files
   379  				tarReader := tarFiles(t, tc.files)
   380  
   381  				var resp api.BzzUploadResponse
   382  
   383  				options := []jsonhttptest.Option{
   384  					jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   385  					jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   386  					jsonhttptest.WithRequestBody(tarReader),
   387  					jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
   388  					jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   389  					jsonhttptest.WithUnmarshalJSONResponse(&resp),
   390  				}
   391  				if tc.indexFilenameOption != nil {
   392  					options = append(options, tc.indexFilenameOption)
   393  				}
   394  				if tc.errorFilenameOption != nil {
   395  					options = append(options, tc.errorFilenameOption)
   396  				}
   397  				if tc.encrypt {
   398  					options = append(options, jsonhttptest.WithRequestHeader(api.SwarmEncryptHeader, "true"))
   399  				}
   400  
   401  				// verify directory tar upload response
   402  				jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource, http.StatusCreated, options...)
   403  
   404  				if resp.Reference.String() == "" {
   405  					t.Fatalf("expected file reference, did not got any")
   406  				}
   407  
   408  				verify(t, resp)
   409  			})
   410  			if tc.doMultipart {
   411  				t.Run("multipart_upload", func(t *testing.T) {
   412  					// tar all the test case files
   413  					mwReader, mwBoundary := multipartFiles(t, tc.files)
   414  
   415  					var resp api.BzzUploadResponse
   416  
   417  					options := []jsonhttptest.Option{
   418  						jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   419  						jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   420  						jsonhttptest.WithRequestBody(mwReader),
   421  						jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True"),
   422  						jsonhttptest.WithRequestHeader(api.ContentTypeHeader, fmt.Sprintf("multipart/form-data; boundary=%q", mwBoundary)),
   423  						jsonhttptest.WithUnmarshalJSONResponse(&resp),
   424  					}
   425  					if tc.indexFilenameOption != nil {
   426  						options = append(options, tc.indexFilenameOption)
   427  					}
   428  					if tc.errorFilenameOption != nil {
   429  						options = append(options, tc.errorFilenameOption)
   430  					}
   431  					if tc.encrypt {
   432  						options = append(options, jsonhttptest.WithRequestHeader(api.SwarmEncryptHeader, "true"))
   433  					}
   434  
   435  					// verify directory tar upload response
   436  					jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource, http.StatusCreated, options...)
   437  
   438  					if resp.Reference.String() == "" {
   439  						t.Fatalf("expected file reference, did not got any")
   440  					}
   441  
   442  					verify(t, resp)
   443  				})
   444  			}
   445  		})
   446  	}
   447  
   448  	t.Run("upload invalid tag", func(t *testing.T) {
   449  		tr := tarFiles(t, []f{
   450  			{
   451  				data: []byte("robots text"),
   452  				name: "robots.txt",
   453  				dir:  "",
   454  				header: http.Header{
   455  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   456  				},
   457  			},
   458  		})
   459  
   460  		jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource, http.StatusBadRequest,
   461  			jsonhttptest.WithRequestHeader(api.SwarmTagHeader, "tag"),
   462  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   463  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   464  			jsonhttptest.WithRequestBody(tr),
   465  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   466  			jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   467  				Message: "invalid header params",
   468  				Code:    http.StatusBadRequest,
   469  				Reasons: []jsonhttp.Reason{
   470  					{
   471  						Field: "Swarm-Tag",
   472  						Error: "invalid syntax",
   473  					},
   474  				},
   475  			}),
   476  		)
   477  	})
   478  
   479  	t.Run("upload tag not found", func(t *testing.T) {
   480  		tr := tarFiles(t, []f{
   481  			{
   482  				data: []byte("robots text"),
   483  				name: "robots.txt",
   484  				dir:  "",
   485  				header: http.Header{
   486  					api.ContentTypeHeader: {"text/plain; charset=utf-8"},
   487  				},
   488  			},
   489  		})
   490  
   491  		jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource, http.StatusNotFound,
   492  			jsonhttptest.WithRequestHeader(api.SwarmTagHeader, strconv.FormatUint(uint64(10000), 10)),
   493  			jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"),
   494  			jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   495  			jsonhttptest.WithRequestBody(tr),
   496  			jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar))
   497  	})
   498  }
   499  
   500  func TestDirsEmtpyDir(t *testing.T) {
   501  	t.Parallel()
   502  
   503  	var (
   504  		dirUploadResource = "/bzz"
   505  		storer            = mockstorer.New()
   506  		client, _, _, _   = newTestServer(t, testServerOptions{
   507  			Storer:          storer,
   508  			PreventRedirect: true,
   509  			Post:            mockpost.New(mockpost.WithAcceptAll()),
   510  		})
   511  	)
   512  
   513  	tarReader := tarEmptyDir(t)
   514  
   515  	jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource,
   516  		http.StatusBadRequest,
   517  		jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr),
   518  		jsonhttptest.WithRequestBody(tarReader),
   519  		jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "true"),
   520  		jsonhttptest.WithRequestHeader(api.ContentTypeHeader, api.ContentTypeTar),
   521  		jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{
   522  			Message: api.EmptyDir.Error(),
   523  			Code:    http.StatusBadRequest,
   524  		}),
   525  	)
   526  }
   527  
   528  // tarFiles receives an array of test case files and creates a new tar with those files as a collection
   529  // it returns a bytes.Buffer which can be used to read the created tar
   530  func tarFiles(t *testing.T, files []f) *bytes.Buffer {
   531  	t.Helper()
   532  
   533  	var buf bytes.Buffer
   534  	tw := tar.NewWriter(&buf)
   535  
   536  	for _, file := range files {
   537  		filePath := path.Join(file.dir, file.name)
   538  		if file.filePath != "" {
   539  			filePath = file.filePath
   540  		}
   541  
   542  		// create tar header and write it
   543  		hdr := &tar.Header{
   544  			Name: filePath,
   545  			Mode: 0600,
   546  			Size: int64(len(file.data)),
   547  		}
   548  		if err := tw.WriteHeader(hdr); err != nil {
   549  			t.Fatal(err)
   550  		}
   551  
   552  		// write the file data to the tar
   553  		if _, err := tw.Write(file.data); err != nil {
   554  			t.Fatal(err)
   555  		}
   556  	}
   557  
   558  	// finally close the tar writer
   559  	if err := tw.Close(); err != nil {
   560  		t.Fatal(err)
   561  	}
   562  
   563  	return &buf
   564  }
   565  
   566  func tarEmptyDir(t *testing.T) *bytes.Buffer {
   567  	t.Helper()
   568  
   569  	var buf bytes.Buffer
   570  	tw := tar.NewWriter(&buf)
   571  
   572  	hdr := &tar.Header{
   573  		Name: "empty/",
   574  		Mode: 0600,
   575  	}
   576  
   577  	if err := tw.WriteHeader(hdr); err != nil {
   578  		t.Fatal(err)
   579  	}
   580  
   581  	// finally close the tar writer
   582  	if err := tw.Close(); err != nil {
   583  		t.Fatal(err)
   584  	}
   585  
   586  	return &buf
   587  }
   588  
   589  func multipartFiles(t *testing.T, files []f) (*bytes.Buffer, string) {
   590  	t.Helper()
   591  
   592  	var buf bytes.Buffer
   593  	mw := multipart.NewWriter(&buf)
   594  
   595  	for _, file := range files {
   596  		filePath := path.Join(file.dir, file.name)
   597  		if file.filePath != "" {
   598  			filePath = file.filePath
   599  		}
   600  
   601  		hdr := make(textproto.MIMEHeader)
   602  		hdr.Set(api.ContentDispositionHeader, fmt.Sprintf("form-data; name=%q", filePath))
   603  
   604  		contentType := file.header.Get(api.ContentTypeHeader)
   605  		if contentType != "" {
   606  			hdr.Set(api.ContentTypeHeader, contentType)
   607  
   608  		}
   609  		if len(file.data) > 0 {
   610  			hdr.Set(api.ContentLengthHeader, strconv.Itoa(len(file.data)))
   611  
   612  		}
   613  		part, err := mw.CreatePart(hdr)
   614  		if err != nil {
   615  			t.Fatal(err)
   616  		}
   617  		if _, err = io.Copy(part, bytes.NewBuffer(file.data)); err != nil {
   618  			t.Fatal(err)
   619  		}
   620  	}
   621  
   622  	// finally close the tar writer
   623  	if err := mw.Close(); err != nil {
   624  		t.Fatal(err)
   625  	}
   626  
   627  	return &buf, mw.Boundary()
   628  }
   629  
   630  // struct for dir files for test cases
   631  type f struct {
   632  	data     []byte
   633  	name     string
   634  	dir      string
   635  	filePath string
   636  	header   http.Header
   637  }