aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlain Schneble2015-12-26 00:50:25 +0100
committerLars Ingebrigtsen2015-12-26 14:53:08 +0100
commit8dea6fe5b5bc2936b046e799ea61afc508e28752 (patch)
treec5c5651b6b5be2eba2001417c656ff39f79165af
parent4021027db72629b66c543be0f0e249ab3d6f3b00 (diff)
downloademacs-8dea6fe5b5bc2936b046e799ea61afc508e28752.tar.gz
emacs-8dea6fe5b5bc2936b046e799ea61afc508e28752.zip
Make relative URL parsing and resolution consistent with RFC 3986 (bug#22044)
* test/lisp/url/url-parse-tests.el: Add tests covering url-generic-parse-url. * test/lisp/url/url-expand-tests.el: Add tests covering url-expand-file-name. * lisp/url/url-parse.el (url-generic-parse-url): Keep empty fragment information in URL-struct. * lisp/url/url-parse.el (url-path-and-query): Do not artificially turn empty path and query into nil path and query, respectively. * lisp/url/url-expand.el (url-expander-remove-relative-links): Do not turn empty path into an absolute ("/") path. * lisp/url/url-expand.el (url-expand-file-name): Properly resolve fragment-only URIs. Do not just return them unchanged. * lisp/url/url-expand.el (url-default-expander): An empty path in the relative reference URI should not drop the last segment. Backport: (cherry picked from commit b792ecea1715e080ad8e232d3d154b8a25d2edfb)
-rw-r--r--lisp/url/url-expand.el84
-rw-r--r--lisp/url/url-parse.el5
-rw-r--r--test/lisp/url/url-expand-tests.el105
3 files changed, 146 insertions, 48 deletions
diff --git a/lisp/url/url-expand.el b/lisp/url/url-expand.el
index c468a7952ec..600a36dc73d 100644
--- a/lisp/url/url-expand.el
+++ b/lisp/url/url-expand.el
@@ -26,32 +26,35 @@
26(require 'url-parse) 26(require 'url-parse)
27 27
28(defun url-expander-remove-relative-links (name) 28(defun url-expander-remove-relative-links (name)
29 ;; Strip . and .. from pathnames 29 (if (equal name "")
30 (let ((new (if (not (string-match "^/" name)) 30 ;; An empty name is a properly valid relative URL reference/path.
31 (concat "/" name) 31 ""
32 name))) 32 ;; Strip . and .. from pathnames
33 33 (let ((new (if (not (string-match "^/" name))
34 ;; If it ends with a '/.' or '/..', tack on a trailing '/' sot hat 34 (concat "/" name)
35 ;; the tests that follow are not too complicated in terms of 35 name)))
36 ;; looking for '..' or '../', etc. 36
37 (if (string-match "/\\.+$" new) 37 ;; If it ends with a '/.' or '/..', tack on a trailing '/' sot hat
38 (setq new (concat new "/"))) 38 ;; the tests that follow are not too complicated in terms of
39 39 ;; looking for '..' or '../', etc.
40 ;; Remove '/./' first 40 (if (string-match "/\\.+$" new)
41 (while (string-match "/\\(\\./\\)" new) 41 (setq new (concat new "/")))
42 (setq new (concat (substring new 0 (match-beginning 1)) 42
43 (substring new (match-end 1))))) 43 ;; Remove '/./' first
44 44 (while (string-match "/\\(\\./\\)" new)
45 ;; Then remove '/../' 45 (setq new (concat (substring new 0 (match-beginning 1))
46 (while (string-match "/\\([^/]*/\\.\\./\\)" new) 46 (substring new (match-end 1)))))
47 (setq new (concat (substring new 0 (match-beginning 1)) 47
48 (substring new (match-end 1))))) 48 ;; Then remove '/../'
49 49 (while (string-match "/\\([^/]*/\\.\\./\\)" new)
50 ;; Remove cruft at the beginning of the string, so people that put 50 (setq new (concat (substring new 0 (match-beginning 1))
51 ;; in extraneous '..' because they are morons won't lose. 51 (substring new (match-end 1)))))
52 (while (string-match "^/\\.\\.\\(/\\)" new) 52
53 (setq new (substring new (match-beginning 1) nil))) 53 ;; Remove cruft at the beginning of the string, so people that put
54 new)) 54 ;; in extraneous '..' because they are morons won't lose.
55 (while (string-match "^/\\.\\.\\(/\\)" new)
56 (setq new (substring new (match-beginning 1) nil)))
57 new)))
55 58
56(defun url-expand-file-name (url &optional default) 59(defun url-expand-file-name (url &optional default)
57 "Convert URL to a fully specified URL, and canonicalize it. 60 "Convert URL to a fully specified URL, and canonicalize it.
@@ -89,8 +92,6 @@ path components followed by `..' are removed, along with the `..' itself."
89 (cond 92 (cond
90 ((= (length url) 0) ; nil or empty string 93 ((= (length url) 0) ; nil or empty string
91 (url-recreate-url default)) 94 (url-recreate-url default))
92 ((string-match "^#" url) ; Offset link, use it raw
93 url)
94 ((string-match url-nonrelative-link url) ; Fully-qualified URL, return it immediately 95 ((string-match url-nonrelative-link url) ; Fully-qualified URL, return it immediately
95 url) 96 url)
96 (t 97 (t
@@ -120,29 +121,24 @@ path components followed by `..' are removed, along with the `..' itself."
120 (setf (url-host urlobj) (or (url-host urlobj) (url-host defobj)))) 121 (setf (url-host urlobj) (or (url-host urlobj) (url-host defobj))))
121 (if (string= "ftp" (url-type urlobj)) 122 (if (string= "ftp" (url-type urlobj))
122 (setf (url-user urlobj) (or (url-user urlobj) (url-user defobj)))) 123 (setf (url-user urlobj) (or (url-user urlobj) (url-user defobj))))
123 (if (string= (url-filename urlobj) "")
124 (setf (url-filename urlobj) "/"))
125 ;; If the object we're expanding from is full, then we are now 124 ;; If the object we're expanding from is full, then we are now
126 ;; full. 125 ;; full.
127 (unless (url-fullness urlobj) 126 (unless (url-fullness urlobj)
128 (setf (url-fullness urlobj) (url-fullness defobj))) 127 (setf (url-fullness urlobj) (url-fullness defobj)))
129 (if (string-match "^/" (url-filename urlobj)) 128 (let* ((pathandquery (url-path-and-query urlobj))
130 nil 129 (defpathandquery (url-path-and-query defobj))
131 (let ((query nil) 130 (file (car pathandquery))
132 (file nil) 131 (query (or (cdr pathandquery) (and (equal file "") (cdr defpathandquery)))))
133 (sepchar nil)) 132 (if (string-match "^/" (url-filename urlobj))
134 (if (string-match "[?#]" (url-filename urlobj)) 133 (setq file (url-expander-remove-relative-links file))
135 (setq query (substring (url-filename urlobj) (match-end 0))
136 file (substring (url-filename urlobj) 0 (match-beginning 0))
137 sepchar (substring (url-filename urlobj) (match-beginning 0) (match-end 0)))
138 (setq file (url-filename urlobj)))
139 ;; We use concat rather than expand-file-name to combine 134 ;; We use concat rather than expand-file-name to combine
140 ;; directory and file name, since urls do not follow the same 135 ;; directory and file name, since urls do not follow the same
141 ;; rules as local files on all platforms. 136 ;; rules as local files on all platforms.
142 (setq file (url-expander-remove-relative-links 137 (setq file (url-expander-remove-relative-links
143 (concat (url-file-directory (url-filename defobj)) file))) 138 (if (equal file "")
144 (setf (url-filename urlobj) 139 (or (car (url-path-and-query defobj)) "")
145 (if query (concat file sepchar query) file)))))) 140 (concat (url-file-directory (url-filename defobj)) file)))))
141 (setf (url-filename urlobj) (if query (concat file "?" query) file)))))
146 142
147(provide 'url-expand) 143(provide 'url-expand)
148 144
diff --git a/lisp/url/url-parse.el b/lisp/url/url-parse.el
index dbf0c386871..c3159a7e103 100644
--- a/lisp/url/url-parse.el
+++ b/lisp/url/url-parse.el
@@ -59,8 +59,6 @@ where each of PATH and QUERY are strings or nil."
59 (setq path (substring name 0 (match-beginning 0)) 59 (setq path (substring name 0 (match-beginning 0))
60 query (substring name (match-end 0))) 60 query (substring name (match-end 0)))
61 (setq path name))) 61 (setq path name)))
62 (if (equal path "") (setq path nil))
63 (if (equal query "") (setq query nil))
64 (cons path query))) 62 (cons path query)))
65 63
66(defun url-port-if-non-default (urlobj) 64(defun url-port-if-non-default (urlobj)
@@ -217,8 +215,7 @@ parses to
217 (when (looking-at "#") 215 (when (looking-at "#")
218 (let ((opoint (point))) 216 (let ((opoint (point)))
219 (forward-char 1) 217 (forward-char 1)
220 (unless (eobp) 218 (setq fragment (buffer-substring (point) (point-max)))
221 (setq fragment (buffer-substring (point) (point-max))))
222 (delete-region opoint (point-max))))) 219 (delete-region opoint (point-max)))))
223 220
224 (if (and host (string-match "%[0-9][0-9]" host)) 221 (if (and host (string-match "%[0-9][0-9]" host))
diff --git a/test/lisp/url/url-expand-tests.el b/test/lisp/url/url-expand-tests.el
new file mode 100644
index 00000000000..2bd28687f8d
--- /dev/null
+++ b/test/lisp/url/url-expand-tests.el
@@ -0,0 +1,105 @@
1;;; url-expand-tests.el --- Test suite for relative URI/URL resolution.
2
3;; Copyright (C) 2012-2015 Free Software Foundation, Inc.
4
5;; Author: Alain Schneble <a.s@realize.ch>
6;; Version: 1.0
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; Test cases covering URI reference resolution as described in RFC3986,
26;; section 5. Reference Resolution and especially the relative resolution
27;; rules specified in section 5.2. Relative Resolution.
28
29;; Each test calls `url-expand-file-name', typically with a relative
30;; reference URI and a base URI as string and compares the result (Actual)
31;; against a manually specified URI (Expected)
32
33;;; Code:
34
35(require 'url-expand)
36(require 'ert)
37
38(ert-deftest url-expand-file-name/relative-resolution-normal-examples ()
39 "RFC 3986, Section 5.4 Reference Resolution Examples / Section 5.4.1. Normal Examples"
40 (should (equal (url-expand-file-name "g:h" "http://a/b/c/d;p?q") "g:h"))
41 (should (equal (url-expand-file-name "g" "http://a/b/c/d;p?q") "http://a/b/c/g"))
42 (should (equal (url-expand-file-name "./g" "http://a/b/c/d;p?q") "http://a/b/c/g"))
43 (should (equal (url-expand-file-name "g/" "http://a/b/c/d;p?q") "http://a/b/c/g/"))
44 (should (equal (url-expand-file-name "/g" "http://a/b/c/d;p?q") "http://a/g"))
45 (should (equal (url-expand-file-name "//g" "http://a/b/c/d;p?q") "http://g"))
46 (should (equal (url-expand-file-name "?y" "http://a/b/c/d;p?q") "http://a/b/c/d;p?y"))
47 (should (equal (url-expand-file-name "g?y" "http://a/b/c/d;p?q") "http://a/b/c/g?y"))
48 (should (equal (url-expand-file-name "#s" "http://a/b/c/d;p?q") "http://a/b/c/d;p?q#s"))
49 (should (equal (url-expand-file-name "g#s" "http://a/b/c/d;p?q") "http://a/b/c/g#s"))
50 (should (equal (url-expand-file-name "g?y#s" "http://a/b/c/d;p?q") "http://a/b/c/g?y#s"))
51 (should (equal (url-expand-file-name ";x" "http://a/b/c/d;p?q") "http://a/b/c/;x"))
52 (should (equal (url-expand-file-name "g;x" "http://a/b/c/d;p?q") "http://a/b/c/g;x"))
53 (should (equal (url-expand-file-name "g;x?y#s" "http://a/b/c/d;p?q") "http://a/b/c/g;x?y#s"))
54 (should (equal (url-expand-file-name "" "http://a/b/c/d;p?q") "http://a/b/c/d;p?q"))
55 (should (equal (url-expand-file-name "." "http://a/b/c/d;p?q") "http://a/b/c/"))
56 (should (equal (url-expand-file-name "./" "http://a/b/c/d;p?q") "http://a/b/c/"))
57 (should (equal (url-expand-file-name ".." "http://a/b/c/d;p?q") "http://a/b/"))
58 (should (equal (url-expand-file-name "../" "http://a/b/c/d;p?q") "http://a/b/"))
59 (should (equal (url-expand-file-name "../g" "http://a/b/c/d;p?q") "http://a/b/g"))
60 (should (equal (url-expand-file-name "../.." "http://a/b/c/d;p?q") "http://a/"))
61 (should (equal (url-expand-file-name "../../" "http://a/b/c/d;p?q") "http://a/"))
62 (should (equal (url-expand-file-name "../../g" "http://a/b/c/d;p?q") "http://a/g")))
63
64(ert-deftest url-expand-file-name/relative-resolution-absolute-examples ()
65 "RFC 3986, Section 5.4 Reference Resolution Examples / Section 5.4.2. Abnormal Examples"
66 (should (equal (url-expand-file-name "../../../g" "http://a/b/c/d;p?q") "http://a/g"))
67 (should (equal (url-expand-file-name "../../../../g" "http://a/b/c/d;p?q") "http://a/g"))
68
69 (should (equal (url-expand-file-name "/./g" "http://a/b/c/d;p?q") "http://a/g"))
70 (should (equal (url-expand-file-name "/../g" "http://a/b/c/d;p?q") "http://a/g"))
71 (should (equal (url-expand-file-name "g." "http://a/b/c/d;p?q") "http://a/b/c/g."))
72 (should (equal (url-expand-file-name ".g" "http://a/b/c/d;p?q") "http://a/b/c/.g"))
73 (should (equal (url-expand-file-name "g.." "http://a/b/c/d;p?q") "http://a/b/c/g.."))
74 (should (equal (url-expand-file-name "..g" "http://a/b/c/d;p?q") "http://a/b/c/..g"))
75
76 (should (equal (url-expand-file-name "./../g" "http://a/b/c/d;p?q") "http://a/b/g"))
77 (should (equal (url-expand-file-name "./g/." "http://a/b/c/d;p?q") "http://a/b/c/g/"))
78 (should (equal (url-expand-file-name "g/./h" "http://a/b/c/d;p?q") "http://a/b/c/g/h"))
79 (should (equal (url-expand-file-name "g/../h" "http://a/b/c/d;p?q") "http://a/b/c/h"))
80 (should (equal (url-expand-file-name "g;x=1/./y" "http://a/b/c/d;p?q") "http://a/b/c/g;x=1/y"))
81 (should (equal (url-expand-file-name "g;x=1/../y" "http://a/b/c/d;p?q") "http://a/b/c/y"))
82
83 (should (equal (url-expand-file-name "g?y/./x" "http://a/b/c/d;p?q") "http://a/b/c/g?y/./x"))
84 (should (equal (url-expand-file-name "g?y/../x" "http://a/b/c/d;p?q") "http://a/b/c/g?y/../x"))
85 (should (equal (url-expand-file-name "g#s/./x" "http://a/b/c/d;p?q") "http://a/b/c/g#s/./x"))
86 (should (equal (url-expand-file-name "g#s/../x" "http://a/b/c/d;p?q") "http://a/b/c/g#s/../x"))
87
88 (should (equal (url-expand-file-name "http:g" "http://a/b/c/d;p?q") "http:g")) ; for strict parsers
89 )
90
91(ert-deftest url-expand-file-name/relative-resolution-additional-examples ()
92 "Reference Resolution Examples / Arbitrary Examples"
93 (should (equal (url-expand-file-name "" "http://host/foobar") "http://host/foobar"))
94 (should (equal (url-expand-file-name "?y" "http://a/b/c/d") "http://a/b/c/d?y"))
95 (should (equal (url-expand-file-name "?y" "http://a/b/c/d/") "http://a/b/c/d/?y"))
96 (should (equal (url-expand-file-name "?y#fragment" "http://a/b/c/d;p?q") "http://a/b/c/d;p?y#fragment"))
97 (should (equal (url-expand-file-name "#bar" "http://host") "http://host#bar"))
98 (should (equal (url-expand-file-name "#bar" "http://host/") "http://host/#bar"))
99 (should (equal (url-expand-file-name "#bar" "http://host/foo") "http://host/foo#bar"))
100 (should (equal (url-expand-file-name "foo#bar" "http://host/foobar") "http://host/foo#bar"))
101 (should (equal (url-expand-file-name "foo#bar" "http://host/foobar/") "http://host/foobar/foo#bar")))
102
103(provide 'url-expand-tests)
104
105;;; url-expand-tests.el ends here