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 } |