Subject: bug#76982: 30.1; url-generic-parse-url cannot parse Windows file paths

From: Sebastián Monía <[email-masked]>
Date: Mon, 07 Apr 2025 00:18:57 -0400

Hello,

Attached a new patch, with some tests and updated changelog.

Eli Zaretskii <[email-masked]> writes:
> Ping!  Sebastián, would you like to submit an updated patch which
> addresses my comments?
>
>> > @@ -210,6 +215,13 @@ url-generic-parse-url
>> >  
>> >            (if (and host (string-match "%[0-9][0-9]" host))
>> >                (setq host (url-unhex-string host)))
>> > +
>> > +          ;; For file:// URIs, if the path "looks like" Windows/DOS, it
>> > +          ;; makes sense to strip the leading slash (bug#76982)
>> > +          (when (and (string= "file" scheme)
>> > +                     (string-match "^/[A-Za-z]:[/\\]" file))
>> > +            (setq file (substring file 1)))
>> > +
>> >            (url-parse-make-urlobj scheme user pass host port file
>> >  				 fragment nil full))))))
>> 
>> Isn't it cleaner to go one more character forward here:
>> 
>>           ;; 3.2. Authority
>>           (when (looking-at "//")
>>             (setq full t)
>>             (forward-char 2)  <<<<<<<<<<<<<<<<<<<<
>>             (setq save-pos (point))
>>             (skip-chars-forward "^/?#")
>> 
>> if the conditions for Windows-style absolute file name are fulfilled?

The URI could potentially have components before we get to the filename,
so the look head for "path starts with" isn't as clean.
In this patch I still do that check in the same place as in the
original.

Thank you,
Seb

>From 116aeca8bcb1e687b5d09481f7bc6a6dd15535fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebasti=C3=A1n=20Mon=C3=ADa?= <[email-masked]>
Date: Mon, 7 Apr 2025 00:08:08 -0400
Subject: [PATCH] Special treatment for file URIs that look like Windows paths

* lisp/url/url-parse.el (url-generic-parse-url): remove prefix /
when a file URI's filename starts with a single letter followed
by a colon and a slash or backslash.
(url-recreate-url): mirror the change applied when parsing, so
the URL is recreated properly.
---
 lisp/url/url-parse.el            | 14 +++++++++++++-
 test/lisp/url/url-parse-tests.el | 11 +++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/lisp/url/url-parse.el b/lisp/url/url-parse.el
index 4c65721b83d..1dff66c6228 100644
--- a/lisp/url/url-parse.el
+++ b/lisp/url/url-parse.el
@@ -83,7 +83,12 @@ url-recreate-url
 	 ;; port is empty or if its value would be the same as that of
 	 ;; the scheme's default."
 	 (port (url-port-if-non-default urlobj))
-	 (file (url-filename urlobj))
+         ;; For Windows/DOS-like paths, `url-generic-parse-url' strips
+         ;; the leading /, so we need to add it back (bug#76982)
+	 (file (if (and (string= "file" type)
+                        (string-match "^[A-Za-z]:[/\\]" (url-filename urlobj)))
+                   (concat "/" (url-filename urlobj))
+                 (url-filename urlobj)))
 	 (frag (url-target urlobj)))
     (concat (if type (concat type ":"))
 	    (if (url-fullness urlobj) "//")
@@ -210,6 +215,13 @@ url-generic-parse-url
 
           (if (and host (string-match "%[0-9][0-9]" host))
               (setq host (url-unhex-string host)))
+
+          ;; For file:// URIs, if the path "looks like" Windows/DOS, it
+          ;; makes sense to strip the leading slash (bug#76982)
+          (when (and (string= "file" scheme)
+                     (string-match "^/[A-Za-z]:[/\\]" file))
+            (setq file (substring file 1)))
+
           (url-parse-make-urlobj scheme user pass host port file
 				 fragment nil full))))))
 
diff --git a/test/lisp/url/url-parse-tests.el b/test/lisp/url/url-parse-tests.el
index e70c1eef32f..8039c422df8 100644
--- a/test/lisp/url/url-parse-tests.el
+++ b/test/lisp/url/url-parse-tests.el
@@ -166,6 +166,17 @@ url-generic-parse-url/multibyte-host-and-path
                  (url-parse-make-urlobj "http" nil nil "банки.рф" nil
                                         "/фыва/" nil nil t))))
 
+(ert-deftest url-generic-parse-url/ms-windows-file-uri-hanlding ()
+  "bug#76982  Make an exception if a URI refers to a filename and it \"looks like\" a Windows path: strip the leading /"
+  (should (equal (url-generic-parse-url "file:///c:/windows-path") (url-parse-make-urlobj "file" nil nil "" nil "c:/windows-path" nil nil t)))
+  (should (equal (url-filename (url-generic-parse-url "file:///c:/directory/file.txt")) "c:/directory/file.txt"))
+  (should (equal (url-recreate-url (url-parse-make-urlobj "file" nil nil "" nil "c:/directory/file.txt" nil nil t)) "file:///c:/directory/file.txt"))
+  ;; https://www.rfc-editor.org/rfc/rfc8089.html#appendix-E.2
+  (should (equal (url-generic-parse-url "file:c:/path/to/file") (url-parse-make-urlobj "file" nil nil nil nil "c:/path/to/file" nil nil nil)))
+  (should (equal (url-recreate-url (url-parse-make-urlobj "file" nil nil nil nil "c:/path/to/file" nil nil nil)) "file:c:/path/to/file"))
+  ;; accept backslashes too
+  (should (equal (url-filename (url-generic-parse-url "file:///c:\\directory\\file.txt")) "c:\\directory\\file.txt")))
+
 (provide 'url-parse-tests)
 
 ;;; url-parse-tests.el ends here
-- 
2.49.0

-- 
Sebastián Monía
https://site.sebasmonia.com/