From 0f97832282080a0c36a08d6da303671f5636db1f Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 11 Jan 2023 14:59:07 +0100 Subject: [PATCH 1/3] fix: URL path encoding on Windows: `C:` -> `c%3A` - Drive is lower-cased: `C:` -> `c:` Ref: Microsoft/vscode#68325 Signed-off-by: Akos Kitta --- uri.go | 49 ++++++++++++++++++++++++++++++++++++++++++++----- uri_test.go | 6 ++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/uri.go b/uri.go index 7deaad1..8a45761 100644 --- a/uri.go +++ b/uri.go @@ -11,6 +11,8 @@ import ( "net/url" "path/filepath" "regexp" + "strings" + "unicode" "github.com/arduino/go-paths-helper" "go.bug.st/json" @@ -29,7 +31,11 @@ type DocumentURI struct { // NilURI is the empty DocumentURI var NilURI = DocumentURI{} -var expDriveID = regexp.MustCompile("^/[a-zA-Z]:") +// for example, `"/c:"` or `"/A:"` +var expDriveWithLeadingSlashID = regexp.MustCompile("^/[a-zA-Z]:") + +// for example, `"C:"` or `"A:"` +var expUppercaseDriveID = regexp.MustCompile("^[A-Z]:") // AsPath convert the DocumentURI to a paths.Path func (uri DocumentURI) AsPath() *paths.Path { @@ -39,12 +45,26 @@ func (uri DocumentURI) AsPath() *paths.Path { // unbox convert the DocumentURI to a file path string func (uri DocumentURI) unbox() string { path := uri.url.Path - if expDriveID.MatchString(path) { + if expDriveWithLeadingSlashID.MatchString(path) { return path[1:] } return path } +// Converts `"C:"` to `"c:"` to be compatible with VS Code URI's drive letter casing +// https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 +func lowercaseDriveSegment(pathSegment string) string { + if len(pathSegment) == 0 { + return pathSegment + } + if expUppercaseDriveID.MatchString(pathSegment) { + chars := []rune(pathSegment) + chars[0] = unicode.ToLower(chars[0]) + return string(chars) + } + return pathSegment +} + func (uri DocumentURI) String() string { return uri.url.String() } @@ -68,11 +88,30 @@ func NewDocumentURI(path string) DocumentURI { if len(path) == 0 || path[0] != '/' { path = "/" + path } - uri, err := NewDocumentURIFromURL("file://") + segments := strings.Split(path, "/") + encodedSegments := make([]string, len(segments)) + for i, segment := range segments { + if len(segment) == 0 { + encodedSegments[i] = segment + } else { + segment = lowercaseDriveSegment(segment) + chars := strings.SplitAfter(segment, "") + for i, c := range chars { + // Spaces must be turned into `%20`. Otherwise, `url.QueryEscape`` encodes them to `+`. + if c == " " { + chars[i] = "%20" + } else { + chars[i] = url.QueryEscape(c) + } + } + encodedSegments[i] = strings.Join(chars, "") + } + } + urlPath := strings.Join(encodedSegments, "/") + uri, err := NewDocumentURIFromURL("file://" + urlPath) if err != nil { panic(err) } - uri.url.Path = path return uri } @@ -89,7 +128,7 @@ func NewDocumentURIFromURL(inURL string) (DocumentURI, error) { func (uri *DocumentURI) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { - return fmt.Errorf("expoected JSON string for DocumentURI: %s", err) + return fmt.Errorf("expected JSON string for DocumentURI: %s", err) } newDocURI, err := NewDocumentURIFromURL(s) diff --git a/uri_test.go b/uri_test.go index dd4f537..5503d96 100644 --- a/uri_test.go +++ b/uri_test.go @@ -48,7 +48,9 @@ func TestPathToUri(t *testing.T) { toSlash = windowsToSlash // Emulate windows cases d = NewDocumentURI("C:\\Users\\test\\Sketch.ino") - require.Equal(t, "file:///C:/Users/test/Sketch.ino", d.String()) + require.Equal(t, "file:///c%3A/Users/test/Sketch.ino", d.String()) // driver letter is converted to lower case https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 + d = NewDocumentURI("c:\\Users\\test\\Sketch.ino") + require.Equal(t, "file:///c%3A/Users/test/Sketch.ino", d.String()) d = NewDocumentURI("/Users/test/Sketch.ino") require.Equal(t, "file:///Users/test/Sketch.ino", d.String()) d = NewDocumentURI("\U0001F61B") @@ -70,7 +72,7 @@ func TestJSONMarshalUnmarshal(t *testing.T) { d = NewDocumentURI("C:\\Users\\test\\Sketch.ino") data, err := json.Marshal(d) require.NoError(t, err) - require.Equal(t, `"file:///C:/Users/test/Sketch.ino"`, string(data)) + require.Equal(t, `"file:///c%3A/Users/test/Sketch.ino"`, string(data)) d = NewDocumentURI("/Users/test/Sketch.ino") data, err = json.Marshal(d) From 68cc092ec6a361bed45f8c3afb6f18080431d95a Mon Sep 17 00:00:00 2001 From: Akos Kitta <1405703+kittaakos@users.noreply.github.com> Date: Wed, 11 Jan 2023 15:40:10 +0100 Subject: [PATCH 2/3] Update uri.go Co-authored-by: Cristian Maglie --- uri.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/uri.go b/uri.go index 8a45761..b2d24ff 100644 --- a/uri.go +++ b/uri.go @@ -54,9 +54,6 @@ func (uri DocumentURI) unbox() string { // Converts `"C:"` to `"c:"` to be compatible with VS Code URI's drive letter casing // https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 func lowercaseDriveSegment(pathSegment string) string { - if len(pathSegment) == 0 { - return pathSegment - } if expUppercaseDriveID.MatchString(pathSegment) { chars := []rune(pathSegment) chars[0] = unicode.ToLower(chars[0]) From ce73b88d217bd3a3da785638b34f3b680b635023 Mon Sep 17 00:00:00 2001 From: Akos Kitta <1405703+kittaakos@users.noreply.github.com> Date: Wed, 11 Jan 2023 15:43:03 +0100 Subject: [PATCH 3/3] Update uri.go Co-authored-by: Cristian Maglie --- uri.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/uri.go b/uri.go index b2d24ff..d88ed5e 100644 --- a/uri.go +++ b/uri.go @@ -92,16 +92,9 @@ func NewDocumentURI(path string) DocumentURI { encodedSegments[i] = segment } else { segment = lowercaseDriveSegment(segment) - chars := strings.SplitAfter(segment, "") - for i, c := range chars { - // Spaces must be turned into `%20`. Otherwise, `url.QueryEscape`` encodes them to `+`. - if c == " " { - chars[i] = "%20" - } else { - chars[i] = url.QueryEscape(c) - } - } - encodedSegments[i] = strings.Join(chars, "") + segment = url.QueryEscape(segment) + // Spaces must be turned into `%20`. Otherwise, `url.QueryEscape`` encodes them to `+`. + encodedSegments[i] = strings.ReplaceAll(segment, "+", "%20") } } urlPath := strings.Join(encodedSegments, "/")