diff options
| author | Alain Schneble | 2015-12-26 00:50:25 +0100 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2015-12-26 14:53:08 +0100 |
| commit | 8dea6fe5b5bc2936b046e799ea61afc508e28752 (patch) | |
| tree | c5c5651b6b5be2eba2001417c656ff39f79165af | |
| parent | 4021027db72629b66c543be0f0e249ab3d6f3b00 (diff) | |
| download | emacs-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.el | 84 | ||||
| -rw-r--r-- | lisp/url/url-parse.el | 5 | ||||
| -rw-r--r-- | test/lisp/url/url-expand-tests.el | 105 |
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 | ||