Rev 503 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
310 | daniel-mar | 1 | <?php |
2 | |||
3 | /* |
||
4 | * OIDplus 2.0 |
||
511 | daniel-mar | 5 | * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft |
310 | daniel-mar | 6 | * |
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
8 | * you may not use this file except in compliance with the License. |
||
9 | * You may obtain a copy of the License at |
||
10 | * |
||
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
12 | * |
||
13 | * Unless required by applicable law or agreed to in writing, software |
||
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
16 | * See the License for the specific language governing permissions and |
||
17 | * limitations under the License. |
||
18 | */ |
||
19 | |||
511 | daniel-mar | 20 | if (!defined('INSIDE_OIDPLUS')) die(); |
21 | |||
310 | daniel-mar | 22 | class OIDplusPagePublicAttachments extends OIDplusPagePluginPublic { |
23 | |||
24 | public static function getUploadDir($id) { |
||
496 | daniel-mar | 25 | $path = realpath(OIDplus::localpath() . 'userdata/attachments/'); |
310 | daniel-mar | 26 | |
27 | $obj = OIDplusObject::parse($id); |
||
360 | daniel-mar | 28 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
310 | daniel-mar | 29 | if ($obj::ns() == 'oid') { |
30 | $oid = $obj->nodeId(false); |
||
31 | } else { |
||
32 | $oid = null; |
||
33 | $alt_ids = $obj->getAltIds(); |
||
34 | foreach ($alt_ids as $alt_id) { |
||
35 | if ($alt_id->getNamespace() == 'oid') { |
||
36 | $oid = $alt_id->getId(); |
||
37 | break; // we prefer the first OID (for GUIDs, the first OID is the OIDplus-OID, and the second OID is the UUID OID) |
||
38 | } |
||
39 | } |
||
40 | } |
||
41 | |||
384 | daniel-mar | 42 | if (!is_null($oid) && ($oid != '')) { |
310 | daniel-mar | 43 | // For OIDs, it is the OID, for other identifiers |
44 | // it it the OID alt ID (generated using the SystemID) |
||
503 | daniel-mar | 45 | $path .= DIRECTORY_SEPARATOR . str_replace('.', '_', $oid); |
310 | daniel-mar | 46 | } else { |
47 | // Can happen if you don't have a system ID (due to missing OpenSSL plugin) |
||
503 | daniel-mar | 48 | $path .= DIRECTORY_SEPARATOR . md5($obj->nodeId(true)); // we don't use $id, because $obj->nodeId(true) is possibly more canonical than $id |
310 | daniel-mar | 49 | } |
503 | daniel-mar | 50 | |
310 | daniel-mar | 51 | return $path; |
52 | } |
||
53 | |||
54 | private function raMayDelete() { |
||
55 | return OIDplus::config()->getValue('attachments_allow_ra_delete', 0); |
||
56 | } |
||
57 | |||
58 | private function raMayUpload() { |
||
59 | return OIDplus::config()->getValue('attachments_allow_ra_upload', 0); |
||
60 | } |
||
61 | |||
321 | daniel-mar | 62 | public function action($actionID, $params) { |
317 | daniel-mar | 63 | |
321 | daniel-mar | 64 | if ($actionID == 'deleteAttachment') { |
65 | $id = $params['id']; |
||
317 | daniel-mar | 66 | $obj = OIDplusObject::parse($id); |
360 | daniel-mar | 67 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
68 | if (!$obj->userHasWriteRights()) throw new OIDplusException(_L('Authentication error. Please log in as admin, or as the RA of "%1" to upload an attachment.',$id)); |
||
310 | daniel-mar | 69 | |
70 | if (!OIDplus::authUtils()::isAdminLoggedIn() && !$this->raMayDelete()) { |
||
360 | daniel-mar | 71 | throw new OIDplusException(_L('The administrator has disabled deleting attachments by RAs.')); |
310 | daniel-mar | 72 | } |
73 | |||
321 | daniel-mar | 74 | $req_filename = $params['filename']; |
360 | daniel-mar | 75 | if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name')); |
76 | if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
77 | if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
78 | if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name')); |
||
310 | daniel-mar | 79 | |
80 | $uploaddir = self::getUploadDir($id); |
||
503 | daniel-mar | 81 | $uploadfile = $uploaddir . DIRECTORY_SEPARATOR . basename($req_filename); |
310 | daniel-mar | 82 | |
360 | daniel-mar | 83 | if (!file_exists($uploadfile)) throw new OIDplusException(_L('File does not exist')); |
310 | daniel-mar | 84 | @unlink($uploadfile); |
85 | if (file_exists($uploadfile)) { |
||
386 | daniel-mar | 86 | OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Attachment file '".basename($uploadfile)."' could not be deleted from object '$id' (problem with permissions?)"); |
87 | $msg = _L('Attachment file "%1" could not be deleted from object "%2" (problem with permissions?)',basename($uploadfile),$id); |
||
310 | daniel-mar | 88 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
89 | throw new OIDplusException($msg); |
||
90 | } else { |
||
360 | daniel-mar | 91 | throw new OIDplusException($msg.'. '._L('Please contact the system administrator.')); |
310 | daniel-mar | 92 | } |
317 | daniel-mar | 93 | } else { |
94 | // If it was the last file, delete the empty directory |
||
503 | daniel-mar | 95 | $ary = glob($uploaddir . DIRECTORY_SEPARATOR . '*'); |
317 | daniel-mar | 96 | if (count($ary) == 0) @rmdir($uploaddir); |
310 | daniel-mar | 97 | } |
98 | |||
99 | OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Deleted attachment '".basename($uploadfile)."' from object '$id'"); |
||
100 | |||
328 | daniel-mar | 101 | return array("status" => 0); |
322 | daniel-mar | 102 | |
321 | daniel-mar | 103 | } else if ($actionID == 'uploadAttachment') { |
322 | daniel-mar | 104 | |
321 | daniel-mar | 105 | $id = $params['id']; |
310 | daniel-mar | 106 | $obj = OIDplusObject::parse($id); |
360 | daniel-mar | 107 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
108 | if (!$obj->userHasWriteRights()) throw new OIDplusException(_L('Authentication error. Please log in as admin, or as the RA of "%1" to upload an attachment.',$id)); |
||
310 | daniel-mar | 109 | |
110 | if (!OIDplus::authUtils()::isAdminLoggedIn() && !$this->raMayUpload()) { |
||
360 | daniel-mar | 111 | throw new OIDplusException(_L('The administrator has disabled uploading attachments by RAs.')); |
310 | daniel-mar | 112 | } |
113 | |||
327 | daniel-mar | 114 | if (!isset($_FILES['userfile'])) { |
360 | daniel-mar | 115 | throw new OIDplusException(_L('Please choose a file.')); |
327 | daniel-mar | 116 | } |
117 | |||
310 | daniel-mar | 118 | if (!OIDplus::authUtils()::isAdminLoggedIn()) { |
119 | $banned = explode(',', OIDplus::config()->getValue('attachments_block_extensions', '')); |
||
120 | foreach ($banned as $ext) { |
||
121 | $ext = trim($ext); |
||
122 | if ($ext == '') continue; |
||
123 | if (strtolower(substr(basename($_FILES['userfile']['name']), -strlen($ext)-1)) == strtolower('.'.$ext)) { |
||
360 | daniel-mar | 124 | throw new OIDplusException(_L('The file extension "%1" is banned by the administrator (it can be uploaded by the administrator though)',$ext)); |
310 | daniel-mar | 125 | } |
126 | } |
||
127 | } |
||
128 | |||
129 | $req_filename = $_FILES['userfile']['name']; |
||
360 | daniel-mar | 130 | if (strpos($req_filename, '/') !== false) throw new OIDplusException(_L('Illegal file name')); |
131 | if (strpos($req_filename, '\\') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
132 | if (strpos($req_filename, '..') !== false) throw new OIDplusException(_L('Illegal file name')); |
||
133 | if (strpos($req_filename, chr(0)) !== false) throw new OIDplusException(_L('Illegal file name')); |
||
310 | daniel-mar | 134 | |
135 | $uploaddir = self::getUploadDir($id); |
||
503 | daniel-mar | 136 | $uploadfile = $uploaddir . DIRECTORY_SEPARATOR . basename($req_filename); |
310 | daniel-mar | 137 | |
138 | if (!is_dir($uploaddir)) { |
||
139 | @mkdir($uploaddir, 0777, true); |
||
140 | if (!is_dir($uploaddir)) { |
||
360 | daniel-mar | 141 | OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Upload attachment '".basename($uploadfile)."' to object '$id' failed: Cannot create directory '".basename($uploaddir)."' (problem with permissions?)"); |
142 | $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot create directory "%1" (problem with permissions?)',basename($uploaddir)); |
||
310 | daniel-mar | 143 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
144 | throw new OIDplusException($msg); |
||
145 | } else { |
||
360 | daniel-mar | 146 | throw new OIDplusException($msg.'. '._L('Please contact the system administrator.')); |
310 | daniel-mar | 147 | } |
148 | } |
||
149 | } |
||
150 | |||
151 | if (!@move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) { |
||
360 | daniel-mar | 152 | OIDplus::logger()->log("[ERR]OID($id)+[ERR]A!", "Upload attachment '".basename($uploadfile)."' to object '$id' failed: Cannot move uploaded file into directory (problem with permissions?)"); |
153 | $msg = _L('Upload attachment "%1" to object "%2" failed',basename($uploadfile),$id).': '._L('Cannot move uploaded file into directory (problem with permissions?)'); |
||
310 | daniel-mar | 154 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
155 | throw new OIDplusException($msg); |
||
156 | } else { |
||
360 | daniel-mar | 157 | throw new OIDplusException($msg.'. '._L('Please contact the system administrator.')); |
310 | daniel-mar | 158 | } |
159 | } |
||
160 | |||
161 | OIDplus::logger()->log("[OK]OID($id)+[?INFO/!OK]OIDRA($id)?/[?INFO/!OK]A?", "Uploaded attachment '".basename($uploadfile)."' to object '$id'"); |
||
162 | |||
328 | daniel-mar | 163 | return array("status" => 0); |
321 | daniel-mar | 164 | } else { |
360 | daniel-mar | 165 | throw new OIDplusException(_L('Unknown action ID')); |
310 | daniel-mar | 166 | } |
167 | } |
||
168 | |||
169 | public function init($html=true) { |
||
170 | OIDplus::config()->prepareConfigKey('attachments_block_extensions', 'Block file name extensions being used in file attachments (comma separated)', 'exe,scr,pif,bat,com,vbs,cmd', OIDplusConfig::PROTECTION_EDITABLE, function($value) { |
||
171 | }); |
||
172 | OIDplus::config()->prepareConfigKey('attachments_allow_ra_delete', 'Allow that RAs delete file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) { |
||
173 | if (!is_numeric($value) || ($value < 0) || ($value > 1)) { |
||
360 | daniel-mar | 174 | throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).')); |
310 | daniel-mar | 175 | } |
176 | }); |
||
177 | OIDplus::config()->prepareConfigKey('attachments_allow_ra_upload', 'Allow that RAs upload file attachments? (0=no, 1=yes)', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) { |
||
178 | if (!is_numeric($value) || ($value < 0) || ($value > 1)) { |
||
360 | daniel-mar | 179 | throw new OIDplusException(_L('Please enter a valid value (0=no, 1=yes).')); |
310 | daniel-mar | 180 | } |
181 | }); |
||
182 | } |
||
183 | |||
184 | public function gui($id, &$out, &$handled) { |
||
185 | // Nothing |
||
186 | } |
||
187 | |||
188 | public function publicSitemap(&$out) { |
||
189 | // Nothing |
||
190 | } |
||
191 | |||
192 | public function tree(&$json, $ra_email=null, $nonjs=false, $req_goto='') { |
||
193 | return false; |
||
194 | } |
||
195 | |||
196 | private static function convert_filesize($bytes, $decimals = 2){ |
||
360 | daniel-mar | 197 | $size = array(_L('Bytes'),_L('KiB'),_L('MiB'),_L('GiB'),_L('TiB'),_L('PiB'),_L('EiB'),_L('ZiB'),_L('YiB')); |
310 | daniel-mar | 198 | $factor = floor((strlen($bytes) - 1) / 3); |
199 | return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor]; |
||
200 | } |
||
201 | |||
202 | public function implementsFeature($id) { |
||
203 | if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.2') return true; // modifyContent |
||
321 | daniel-mar | 204 | if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.3') return true; // beforeObject*, afterObject* |
322 | daniel-mar | 205 | if (strtolower($id) == '1.3.6.1.4.1.37476.2.5.2.3.4') return true; // whois*Attributes |
310 | daniel-mar | 206 | return false; |
207 | } |
||
208 | |||
209 | public function modifyContent($id, &$title, &$icon, &$text) { |
||
210 | // Interface 1.3.6.1.4.1.37476.2.5.2.3.2 |
||
211 | |||
503 | daniel-mar | 212 | $files = glob(self::getUploadDir($id) . DIRECTORY_SEPARATOR . '*'); |
310 | daniel-mar | 213 | $doshow = false; |
214 | $output = ''; |
||
314 | daniel-mar | 215 | $found_files = false; |
310 | daniel-mar | 216 | |
217 | $obj = OIDplusObject::parse($id); |
||
360 | daniel-mar | 218 | if ($obj === null) throw new OIDplusException(_L('Invalid object "%1"',$id)); |
310 | daniel-mar | 219 | $can_upload = OIDplus::authUtils()::isAdminLoggedIn() || ($this->raMayUpload() && $obj->userHasWriteRights()); |
220 | $can_delete = OIDplus::authUtils()::isAdminLoggedIn() || ($this->raMayDelete() && $obj->userHasWriteRights()); |
||
221 | |||
360 | daniel-mar | 222 | $output .= '<h2>'._L('File attachments').'</h2>'; |
310 | daniel-mar | 223 | $output .= '<div class="container box">'; |
224 | |||
225 | if (OIDplus::authUtils()::isAdminLoggedIn()) { |
||
360 | daniel-mar | 226 | $output .= '<p>'._L('Admin info: The directory is %1','<b>'.htmlentities(self::getUploadDir($id)).'</b>').'</p>'; |
310 | daniel-mar | 227 | $doshow = true; |
228 | } |
||
229 | |||
230 | $output .= '<div id="fileattachments_table" class="table-responsive">'; |
||
231 | $output .= '<table class="table table-bordered table-striped">'; |
||
232 | $output .= '<tr>'; |
||
360 | daniel-mar | 233 | $output .= '<th>'._L('Filename').'</th>'; |
234 | $output .= '<th>'._L('Size').'</th>'; |
||
235 | $output .= '<th>'._L('File type').'</th>'; |
||
236 | $output .= '<th>'._L('Download').'</th>'; |
||
237 | if ($can_delete) $output .= '<th>'._L('Delete').'</th>'; |
||
310 | daniel-mar | 238 | $output .= '</tr>'; |
239 | foreach ($files as $file) { |
||
314 | daniel-mar | 240 | if (is_dir($file)) continue; |
241 | |||
310 | daniel-mar | 242 | $output .= '<tr>'; |
243 | $output .= '<td>'.htmlentities(basename($file)).'</td>'; |
||
244 | $output .= '<td>'.htmlentities(self::convert_filesize(filesize($file), 0)).'</td>'; |
||
373 | daniel-mar | 245 | $lookup_files = array( |
496 | daniel-mar | 246 | OIDplus::localpath().'userdata/attachments/filetypes$'.OIDplus::getCurrentLang().'.conf', |
247 | OIDplus::localpath().'userdata/attachments/filetypes.conf', |
||
248 | OIDplus::localpath().'3p/vts_fileformats/filetypes$'.OIDplus::getCurrentLang().'.local', // not recommended |
||
249 | OIDplus::localpath().'3p/vts_fileformats/filetypes.local', // not recommended |
||
250 | OIDplus::localpath().'3p/vts_fileformats/filetypes$'.OIDplus::getCurrentLang().'.conf', |
||
251 | OIDplus::localpath().'3p/vts_fileformats/filetypes.conf' |
||
373 | daniel-mar | 252 | ); |
310 | daniel-mar | 253 | $output .= '<td>'.htmlentities(VtsFileTypeDetect::getDescription($file, $lookup_files)).'</td>'; |
254 | |||
360 | daniel-mar | 255 | $output .= ' <td><button type="button" name="download_'.md5($file).'" id="download_'.md5($file).'" class="btn btn-success btn-xs download" onclick="downloadAttachment('.js_escape(OIDplus::webpath(__DIR__)).', current_node,'.js_escape(basename($file)).')">'._L('Download').'</button></td>'; |
310 | daniel-mar | 256 | if ($can_delete) { |
360 | daniel-mar | 257 | $output .= ' <td><button type="button" name="delete_'.md5($file).'" id="delete_'.md5($file).'" class="btn btn-danger btn-xs delete" onclick="deleteAttachment(current_node,'.js_escape(basename($file)).')">'._L('Delete').'</button></td>'; |
310 | daniel-mar | 258 | } |
259 | |||
260 | $output .= '</tr>'; |
||
261 | $doshow = true; |
||
314 | daniel-mar | 262 | $found_files = true; |
310 | daniel-mar | 263 | } |
264 | |||
360 | daniel-mar | 265 | if (!$found_files) $output .= '<tr><td colspan="'.($can_delete ? 5 : 4).'"><i>'._L('No attachments').'</i></td></tr>'; |
310 | daniel-mar | 266 | |
267 | $output .= '</table></div>'; |
||
268 | |||
269 | if ($can_upload) { |
||
428 | daniel-mar | 270 | $output .= '<form action="javascript:void(0);" onsubmit="return uploadAttachmentOnSubmit(this);" enctype="multipart/form-data" id="uploadAttachmentForm">'; |
310 | daniel-mar | 271 | $output .= '<input type="hidden" name="id" value="'.htmlentities($id).'">'; |
360 | daniel-mar | 272 | $output .= '<div>'._L('Add a file attachment').':<input type="file" name="userfile" value="" id="fileAttachment">'; |
273 | $output .= '<br><input type="submit" value="'._L('Upload').'"></div>'; |
||
310 | daniel-mar | 274 | $output .= '</form>'; |
275 | $doshow = true; |
||
276 | } |
||
277 | |||
278 | $output .= '</div>'; |
||
279 | |||
280 | if ($doshow) $text .= $output; |
||
281 | } |
||
282 | |||
321 | daniel-mar | 283 | public function beforeObjectDelete($id) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
284 | public function afterObjectDelete($id) { |
||
285 | // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
286 | // Delete the attachment folder including all files in it (note: Subfolders are not possible) |
||
287 | $uploaddir = self::getUploadDir($id); |
||
288 | if ($uploaddir != '') { |
||
503 | daniel-mar | 289 | $ary = glob($uploaddir . DIRECTORY_SEPARATOR . '*'); |
321 | daniel-mar | 290 | foreach ($ary as $a) @unlink($a); |
291 | @rmdir($uploaddir); |
||
292 | if (is_dir($uploaddir)) { |
||
293 | OIDplus::logger()->log("[WARN]OID($id)+[WARN]A!", "Attachment directory '$uploaddir' could not be deleted during the deletion of the OID"); |
||
294 | } |
||
295 | } |
||
296 | } |
||
297 | public function beforeObjectUpdateSuperior($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
298 | public function afterObjectUpdateSuperior($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
299 | public function beforeObjectUpdateSelf($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
300 | public function afterObjectUpdateSelf($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
301 | public function beforeObjectInsert($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
302 | public function afterObjectInsert($id, &$params) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.3 |
||
303 | |||
310 | daniel-mar | 304 | public function tree_search($request) { |
305 | return false; |
||
306 | } |
||
322 | daniel-mar | 307 | |
308 | public function whoisObjectAttributes($id, &$out) { |
||
309 | // Interface 1.3.6.1.4.1.37476.2.5.2.3.4 |
||
310 | |||
503 | daniel-mar | 311 | $files = glob(self::getUploadDir($id) . DIRECTORY_SEPARATOR . '*'); |
322 | daniel-mar | 312 | foreach ($files as $file) { |
313 | $out[] = 'attachment-name: '.basename($file); |
||
496 | daniel-mar | 314 | $out[] = 'attachment-url: '.OIDplus::webpath(__DIR__).'download.php?id='.urlencode($id).'&filename='.urlencode(basename($file)); |
322 | daniel-mar | 315 | } |
316 | |||
317 | } |
||
318 | public function whoisRaAttributes($email, &$out) {} // Interface 1.3.6.1.4.1.37476.2.5.2.3.4 |
||
373 | daniel-mar | 319 | } |