Mercurial > hg > rc1
comparison plugins/filesystem_attachments/filesystem_attachments.php @ 0:1e000243b222
vanilla 1.3.3 distro, I hope
| author | Charlie Root |
|---|---|
| date | Thu, 04 Jan 2018 15:50:29 -0500 |
| parents | |
| children | 7498e7cacd71 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:1e000243b222 |
|---|---|
| 1 <?php | |
| 2 /** | |
| 3 * Filesystem Attachments | |
| 4 * | |
| 5 * This is a core plugin which provides basic, filesystem based | |
| 6 * attachment temporary file handling. This includes storing | |
| 7 * attachments of messages currently being composed, writing attachments | |
| 8 * to disk when drafts with attachments are re-opened and writing | |
| 9 * attachments to disk for inline display in current html compositions. | |
| 10 * It also handles uploaded files for other uses, so not only attachments. | |
| 11 * | |
| 12 * Developers may wish to extend this class when creating attachment | |
| 13 * handler plugins: | |
| 14 * require_once('plugins/filesystem_attachments/filesystem_attachments.php'); | |
| 15 * class myCustom_attachments extends filesystem_attachments | |
| 16 * | |
| 17 * Note for developers: It is plugin's responsibility to care about security. | |
| 18 * So, e.g. if the plugin is asked about some file path it should check | |
| 19 * if it's really the storage path of the plugin and not e.g. /etc/passwd. | |
| 20 * It is done by setting 'status' flag on every plugin hook it uses. | |
| 21 * Roundcube core will trust the returned path if status=true. | |
| 22 * | |
| 23 * @license GNU GPLv3+ | |
| 24 * @author Ziba Scott <ziba@umich.edu> | |
| 25 * @author Thomas Bruederli <roundcube@gmail.com> | |
| 26 */ | |
| 27 class filesystem_attachments extends rcube_plugin | |
| 28 { | |
| 29 public $task = '?(?!login).*'; | |
| 30 | |
| 31 function init() | |
| 32 { | |
| 33 // Save a newly uploaded attachment | |
| 34 $this->add_hook('attachment_upload', array($this, 'upload')); | |
| 35 | |
| 36 // Save an attachment from a non-upload source (draft or forward) | |
| 37 $this->add_hook('attachment_save', array($this, 'save')); | |
| 38 | |
| 39 // Remove an attachment from storage | |
| 40 $this->add_hook('attachment_delete', array($this, 'remove')); | |
| 41 | |
| 42 // When composing an html message, image attachments may be shown | |
| 43 $this->add_hook('attachment_display', array($this, 'display')); | |
| 44 | |
| 45 // Get the attachment from storage and place it on disk to be sent | |
| 46 $this->add_hook('attachment_get', array($this, 'get')); | |
| 47 | |
| 48 // Delete all temp files associated with this user | |
| 49 $this->add_hook('attachments_cleanup', array($this, 'cleanup')); | |
| 50 $this->add_hook('session_destroy', array($this, 'cleanup')); | |
| 51 } | |
| 52 | |
| 53 /** | |
| 54 * Save a newly uploaded attachment | |
| 55 */ | |
| 56 function upload($args) | |
| 57 { | |
| 58 $args['status'] = false; | |
| 59 $group = $args['group']; | |
| 60 $rcmail = rcube::get_instance(); | |
| 61 | |
| 62 // use common temp dir for file uploads | |
| 63 $temp_dir = $rcmail->config->get('temp_dir'); | |
| 64 $tmpfname = tempnam($temp_dir, 'rcmAttmnt'); | |
| 65 | |
| 66 if (move_uploaded_file($args['path'], $tmpfname) && file_exists($tmpfname)) { | |
| 67 $args['id'] = $this->file_id(); | |
| 68 $args['path'] = $tmpfname; | |
| 69 $args['status'] = true; | |
| 70 @chmod($tmpfname, 0600); // set correct permissions (#1488996) | |
| 71 | |
| 72 // Note the file for later cleanup | |
| 73 $_SESSION['plugins']['filesystem_attachments'][$group][$args['id']] = $tmpfname; | |
| 74 } | |
| 75 | |
| 76 return $args; | |
| 77 } | |
| 78 | |
| 79 /** | |
| 80 * Save an attachment from a non-upload source (draft or forward) | |
| 81 */ | |
| 82 function save($args) | |
| 83 { | |
| 84 $group = $args['group']; | |
| 85 $args['status'] = false; | |
| 86 | |
| 87 if (!$args['path']) { | |
| 88 $rcmail = rcube::get_instance(); | |
| 89 $temp_dir = $rcmail->config->get('temp_dir'); | |
| 90 $tmp_path = tempnam($temp_dir, 'rcmAttmnt'); | |
| 91 | |
| 92 if ($fp = fopen($tmp_path, 'w')) { | |
| 93 fwrite($fp, $args['data']); | |
| 94 fclose($fp); | |
| 95 $args['path'] = $tmp_path; | |
| 96 } | |
| 97 else { | |
| 98 return $args; | |
| 99 } | |
| 100 } | |
| 101 | |
| 102 $args['id'] = $this->file_id(); | |
| 103 $args['status'] = true; | |
| 104 | |
| 105 // Note the file for later cleanup | |
| 106 $_SESSION['plugins']['filesystem_attachments'][$group][$args['id']] = $args['path']; | |
| 107 | |
| 108 return $args; | |
| 109 } | |
| 110 | |
| 111 /** | |
| 112 * Remove an attachment from storage | |
| 113 * This is triggered by the remove attachment button on the compose screen | |
| 114 */ | |
| 115 function remove($args) | |
| 116 { | |
| 117 $args['status'] = $this->verify_path($args['path']) && @unlink($args['path']); | |
| 118 return $args; | |
| 119 } | |
| 120 | |
| 121 /** | |
| 122 * When composing an html message, image attachments may be shown | |
| 123 * For this plugin, the file is already in place, just check for | |
| 124 * the existence of the proper metadata | |
| 125 */ | |
| 126 function display($args) | |
| 127 { | |
| 128 $args['status'] = $this->verify_path($args['path']) && file_exists($args['path']); | |
| 129 return $args; | |
| 130 } | |
| 131 | |
| 132 /** | |
| 133 * This attachment plugin doesn't require any steps to put the file | |
| 134 * on disk for use. This stub function is kept here to make this | |
| 135 * class handy as a parent class for other plugins which may need it. | |
| 136 */ | |
| 137 function get($args) | |
| 138 { | |
| 139 if (!$this->verify_path($args['path'])) { | |
| 140 $args['path'] = null; | |
| 141 } | |
| 142 | |
| 143 return $args; | |
| 144 } | |
| 145 | |
| 146 /** | |
| 147 * Delete all temp files associated with this user | |
| 148 */ | |
| 149 function cleanup($args) | |
| 150 { | |
| 151 // $_SESSION['compose']['attachments'] is not a complete record of | |
| 152 // temporary files because loading a draft or starting a forward copies | |
| 153 // the file to disk, but does not make an entry in that array | |
| 154 if (is_array($_SESSION['plugins']['filesystem_attachments'])) { | |
| 155 foreach ($_SESSION['plugins']['filesystem_attachments'] as $group => $files) { | |
| 156 if ($args['group'] && $args['group'] != $group) { | |
| 157 continue; | |
| 158 } | |
| 159 | |
| 160 foreach ((array)$files as $filename) { | |
| 161 if (file_exists($filename)) { | |
| 162 unlink($filename); | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 unset($_SESSION['plugins']['filesystem_attachments'][$group]); | |
| 167 } | |
| 168 } | |
| 169 return $args; | |
| 170 } | |
| 171 | |
| 172 function file_id() | |
| 173 { | |
| 174 $userid = rcube::get_instance()->user->ID; | |
| 175 list($usec, $sec) = explode(' ', microtime()); | |
| 176 $id = preg_replace('/[^0-9]/', '', $userid . $sec . $usec); | |
| 177 | |
| 178 // make sure the ID is really unique (#1489546) | |
| 179 while ($this->find_file_by_id($id)) { | |
| 180 // increment last four characters | |
| 181 $x = substr($id, -4) + 1; | |
| 182 $id = substr($id, 0, -4) . sprintf('%04d', ($x > 9999 ? $x - 9999 : $x)); | |
| 183 } | |
| 184 | |
| 185 return $id; | |
| 186 } | |
| 187 | |
| 188 private function find_file_by_id($id) | |
| 189 { | |
| 190 foreach ((array) $_SESSION['plugins']['filesystem_attachments'] as $group => $files) { | |
| 191 if (isset($files[$id])) { | |
| 192 return true; | |
| 193 } | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 /** | |
| 198 * For security we'll always verify the file path stored in session, | |
| 199 * as session entries can be faked in various ways e.g. #6026. | |
| 200 * We allow only files in Roundcube temp dir | |
| 201 */ | |
| 202 protected function verify_path($path) | |
| 203 { | |
| 204 if (empty($path)) { | |
| 205 return false; | |
| 206 } | |
| 207 | |
| 208 $rcmail = rcube::get_instance(); | |
| 209 $temp_dir = $rcmail->config->get('temp_dir'); | |
| 210 $file_path = pathinfo($path, PATHINFO_DIRNAME); | |
| 211 | |
| 212 if ($temp_dir !== $file_path) { | |
| 213 rcube::raise_error(array( | |
| 214 'code' => 403, | |
| 215 'file' => __FILE__, | |
| 216 'line' => __LINE__, | |
| 217 'message' => sprintf("%s can't read %s (not in temp_dir)", | |
| 218 $rcmail->get_user_name(), substr($path, 0, 512)) | |
| 219 ), true, false); | |
| 220 | |
| 221 return false; | |
| 222 } | |
| 223 | |
| 224 return true; | |
| 225 } | |
| 226 } |
