From 652daf1c4b3dbc57e5b16df1a9c8e1f65492fd35 Mon Sep 17 00:00:00 2001 From: wiserain Date: Tue, 10 Dec 2024 12:43:54 +0900 Subject: [PATCH 1/5] 115: implement a new option to read cookies from other remotes Use `cookie_from = 115_1: 115_2:` in your rclone.conf --- backend/115/115.go | 156 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 126 insertions(+), 30 deletions(-) diff --git a/backend/115/115.go b/backend/115/115.go index 26a5a4dd58ee6..5f88d7d251519 100644 --- a/backend/115/115.go +++ b/backend/115/115.go @@ -25,6 +25,7 @@ import ( "io" "net/http" "path" + "reflect" "regexp" "strings" "sync" @@ -104,6 +105,10 @@ Additionally, you can provide a comma-separated list of cookies to distribute re across multiple client instances for load balancing.`, Required: true, Sensitive: true, + }, { + Name: "cookie_from", + Help: `Specify a space-separated list of remote names to read cookies from`, + Advanced: true, }, { Name: "share_code", Help: "share code from share link", @@ -255,7 +260,8 @@ type Options struct { CID string `config:"cid"` SEID string `config:"seid"` KID string `config:"kid"` - Cookie string `config:"cookie"` + Cookie fs.CommaSepList `config:"cookie"` + CookieFrom fs.SpaceSepList `config:"cookie_from"` ShareCode string `config:"share_code"` ReceiveCode string `config:"receive_code"` UserAgent string `config:"user_agent"` @@ -270,7 +276,7 @@ type Options struct { ChunkSize fs.SizeSuffix `config:"chunk_size"` MaxUploadParts int `config:"max_upload_parts"` UploadConcurrency int `config:"upload_concurrency"` - DownloadCookie string `config:"download_cookie"` + DownloadCookie fs.CommaSepList `config:"download_cookie"` DownloadNoProxy bool `config:"download_no_proxy"` Enc encoder.MultiEncoder `config:"encoding"` } @@ -377,14 +383,27 @@ func errorHandler(resp *http.Response) error { return errResponse } -// getCookies extracts UID, CID, SEID and KID from a cookie string and returns of a list of *http.Cookie -func getCookies(cookie string) (cks []*http.Cookie) { - if cookie == "" { - return +type Credential struct { + UID string + CID string + SEID string + KID string +} + +// Valid reports whether the credential is valid. +func (cr *Credential) Valid() error { + if cr == nil { + return fmt.Errorf("nil credential") + } + if cr.UID == "" || cr.CID == "" || cr.SEID == "" { + return fmt.Errorf("missing UID, CID, or SEID") } + return nil +} - items := strings.Split(cookie, ";") - for _, item := range items { +// FromCookie loads credential from cookie string +func (cr *Credential) FromCookie(cookieStr string) *Credential { + for _, item := range strings.Split(cookieStr, ";") { kv := strings.Split(strings.TrimSpace(item), "=") if len(kv) != 2 { continue @@ -393,12 +412,26 @@ func getCookies(cookie string) (cks []*http.Cookie) { val := strings.TrimSpace(kv[1]) switch key { case "UID", "CID", "SEID", "KID": - if val != "" { - cks = append(cks, &http.Cookie{Name: key, Value: val, Domain: domain, Path: "/", HttpOnly: true}) - } + reflect.ValueOf(cr).Elem().FieldByName(key).SetString(val) } } - return + return cr +} + +// Cookie turns the credential into a list of http cookie +func (cr *Credential) Cookie() []*http.Cookie { + return []*http.Cookie{ + {Name: "UID", Value: cr.UID, Domain: domain, Path: "/", HttpOnly: true}, + {Name: "CID", Value: cr.CID, Domain: domain, Path: "/", HttpOnly: true}, + {Name: "KID", Value: cr.KID, Domain: domain, Path: "/", HttpOnly: true}, + {Name: "SEID", Value: cr.SEID, Domain: domain, Path: "/", HttpOnly: true}, + } +} + +// UserID parses userID from UID field +func (cr *Credential) UserID() string { + userID, _, _ := strings.Cut(cr.UID, "_") + return userID } // getClient makes an http client according to the options @@ -406,7 +439,7 @@ func getClient(ctx context.Context, opt *Options) *http.Client { t := fshttp.NewTransportCustom(ctx, func(t *http.Transport) { t.TLSHandshakeTimeout = time.Duration(opt.ConTimeout) t.ResponseHeaderTimeout = time.Duration(opt.Timeout) - if opt.DownloadCookie != "" && opt.DownloadNoProxy { + if len(opt.DownloadCookie) != 0 && opt.DownloadNoProxy { t.Proxy = nil } }) @@ -420,6 +453,7 @@ type poolClient struct { clients []*rest.Client clientMu *sync.Mutex currentIndex int + credentials []*Credential } func (p *poolClient) client() *rest.Client { @@ -445,22 +479,64 @@ func (p *poolClient) Do(req *http.Request) (*http.Response, error) { return p.client().Do(req) } -func newPoolClient(ctx context.Context, opt *Options, cookies string) *poolClient { - var clients []*rest.Client - for _, cookie := range strings.Split(cookies, ",") { - if cks := getCookies(cookie); len(cks) == 4 { - cli := rest.NewClient(getClient(ctx, opt)).SetRoot(rootURL).SetErrorHandler(errorHandler) - cli.SetCookie(cks...) - clients = append(clients, cli) +func newPoolClient(ctx context.Context, opt *Options, cookies fs.CommaSepList) (pc *poolClient, err error) { + // cookies -> credentials + var creds []*Credential + seen := make(map[string]bool) + for _, cookie := range cookies { + cred := (&Credential{}).FromCookie(cookie) + if err = cred.Valid(); err != nil { + return nil, fmt.Errorf("%w: %q", err, cookie) + } + if seen[cred.UID] { + return nil, fmt.Errorf("duplicate UID: %q", cookie) } + seen[cred.UID] = true + creds = append(creds, cred) } - if len(clients) == 0 { - return nil + if len(creds) == 0 { + return nil, nil + } + if len(creds) > 1 { + UserID := creds[0].UserID() + for _, cred := range creds[1:] { + if user := cred.UserID(); UserID != user { + return nil, fmt.Errorf("inconsistent UserID: %s != %s", UserID, user) + } + } + } + + // credentials -> rest clients + var clients []*rest.Client + for _, cred := range creds { + cli := rest.NewClient(getClient(ctx, opt)).SetRoot(rootURL).SetErrorHandler(errorHandler) + clients = append(clients, cli.SetCookie(cred.Cookie()...)) } return &poolClient{ - clients: clients, - clientMu: new(sync.Mutex), + clients: clients, + clientMu: new(sync.Mutex), + credentials: creds, + }, nil +} + +// getCookieFrom retrieves a cookie from `remote` configured in rclone.conf +func getCookieFrom(remote string) (cookie fs.CommaSepList, err error) { + fsInfo, _, _, config, err := fs.ConfigFs(remote) + if err != nil { + return nil, err + } + if fsInfo.Name != "115" { + return nil, fmt.Errorf("not 115 remote") + } + opt := new(Options) + err = configstruct.Set(config, opt) + if err != nil { + return nil, err } + if len(opt.Cookie) == 0 { + return nil, fmt.Errorf("empty cookie") + } + return opt.Cookie, nil } // newClientWithPacer sets a new pool client with a pacer to Fs @@ -469,19 +545,39 @@ func (f *Fs) newClientWithPacer(ctx context.Context, opt *Options) (err error) { newCtx, ci := fs.AddConfig(ctx) ci.UserAgent = opt.UserAgent - f.srv = newPoolClient(newCtx, opt, opt.Cookie) + var remoteCookies fs.CommaSepList + for _, remote := range opt.CookieFrom { + cookie, err := getCookieFrom(remote) + if err != nil { + return fmt.Errorf("couldn't get cookie from %q: %w", remote, err) + } + remoteCookies = append(remoteCookies, cookie...) + } + if f.srv, err = newPoolClient(newCtx, opt, remoteCookies); err != nil { + return err + } if f.srv == nil { - // if not found from opt.Cookie - cookie := fmt.Sprintf("UID=%s;CID=%s;SEID=%s;KID=%s", opt.UID, opt.CID, opt.SEID, opt.KID) - f.srv = newPoolClient(newCtx, opt, cookie) + // if not any from opt.CookieFrom + if f.srv, err = newPoolClient(newCtx, opt, opt.Cookie); err != nil { + return err + } + } + if f.srv == nil { + // if not any from opt.Cookie + oldCookie := fmt.Sprintf("UID=%s;CID=%s;SEID=%s;KID=%s", opt.UID, opt.CID, opt.SEID, opt.KID) + if f.srv, err = newPoolClient(newCtx, opt, fs.CommaSepList{oldCookie}); err != nil { + return err + } } if f.srv == nil { return fmt.Errorf("no cookies") } // download-only clients - f.dsrv = newPoolClient(newCtx, opt, opt.DownloadCookie) - f.userID, _, _ = strings.Cut(opt.UID, "_") + if f.dsrv, err = newPoolClient(newCtx, opt, opt.DownloadCookie); err != nil { + return err + } + f.userID = f.srv.credentials[0].UserID() adjustedMinSleep := time.Duration(opt.PacerMinSleep) if numClients := len(f.srv.clients); numClients > 1 { adjustedMinSleep /= time.Duration(numClients) From b307062666a84b28ba85543bf8efed824548f6db Mon Sep 17 00:00:00 2001 From: wiserain Date: Mon, 16 Dec 2024 02:50:55 +0900 Subject: [PATCH 2/5] 115: increase the default minimum sleep duration for the pacer It is now 1032ms --- backend/115/115.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/115/115.go b/backend/115/115.go index 5f88d7d251519..27a922fc6661a 100644 --- a/backend/115/115.go +++ b/backend/115/115.go @@ -52,7 +52,7 @@ const ( rootURL = "https://webapi.115.com" defaultUserAgent = "Mozilla/5.0 115Browser/27.0.7.5" - defaultMinSleep = fs.Duration(1000 * time.Millisecond) // 1 transactions per second + defaultMinSleep = fs.Duration(1032 * time.Millisecond) maxSleep = 2 * time.Second decayConstant = 2 // bigger for slower decay, exponential From 6120935ed10d8a218d971d5946881abb2e668893 Mon Sep 17 00:00:00 2001 From: wiserain Date: Fri, 20 Dec 2024 02:12:59 +0900 Subject: [PATCH 3/5] 115: use download URL expiry from parameter, not fixed 100s --- backend/115/api/types.go | 41 ++++++++++++++++++++++++++++++++-------- backend/115/helper.go | 5 +---- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/backend/115/api/types.go b/backend/115/api/types.go index 874b1998194bd..286c20d568c54 100644 --- a/backend/115/api/types.go +++ b/backend/115/api/types.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "strings" "time" @@ -270,18 +271,42 @@ type SizeInfo struct { // ------------------------------------------------------------ type DownloadURL struct { - URL string `json:"url"` - Client Int `json:"client"` - Desc string `json:"desc"` - OssID string `json:"oss_id"` - Cookies []*http.Cookie - CreateTime time.Time + URL string `json:"url"` + Client Int `json:"client"` + Desc string `json:"desc"` + OssID string `json:"oss_id"` + Cookies []*http.Cookie +} + +// expiry parses expiry from URL parameter t +func (u *DownloadURL) expiry() time.Time { + if p, err := url.Parse(u.URL); err == nil { + if q, err := url.ParseQuery(p.RawQuery); err == nil { + if t := q.Get("t"); t != "" { + if i, err := strconv.ParseInt(t, 10, 64); err == nil { + return time.Unix(i, 0) + } + } + } + } + return time.Time{} +} + +// expired reports whether the token is expired. +// u must be non-nil. +func (u *DownloadURL) expired() bool { + expiry := u.expiry() + if expiry.IsZero() { + return false + } + + expiryDelta := time.Duration(10) * time.Second + return expiry.Round(0).Add(-expiryDelta).Before(time.Now()) } // Valid reports whether u is non-nil, has an URL, and is not expired. func (u *DownloadURL) Valid() bool { - return u != nil && u.URL != "" && time.Since(u.CreateTime) < 100*time.Second - // TODO: how sure for 100s expiry + return u != nil && u.URL != "" && !u.expired() } func (u *DownloadURL) Cookie() string { diff --git a/backend/115/helper.go b/backend/115/helper.go index 97be6766b4867..7c69808336031 100644 --- a/backend/115/helper.go +++ b/backend/115/helper.go @@ -377,8 +377,7 @@ func (f *Fs) _getDownloadURL(ctx context.Context, input []byte) (output []byte, if err != nil { return nil, nil, fmt.Errorf("failed to decode data: %w", err) } - cookies = append(cookies, resp.Cookies()...) // including uid, cid, and seid - cookies = append(cookies, resp.Request.Cookies()...) // including access key value pari with Max-Age=900 + cookies = append(cookies, resp.Cookies()...) // including access key value pair with Max-Age=900 return } @@ -397,7 +396,6 @@ func (f *Fs) getDownloadURL(ctx context.Context, pickCode string) (durl *api.Dow for _, downInfo := range downData { durl = &downInfo.URL durl.Cookies = cookies - durl.CreateTime = time.Now() return } return nil, fs.ErrorObjectNotFound @@ -683,6 +681,5 @@ func (f *Fs) getDownloadURLFromShare(ctx context.Context, fid string) (durl *api durl = &downInfo.URL durl.Cookies = cookies - durl.CreateTime = time.Now() return } From 14ad0d63b4893c52186a5acf99636e4781daa1ce Mon Sep 17 00:00:00 2001 From: wiserain Date: Fri, 20 Dec 2024 18:46:36 +0900 Subject: [PATCH 4/5] 115: update alibabacloud-oss-go-sdk-v2 to v1.1.3 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9773a1bee7c00..03be071f1325a 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3 github.com/abbot/go-http-auth v0.4.0 github.com/aead/ecdh v0.2.0 - github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.1 + github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.3 github.com/anacrolix/dms v1.7.1 github.com/anacrolix/log v0.15.2 github.com/andreburgaud/crypt2go v1.4.2 diff --git a/go.sum b/go.sum index b1d440e1d2e66..93013a3648a89 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ= github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U= github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.1 h1:Sc2T9vs8SCq0DErL/QIY3ZVx8F+dm+DIv4RFP9NLwC4= -github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.1/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M= +github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.3 h1:grJyLSdRJtfxKKhCTWSeJhnOQsp2WoLNdK8XA5FE9oo= +github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.3/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M= github.com/anacrolix/dms v1.7.1 h1:XVOpT3eoO5Ds34B1X+TE3R2ApfqGGeqotEoCVNP8BaI= github.com/anacrolix/dms v1.7.1/go.mod h1:excFJW5MKBhn5yt5ZMyeE9iFVqnO6tEGQl7YG/2tUoQ= github.com/anacrolix/generics v0.0.1 h1:4WVhK6iLb3UAAAQP6I3uYlMOHcp9FqJC9j4n81Wv9Ks= From 12305d0fa65ce913a376c83e7286bfff41e3e7f8 Mon Sep 17 00:00:00 2001 From: wiserain Date: Fri, 20 Dec 2024 18:49:55 +0900 Subject: [PATCH 5/5] build: update golang.org/x/net to v0.33.0 to resolve GO-2024-3333 --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 03be071f1325a..b48cde413ed04 100644 --- a/go.mod +++ b/go.mod @@ -82,13 +82,13 @@ require ( github.com/yunify/qingstor-sdk-go/v3 v3.2.0 go.etcd.io/bbolt v1.3.10 goftp.io/server/v2 v2.0.1 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/net v0.27.0 + golang.org/x/net v0.33.0 golang.org/x/oauth2 v0.21.0 - golang.org/x/sync v0.8.0 - golang.org/x/sys v0.22.0 - golang.org/x/text v0.17.0 + golang.org/x/sync v0.10.0 + golang.org/x/sys v0.28.0 + golang.org/x/text v0.21.0 golang.org/x/time v0.5.0 google.golang.org/api v0.188.0 gopkg.in/validator.v2 v2.0.1 @@ -231,5 +231,5 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/pkg/xattr v0.4.9 golang.org/x/mobile v0.0.0-20240716161057-1ad2df20a8b6 - golang.org/x/term v0.22.0 + golang.org/x/term v0.27.0 ) diff --git a/go.sum b/go.sum index 93013a3648a89..6a4673d3c59e7 100644 --- a/go.sum +++ b/go.sum @@ -684,8 +684,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -768,8 +768,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -791,8 +791,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -848,8 +848,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -860,8 +860,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -875,8 +875,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=