Przeglądaj źródła

ftpd: add basic wildcard support

this is the minimal implementation to allow mget and similar commands with
wildcards.

We only support wildcard for the last path level, for example:

- mget *.xml is supported
- mget dir/*.xml is supported
- mget */*.xml is not supported

Removed . and .. from FTP directory listing

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 3 lat temu
rodzic
commit
61947e67ae
6 zmienionych plików z 225 dodań i 41 usunięć
  1. 3 3
      ftpd/cryptfs_test.go
  2. 108 11
      ftpd/ftpd_test.go
  3. 65 7
      ftpd/handler.go
  4. 25 0
      ftpd/internal_test.go
  5. 8 8
      go.mod
  6. 16 12
      go.sum

+ 3 - 3
ftpd/cryptfs_test.go

@@ -57,9 +57,9 @@ func TestBasicFTPHandlingCryptFs(t *testing.T) {
 		}
 		list, err := client.List(".")
 		if assert.NoError(t, err) {
-			assert.Len(t, list, 2)
-			assert.Equal(t, ".", list[0].Name)
-			assert.Equal(t, testFileSize, int64(list[1].Size))
+			if assert.Len(t, list, 1) {
+				assert.Equal(t, testFileSize, int64(list[0].Size))
+			}
 		}
 		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
 		assert.NoError(t, err)

+ 108 - 11
ftpd/ftpd_test.go

@@ -550,15 +550,11 @@ func TestBasicFTPHandling(t *testing.T) {
 			}
 			res, err := client.List(path.Join("/", testDir))
 			assert.NoError(t, err)
-			if assert.Len(t, res, 2) {
-				assert.Equal(t, ".", res[0].Name)
-				assert.Equal(t, "..", res[1].Name)
-			}
+			assert.Len(t, res, 0)
 			res, err = client.List(path.Join("/"))
 			assert.NoError(t, err)
-			if assert.Len(t, res, 2) {
-				assert.Equal(t, ".", res[0].Name)
-				assert.Equal(t, testDir, res[1].Name)
+			if assert.Len(t, res, 1) {
+				assert.Equal(t, testDir, res[0].Name)
 			}
 			err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
 			assert.NoError(t, err)
@@ -597,6 +593,105 @@ func TestBasicFTPHandling(t *testing.T) {
 		50*time.Millisecond)
 }
 
