diff options
| author | Michael Albinus | 2009-09-28 11:59:22 +0000 |
|---|---|---|
| committer | Michael Albinus | 2009-09-28 11:59:22 +0000 |
| commit | 0e66507834086cf65c608cf4e4182f3726157b73 (patch) | |
| tree | 0bd7762f08eb6a2d52c04d627cdd7342906609ed | |
| parent | f742666ae3fe26bd51592cfbaff44ab19d7895c1 (diff) | |
| download | emacs-0e66507834086cf65c608cf4e4182f3726157b73.tar.gz emacs-0e66507834086cf65c608cf4e4182f3726157b73.zip | |
* net/tramp-imap.el: New package.
| -rw-r--r-- | lisp/net/tramp-imap.el | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/lisp/net/tramp-imap.el b/lisp/net/tramp-imap.el new file mode 100644 index 00000000000..cf0933db226 --- /dev/null +++ b/lisp/net/tramp-imap.el | |||
| @@ -0,0 +1,801 @@ | |||
| 1 | ;;; tramp-imap.el --- Tramp interface to IMAP through imap.el | ||
| 2 | |||
| 3 | ;; Copyright (C) 2009 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Teodor Zlatanov <tzz@lifelogs.com> | ||
| 6 | ;; Keywords: mail, comm | ||
| 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 | ;; Package to provide Tramp over IMAP | ||
| 26 | |||
| 27 | ;;; Setup: | ||
| 28 | |||
| 29 | ;; just load and open files, e.g. | ||
| 30 | ;; /imaps:user@yourhosthere.com:/INBOX.test/1 | ||
| 31 | ;; or | ||
| 32 | ;; /imap:user@yourhosthere.com:/INBOX.test/1 | ||
| 33 | |||
| 34 | ;; where `imap' goes over IMAP, while `imaps' goes over IMAP+SSL | ||
| 35 | |||
| 36 | ;; This module will use imap-hash.el to access the IMAP mailbox. | ||
| 37 | |||
| 38 | ;; This module will use auth-source.el to authenticate against the | ||
| 39 | ;; IMAP server, PLUS it will use auth-source.el to get your passphrase | ||
| 40 | ;; for the symmetrically encrypted messages. For the former, use the | ||
| 41 | ;; usual IMAP ports. For the latter, use the port "tramp-imap". | ||
| 42 | |||
| 43 | ;; example .authinfo / .netrc file: | ||
| 44 | |||
| 45 | ;; machine yourhosthere.com port tramp-imap login USER password SYMMETRIC-PASSPHRASE | ||
| 46 | |||
| 47 | ;; note above is the symmetric encryption passphrase for GPG | ||
| 48 | ;; below is the regular password for IMAP itself and other things on that host | ||
| 49 | |||
| 50 | ;; machine yourhosthere.com login USER password NORMAL-PASSWORD | ||
| 51 | |||
| 52 | |||
| 53 | ;;; Code: | ||
| 54 | |||
| 55 | (require 'assoc) | ||
| 56 | (require 'tramp) | ||
| 57 | (require 'tramp-compat) | ||
| 58 | (require 'message) | ||
| 59 | (require 'imap-hash) | ||
| 60 | (require 'epa) | ||
| 61 | (autoload 'auth-source-user-or-password "auth-source") | ||
| 62 | |||
| 63 | ;; Define Tramp IMAP method ... | ||
| 64 | (defconst tramp-imap-method "imap" | ||
| 65 | "*Method to connect via IMAP protocol.") | ||
| 66 | |||
| 67 | (add-to-list 'tramp-methods (list tramp-imap-method '(tramp-default-port 143))) | ||
| 68 | |||
| 69 | ;; Add a default for `tramp-default-user-alist'. Default is the local user. | ||
| 70 | (add-to-list 'tramp-default-user-alist | ||
| 71 | `(,tramp-imap-method nil ,(user-login-name))) | ||
| 72 | |||
| 73 | ;; Define Tramp IMAPS method ... | ||
| 74 | (defconst tramp-imaps-method "imaps" | ||
| 75 | "*Method to connect via secure IMAP protocol.") | ||
| 76 | |||
| 77 | ;; ... and add it to the method list. | ||
| 78 | (add-to-list 'tramp-methods (list tramp-imaps-method '(tramp-default-port 993))) | ||
| 79 | |||
| 80 | ;; Add a default for `tramp-default-user-alist'. Default is the local user. | ||
| 81 | (add-to-list 'tramp-default-user-alist | ||
| 82 | `(,tramp-imaps-method nil ,(user-login-name))) | ||
| 83 | |||
| 84 | ;; Add completion function for IMAP method. | ||
| 85 | ;; (tramp-set-completion-function | ||
| 86 | ;; tramp-imap-method tramp-completion-function-alist-ssh) ; TODO: test this | ||
| 87 | ;; tramp-imaps-method tramp-completion-function-alist-ssh) ; TODO: test this | ||
| 88 | |||
| 89 | ;; New handlers should be added here. | ||
| 90 | (defconst tramp-imap-file-name-handler-alist | ||
| 91 | '( | ||
| 92 | ;; `access-file' performed by default handler | ||
| 93 | (add-name-to-file . ignore) | ||
| 94 | ;; `byte-compiler-base-file-name' performed by default handler | ||
| 95 | (copy-file . tramp-imap-handle-copy-file) | ||
| 96 | (delete-directory . ignore) ;; tramp-imap-handle-delete-directory) | ||
| 97 | (delete-file . tramp-imap-handle-delete-file) | ||
| 98 | ;; `diff-latest-backup-file' performed by default handler | ||
| 99 | (directory-file-name . tramp-handle-directory-file-name) | ||
| 100 | (directory-files . tramp-handle-directory-files) | ||
| 101 | (directory-files-and-attributes | ||
| 102 | . tramp-imap-handle-directory-files-and-attributes) | ||
| 103 | ;; `dired-call-process' performed by default handler | ||
| 104 | ;; `dired-compress-file' performed by default handler | ||
| 105 | ;; `dired-uncache' performed by default handler | ||
| 106 | (expand-file-name . tramp-imap-handle-expand-file-name) | ||
| 107 | ;; `file-accessible-directory-p' performed by default handler | ||
| 108 | (file-attributes . tramp-imap-handle-file-attributes) | ||
| 109 | (file-directory-p . tramp-imap-handle-file-directory-p) | ||
| 110 | (file-executable-p . tramp-imap-handle-file-executable-p) | ||
| 111 | (file-exists-p . tramp-imap-handle-file-exists-p) | ||
| 112 | (file-local-copy . tramp-imap-handle-file-local-copy) | ||
| 113 | (file-remote-p . tramp-handle-file-remote-p) | ||
| 114 | (file-modes . tramp-handle-file-modes) | ||
| 115 | (file-name-all-completions . tramp-imap-handle-file-name-all-completions) | ||
| 116 | (file-name-as-directory . tramp-handle-file-name-as-directory) | ||
| 117 | (file-name-completion . tramp-handle-file-name-completion) | ||
| 118 | (file-name-directory . tramp-handle-file-name-directory) | ||
| 119 | (file-name-nondirectory . tramp-handle-file-name-nondirectory) | ||
| 120 | ;; `file-name-sans-versions' performed by default handler | ||
| 121 | (file-newer-than-file-p . tramp-imap-handle-file-newer-than-file-p) | ||
| 122 | (file-ownership-preserved-p . ignore) | ||
| 123 | (file-readable-p . tramp-imap-handle-file-readable-p) | ||
| 124 | (file-regular-p . tramp-handle-file-regular-p) | ||
| 125 | (file-symlink-p . tramp-handle-file-symlink-p) | ||
| 126 | ;; `file-truename' performed by default handler | ||
| 127 | (file-writable-p . tramp-imap-handle-file-writable-p) | ||
| 128 | (find-backup-file-name . tramp-handle-find-backup-file-name) | ||
| 129 | ;; `find-file-noselect' performed by default handler | ||
| 130 | ;; `get-file-buffer' performed by default handler | ||
| 131 | (insert-directory . tramp-imap-handle-insert-directory) | ||
| 132 | (insert-file-contents . tramp-imap-handle-insert-file-contents) | ||
| 133 | (load . tramp-handle-load) | ||
| 134 | (make-directory . ignore) ;; tramp-imap-handle-make-directory) | ||
| 135 | (make-directory-internal . ignore) ;; tramp-imap-handle-make-directory-internal) | ||
| 136 | (make-symbolic-link . ignore) | ||
| 137 | (rename-file . tramp-imap-handle-rename-file) | ||
| 138 | (set-file-modes . ignore) | ||
| 139 | (set-file-times . ignore) ;; tramp-imap-handle-set-file-times) | ||
| 140 | (set-visited-file-modtime . ignore) | ||
| 141 | (shell-command . ignore) | ||
| 142 | (substitute-in-file-name . tramp-handle-substitute-in-file-name) | ||
| 143 | (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) | ||
| 144 | (vc-registered . ignore) | ||
| 145 | (verify-visited-file-modtime . ignore) | ||
| 146 | (write-region . tramp-imap-handle-write-region) | ||
| 147 | (executable-find . ignore) | ||
| 148 | (start-file-process . ignore) | ||
| 149 | (process-file . ignore) | ||
| 150 | ) | ||
| 151 | "Alist of handler functions for Tramp IMAP method. | ||
| 152 | Operations not mentioned here will be handled by the default Emacs primitives.") | ||
| 153 | |||
| 154 | (defgroup tramp-imap nil | ||
| 155 | "Tramp over IMAP configuration." | ||
| 156 | :version "23.2" | ||
| 157 | :group 'applications) | ||
| 158 | |||
| 159 | (defcustom tramp-imap-subject-marker "tramp-imap-subject-marker" | ||
| 160 | "The subject marker that Tramp-IMAP will use." | ||
| 161 | :type 'string | ||
| 162 | :version "23.2" | ||
| 163 | :group 'tramp-imap) | ||
| 164 | |||
| 165 | ;; TODO: these will be defcustoms later. | ||
| 166 | (defvar tramp-imap-passphrase-cache nil) ;; can be t or 'never | ||
| 167 | (defvar tramp-imap-passphrase nil) | ||
| 168 | |||
| 169 | (defun tramp-imap-file-name-p (filename) | ||
| 170 | "Check if it's a filename for IMAP protocol." | ||
| 171 | (let ((v (tramp-dissect-file-name filename))) | ||
| 172 | (or | ||
| 173 | (string= (tramp-file-name-method v) tramp-imap-method) | ||
| 174 | (string= (tramp-file-name-method v) tramp-imaps-method)))) | ||
| 175 | |||
| 176 | (defun tramp-imap-file-name-handler (operation &rest args) | ||
| 177 | "Invoke the IMAP related OPERATION. | ||
| 178 | First arg specifies the OPERATION, second arg is a list of arguments to | ||
| 179 | pass to the OPERATION." | ||
| 180 | (let ((fn (assoc operation tramp-imap-file-name-handler-alist))) | ||
| 181 | (if fn | ||
| 182 | (save-match-data (apply (cdr fn) args)) | ||
| 183 | (tramp-run-real-handler operation args)))) | ||
| 184 | |||
| 185 | (add-to-list 'tramp-foreign-file-name-handler-alist | ||
| 186 | (cons 'tramp-imap-file-name-p 'tramp-imap-file-name-handler)) | ||
| 187 | |||
| 188 | (defun tramp-imap-handle-copy-file | ||
| 189 | (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) | ||
| 190 | "Like `copy-file' for Tramp files." | ||
| 191 | (tramp-imap-do-copy-or-rename-file | ||
| 192 | 'copy filename newname ok-if-already-exists keep-date preserve-uid-gid)) | ||
| 193 | |||
| 194 | (defun tramp-imap-handle-rename-file | ||
| 195 | (filename newname &optional ok-if-already-exists) | ||
| 196 | "Like `rename-file' for Tramp files." | ||
| 197 | (tramp-imap-do-copy-or-rename-file | ||
| 198 | 'rename filename newname ok-if-already-exists t t)) | ||
| 199 | |||
| 200 | (defun tramp-imap-do-copy-or-rename-file | ||
| 201 | (op filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) | ||
| 202 | "Copy or rename a remote file. | ||
| 203 | OP must be `copy' or `rename' and indicates the operation to perform. | ||
| 204 | FILENAME specifies the file to copy or rename, NEWNAME is the name of | ||
| 205 | the new file (for copy) or the new name of the file (for rename). | ||
| 206 | OK-IF-ALREADY-EXISTS means don't barf if NEWNAME exists already. | ||
| 207 | KEEP-DATE means to make sure that NEWNAME has the same timestamp | ||
| 208 | as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep | ||
| 209 | the uid and gid if both files are on the same host. | ||
| 210 | |||
| 211 | This function is invoked by `tramp-imap-handle-copy-file' and | ||
| 212 | `tramp-imap-handle-rename-file'. It is an error if OP is neither | ||
| 213 | of `copy' and `rename'." | ||
| 214 | (unless (memq op '(copy rename)) | ||
| 215 | (error "Unknown operation `%s', must be `copy' or `rename'" op)) | ||
| 216 | (setq filename (expand-file-name filename)) | ||
| 217 | (setq newname (expand-file-name newname)) | ||
| 218 | (when (file-directory-p newname) | ||
| 219 | (setq newname (expand-file-name (file-name-nondirectory filename) newname))) | ||
| 220 | |||
| 221 | (let ((t1 (and (tramp-tramp-file-p filename) | ||
| 222 | (tramp-imap-file-name-p filename))) | ||
| 223 | (t2 (and (tramp-tramp-file-p newname) | ||
| 224 | (tramp-imap-file-name-p newname)))) | ||
| 225 | |||
| 226 | (when (and (not ok-if-already-exists) (file-exists-p newname)) | ||
| 227 | (with-parsed-tramp-file-name (if t1 filename newname) nil | ||
| 228 | (tramp-error | ||
| 229 | v 'file-already-exists "File %s already exists" newname))) | ||
| 230 | |||
| 231 | (with-parsed-tramp-file-name (if t1 filename newname) nil | ||
| 232 | (tramp-message v 0 "Transferring %s to %s..." filename newname)) | ||
| 233 | |||
| 234 | ;; We just make a local copy of FILENAME, and write it then to | ||
| 235 | ;; NEWNAME. This must be optimized, when both files are located | ||
| 236 | ;; on the same IMAP server. | ||
| 237 | (with-temp-buffer | ||
| 238 | (if (and t1 t2) | ||
| 239 | ;; We don't encrypt. | ||
| 240 | (with-parsed-tramp-file-name newname nil | ||
| 241 | (insert (tramp-imap-get-file filename nil)) | ||
| 242 | (tramp-imap-put-file | ||
| 243 | v (current-buffer) | ||
| 244 | (tramp-imap-file-name-name v) | ||
| 245 | (tramp-imap-get-file-inode newname) | ||
| 246 | nil)) | ||
| 247 | ;; One of them is not located on a IMAP mailbox. | ||
| 248 | (insert-file-contents filename) | ||
| 249 | (write-region (point-min) (point-max) newname))) | ||
| 250 | |||
| 251 | (with-parsed-tramp-file-name (if t1 filename newname) nil | ||
| 252 | (tramp-message v 0 "Transferring %s to %s...done" filename newname)) | ||
| 253 | |||
| 254 | (when (eq op 'rename) | ||
| 255 | (delete-file filename)))) | ||
| 256 | |||
| 257 | ;; TODO: revise this much | ||
| 258 | (defun tramp-imap-handle-expand-file-name (name &optional dir) | ||
| 259 | "Like `expand-file-name' for Tramp files." | ||
| 260 | ;; If DIR is not given, use DEFAULT-DIRECTORY or "/". | ||
| 261 | (setq dir (or dir default-directory "/")) | ||
| 262 | ;; Unless NAME is absolute, concat DIR and NAME. | ||
| 263 | (unless (file-name-absolute-p name) | ||
| 264 | (setq name (concat (file-name-as-directory dir) name))) | ||
| 265 | ;; If NAME is not a Tramp file, run the real handler. | ||
| 266 | (if (or (tramp-completion-mode-p) (not (tramp-tramp-file-p name))) | ||
| 267 | (tramp-drop-volume-letter | ||
| 268 | (tramp-run-real-handler 'expand-file-name (list name nil))) | ||
| 269 | ;; Dissect NAME. | ||
| 270 | (with-parsed-tramp-file-name name nil | ||
| 271 | (unless (tramp-run-real-handler 'file-name-absolute-p (list localname)) | ||
| 272 | (setq localname (concat "/" localname))) | ||
| 273 | ;; There might be a double slash, for example when "~/" | ||
| 274 | ;; expands to "/". Remove this. | ||
| 275 | (while (string-match "//" localname) | ||
| 276 | (setq localname (replace-match "/" t t localname))) | ||
| 277 | ;; Do normal `expand-file-name' (this does "/./" and "/../"). | ||
| 278 | ;; We bind `directory-sep-char' here for XEmacs on Windows, | ||
| 279 | ;; which would otherwise use backslash. `default-directory' is | ||
| 280 | ;; bound, because on Windows there would be problems with UNC | ||
| 281 | ;; shares or Cygwin mounts. | ||
| 282 | (let ((default-directory (tramp-compat-temporary-file-directory))) | ||
| 283 | (tramp-make-tramp-file-name | ||
| 284 | method user host | ||
| 285 | (tramp-drop-volume-letter | ||
| 286 | (tramp-run-real-handler | ||
| 287 | 'expand-file-name (list localname)))))))) | ||
| 288 | |||
| 289 | ;; This function should return "foo/" for directories and "bar" for | ||
| 290 | ;; files. | ||
| 291 | (defun tramp-imap-handle-file-name-all-completions (filename directory) | ||
| 292 | "Like `file-name-all-completions' for Tramp files." | ||
| 293 | (all-completions | ||
| 294 | filename | ||
| 295 | (with-parsed-tramp-file-name (expand-file-name directory) nil | ||
| 296 | (save-match-data | ||
| 297 | (let ((entries | ||
| 298 | (tramp-imap-get-file-entries v localname))) | ||
| 299 | (mapcar | ||
| 300 | (lambda (x) | ||
| 301 | (list | ||
| 302 | (if (string-match "d" (nth 9 x)) | ||
| 303 | (file-name-as-directory (nth 0 x)) | ||
| 304 | (nth 0 x)))) | ||
| 305 | entries)))))) | ||
| 306 | |||
| 307 | (defun tramp-imap-get-file-entries (vec localname &optional exact) | ||
| 308 | "Read entries returned by IMAP server. EXACT limits to exact matches. | ||
| 309 | Result is a list of (LOCALNAME LINK COUNT UID GID ATIME MTIME CTIME | ||
| 310 | SIZE MODE WEIRD INODE DEVICE)." | ||
| 311 | (tramp-message vec 5 "working on %s" localname) | ||
| 312 | (let* ((name (tramp-imap-file-name-name vec)) | ||
| 313 | (search-name (or name "")) | ||
| 314 | (search-name (if exact (concat search-name "$") search-name)) | ||
| 315 | (iht (tramp-imap-make-iht vec search-name))) | ||
| 316 | ;; TODO: catch errors | ||
| 317 | ;; (tramp-error vec 'none "bad name %s or mailbox %s" name mbox)) | ||
| 318 | (imap-hash-map (lambda (uid headers body) | ||
| 319 | (let ((subject (substring | ||
| 320 | (aget headers 'Subject "") | ||
| 321 | (length tramp-imap-subject-marker)))) | ||
| 322 | (list | ||
| 323 | subject | ||
| 324 | nil | ||
| 325 | -1 | ||
| 326 | 1 | ||
| 327 | 1 | ||
| 328 | '(0 0) | ||
| 329 | '(0 0) | ||
| 330 | '(0 0) | ||
| 331 | 1 | ||
| 332 | "-rw-rw-rw-" | ||
| 333 | nil | ||
| 334 | uid | ||
| 335 | (tramp-get-device vec)))) | ||
| 336 | iht t))) | ||
| 337 | |||
| 338 | (defun tramp-imap-handle-write-region (start end filename &optional append visit lockname confirm) | ||
| 339 | "Like `write-region' for Tramp files." | ||
| 340 | (setq filename (expand-file-name filename)) | ||
| 341 | (with-parsed-tramp-file-name filename nil | ||
| 342 | ;; XEmacs takes a coding system as the seventh argument, not `confirm'. | ||
| 343 | (when (and (not (featurep 'xemacs)) | ||
| 344 | confirm (file-exists-p filename)) | ||
| 345 | (unless (y-or-n-p (format "File %s exists; overwrite anyway? " | ||
| 346 | filename)) | ||
| 347 | (tramp-error v 'file-error "File not overwritten"))) | ||
| 348 | (tramp-flush-file-property v localname) | ||
| 349 | (let* ((old-buffer (current-buffer)) | ||
| 350 | (inode (tramp-imap-get-file-inode filename)) | ||
| 351 | (min 1) | ||
| 352 | (max (point-max)) | ||
| 353 | ;; Make sure we have good start and end values. | ||
| 354 | (start (or start min)) | ||
| 355 | (end (or end max)) | ||
| 356 | temp-buffer) | ||
| 357 | (with-temp-buffer | ||
| 358 | (setq temp-buffer (if (and (eq start min) (eq end max)) | ||
| 359 | old-buffer | ||
| 360 | ;; If this is a region write, insert the substring. | ||
| 361 | (insert | ||
| 362 | (with-current-buffer old-buffer | ||
| 363 | (buffer-substring-no-properties start end))) | ||
| 364 | (current-buffer))) | ||
| 365 | (tramp-imap-put-file v | ||
| 366 | temp-buffer | ||
| 367 | (tramp-imap-file-name-name v) | ||
| 368 | inode | ||
| 369 | t))) | ||
| 370 | (when (eq visit t) | ||
| 371 | (set-visited-file-modtime)))) | ||
| 372 | |||
| 373 | (defun tramp-imap-handle-insert-directory | ||
| 374 | (filename switches &optional wildcard full-directory-p) | ||
| 375 | "Like `insert-directory' for Tramp files." | ||
| 376 | (setq filename (expand-file-name filename)) | ||
| 377 | (when full-directory-p | ||
| 378 | ;; Called from `dired-add-entry'. | ||
| 379 | (setq filename (file-name-as-directory filename))) | ||
| 380 | (with-parsed-tramp-file-name filename nil | ||
| 381 | (save-match-data | ||
| 382 | (let ((base (file-name-nondirectory localname)) | ||
| 383 | (entries (copy-sequence | ||
| 384 | (tramp-imap-get-file-entries | ||
| 385 | v (file-name-directory localname))))) | ||
| 386 | |||
| 387 | (when wildcard | ||
| 388 | (when (string-match "\\." base) | ||
| 389 | (setq base (replace-match "\\\\." nil nil base))) | ||
| 390 | (when (string-match "\\*" base) | ||
| 391 | (setq base (replace-match ".*" nil nil base))) | ||
| 392 | (when (string-match "\\?" base) | ||
| 393 | (setq base (replace-match ".?" nil nil base)))) | ||
| 394 | |||
| 395 | ;; Filter entries. | ||
| 396 | (setq entries | ||
| 397 | (delq | ||
| 398 | nil | ||
| 399 | (if (or wildcard (zerop (length base))) | ||
| 400 | ;; Check for matching entries. | ||
| 401 | (mapcar | ||
| 402 | (lambda (x) | ||
| 403 | (when (string-match | ||
| 404 | (format "^%s" base) (nth 0 x)) | ||
| 405 | x)) | ||
| 406 | entries) | ||
| 407 | ;; We just need the only and only entry FILENAME. | ||
| 408 | (list (assoc base entries))))) | ||
| 409 | |||
| 410 | ;; Sort entries. | ||
| 411 | (setq entries | ||
| 412 | (sort | ||
| 413 | entries | ||
| 414 | (lambda (x y) | ||
| 415 | (if (string-match "t" switches) | ||
| 416 | ;; Sort by date. | ||
| 417 | (tramp-time-less-p (nth 6 y) (nth 6 x)) | ||
| 418 | ;; Sort by name. | ||
| 419 | (string-lessp (nth 0 x) (nth 0 y)))))) | ||
| 420 | |||
| 421 | ;; Handle "-F" switch. | ||
| 422 | (when (string-match "F" switches) | ||
| 423 | (mapc | ||
| 424 | (lambda (x) | ||
| 425 | (when (not (zerop (length (car x)))) | ||
| 426 | (cond | ||
| 427 | ((char-equal ?d (string-to-char (nth 9 x))) | ||
| 428 | (setcar x (concat (car x) "/"))) | ||
| 429 | ((char-equal ?x (string-to-char (nth 9 x))) | ||
| 430 | (setcar x (concat (car x) "*")))))) | ||
| 431 | entries)) | ||
| 432 | |||
| 433 | ;; Print entries. | ||
| 434 | (mapcar | ||
| 435 | (lambda (x) | ||
| 436 | (when (not (zerop (length (nth 0 x)))) | ||
| 437 | (insert | ||
| 438 | (format | ||
| 439 | "%10s %3d %-8s %-8s %8s %s " | ||
| 440 | (nth 9 x) ; mode | ||
| 441 | (nth 11 x) ; inode | ||
| 442 | "nobody" "nogroup" | ||
| 443 | (nth 8 x) ; size | ||
| 444 | (format-time-string | ||
| 445 | (if (tramp-time-less-p | ||
| 446 | (tramp-time-subtract (current-time) (nth 6 x)) | ||
| 447 | tramp-half-a-year) | ||
| 448 | "%b %e %R" | ||
| 449 | "%b %e %Y") | ||
| 450 | (nth 6 x)))) ; date | ||
| 451 | ;; For the file name, we set the `dired-filename' | ||
| 452 | ;; property. This allows to handle file names with | ||
| 453 | ;; leading or trailing spaces as well. | ||
| 454 | (let ((pos (point))) | ||
| 455 | (insert (format "%s" (nth 0 x))) ; file name | ||
| 456 | (put-text-property pos (point) 'dired-filename t)) | ||
| 457 | (insert "\n") | ||
| 458 | (forward-line) | ||
| 459 | (beginning-of-line))) | ||
| 460 | entries))))) | ||
| 461 | |||
| 462 | (defun tramp-imap-handle-insert-file-contents | ||
| 463 | (filename &optional visit beg end replace) | ||
| 464 | "Like `insert-file-contents' for Tramp files." | ||
| 465 | (barf-if-buffer-read-only) | ||
| 466 | (when visit | ||
| 467 | (setq buffer-file-name (expand-file-name filename)) | ||
| 468 | (set-visited-file-modtime) | ||
| 469 | (set-buffer-modified-p nil)) | ||
| 470 | (with-parsed-tramp-file-name filename nil | ||
| 471 | (if (not (file-exists-p filename)) | ||
| 472 | (tramp-error | ||
| 473 | v 'file-error "File `%s' not found on remote host" filename) | ||
| 474 | (let ((point (point)) | ||
| 475 | size data) | ||
| 476 | (tramp-message v 4 "Fetching file %s..." filename) | ||
| 477 | (insert (tramp-imap-get-file filename t)) | ||
| 478 | (setq size (- (point) point)) | ||
| 479 | ;;; TODO: handle ranges. | ||
| 480 | ;;; (let ((beg (or beg (point-min))) | ||
| 481 | ;;; (end (min (or end (point-max)) (point-max)))) | ||
| 482 | ;;; (setq size (- end beg)) | ||
| 483 | ;;; (buffer-substring beg end)) | ||
| 484 | (goto-char point) | ||
| 485 | (tramp-message v 4 "Fetching file %s...done" filename) | ||
| 486 | (list (expand-file-name filename) size))))) | ||
| 487 | |||
| 488 | (defun tramp-imap-handle-file-exists-p (filename) | ||
| 489 | "Like `file-exists-p' for Tramp files." | ||
| 490 | (and (file-attributes filename) t)) | ||
| 491 | |||
| 492 | (defun tramp-imap-handle-file-directory-p (filename) | ||
| 493 | "Like `file-directory-p' for Tramp-IMAP files." | ||
| 494 | ;; We allow only mailboxes to be a directory. | ||
| 495 | (with-parsed-tramp-file-name (expand-file-name filename default-directory) nil | ||
| 496 | (and (string-match "^/[^/]*$" (directory-file-name localname)) t))) | ||
| 497 | |||
| 498 | (defun tramp-imap-handle-file-attributes (filename &optional id-format) | ||
| 499 | "Like `file-attributes' for Tramp-IMAP FILENAME." | ||
| 500 | (with-parsed-tramp-file-name (expand-file-name filename) nil | ||
| 501 | (cdr-safe (nth 0 (tramp-imap-get-file-entries v localname))))) | ||
| 502 | |||
| 503 | (defun tramp-imap-get-file-inode (filename &optional id-format) | ||
| 504 | "Get inode equivalent \(actually the UID) for Tramp-IMAP FILENAME." | ||
| 505 | (nth 10 (tramp-compat-file-attributes filename id-format))) | ||
| 506 | |||
| 507 | (defun tramp-imap-handle-file-executable-p (filename) | ||
| 508 | "Like `file-executable-p' for Tramp files. False for IMAP." | ||
| 509 | nil) | ||
| 510 | |||
| 511 | (defun tramp-imap-handle-file-readable-p (filename) | ||
| 512 | "Like `file-readable-p' for Tramp files. True for IMAP." | ||
| 513 | (file-exists-p filename)) | ||
| 514 | |||
| 515 | (defun tramp-imap-handle-file-writable-p (filename) | ||
| 516 | "Like `file-writable-p' for Tramp files. True for IMAP." | ||
| 517 | ;; `file-exists-p' does not work yet for directories. | ||
| 518 | ;; (file-exists-p (file-name-directory filename))) | ||
| 519 | (file-directory-p (file-name-directory filename))) | ||
| 520 | |||
| 521 | (defun tramp-imap-handle-delete-file (filename) | ||
| 522 | "Like `delete-file' for Tramp files." | ||
| 523 | (cond | ||
| 524 | ((not (file-exists-p filename)) nil) | ||
| 525 | (t (with-parsed-tramp-file-name (expand-file-name filename) nil | ||
| 526 | (let ((iht (tramp-imap-make-iht v))) | ||
| 527 | (imap-hash-rem (tramp-imap-get-file-inode filename) iht)))))) | ||
| 528 | |||
| 529 | (defun tramp-imap-handle-directory-files-and-attributes | ||
| 530 | (directory &optional full match nosort id-format) | ||
| 531 | "Like `directory-files-and-attributes' for Tramp files." | ||
| 532 | (mapcar | ||
| 533 | (lambda (x) | ||
| 534 | (cons x (tramp-compat-file-attributes | ||
| 535 | (if full x (expand-file-name x directory)) id-format))) | ||
| 536 | (directory-files directory full match nosort))) | ||
| 537 | |||
| 538 | ;; TODO: fix this in tramp-imap-get-file-entries. | ||
| 539 | (defun tramp-imap-handle-file-newer-than-file-p (file1 file2) | ||
| 540 | "Like `file-newer-than-file-p' for Tramp files." | ||
| 541 | (cond | ||
| 542 | ((not (file-exists-p file1)) nil) | ||
| 543 | ((not (file-exists-p file2)) t) | ||
| 544 | (t (tramp-time-less-p (nth 5 (file-attributes file2)) | ||
| 545 | (nth 5 (file-attributes file1)))))) | ||
| 546 | |||
| 547 | (defun tramp-imap-handle-file-local-copy (filename) | ||
| 548 | "Like `file-local-copy' for Tramp files." | ||
| 549 | (with-parsed-tramp-file-name (expand-file-name filename) nil | ||
| 550 | (unless (file-exists-p filename) | ||
| 551 | (tramp-error | ||
| 552 | v 'file-error | ||
| 553 | "Cannot make local copy of non-existing file `%s'" filename)) | ||
| 554 | (let ((tmpfile (tramp-compat-make-temp-file filename))) | ||
| 555 | (tramp-message v 4 "Fetching %s to tmp file %s..." filename tmpfile) | ||
| 556 | (with-temp-buffer | ||
| 557 | (insert-file-contents filename) | ||
| 558 | (write-region (point-min) (point-max) tmpfile) | ||
| 559 | (tramp-message v 4 "Fetching %s to tmp file %s...done" filename tmpfile) | ||
| 560 | tmpfile)))) | ||
| 561 | |||
| 562 | (defun tramp-imap-put-file (vec filename-or-buffer &optional subject inode encode) | ||
| 563 | "Write contents of FILENAME-OR-BUFFER to Tramp-IMAP file VEC with name SUBJECT. | ||
| 564 | When INODE is given, delete that old remote file after writing the new one | ||
| 565 | \(normally this is the old file with the same name)." | ||
| 566 | ;; `tramp-current-host' is used in `tramp-imap-passphrase-callback-function'. | ||
| 567 | (let ((tramp-current-host (tramp-file-name-real-host vec)) | ||
| 568 | (iht (tramp-imap-make-iht vec))) | ||
| 569 | (imap-hash-put (list | ||
| 570 | (list (cons | ||
| 571 | 'Subject | ||
| 572 | (format | ||
| 573 | "%s%s" | ||
| 574 | tramp-imap-subject-marker | ||
| 575 | (or subject "no subject")))) | ||
| 576 | (cond ((bufferp filename-or-buffer) | ||
| 577 | (with-current-buffer filename-or-buffer | ||
| 578 | (if encode | ||
| 579 | (tramp-imap-encode-buffer) | ||
| 580 | (buffer-string)))) | ||
| 581 | ;; TODO: allow file names. | ||
| 582 | (t "No body available"))) | ||
| 583 | iht | ||
| 584 | inode))) | ||
| 585 | |||
| 586 | (defun tramp-imap-get-file (filename &optional decode) | ||
| 587 | ;; (debug (tramp-imap-get-file-inode filename)) | ||
| 588 | (with-parsed-tramp-file-name (expand-file-name filename) nil | ||
| 589 | (condition-case () | ||
| 590 | ;; `tramp-current-host' is used in | ||
| 591 | ;; `tramp-imap-passphrase-callback-function'. | ||
| 592 | (let* ((tramp-current-host (tramp-file-name-real-host v)) | ||
| 593 | (iht (tramp-imap-make-iht v)) | ||
| 594 | (inode (tramp-imap-get-file-inode filename)) | ||
| 595 | (data (imap-hash-get inode iht t))) | ||
| 596 | (if decode | ||
| 597 | (with-temp-buffer | ||
| 598 | (insert (nth 1 data)) | ||
| 599 | ;;(debug inode (buffer-string)) | ||
| 600 | (tramp-imap-decode-buffer)) | ||
| 601 | (nth 1 data))) | ||
| 602 | (error (tramp-error | ||
| 603 | v 'file-error "File `%s' could not be read" filename))))) | ||
| 604 | |||
| 605 | (defun tramp-imap-passphrase-callback-function (context key-id handback) | ||
| 606 | "Called by EPG to get a passphrase for Tramp-IMAP. | ||
| 607 | CONTEXT is the encryption/decryption EPG context. | ||
| 608 | HANDBACK is just carried through. | ||
| 609 | KEY-ID can be 'SYM or 'PIN among others." | ||
| 610 | (let* ((server tramp-current-host) | ||
| 611 | (port "tramp-imap") ; this is NOT the server password! | ||
| 612 | (auth-passwd | ||
| 613 | (auth-source-user-or-password "password" server port))) | ||
| 614 | (or | ||
| 615 | (copy-sequence auth-passwd) | ||
| 616 | ;; If we cache the passphrase and we have one. | ||
| 617 | (if (and (eq tramp-imap-passphrase-cache t) | ||
| 618 | tramp-imap-passphrase) | ||
| 619 | ;; Do we reuse it? | ||
| 620 | (if (y-or-n-p "Reuse the passphrase? ") | ||
| 621 | (copy-sequence tramp-imap-passphrase) | ||
| 622 | ;; Don't reuse: revert caching behavior to nil, erase passphrase, | ||
| 623 | ;; call ourselves again. | ||
| 624 | (setq tramp-imap-passphrase-cache nil) | ||
| 625 | (setq tramp-imap-passphrase nil) | ||
| 626 | (tramp-imap-passphrase-callback-function context key-id handback)) | ||
| 627 | (let ((p (if (eq key-id 'SYM) | ||
| 628 | (read-passwd | ||
| 629 | "Tramp-IMAP passphrase for symmetric encryption: " | ||
| 630 | (eq (epg-context-operation context) 'encrypt) | ||
| 631 | tramp-imap-passphrase) | ||
| 632 | (read-passwd | ||
| 633 | (if (eq key-id 'PIN) | ||
| 634 | "Tramp-IMAP passphrase for PIN: " | ||
| 635 | (let ((entry (assoc key-id epg-user-id-alist))) | ||
| 636 | (if entry | ||
| 637 | (format "Tramp-IMAP passphrase for %s %s: " | ||
| 638 | key-id (cdr entry)) | ||
| 639 | (format "Tramp-IMAP passphrase for %s: " key-id)))) | ||
| 640 | nil | ||
| 641 | tramp-imap-passphrase)))) | ||
| 642 | |||
| 643 | ;; If we have an answer, the passphrase has changed, | ||
| 644 | ;; the user hasn't declined keeping the passphrase, | ||
| 645 | ;; and they answer yes to keep it now... | ||
| 646 | (when (and | ||
| 647 | p | ||
| 648 | (not (equal tramp-imap-passphrase p)) | ||
| 649 | (not (eq tramp-imap-passphrase-cache 'never)) | ||
| 650 | (y-or-n-p "Keep the passphrase? ")) | ||
| 651 | (setq tramp-imap-passphrase (copy-sequence p)) | ||
| 652 | (setq tramp-imap-passphrase-cache t)) | ||
| 653 | |||
| 654 | ;; If we still don't have a passphrase, the user didn't want | ||
| 655 | ;; to keep it. | ||
| 656 | (when (and | ||
| 657 | p | ||
| 658 | (not tramp-imap-passphrase)) | ||
| 659 | (setq tramp-imap-passphrase-cache 'never)) | ||
| 660 | |||
| 661 | p))))) | ||
| 662 | |||
| 663 | (defun tramp-imap-encode-buffer () | ||
| 664 | (let ((context (epg-make-context 'OpenPGP)) | ||
| 665 | cipher) | ||
| 666 | (epg-context-set-armor context t) | ||
| 667 | (epg-context-set-passphrase-callback context | ||
| 668 | #'tramp-imap-passphrase-callback-function) | ||
| 669 | (epg-context-set-progress-callback context | ||
| 670 | (cons #'epa-progress-callback-function | ||
| 671 | "Encrypting...")) | ||
| 672 | (message "Encrypting...") | ||
| 673 | (setq cipher (epg-encrypt-string | ||
| 674 | context | ||
| 675 | (encode-coding-string (buffer-string) 'utf-8) | ||
| 676 | nil)) | ||
| 677 | (message "Encrypting...done") | ||
| 678 | cipher)) | ||
| 679 | |||
| 680 | (defun tramp-imap-decode-buffer () | ||
| 681 | (let ((context (epg-make-context 'OpenPGP)) | ||
| 682 | plain) | ||
| 683 | (epg-context-set-passphrase-callback context | ||
| 684 | #'tramp-imap-passphrase-callback-function) | ||
| 685 | (epg-context-set-progress-callback context | ||
| 686 | (cons #'epa-progress-callback-function | ||
| 687 | "Decrypting...")) | ||
| 688 | (message "Decrypting...") | ||
| 689 | (setq plain (decode-coding-string | ||
| 690 | (epg-decrypt-string context (buffer-string)) | ||
| 691 | 'utf-8)) | ||
| 692 | (message "Decrypting...done") | ||
| 693 | plain)) | ||
| 694 | |||
| 695 | (defun tramp-imap-file-name-mailbox (vec) | ||
| 696 | (nth 0 (tramp-imap-file-name-parse vec))) | ||
| 697 | |||
| 698 | (defun tramp-imap-file-name-name (vec) | ||
| 699 | (nth 1 (tramp-imap-file-name-parse vec))) | ||
| 700 | |||
| 701 | (defun tramp-imap-file-name-localname (vec) | ||
| 702 | (nth 1 (tramp-imap-file-name-parse vec))) | ||
| 703 | |||
| 704 | (defun tramp-imap-file-name-parse (vec) | ||
| 705 | (let ((name (substring-no-properties (tramp-file-name-localname vec)))) | ||
| 706 | (if (string-match "^/\\([^/]+\\)/?\\(.*\\)$" name) | ||
| 707 | (list (match-string 1 name) | ||
| 708 | (match-string 2 name)) | ||
| 709 | nil))) | ||
| 710 | |||
| 711 | (defun tramp-imap-make-iht (vec &optional needed-subject) | ||
| 712 | "Translate the Tramp vector VEC to the imap-hash structure. | ||
| 713 | With NEEDED-SUBJECT, alters the imap-hash test accordingly." | ||
| 714 | (let* ((mbox (tramp-imap-file-name-mailbox vec)) | ||
| 715 | (server (tramp-file-name-real-host vec)) | ||
| 716 | (method (tramp-file-name-method vec)) | ||
| 717 | (user (tramp-file-name-user vec)) | ||
| 718 | (ssl (string-equal method tramp-imaps-method)) | ||
| 719 | (port (or (tramp-file-name-port vec) | ||
| 720 | (tramp-get-method-parameter method 'tramp-default-port))) | ||
| 721 | (result (imap-hash-make server port mbox))) | ||
| 722 | ;; Return the IHT with a test override to look for the subject | ||
| 723 | ;; marker. Set also user and ssl tags. | ||
| 724 | (setq result (plist-put result :user user) | ||
| 725 | result (plist-put result :ssl ssl) | ||
| 726 | result (plist-put | ||
| 727 | result | ||
| 728 | :test (format "^%s%s" | ||
| 729 | tramp-imap-subject-marker | ||
| 730 | (if needed-subject needed-subject "")))))) | ||
| 731 | |||
| 732 | ;;; TODO: | ||
| 733 | |||
| 734 | ;; * Implement `tramp-imap-handle-delete-directory', | ||
| 735 | ;; `tramp-imap-handle-make-directory', | ||
| 736 | ;; `tramp-imap-handle-make-directory-internal', | ||
| 737 | ;; `tramp-imap-handle-set-file-times'. | ||
| 738 | |||
| 739 | ;; * Encode the subject. If the filename has trailing spaces (like | ||
| 740 | ;; "test "), those characters get lost, for example in dired listings. | ||
| 741 | |||
| 742 | ;; * When opening a dired buffer, like "/imap::INBOX.test", there are | ||
| 743 | ;; several error messages: | ||
| 744 | ;; "Buffer has a running process; kill it? (yes or no) " | ||
| 745 | ;; "error in process filter: Internal error, tag 6 status BAD code nil text No mailbox selected." | ||
| 746 | ;; Afterwards, everything seems to be fine. | ||
| 747 | |||
| 748 | ;; * imaps works for local IMAP servers. Accessing | ||
| 749 | ;; "/imaps:imap.gmail.com:/INBOX.test/" results in error | ||
| 750 | ;; "error in process filter: Internal error, tag 5 status BAD code nil text UNSELECT not allowed now. | ||
| 751 | |||
| 752 | (provide 'tramp-imap) | ||
| 753 | ;;; tramp-imap.el ends here | ||
| 754 | |||
| 755 | ;; Ignore, for testing only. | ||
| 756 | |||
| 757 | ;;; (setq tramp-imap-subject-marker "T") | ||
| 758 | ;;; (tramp-imap-get-file-entries (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/4") t) | ||
| 759 | ;;; (tramp-imap-get-file-entries (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/") t) | ||
| 760 | ;;; (tramp-imap-get-file-entries (tramp-dissect-file-name "/imap:yourhosthere.com:/test/4") t) | ||
| 761 | ;;; (tramp-imap-get-file-entries (tramp-dissect-file-name "/imap:yourhosthere.com:/test/") t) | ||
| 762 | ;;; (tramp-imap-get-file-entries (tramp-dissect-file-name "/imap:yourhosthere.com:/test/welcommen") t) | ||
| 763 | ;;; (tramp-imap-get-file-entries (tramp-dissect-file-name "/imap:yourhosthere.com:/test/welcommen") t t) | ||
| 764 | ;;;(tramp-imap-get-file-inode "/imap:yourhosthere.com:/test/welcome") | ||
| 765 | ;;; (dired-copy-file "/etc/fstab" "/imap:yourhosthere.com:/test/welcome" t) | ||
| 766 | ;;; (write-region 1 100 "/imap:yourhosthere.com:/test/welcome") | ||
| 767 | ;;; (tramp-imap-get-file "/imap:yourhosthere.com:/test/welcome" t) | ||
| 768 | ;;(with-temp-buffer (insert "hello") (write-file "/imap:yourhosthere.com:/test/welcome")) | ||
| 769 | ;;(with-temp-buffer (insert "hello") (write-file "/imap:yourhosthere.com:/test/welcome2")) | ||
| 770 | ;;(file-writable-p "/imap:yourhosthere.com:/test/welcome2") | ||
| 771 | ;;(file-name-directory "/imap:yourhosthere.com:/test/welcome2") | ||
| 772 | ;;(with-temp-buffer (insert "hello") (delete-file "/tmp/hellotest") (write-file "/tmp/hellotest") (write-file "/imap:yourhosthere.com:/test/welcome2")) | ||
| 773 | ;;;(file-exists-p "/imap:yourhosthere.com:/INBOX.test/4") | ||
| 774 | ;;;(file-attributes "/imap:yourhosthere.com:/INBOX.test/4") | ||
| 775 | ;;;(setq vec (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/4")) | ||
| 776 | ;;;(tramp-imap-handle-file-attributes "/imap:yourhosthere.com:/INBOX.test/4") | ||
| 777 | ;;; (tramp-imap-handle-insert-file-contents "/imap:user@yourhosthere.com:/INBOX.test/4" nil nil nil nil) | ||
| 778 | ;;;(insert-file-contents "/imap:yourhosthere.com:/INBOX.test/4") | ||
| 779 | ;;;(file-attributes "/imap:yourhosthere.com:/test/welcommen") | ||
| 780 | ;;;(insert-file-contents "/imap:yourhosthere.com:/test/welcome") | ||
| 781 | ;;;(file-exists-p "/imap:yourhosthere.com:/test/welcome2") | ||
| 782 | ;;;(tramp-imap-handle-file-attributes "/imap:yourhosthere.com:/test/welcome") | ||
| 783 | ;;;(tramp-imap-get-file-inode "/imap:yourhosthere.com:/test/welcommen") | ||
| 784 | ;;;(tramp-imap-get-file-inode "/imap:yourhosthere.com:/test/welcome") | ||
| 785 | ;;;(file-writable-p "/imap:yourhosthere.com:/test/welcome2") | ||
| 786 | ;;; (delete-file "/imap:yourhosthere.com:/test/welcome") | ||
| 787 | ;;; (tramp-imap-get-file "/imap:yourhosthere.com:/test/welcommen" t) | ||
| 788 | ;;; (tramp-imap-get-file "/imap:yourhosthere.com:/test/welcome" t) | ||
| 789 | ;;;(tramp-imap-file-name-mailbox (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test")) | ||
| 790 | ;;;(tramp-imap-file-name-mailbox (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/new/old")) | ||
| 791 | ;;;(tramp-imap-file-name-mailbox (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/new")) | ||
| 792 | ;;;(tramp-imap-file-name-parse (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/new/two")) | ||
| 793 | ;;;(tramp-imap-file-name-parse (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/new/one")) | ||
| 794 | ;;;(tramp-imap-file-name-parse (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test")) | ||
| 795 | ;;; (tramp-imap-file-name-parse (tramp-dissect-file-name "/imap:yourhosthere.com:/test/4")) | ||
| 796 | ;;; (tramp-imap-file-name-parse (tramp-dissect-file-name "/imap:yourhosthere.com:/test/")) | ||
| 797 | ;;; (tramp-imap-file-name-parse (tramp-dissect-file-name "/imap:yourhosthere.com:/test/welcommen")) | ||
| 798 | ;;; (tramp-imap-file-name-parse (tramp-dissect-file-name "/imap:yourhosthere.com:/test/welcommen")) | ||
| 799 | ;;; (tramp-imap-make-iht (tramp-dissect-file-name "/imap:yourhosthere.com:/test/welcommen")) | ||
| 800 | ;;; (tramp-imap-make-iht (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/4")) | ||
| 801 | ;;; (tramp-imap-make-iht (tramp-dissect-file-name "/imap:yourhosthere.com:/INBOX.test/4") "extra") | ||