protondrive: log Proton's announced drive requirements at debug level
Proton announces requirements it will enforce, such as a minimum client version, in x-pm-drive-requirements response headers. They are now logged at debug level whenever they change, so they show in -vv bug reports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5dbf5b04c6
commit
b4a23a31ed
3 files changed
+63
No files matched your search
@@ -102,6 +102,9 @@ type Server struct {
|
||||
requests map[string]int // count per "METHOD pattern"
|
||||
faults []*fault
|
||||
clockSkew time.Duration
|
||||
// driveRequirements is sent as x-pm-drive-requirements headers if
|
||||
// not empty; see SetDriveRequirements.
|
||||
driveRequirements []string
|
||||
users map[string]*User // by username
|
||||
sessions map[string]*session // by UID
|
||||
srp map[string]*srpLogin // by SRPSession
|
||||
@@ -276,6 +279,15 @@ func (s *Server) SetClockSkew(d time.Duration) {
|
||||
s.clockSkew = d
|
||||
}
|
||||
|
||||
// SetDriveRequirements makes every response carry an
|
||||
// x-pm-drive-requirements header with each of values, as Proton
|
||||
// announces upcoming requirements; none if values is empty.
|
||||
func (s *Server) SetDriveRequirements(values ...string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.driveRequirements = values
|
||||
}
|
||||
|
||||
// handle registers h for method and an http.ServeMux path pattern.
|
||||
func (s *Server) handle(method, pattern string, h http.HandlerFunc) {
|
||||
s.mux.HandleFunc(method+" "+pattern, h)
|
||||
@@ -294,6 +306,9 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.mu.Lock()
|
||||
s.requests[key]++
|
||||
w.Header().Set("Date", time.Now().Add(s.clockSkew).UTC().Format(http.TimeFormat))
|
||||
for _, v := range s.driveRequirements {
|
||||
w.Header().Add("x-pm-drive-requirements", v)
|
||||
}
|
||||
if pattern == storageUploadPattern {
|
||||
s.storageUploads++
|
||||
s.storagePeak = max(s.storagePeak, s.storageUploads)
|
||||
|
||||
@@ -936,6 +936,29 @@ func runSignedInSession(t *testing.T, dump fs.DumpFlags) secretRun {
|
||||
|
||||
// TestNoSecretsInLogs checks no secret reaches rclone's log at debug
|
||||
// level across sign-in, upload, download and Disconnect.
|
||||
func TestDriveRequirementsLogged(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
remote, srv, u := setupFakeRemote(t)
|
||||
u.SeedFile(u.RootID(), "a.txt", []byte("a"), time.Now())
|
||||
srv.SetDriveRequirements("min-version=1.2.3", "crypto=v2")
|
||||
capture := captureLog(t, 0)
|
||||
f := newFakeFs(t, remote, "")
|
||||
for range 3 {
|
||||
mustList(ctx, t, f, "")
|
||||
}
|
||||
srv.SetDriveRequirements("min-version=2.0.0")
|
||||
for range 2 {
|
||||
_, err := f.Features().About(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
log := capture.String()
|
||||
first := `DEBUG : protondrive: Proton announces drive requirements: "min-version=1.2.3, crypto=v2"`
|
||||
assert.Equal(t, 1, strings.Count(log, first), "%q logged other than once in:\n%s", first, log)
|
||||
second := `DEBUG : protondrive: Proton announces drive requirements: "min-version=2.0.0"`
|
||||
assert.Equal(t, 1, strings.Count(log, second), "%q logged other than once in:\n%s", second, log)
|
||||
}
|
||||
|
||||
func TestNoSecretsInLogs(t *testing.T) {
|
||||
t.Run("debug", func(t *testing.T) {
|
||||
run := runSignedInSession(t, 0)
|
||||
|
||||
@@ -143,6 +143,7 @@ type session struct {
|
||||
deadRefresh string // refresh token the server rejected
|
||||
serverDate time.Time // Date of the last response
|
||||
serverDateLocal time.Time // local time serverDate was received
|
||||
requirements string // x-pm-drive-requirements last logged
|
||||
}
|
||||
|
||||
// newSession returns a session for the API at baseURL using the
|
||||
@@ -252,6 +253,7 @@ func (s *session) do(ctx context.Context, opts *rest.Opts, request, response any
|
||||
resp, err := s.srv.CallJSON(ctx, opts, request, &body)
|
||||
if resp != nil {
|
||||
s.observeDate(resp.Header.Get("Date"))
|
||||
s.observeRequirements(resp.Header.Values("x-pm-drive-requirements"))
|
||||
}
|
||||
if err != nil || opts.NoResponse {
|
||||
return resp, err
|
||||
@@ -288,6 +290,29 @@ func (s *session) observeDate(date string) {
|
||||
s.serverDateLocal = time.Now()
|
||||
}
|
||||
|
||||
// maxRequirementsLog bounds the x-pm-drive-requirements text logged.
|
||||
const maxRequirementsLog = 1024
|
||||
|
||||
// observeRequirements logs at debug level the x-pm-drive-requirements
|
||||
// header values of a response, by which Proton announces requirements
|
||||
// it will enforce, when they differ from those last logged.
|
||||
func (s *session) observeRequirements(values []string) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
v := strings.Join(values, ", ")
|
||||
if len(v) > maxRequirementsLog {
|
||||
v = v[:maxRequirementsLog]
|
||||
}
|
||||
s.mu.Lock()
|
||||
changed := v != s.requirements
|
||||
s.requirements = v
|
||||
s.mu.Unlock()
|
||||
if changed {
|
||||
fs.Debugf(nil, "protondrive: Proton announces drive requirements: %q", v)
|
||||
}
|
||||
}
|
||||
|
||||
// refresh replaces the access token rejected with a working one.
|
||||
//
|
||||
// It uses tokens another goroutine or another user of the store has
|
||||
|
||||
Reference in new issue
Block a user