package xdrive import ( "context" "encoding/json" "fmt" "io" "mime" "mime/multipart" "net/http" "net/http/httptest" "net/url" "strings" "sync" "testing" "time" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/transport/internet" ) type fakeFile struct { id string name string data []byte description string } type fakeDrive struct { server *httptest.Server mu sync.Mutex files map[string]*fakeFile nextID int failOnce map[string]bool failStatus map[string]int failBody map[string]string tokens int hosts map[string]bool } func newFakeDrive(t *testing.T) *fakeDrive { t.Helper() drive := &fakeDrive{ files: make(map[string]*fakeFile), failOnce: make(map[string]bool), failStatus: make(map[string]int), failBody: make(map[string]string), hosts: make(map[string]bool), } mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) })) record := func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { drive.mu.Lock() drive.hosts[r.Host] = true drive.mu.Unlock() next(w, r) } } mux.HandleFunc("/token", record(drive.handleToken)) mux.HandleFunc("/upload", record(drive.handleUpload)) mux.HandleFunc("/files", record(drive.handleFiles)) mux.HandleFunc("/files/", record(drive.handleFile)) drive.server = httptest.NewServer(mux) resetSharedStorage() previous := []string{driveTokenURL, driveFilesURL, driveUploadURL} previousBackoff := driveInitialBackoff driveTokenURL = drive.server.URL + "/token" driveFilesURL = drive.server.URL + "/files" driveUploadURL = drive.server.URL + "/upload" driveInitialBackoff = 5 * time.Millisecond t.Cleanup(func() { driveTokenURL, driveFilesURL, driveUploadURL = previous[0], previous[1], previous[2] driveInitialBackoff = previousBackoff drive.server.Close() resetSharedStorage() }) return drive } func (d *fakeDrive) handleToken(w http.ResponseWriter, r *http.Request) { d.mu.Lock() d.tokens++ d.mu.Unlock() json.NewEncoder(w).Encode(map[string]interface{}{ "access_token": "fake-token", "expires_in": 3600, }) } func (d *fakeDrive) handleUpload(w http.ResponseWriter, r *http.Request) { if d.shouldFail("upload") { if status, body := d.failure("upload"); status != 0 { w.WriteHeader(status) w.Write([]byte(body)) return } w.WriteHeader(http.StatusTooManyRequests) return } _, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil { w.WriteHeader(http.StatusBadRequest) return } reader := multipart.NewReader(r.Body, params["boundary"]) metaPart, err := reader.NextPart() if err != nil { w.WriteHeader(http.StatusBadRequest) return } var metadata struct { Name string `json:"name"` } if err := json.NewDecoder(metaPart).Decode(&metadata); err != nil { w.WriteHeader(http.StatusBadRequest) return } var data []byte if dataPart, err := reader.NextPart(); err == nil { data, _ = io.ReadAll(dataPart) } d.mu.Lock() d.nextID++ id := fmt.Sprintf("id-%d", d.nextID) d.files[id] = &fakeFile{id: id, name: metadata.Name, data: data} d.mu.Unlock() json.NewEncoder(w).Encode(map[string]string{"id": id, "name": metadata.Name}) } func (d *fakeDrive) handleFiles(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPost { d.handleCreate(w, r) return } d.handleList(w, r) } func (d *fakeDrive) handleCreate(w http.ResponseWriter, r *http.Request) { if d.shouldFail("upload") { if status, body := d.failure("upload"); status != 0 { w.WriteHeader(status) w.Write([]byte(body)) return } w.WriteHeader(http.StatusTooManyRequests) return } var meta struct { Name string `json:"name"` Description string `json:"description"` } if err := json.NewDecoder(r.Body).Decode(&meta); err != nil { w.WriteHeader(http.StatusBadRequest) return } d.mu.Lock() d.nextID++ id := fmt.Sprintf("id-%d", d.nextID) d.files[id] = &fakeFile{id: id, name: meta.Name, description: meta.Description} d.mu.Unlock() json.NewEncoder(w).Encode(map[string]string{"id": id, "name": meta.Name}) } func (d *fakeDrive) handleList(w http.ResponseWriter, r *http.Request) { if d.shouldFail("list") { w.WriteHeader(http.StatusServiceUnavailable) return } query := r.URL.Query().Get("q") exact, prefix := parseFakeQuery(query) type entry struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` } result := struct { Files []entry `json:"files"` }{} d.mu.Lock() for _, file := range d.files { match := false switch { case exact != "": match = file.name == exact case prefix != "": match = strings.HasPrefix(file.name, prefix) } if match { result.Files = append(result.Files, entry{ ID: file.id, Name: file.name, Description: file.description, }) } } d.mu.Unlock() json.NewEncoder(w).Encode(result) } func (d *fakeDrive) handleFile(w http.ResponseWriter, r *http.Request) { id := strings.TrimPrefix(r.URL.Path, "/files/") d.mu.Lock() file, ok := d.files[id] if ok && r.Method == http.MethodDelete { delete(d.files, id) } d.mu.Unlock() if !ok { w.WriteHeader(http.StatusNotFound) return } if r.Method == http.MethodDelete { w.WriteHeader(http.StatusNoContent) return } if strings.Contains(r.URL.RawQuery, "fields=description") { json.NewEncoder(w).Encode(map[string]string{"description": file.description}) return } w.Write(file.data) } func (d *fakeDrive) shouldFail(kind string) bool { d.mu.Lock() defer d.mu.Unlock() if d.failOnce[kind] { d.failOnce[kind] = false return true } return false } func (d *fakeDrive) failOnceWith(kind string, status int, body string) { d.mu.Lock() d.failOnce[kind] = true d.failStatus[kind] = status d.failBody[kind] = body d.mu.Unlock() } func (d *fakeDrive) failure(kind string) (int, string) { d.mu.Lock() defer d.mu.Unlock() if status, ok := d.failStatus[kind]; ok { return status, d.failBody[kind] } return 0, "" } func (d *fakeDrive) failNext(kind string) { d.mu.Lock() d.failOnce[kind] = true d.mu.Unlock() } func (d *fakeDrive) seenHosts() []string { d.mu.Lock() defer d.mu.Unlock() out := make([]string, 0, len(d.hosts)) for h := range d.hosts { out = append(out, h) } return out } func (d *fakeDrive) count() int { d.mu.Lock() defer d.mu.Unlock() return len(d.files) } func parseFakeQuery(query string) (exact, prefix string) { if value, ok := cutQuoted(query, "name = '"); ok { return value, "" } if value, ok := cutQuoted(query, "name contains '"); ok { return "", value } return "", "" } func cutQuoted(query, marker string) (string, bool) { start := strings.Index(query, marker) if start < 0 { return "", false } rest := query[start+len(marker):] end := strings.Index(rest, "'") if end < 0 { return "", false } return rest[:end], true } func driveSettings() *internet.MemoryStreamConfig { return &internet.MemoryStreamConfig{ ProtocolName: protocolName, ProtocolSettings: &Config{ RemoteFolder: "folder-id", Service: "Google Drive", Secrets: []string{"client", "secret", "refresh"}, FlushIntervalMs: 5, PollIntervalMs: 5, MaxPollIntervalMs: 20, SessionTtlSeconds: 1, }, } } func newDriveBackend(t *testing.T) *driveStorage { t.Helper() storage, err := newDriveStorage(driveSettings(), driveSettings().ProtocolSettings.(*Config)) if err != nil { t.Fatalf("newDriveStorage: %v", err) } return storage } func TestDriveSecrets(t *testing.T) { if _, err := newDriveStorage(nil, &Config{RemoteFolder: "f", Secrets: []string{"a", "b"}}); err == nil { t.Fatal("accepted two secrets") } if _, err := newDriveStorage(nil, &Config{Secrets: []string{"a", "b", "c"}}); err == nil { t.Fatal("accepted an empty remoteFolder") } if _, err := newDriveStorage(nil, &Config{RemoteFolder: "f", Secrets: []string{"a", "", "c"}}); err == nil { t.Fatal("accepted an empty secret") } } func TestDriveRoundTrip(t *testing.T) { drive := newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() if err := storage.Put(ctx, "streams/abc/c2s/000000000.seg", []byte("hello")); err != nil { t.Fatalf("Put: %v", err) } data, err := storage.Get(ctx, "streams/abc/c2s/000000000.seg") if err != nil { t.Fatalf("Get: %v", err) } if string(data) != "hello" { t.Fatalf("Get returned %q, want %q", data, "hello") } names, err := storage.List(ctx, "streams/abc/c2s") if err != nil { t.Fatalf("List: %v", err) } if len(names) != 1 || names[0].Name != "000000000.seg" { t.Fatalf("List returned %v, want 1 segment", names) } if _, err := storage.Get(ctx, "streams/abc/c2s/000000009.seg"); err != errNotFound { t.Fatalf("Get returned %v, want errNotFound", err) } if err := storage.Delete(ctx, "streams/abc/c2s/000000000.seg"); err != nil { t.Fatalf("Delete: %v", err) } if drive.count() != 0 { t.Fatalf("fake drive still holds %d files", drive.count()) } } func TestDriveListChildren(t *testing.T) { newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() for _, name := range []string{ "streams/one/c2s/000000000.seg", "streams/one/s2c/000000000.seg", "streams/two/c2s/000000000.seg", } { if err := storage.Put(ctx, name, []byte("x")); err != nil { t.Fatalf("Put %s: %v", name, err) } } names, err := storage.List(ctx, "streams") if err != nil { t.Fatalf("List: %v", err) } if len(names) != 2 { t.Fatalf("List returned %v, want 2 sessions", names) } } func TestDriveDeleteSession(t *testing.T) { drive := newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() for _, name := range []string{ "streams/one/c2s/000000000.seg", "streams/one/c2s/000000001.end", "streams/one/s2c/000000000.seg", "streams/two/c2s/000000000.seg", } { if err := storage.Put(ctx, name, []byte("x")); err != nil { t.Fatalf("Put %s: %v", name, err) } } if err := storage.Delete(ctx, "streams/one"); err != nil { t.Fatalf("Delete: %v", err) } if drive.count() != 1 { t.Fatalf("fake drive holds %d files, want 1", drive.count()) } } func TestDriveRetry(t *testing.T) { drive := newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() drive.failNext("upload") if err := storage.Put(ctx, "sessions/abc", nil); err != nil { t.Fatalf("Put did not survive a 429: %v", err) } drive.failNext("list") if _, err := storage.List(ctx, "sessions"); err != nil { t.Fatalf("List did not survive a 503: %v", err) } } func TestDriveTokenCache(t *testing.T) { drive := newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() for i := 0; i < 5; i++ { if err := storage.Put(ctx, fmt.Sprintf("sessions/s%d", i), nil); err != nil { t.Fatalf("Put: %v", err) } } if drive.tokens != 1 { t.Fatalf("token endpoint hit %d times, want 1", drive.tokens) } } func TestDriveTransport(t *testing.T) { newFakeDrive(t) client, server, cleanup := pairWith(t, driveSettings()) defer cleanup() if _, err := client.Write([]byte("ping")); err != nil { t.Fatalf("client write: %v", err) } expectRead(t, server, "ping") if _, err := server.Write([]byte("pong")); err != nil { t.Fatalf("server write: %v", err) } expectRead(t, client, "pong") } func TestDriveLargeTransfer(t *testing.T) { newFakeDrive(t) client, server, cleanup := pairWith(t, driveSettings()) defer cleanup() payload := make([]byte, 300000) for i := range payload { payload[i] = byte(i % 251) } go func() { client.Write(payload) }() if err := server.SetReadDeadline(time.Now().Add(60 * time.Second)); err != nil { t.Fatalf("SetReadDeadline: %v", err) } got := make([]byte, len(payload)) if _, err := io.ReadFull(server, got); err != nil { t.Fatalf("ReadFull: %v", err) } for i := range got { if got[i] != payload[i] { t.Fatalf("payload mismatch at byte %d", i) } } } func TestRateLimited(t *testing.T) { limited := []string{ `{"error":{"code":403,"errors":[{"reason":"userRateLimitExceeded"}]}}`, `{"error":{"code":403,"errors":[{"reason":"rateLimitExceeded"}]}}`, `{"error":{"code":403,"errors":[{"reason":"sharingRateLimitExceeded"}]}}`, `{"error":{"status":"RESOURCE_EXHAUSTED"}}`, } for _, payload := range limited { if !rateLimited([]byte(payload)) { t.Fatalf("rateLimited missed %s", payload) } } permanent := []string{ `{"error":{"code":403,"errors":[{"reason":"insufficientFilePermissions"}]}}`, `{"error":{"code":403,"errors":[{"reason":"storageQuotaExceeded"}]}}`, `not json at all`, } for _, payload := range permanent { if rateLimited([]byte(payload)) { t.Fatalf("rateLimited treated %s as temporary", payload) } } } func TestDriveRetryRateLimit(t *testing.T) { drive := newFakeDrive(t) storage := newDriveBackend(t) drive.failOnceWith("upload", http.StatusForbidden, `{"error":{"code":403,"errors":[{"reason":"userRateLimitExceeded"}]}}`) if err := storage.Put(context.Background(), "sessions/abc", nil); err != nil { t.Fatalf("Put did not survive a 403: %v", err) } } func TestDriveSharedClient(t *testing.T) { newFakeDrive(t) first, err := newStorage(driveSettings()) if err != nil { t.Fatalf("newStorage: %v", err) } second, err := newStorage(driveSettings()) if err != nil { t.Fatalf("newStorage: %v", err) } if first != second { t.Fatal("same settings did not share one storage") } } func TestDriveInlineListing(t *testing.T) { drive := newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() payload := []byte("small enough to ride along with the listing") if err := storage.Put(ctx, "streams/abc/c2s/000000000.seg", payload); err != nil { t.Fatalf("Put: %v", err) } entries, err := storage.List(ctx, "streams/abc/c2s") if err != nil { t.Fatalf("List: %v", err) } if len(entries) != 1 { t.Fatalf("List returned %v, want 1 entry", entries) } if string(entries[0].Inline) != string(payload) { t.Fatalf("listing carried %q, want %q", entries[0].Inline, payload) } d := drive d.mu.Lock() var stored *fakeFile for _, f := range d.files { stored = f } d.mu.Unlock() if len(stored.data) != 0 { t.Fatal("small payload was uploaded as content") } } func TestDriveLargeContent(t *testing.T) { drive := newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() payload := make([]byte, driveInlineLimit+1) for i := range payload { payload[i] = byte(i) } if err := storage.Put(ctx, "streams/abc/c2s/000000000.seg", payload); err != nil { t.Fatalf("Put: %v", err) } entries, err := storage.List(ctx, "streams/abc/c2s") if err != nil { t.Fatalf("List: %v", err) } if len(entries) != 1 || entries[0].Inline != nil { t.Fatalf("large payload was inlined, got %v", entries) } got, err := storage.Get(ctx, "streams/abc/c2s/000000000.seg") if err != nil { t.Fatalf("Get: %v", err) } if len(got) != len(payload) { t.Fatalf("Get returned %d bytes, want %d", len(got), len(payload)) } _ = drive } func TestDriveInlineGet(t *testing.T) { newFakeDrive(t) storage := newDriveBackend(t) ctx := context.Background() payload := []byte("only in the description") if err := storage.Put(ctx, "sessions/abc", payload); err != nil { t.Fatalf("Put: %v", err) } got, err := storage.Get(ctx, "sessions/abc") if err != nil { t.Fatalf("Get: %v", err) } if string(got) != string(payload) { t.Fatalf("Get returned %q, want %q", got, payload) } } func TestDriveFronting(t *testing.T) { drive := newFakeDrive(t) fake, err := url.Parse(drive.server.URL) if err != nil { t.Fatalf("parse: %v", err) } port, err := net.PortFromString(fake.Port()) if err != nil { t.Fatalf("port: %v", err) } driveTokenURL = "http://www.googleapis.com/token" driveFilesURL = "http://www.googleapis.com/files" driveUploadURL = "http://www.googleapis.com/upload" settings := driveSettings() settings.Destination = &net.Destination{ Address: net.ParseAddress(fake.Hostname()), Port: port, Network: net.Network_TCP, } storage, err := newDriveStorage(settings, settings.ProtocolSettings.(*Config)) if err != nil { t.Fatalf("newDriveStorage: %v", err) } if err := storage.Put(context.Background(), "sessions/fronted", []byte("x")); err != nil { t.Fatalf("Put: %v", err) } if drive.count() != 1 { t.Fatalf("fake drive holds %d files, want 1", drive.count()) } for _, host := range drive.seenHosts() { if host != "www.googleapis.com" { t.Fatalf("inner host was %q, want www.googleapis.com regardless of address", host) } } }