Jelajahi Sumber

fix(db): handle path names that include URL special chars (fixes #10245) (#10247)

😬
Jakob Borg 2 bulan lalu
induk
melakukan
370bbb8f26
2 mengubah file dengan 61 tambahan dan 1 penghapusan
  1. 17 1
      internal/db/sqlite/basedb.go
  2. 44 0
      internal/db/sqlite/db_test.go

+ 17 - 1
internal/db/sqlite/basedb.go

@@ -10,6 +10,7 @@ import (
 	"database/sql"
 	"embed"
 	"io/fs"
+	"net/url"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -44,7 +45,12 @@ type baseDB struct {
 func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScripts []string) (*baseDB, error) {
 	// Open the database with options to enable foreign keys and recursive
 	// triggers (needed for the delete+insert triggers on row replace).
-	sqlDB, err := sqlx.Open(dbDriver, "file:"+path+"?"+commonOptions)
+	pathURL := url.URL{
+		Scheme:   "file",
+		Path:     fileToUriPath(path),
+		RawQuery: commonOptions,
+	}
+	sqlDB, err := sqlx.Open(dbDriver, pathURL.String())
 	if err != nil {
 		return nil, wrap(err)
 	}
@@ -110,6 +116,16 @@ func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScript
 	return db, nil
 }
 
+func fileToUriPath(path string) string {
+	path = filepath.ToSlash(path)
+	if (build.IsWindows && len(path) >= 2 && path[1] == ':') ||
+		(strings.HasPrefix(path, "//") && !strings.HasPrefix(path, "///")) {
+		// Add an extra leading slash for Windows drive letter or UNC path
+		path = "/" + path
+	}
+	return path
+}
+
 func (s *baseDB) Close() error {
 	s.updateLock.Lock()
 	s.statementsMut.Lock()

+ 44 - 0
internal/db/sqlite/db_test.go

@@ -12,6 +12,8 @@ import (
 	"encoding/binary"
 	"errors"
 	"iter"
+	"os"
+	"path"
 	"path/filepath"
 	"sync"
 	"testing"
@@ -20,6 +22,7 @@ import (
 	"github.com/syncthing/syncthing/internal/db"
 	"github.com/syncthing/syncthing/internal/itererr"
 	"github.com/syncthing/syncthing/internal/timeutil"
+	"github.com/syncthing/syncthing/lib/build"
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/protocol"
 )
@@ -1157,6 +1160,47 @@ func TestStrangeDeletedGlobalBug(t *testing.T) {
 	}
 }
 
+func TestOpenSpecialName(t *testing.T) {
+	dir := t.TempDir()
+
+	// Create a "base" dir that is in the way if the path becomes
+	// incorrectly truncated in the next steps.
+	base := path.Join(dir, "test")
+	if err := os.Mkdir(base, 0o755); err != nil {
+		t.Fatal(err)
+	}
+
+	// Should be able to open a path with a hash sign in it.
+	p1 := base + "#foo"
+	db, err := Open(p1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(db.path)
+	db.Close()
+
+	if !build.IsWindows {
+		// Should be able to open a path with something that looks like
+		// query params.
+		p2 := base + "?foo=bar"
+		db, err = Open(p2)
+		if err != nil {
+			t.Fatal(err)
+		}
+		t.Log(db.path)
+		db.Close()
+	}
+
+	// Better not a have problem with a single ampersand either.
+	p2 := base + "&foo"
+	db, err = Open(p2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(db.path)
+	db.Close()
+}
+
 func mustCollect[T any](t *testing.T) func(it iter.Seq[T], errFn func() error) []T {
 	t.Helper()
 	return func(it iter.Seq[T], errFn func() error) []T {