+func TestListDirWithWildcards(t *testing.T) {
+	localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
+	assert.NoError(t, err)
+	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
+	assert.NoError(t, err)
+	for _, user := range []dataprovider.User{localUser, sftpUser} {
+		client, err := getFTPClient(user, true, nil)
+		if assert.NoError(t, err) {
+			dir1 := "test.dir"
+			dir2 := "test.dir1"
+			err = client.MakeDir(dir1)
+			assert.NoError(t, err)
+			err = client.MakeDir(dir2)
+			assert.NoError(t, err)
+			testFilePath := filepath.Join(homeBasePath, testFileName)
+			testFileSize := int64(65535)
+			err = createTestFile(testFilePath, testFileSize)
+			assert.NoError(t, err)
+			fileName := "fil*e.dat"
+			err = ftpUploadFile(testFilePath, fileName, testFileSize, client, 0)
+			assert.NoError(t, err)
+			localDownloadPath := filepath.Join(homeBasePath, testDLFileName)
+			err = ftpDownloadFile(fileName, localDownloadPath, testFileSize, client, 0)
+			assert.NoError(t, err)
+			entries, err := client.NameList(fileName)
+			if assert.NoError(t, err) {
+				assert.Len(t, entries, 1)
+				assert.Contains(t, entries, fileName)
+			}
+			entries, err = client.NameList(".")
+			assert.NoError(t, err)
+			assert.Len(t, entries, 3)
+			entries, err = client.NameList("/test.*")
+			if assert.NoError(t, err) {
+				assert.Len(t, entries, 2)
+				assert.Contains(t, entries, dir1)
+				assert.Contains(t, entries, dir2)
+			}
+			entries, err = client.NameList("/*.dir?")
+			if assert.NoError(t, err) {
+				assert.Len(t, entries, 1)
+				assert.Contains(t, entries, dir2)
+			}
+			entries, err = client.NameList("/test.???")
+			if assert.NoError(t, err) {
+				assert.Len(t, entries, 1)
+				assert.Contains(t, entries, dir1)
+			}
+			_, err = client.NameList("/missingdir/test.*")
+			assert.Error(t, err)
+			_, err = client.NameList("test[-]")
+			if assert.Error(t, err) {
+				assert.Contains(t, err.Error(), path.ErrBadPattern.Error())
+			}
+			subDir := path.Join(dir1, "sub.d")
+			err = client.MakeDir(subDir)
+			assert.NoError(t, err)
+			err = client.ChangeDir(path.Dir(subDir))
+			assert.NoError(t, err)
+			entries, err = client.NameList("sub.?")
+			if assert.NoError(t, err) {
+				assert.Len(t, entries, 1)
+				assert.Contains(t, entries, path.Base(subDir))
+			}
+			entries, err = client.NameList("../*.dir?")
+			if assert.NoError(t, err) {
+				assert.Len(t, entries, 1)
+				assert.Contains(t, entries, path.Join("../", dir2))
+			}
+			err = client.ChangeDir("/")
+			assert.NoError(t, err)
+			entries, err = client.NameList(path.Join(dir1, "sub.*"))
+			if assert.NoError(t, err) {
+				assert.Len(t, entries, 1)
+				assert.Contains(t, entries, path.Join(dir1, "sub.d"))
+			}
+			err = client.RemoveDir(subDir)
+			assert.NoError(t, err)
+			err = client.RemoveDir(dir1)
+			assert.NoError(t, err)
+			err = client.RemoveDir(dir2)
+			assert.NoError(t, err)
+			err = os.Remove(testFilePath)
+			assert.NoError(t, err)
+			err = os.Remove(localDownloadPath)
+			assert.NoError(t, err)
+			err = client.Quit()
+			assert.NoError(t, err)
+		}
+	}
+
+	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
+	assert.NoError(t, err)
+	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
+	assert.NoError(t, err)
+	err = os.RemoveAll(localUser.GetHomeDir())
+	assert.NoError(t, err)
+}
+
 func TestStartDirectory(t *testing.T) {
 	startDir := "/start/dir"
 	u := getTestUser()
@@ -625,12 +720,14 @@ func TestStartDirectory(t *testing.T) {
 			assert.NoError(t, err)
 			entries, err := client.List(".")
 			assert.NoError(t, err)
-			assert.Len(t, entries, 3)
-
+			if assert.Len(t, entries, 1) {
+				assert.Equal(t, testFileName, entries[0].Name)
+			}
 			entries, err = client.List("/")
 			assert.NoError(t, err)
-			assert.Len(t, entries, 2)
-
+			if assert.Len(t, entries, 1) {
+				assert.Equal(t, "start", entries[0].Name)
+			}
 			err = client.ChangeDirToParent()
 			assert.NoError(t, err)
 			currentDir, err = client.CurrentDir()

+ 65 - 7
ftpd/handler.go

@@ -6,6 +6,7 @@ import (
 	"io"
 	"os"
 	"path"
+	"strings"
 	"time"
 
 	ftpserver "github.com/fclairamb/ftpserverlib"
@@ -14,7 +15,6 @@ import (
 	"github.com/drakkan/sftpgo/v2/common"
 	"github.com/drakkan/sftpgo/v2/dataprovider"
 	"github.com/drakkan/sftpgo/v2/logger"
-	"github.com/drakkan/sftpgo/v2/util"
 	"github.com/drakkan/sftpgo/v2/vfs"
 )
 
@@ -147,6 +147,9 @@ func (c *Connection) Stat(name string) (os.FileInfo, error) {
 
 	fi, err := c.DoStat(name, 0, true)
 	if err != nil {
+		if c.isListDirWithWildcards(path.Base(name), err) {
+			return vfs.NewFileInfo(name, true, 0, time.Now(), false), nil
+		}
 		return nil, err
 	}
 	return fi, nil
@@ -276,13 +279,15 @@ func (c *Connection) ReadDir(name string) ([]os.FileInfo, error) {
 
 	files, err := c.ListDir(name)
 	if err != nil {
-		return files, err
-	}
-	if name != "/" {
-		files = util.PrependFileInfo(files, vfs.NewFileInfo("..", true, 0, time.Now(), false))
+		baseName := path.Base(name)
+		if c.isListDirWithWildcards(baseName, err) {
+			// we only support wildcards for the last path level, for example:
+			// - *.xml is supported
+			// - dir*/*.xml is not supported
+			return c.getListDirWithWildcards(path.Dir(name), baseName)
+		}
 	}
-	files = util.PrependFileInfo(files, vfs.NewFileInfo(".", true, 0, time.Now(), false))
-	return files, nil
+	return files, err
 }
 
 // GetHandle implements ClientDriverExtentionFileTransfer
@@ -482,3 +487,56 @@ func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolve
 
 	return t, nil
 }
+
+func (c *Connection) getListDirWithWildcards(dirName, pattern string) ([]os.FileInfo, error) {
+	files, err := c.ListDir(dirName)
+	if err != nil {
+		return files, err
+	}
+	validIdx := 0
+	relativeBase := getPathRelativeTo(c.clientContext.Path(), dirName)
+	for _, fi := range files {
+		match, err := path.Match(pattern, fi.Name())
+		if err != nil {
+			return files, err
+		}
+		if match {
+			files[validIdx] = vfs.NewFileInfo(path.Join(relativeBase, fi.Name()), fi.IsDir(), fi.Size(),
+				fi.ModTime(), true)
+			validIdx++
+		}
+	}
+
+	return files[:validIdx], nil
+}
+
+func (c *Connection) isListDirWithWildcards(name string, err error) bool {
+	if errors.Is(err, c.GetNotExistError()) {
+		lastCommand := c.clientContext.GetLastCommand()
+		if lastCommand == "LIST" || lastCommand == "NLST" {
+			return strings.ContainsAny(name, "*?[]")
+		}
+	}
+	return false
+}
+
+func getPathRelativeTo(base, target string) string {
+	var sb strings.Builder
+	for {
+		if base == target {
+			return sb.String()
+		}
+		if !strings.HasSuffix(base, "/") {
+			base += "/"
+		}
+		if strings.HasPrefix(target, base) {
+			sb.WriteString(strings.TrimPrefix(target, base))
+			return sb.String()
+		}
+		if base == "/" || base == "./" {
+			return target
+		}
+		sb.WriteString("../")
+		base = path.Dir(path.Clean(base))
+	}
+}

+ 25 - 0
ftpd/internal_test.go

@@ -1002,3 +1002,28 @@ func TestPassiveIPResolver(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, b.ForcePassiveIP, passiveIP)
 }
+
+func TestRelativePath(t *testing.T) {
+	rel := getPathRelativeTo("/testpath", "/testpath")
+	assert.Empty(t, rel)
+	rel = getPathRelativeTo("/", "/")
+	assert.Empty(t, rel)
+	rel = getPathRelativeTo("/", "/dir/sub")
+	assert.Equal(t, "dir/sub", rel)
+	rel = getPathRelativeTo("./", "/dir/sub")
+	assert.Equal(t, "/dir/sub", rel)
+	rel = getPathRelativeTo("/sub", "/dir/sub")
+	assert.Equal(t, "../dir/sub", rel)
+	rel = getPathRelativeTo("/dir", "/dir/sub")
+	assert.Equal(t, "sub", rel)
+	rel = getPathRelativeTo("/dir/sub", "/dir")
+	assert.Equal(t, "../", rel)
+	rel = getPathRelativeTo("dir", "/dir1")
+	assert.Equal(t, "/dir1", rel)
+	rel = getPathRelativeTo("", "/dir2")
+	assert.Equal(t, "dir2", rel)
+	rel = getPathRelativeTo(".", "/dir2")
+	assert.Equal(t, "/dir2", rel)
+	rel = getPathRelativeTo("/dir3", "dir3")
+	assert.Equal(t, "dir3", rel)
+}

+ 8 - 8
go.mod

@@ -12,9 +12,9 @@ require (
 	github.com/aws/aws-sdk-go-v2/config v1.15.4
 	github.com/aws/aws-sdk-go-v2/credentials v1.12.0
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4
-	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.7
+	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.8
 	github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.4
-	github.com/aws/aws-sdk-go-v2/service/s3 v1.26.7
+	github.com/aws/aws-sdk-go-v2/service/s3 v1.26.8
 	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.6
 	github.com/aws/aws-sdk-go-v2/service/sts v1.16.4
 	github.com/cockroachdb/cockroach-go/v2 v2.2.8
@@ -65,16 +65,16 @@ require (
 	go.uber.org/automaxprocs v1.5.1
 	gocloud.dev v0.25.0
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
-	golang.org/x/net v0.0.0-20220421235706-1d1ef9303861
+	golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
 	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
-	golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32
+	golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
 	golang.org/x/time v0.0.0-20220411224347-583f2d630306
-	google.golang.org/api v0.77.0
+	google.golang.org/api v0.78.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 )
 
 require (
-	cloud.google.com/go v0.101.0 // indirect
+	cloud.google.com/go v0.101.1 // indirect
 	cloud.google.com/go/compute v1.6.1 // indirect
 	cloud.google.com/go/iam v0.3.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2 // indirect
@@ -152,7 +152,7 @@ require (
 	golang.org/x/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e // indirect
+	google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72 // indirect
 	google.golang.org/grpc v1.46.0 // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
@@ -164,5 +164,5 @@ require (
 replace (
 	github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
 	golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20220412172350-e76a61f8e7d2
-	golang.org/x/net => github.com/drakkan/net v0.0.0-20220425150817-e26421ba5d2e
+	golang.org/x/net => github.com/drakkan/net v0.0.0-20220504073018-5d1702f4106f
 )

+ 16 - 12
go.sum

@@ -31,8 +31,8 @@ cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Ud
 cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
 cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
 cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
-cloud.google.com/go v0.101.0 h1:g+LL+JvpvdyGtcaD2xw2mSByE/6F9s471eJSoaysM84=
-cloud.google.com/go v0.101.0/go.mod h1:hEiddgDb77jDQ+I80tURYNJEnuwPzFU8awCFFRLKjW0=
+cloud.google.com/go v0.101.1 h1:3+/0TAm9JD/PyhkrDWQWi2L197h3euCsM+H+J4iYTR8=
+cloud.google.com/go v0.101.1/go.mod h1:55HwjsGW4CHD3JrNuMdZtSDsgTs0CuCB/bBTugD+7AA=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -150,8 +150,8 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnq
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 h1:FP8gquGeGHHdfY6G5llaMQDF+HAf20VKc8opRwmjf04=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4/go.mod h1:u/s5/Z+ohUQOPXl00m2yJVyioWDECsbpXTQlaqSlufc=
 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.7 h1:h1y9Jn+1VTEjB4TG5prxtQjW9DM5o8y7Cu9ZdNmkXWA=
-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.7/go.mod h1:NZ2wPktB/I111CyzF3ezVf8jrAg/PqKeYkdR11oBWeU=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.8 h1:DrnslKj0yz31roe7+tmMblGhbr6OV7VQUJo5ylBU9YQ=
+github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.8/go.mod h1:PCN2NuDpHRYLcO2Gl9yVToeahcRBkf+87inUVGwtM+Q=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 h1:uFWgo6mGJI1n17nbcvSc6fxVuR3xLNqvXt12JCnEcT8=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4=
@@ -178,8 +178,8 @@ github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYY
 github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.4 h1:qmHavnjRtgdH54nyG4iEk6ZCde9m2S++32INurhaNTk=
 github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.13.4/go.mod h1:CloMDruFIVZJ8qv2OsY5ENIqzg5c0eeTciVVW3KHdvE=
 github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.26.7 h1:ZEPH6aBywdyn5LGr7hSNEwuPaKpKZodX0R9AjPj5A7c=
-github.com/aws/aws-sdk-go-v2/service/s3 v1.26.7/go.mod h1:iMYipLPXlWpBJ0KFX7QJHZ84rBydHBY8as2aQICTPWk=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.8 h1:XInp7WokGrNBYC+5rm9npWC0vrF2xtfNfM/qozGI6U0=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.26.8/go.mod h1:iMYipLPXlWpBJ0KFX7QJHZ84rBydHBY8as2aQICTPWk=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.6 h1:m+mxqLIrGq7GJo5qw4rHn8BbUqHrvxvwFx54N1Pglvw=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.6/go.mod h1:Z+i6uqZgCOBXhNoEGoRm/ZaLsaJA9rGUAmkVKM/3+g4=
@@ -255,8 +255,8 @@ github.com/drakkan/crypto v0.0.0-20220412172350-e76a61f8e7d2 h1:5XmEywX1u5gPgOC+
 github.com/drakkan/crypto v0.0.0-20220412172350-e76a61f8e7d2/go.mod h1:SiM6ypd8Xu1xldObYtbDztuUU7xUzMnUULfphXFZmro=
 github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHPMtBLXhQmjaga91/DDjWk9jWA=
 github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
-github.com/drakkan/net v0.0.0-20220425150817-e26421ba5d2e h1:QCodCQfdiYnXHR+1nnzi69+tM6FEThtzthP8ZhpHkrk=
-github.com/drakkan/net v0.0.0-20220425150817-e26421ba5d2e/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+github.com/drakkan/net v0.0.0-20220504073018-5d1702f4106f h1:BUIQ/l47ZPdruH1S17AdCCq6YNR/DyLpvysRXikD/Xg=
+github.com/drakkan/net v0.0.0-20220504073018-5d1702f4106f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001 h1:/ZshrfQzayqRSBDodmp3rhNCHJCff+utvgBuWRbiqu4=
 github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001/go.mod h1:kltMsfRMTHSFdMbK66XdS8mfMW77+FZA1fGY1xYMF84=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -965,8 +965,9 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 h1:Js08h5hqB5xyWR789+QqueR6sDE8mk+YvpETZ+F6X9Y=
-golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1102,8 +1103,9 @@ google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/S
 google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
 google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
 google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
-google.golang.org/api v0.77.0 h1:msijLTxwkJ7Jub5tv9KBVCKtHOQwnvnvkX7ErFFCVxY=
 google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0 h1:5ewPyCwP43C3i8B6C2Kb+eVAevbnke2xR8VbcSWjS4I=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1203,8 +1205,10 @@ google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX
 google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e h1:gMjH4zLGs9m+dGzR7qHCHaXMOwsJHJKKkHtyXhtOrJk=
 google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72 h1:iif0mpUetMBqcQPUoq+JnCcmzvfpp8wRx515va8wP1c=
+google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=