diff options
Diffstat (limited to 'lisp/emacs-lisp/package.el')
| -rw-r--r-- | lisp/emacs-lisp/package.el | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el index 308f9eb3a63..1e73a1690cc 100644 --- a/lisp/emacs-lisp/package.el +++ b/lisp/emacs-lisp/package.el | |||
| @@ -360,6 +360,15 @@ should normally not be used since it will decrease security." | |||
| 360 | :risky t | 360 | :risky t |
| 361 | :version "28.1") | 361 | :version "28.1") |
| 362 | 362 | ||
| 363 | (defcustom package-check-timestamp t | ||
| 364 | "Non-nil means to verify the package archive timestamp. | ||
| 365 | |||
| 366 | Note that setting this to nil is intended for debugging, and | ||
| 367 | should normally not be used since it will decrease security." | ||
| 368 | :type 'boolean | ||
| 369 | :risky t | ||
| 370 | :version "28.1") | ||
| 371 | |||
| 363 | (defcustom package-check-signature 'allow-unsigned | 372 | (defcustom package-check-signature 'allow-unsigned |
| 364 | "Non-nil means to check package signatures when installing. | 373 | "Non-nil means to check package signatures when installing. |
| 365 | More specifically the value can be: | 374 | More specifically the value can be: |
| @@ -449,6 +458,7 @@ synchronously." | |||
| 449 | (define-error 'bad-size "Package size mismatch" 'package-error) | 458 | (define-error 'bad-size "Package size mismatch" 'package-error) |
| 450 | (define-error 'bad-signature "Failed to verify signature" 'package-error) | 459 | (define-error 'bad-signature "Failed to verify signature" 'package-error) |
| 451 | (define-error 'bad-checksum "Failed to verify checksum" 'package-error) | 460 | (define-error 'bad-checksum "Failed to verify checksum" 'package-error) |
| 461 | (define-error 'bad-timestamp "Failed to verify timestamp" 'package-error) | ||
| 452 | 462 | ||
| 453 | 463 | ||
| 454 | ;;; `package-desc' object definition | 464 | ;;; `package-desc' object definition |
| @@ -1812,6 +1822,100 @@ Once it's empty, run `package--post-download-archives-hook'." | |||
| 1812 | (message "Package refresh done") | 1822 | (message "Package refresh done") |
| 1813 | (run-hooks 'package--post-download-archives-hook))) | 1823 | (run-hooks 'package--post-download-archives-hook))) |
| 1814 | 1824 | ||
| 1825 | (defun package--parse-header-from-buffer (header name) | ||
| 1826 | "Find and return \"archive-contents\" HEADER for archive NAME. | ||
| 1827 | This function assumes that the current buffer contains the | ||
| 1828 | \"archive-contents\" file. | ||
| 1829 | |||
| 1830 | A valid header looks like: \";; HEADER: <TIMESTAMP>\" | ||
| 1831 | |||
| 1832 | Where <TIMESTAMP> is a valid ISO-8601 (RFC 3339) date. If there | ||
| 1833 | is such a line but <TIMESTAMP> is invalid, show a warning and | ||
| 1834 | return nil. If there is no valid header, return nil." | ||
| 1835 | (save-excursion | ||
| 1836 | (goto-char (point-min)) | ||
| 1837 | (when (re-search-forward (concat "^;; " header ": *\\(.+?\\) *$") nil t) | ||
| 1838 | (condition-case-unless-debug nil | ||
| 1839 | (encode-time (iso8601-parse (match-string 1))) | ||
| 1840 | (lwarn '(package timestamp) | ||
| 1841 | (list (format "Malformed timestamp for archive `%s': `%s'" | ||
| 1842 | name (match-string 1)))))))) | ||
| 1843 | |||
| 1844 | (defun package--parse-valid-until-from-buffer (name) | ||
| 1845 | "Find and return \"Valid-Until\" header for archive NAME." | ||
| 1846 | (package--parse-header-from-buffer "Valid-Until" name)) | ||
| 1847 | |||
| 1848 | (defun package--parse-last-updated-from-buffer (name) | ||
| 1849 | "Find and return \"Last-Updated\" header for archive NAME." | ||
| 1850 | (package--parse-header-from-buffer "Last-Updated" name)) | ||
| 1851 | |||
| 1852 | (defun package--archive-verify-timestamp (new old name) | ||
| 1853 | "Return t if timestamp NEW is more recent than OLD for archive NAME. | ||
| 1854 | Signal error otherwise. | ||
| 1855 | Warn if NEW is in the future." | ||
| 1856 | ;; If timestamp is missing on cached (old) file, do nothing here. | ||
| 1857 | ;; This package archive recently introduced support for timestamps. | ||
| 1858 | ;; We will require a timestamp for that archive in future updates. | ||
| 1859 | (if old | ||
| 1860 | (cond | ||
| 1861 | ((not new) | ||
| 1862 | (signal 'bad-timestamp | ||
| 1863 | (list (format-message | ||
| 1864 | (concat | ||
| 1865 | "New archive contents for `%s' missing " | ||
| 1866 | "timestamp, refusing to proceed") | ||
| 1867 | name)))) | ||
| 1868 | ((time-less-p new old) | ||
| 1869 | (signal 'bad-timestamp | ||
| 1870 | (list (format-message | ||
| 1871 | (concat | ||
| 1872 | "New archive contents for `%s' older than " | ||
| 1873 | "cached, refusing to proceed") | ||
| 1874 | name)))) | ||
| 1875 | ((time-less-p (current-time) new) | ||
| 1876 | (signal 'bad-timestamp | ||
| 1877 | (list (format-message | ||
| 1878 | (concat | ||
| 1879 | "New archive contents for `%s' is " | ||
| 1880 | "in the future: %s") | ||
| 1881 | name (format-time-string "%c" new))))) | ||
| 1882 | ;; Check ok, return t. | ||
| 1883 | (t)) | ||
| 1884 | t)) | ||
| 1885 | |||
| 1886 | (defun package--archive-verify-not-expired (timestamp name) | ||
| 1887 | "Return t if TIMESTAMP has not yet expired for archive NAME. | ||
| 1888 | Signal error otherwise." | ||
| 1889 | (unless (time-less-p (current-time) timestamp) | ||
| 1890 | (signal 'bad-timestamp | ||
| 1891 | (list (format-message | ||
| 1892 | (concat | ||
| 1893 | "Package archive `%s' has sent " | ||
| 1894 | "an expired `archive-contents' file") | ||
| 1895 | name))))) | ||
| 1896 | |||
| 1897 | (defun package--check-archive-timestamp (name) | ||
| 1898 | "Verify timestamp of \"archive-contents\" file for archive NAME. | ||
| 1899 | Compare the archive timestamp of the previously downloaded | ||
| 1900 | \"archive-contents\" file to the timestamp in the current buffer. | ||
| 1901 | Signal error if the old timestamp is more recent than the new one. | ||
| 1902 | |||
| 1903 | Do nothing if there is no previously downloaded file, if such a | ||
| 1904 | file exists but does not contain any timestamp, or if | ||
| 1905 | `package-check-timestamp' is nil." | ||
| 1906 | (let ((old-file (expand-file-name | ||
| 1907 | (concat "archives/" name "/archive-contents") | ||
| 1908 | package-user-dir))) | ||
| 1909 | (when (and package-check-timestamp | ||
| 1910 | (file-readable-p old-file)) | ||
| 1911 | (let ((old (with-temp-buffer | ||
| 1912 | (insert-file-contents old-file) | ||
| 1913 | (package--parse-last-updated-from-buffer name))) | ||
| 1914 | (new (package--parse-last-updated-from-buffer name)) | ||
| 1915 | (new-expires (package--parse-valid-until-from-buffer name))) | ||
| 1916 | (package--archive-verify-timestamp new old name) | ||
| 1917 | (package--archive-verify-not-expired new-expires name))))) | ||
| 1918 | |||
| 1815 | (defun package--download-one-archive (archive file &optional async) | 1919 | (defun package--download-one-archive (archive file &optional async) |
| 1816 | "Retrieve an archive file FILE from ARCHIVE, and cache it. | 1920 | "Retrieve an archive file FILE from ARCHIVE, and cache it. |
| 1817 | ARCHIVE should be a cons cell of the form (NAME . LOCATION), | 1921 | ARCHIVE should be a cons cell of the form (NAME . LOCATION), |
| @@ -1825,6 +1929,7 @@ similar to an entry in `package-alist'. Save the cached copy to | |||
| 1825 | (content (buffer-string)) | 1929 | (content (buffer-string)) |
| 1826 | (dir (expand-file-name (concat "archives/" name) package-user-dir)) | 1930 | (dir (expand-file-name (concat "archives/" name) package-user-dir)) |
| 1827 | (local-file (expand-file-name file dir))) | 1931 | (local-file (expand-file-name file dir))) |
| 1932 | (package--check-archive-timestamp name) | ||
| 1828 | (when (listp (read content)) | 1933 | (when (listp (read content)) |
| 1829 | (make-directory dir t) | 1934 | (make-directory dir t) |
| 1830 | (if (or (not (package-check-signature)) | 1935 | (if (or (not (package-check-signature)) |