Applied patch #411
[elgg.git] / lib / uploadlib.php
blob1e46bf0e204a856bc1211eabeba5cdc0d30b9ab1
1 <?php
3 /**
4 * uploadlib.php - This class handles all aspects of fileuploading
6 * @author Penny Leach
7 * @version 1.5
8 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
9 * @package moodlecore
13 /**
14 * This class handles all aspects of fileuploading
16 class upload_manager {
18 /**
19 * Array to hold local copies of stuff in $_FILES
20 * @var array $files
22 var $files;
23 /**
24 * Holds all configuration stuff
25 * @var array $config
27 var $config;
28 /**
29 * Keep track of if we're ok
30 * (errors for each file are kept in $files['whatever']['uploadlog']
31 * @var boolean $status
33 var $status;
34 /**
35 * If we're only getting one file.
36 * (for logging and virus notifications)
37 * @var string $inputname
39 var $inputname;
40 /**
41 * If we're given silent=true in the constructor, this gets built
42 * up to hold info about the process.
43 * @var string $notify
45 var $notify;
47 /**
48 * Constructor, sets up configuration stuff so we know how to act.
50 * Note: destination not taken as parameter as some modules want to use the insertid in the path and we need to check the other stuff first.
52 * @uses $CFG
53 * @param string $inputname If this is given the upload manager will only process the file in $_FILES with this name.
54 * @param boolean $deleteothers Whether to delete other files in the destination directory (optional, defaults to false)
55 * @param boolean $handlecollisions Whether to use {@link handle_filename_collision()} or not. (optional, defaults to false)
56 * @param boolean $recoverifmultiple If we come across a virus, or if a file doesn't validate or whatever, do we continue? optional, defaults to true.
57 * @param int $maxbytes max bytes for this file {@link get_max_upload_file_size()}.
58 * @param boolean $silent Whether to notify errors or not.
59 * @param boolean $allownull Whether we care if there's no file when we've set the input name.
60 * @param boolean $allownullmultiple Whether we care if there's no files AT ALL when we've got multiples. This won't complain if we have file 1 and file 3 but not file 2, only for NO FILES AT ALL.
62 function upload_manager($inputname='', $deleteothers=false, $handlecollisions=false, $recoverifmultiple=false, $maxbytes=0, $silent=false, $allownull=false, $allownullmultiple=true) {
64 global $CFG;
66 $this->config->deleteothers = $deleteothers;
67 $this->config->handlecollisions = $handlecollisions;
68 $this->config->recoverifmultiple = $recoverifmultiple;
69 $this->config->maxbytes = get_max_upload_file_size($maxbytes);
70 $this->config->silent = $silent;
71 $this->config->allownull = $allownull;
72 $this->files = array();
73 $this->status = false;
74 $this->inputname = $inputname;
75 if (empty($this->inputname)) {
76 $this->config->allownull = $allownullmultiple;
80 /**
81 * Gets all entries out of $_FILES and stores them locally in $files and then
82 * checks each one against {@link get_max_upload_file_size()} and calls {@link cleanfilename()}
83 * and scans them for viruses etc.
84 * @uses $CFG
85 * @uses $_FILES
86 * @return boolean
88 function preprocess_files() {
89 global $CFG;
91 foreach ($_FILES as $name => $file) {
92 $this->status = true; // only set it to true here so that we can check if this function has been called.
93 if (empty($this->inputname) || $name == $this->inputname) { // if we have input name, only process if it matches.
94 $file['originalname'] = $file['name']; // do this first for the log.
95 $this->files[$name] = $file; // put it in first so we can get uploadlog out in print_upload_log.
96 $this->files[$name]['uploadlog'] = '';
97 $this->status = $this->validate_file($this->files[$name]); // default to only allowing empty on multiple uploads.
98 if (!$this->status && ($this->files[$name]['error'] == 0 || $this->files[$name]['error'] == 4) && ($this->config->allownull || empty($this->inputname))) {
99 // this shouldn't cause everything to stop.. modules should be responsible for knowing which if any are compulsory.
100 continue;
102 if ($this->status && !empty($CFG->runclamonupload)) {
103 $this->status = clam_scan_file($this->files[$name]);
105 if (!$this->status) {
106 if (!$this->config->recoverifmultiple && count($this->files) > 1) {
107 $a->name = $this->files[$name]['originalname'];
108 $a->problem = $this->files[$name]['uploadlog'];
109 $msg = sprintf(__gettext('Your file upload has failed because there was a problem with one of the files, %s.<br /> Here is a log of the problems:<br />%s<br />Not recovering.'), $a->name, $a->problem);
110 if (!$this->config->silent) {
111 notify($msg);
113 else {
114 $this->notify .= '<br />'. $msg;
116 $this->status = false;
117 return false;
119 } else if (count($this->files) == 1) {
121 if (!$this->config->silent and !$this->config->allownull) {
122 notify($this->files[$name]['uploadlog']);
123 } else {
124 $this->notify .= '<br />'. $this->files[$name]['uploadlog'];
126 $this->status = false;
127 return false;
130 else {
131 $newname = clean_filename($this->files[$name]['name']);
132 if ($newname != $this->files[$name]['name']) {
133 $a->oldname = $this->files[$name]['name'];
134 $a->newname = $newname;
135 $this->files[$name]['uploadlog'] .= sprintf(__gettext('File was renamed from %s to %s because of invalid characters.'), $a->oldname, $a->newname);
137 $this->files[$name]['name'] = $newname;
138 $this->files[$name]['clear'] = true; // ok to save.
142 if (!is_array($_FILES) || count($_FILES) == 0) {
143 return $this->config->allownull;
145 $this->status = true;
146 return true; // if we've got this far it means that we're recovering so we want status to be ok.
150 * Validates a single file entry from _FILES
152 * @param object $file The entry from _FILES to validate
153 * @return boolean True if ok.
155 function validate_file(&$file) {
156 if (empty($file)) {
157 return false;
159 if (!is_uploaded_file($file['tmp_name']) || $file['size'] == 0) {
160 $file['uploadlog'] .= "\n".$this->get_file_upload_error($file);
161 return false;
163 if ($file['size'] > $this->config->maxbytes) {
164 $file['uploadlog'] .= "\n". sprintf(__gettext('Sorry, but that file is too big (limit is %s)'), display_size($this->config->maxbytes));
165 return false;
167 return true;
170 /**
171 * Moves all the files to the destination directory.
173 * @uses $CFG
174 * @uses $USER
175 * @param string $destination The destination directory.
176 * @return boolean status;
178 function save_files($destination) {
179 global $CFG, $USER;
180 $textlib = textlib_get_instance();
182 if (!$this->status) { // preprocess_files hasn't been run
183 $this->preprocess_files();
185 if ($this->status) {
186 if (!($textlib->strpos($destination, $CFG->dataroot) === false)) {
187 // take it out for giving to make_upload_directory
188 $destination = $textlib->substr($destination, $textlib->strlen($CFG->dataroot));
191 if ($destination{$textlib->strlen($destination)-1} == '/') { // strip off a trailing / if we have one
192 $destination = $textlib->substr($destination, 0, -1);
195 if (!make_upload_directory($destination, true)) { //TODO maybe put this function here instead of moodlelib.php now.
196 $this->status = false;
197 return false;
200 $destination = $CFG->dataroot . $destination; // now add it back in so we have a full path
202 $exceptions = array(); //need this later if we're deleting other files.
204 foreach (array_keys($this->files) as $i) {
206 if (!$this->files[$i]['clear'] || !isset($this->files[$i]['clear'])) {
207 // not ok to save
208 continue;
211 if ($this->config->handlecollisions) {
212 $this->handle_filename_collision($destination, $this->files[$i]);
214 if (move_uploaded_file($this->files[$i]['tmp_name'], $destination.'/'.$this->files[$i]['name'])) {
215 chmod($destination .'/'. $this->files[$i]['name'], $CFG->filepermissions);
216 $this->files[$i]['fullpath'] = $destination.'/'.$this->files[$i]['name'];
217 $this->files[$i]['uploadlog'] .= "\n".__gettext('File uploaded successfully');
218 $this->files[$i]['saved'] = true;
219 $exceptions[] = $this->files[$i]['name'];
220 // now add it to the log (this is important so we know who to notify if a virus is found later on)
221 clam_log_upload($this->files[$i]['fullpath']);
222 $savedsomething=true;
225 if (!empty($savedsomething) && $this->config->deleteothers) {
226 $this->delete_other_files($destination, $exceptions);
229 if (empty($savedsomething)) {
230 $this->status = false;
231 if ((empty($this->config->allownull) && !empty($this->inputname)) || (empty($this->inputname) && empty($this->config->allownullmultiple))) {
232 notify(__gettext('No file was found - are you sure you selected one to upload?'));
234 return false;
236 return $this->status;
240 * Wrapper function that calls {@link preprocess_files()} and {@link viruscheck_files()} and then {@link save_files()}
241 * Modules that require the insert id in the filepath should not use this and call these functions seperately in the required order.
242 * @parameter string $destination Where to save the uploaded files to.
243 * @return boolean
245 function process_file_uploads($destination) {
246 if ($this->preprocess_files()) {
247 return $this->save_files($destination);
249 return false;
252 /**
253 * Deletes all the files in a given directory except for the files in $exceptions (full paths)
255 * @param string $destination The directory to clean up.
256 * @param array $exceptions Full paths of files to KEEP.
258 function delete_other_files($destination, $exceptions=null) {
259 if ($filestodel = get_directory_list($destination)) {
260 foreach ($filestodel as $file) {
261 if (!is_array($exceptions) || !in_array($file, $exceptions)) {
262 unlink($destination .'/'. $file);
263 $deletedsomething = true;
267 if ($deletedsomething) {
268 $msg = __gettext('The old file(s) in your upload area have been deleted');
269 if (!$this->config->silent) {
270 notify($msg);
272 else {
273 $this->notify .= '<br />'. $msg;
279 * Handles filename collisions - if the desired filename exists it will rename it according to the pattern in $format
280 * @param string $destination Destination directory (to check existing files against)
281 * @param object $file Passed in by reference. The current file from $files we're processing.
282 * @param string $format The printf style format to rename the file to (defaults to filename_number.extn)
283 * @return string The new filename.
284 * @todo verify return type - this function does not appear to return anything since $file is passed in by reference
286 function handle_filename_collision($destination, &$file, $format='%s_%d.%s') {
287 $bits = explode('.', $file['name']);
288 // check for collisions and append a nice numberydoo.
289 if (file_exists($destination .'/'. $file['name'])) {
290 $a->oldname = $file['name'];
291 for ($i = 1; true; $i++) {
292 $try = sprintf($format, $bits[0], $i, $bits[1]);
293 if ($this->check_before_renaming($destination, $try, $file)) {
294 $file['name'] = $try;
295 break;
298 $a->newname = $file['name'];
299 $file['uploadlog'] .= "\n". sprintf(__gettext('File was renamed from %s to %s because there was a filename conflict.'), $a->oldname, $a->newname);
304 * This function checks a potential filename against what's on the filesystem already and what's been saved already.
305 * @param string $destination Destination directory (to check existing files against)
306 * @param string $nametocheck The filename to be compared.
307 * @param object $file The current file from $files we're processing.
308 * return boolean
310 function check_before_renaming($destination, $nametocheck, $file) {
311 if (!file_exists($destination .'/'. $nametocheck)) {
312 return true;
314 if ($this->config->deleteothers) {
315 foreach ($this->files as $tocheck) {
316 // if we're deleting files anyway, it's not THIS file and we care about it and it has the same name and has already been saved..
317 if ($file['tmp_name'] != $tocheck['tmp_name'] && $tocheck['clear'] && $nametocheck == $tocheck['name'] && $tocheck['saved']) {
318 $collision = true;
321 if (!$collision) {
322 return true;
325 return false;
331 * @param object $file Passed in by reference. The current file from $files we're processing.
332 * @return string
333 * @todo Finish documenting this function
335 function get_file_upload_error(&$file) {
337 switch ($file['error']) {
338 case 0: // UPLOAD_ERR_OK
339 if ($file['size'] > 0) {
340 $errmessage = sprintf(__gettext('An unknown problem occurred while uploading the file \'%s\' (perhaps it was too large?)'), $file['name']);
341 } else {
342 $errmessage = __gettext('No file was found - are you sure you selected one to upload?'); /// probably a dud file name
344 break;
346 case 1: // UPLOAD_ERR_INI_SIZE
347 $errmessage = __gettext('Uploaded file exceeded the maximum size limit set by the server');
348 break;
350 case 2: // UPLOAD_ERR_FORM_SIZE
351 $errmessage = __gettext('Uploaded file exceeded the maximum size limit set by the form');
352 break;
354 case 3: // UPLOAD_ERR_PARTIAL
355 $errmessage = __gettext('File was only partially uploaded');
356 break;
358 case 4: // UPLOAD_ERR_NO_FILE
359 $errmessage = __gettext('No file was found - are you sure you selected one to upload?');
360 break;
362 default:
363 $errmessage = sprintf(__gettext('An unknown problem occurred while uploading the file \'%s\' (perhaps it was too large?)'), $file['name']);
365 return $errmessage;
369 * prints a log of everything that happened (of interest) to each file in _FILES
370 * @param $return - optional, defaults to false (log is echoed)
372 function print_upload_log($return=false,$skipemptyifmultiple=false) {
373 foreach (array_keys($this->files) as $i => $key) {
374 if (count($this->files) > 1 && !empty($skipemptyifmultiple) && $this->files[$key]['error'] == 4) {
375 continue;
377 $str .= '<strong>'. sprintf(__gettext('Upload log for file %u'), $i+1) .' '
378 .((!empty($this->files[$key]['originalname'])) ? '('.$this->files[$key]['originalname'].')' : '')
379 .'</strong> :'. nl2br($this->files[$key]['uploadlog']) .'<br />';
381 if ($return) {
382 return $str;
384 echo $str;
388 * If we're only handling one file (if inputname was given in the constructor) this will return the (possibly changed) filename of the file.
389 @return boolean
391 function get_new_filename() {
392 if (!empty($this->inputname) && count($this->files) == 1) {
393 return $this->files[$this->inputname]['name'];
395 return false;
398 /**
399 * If we're only handling one file (if input name was given in the constructor) this will return the full path to the saved file.
400 * @return boolean
402 function get_new_filepath() {
403 if (!empty($this->inputname) && count($this->files) == 1) {
404 return $this->files[$this->inputname]['fullpath'];
406 return false;
409 /**
410 * If we're only handling one file (if inputname was given in the constructor) this will return the ORIGINAL filename of the file.
411 * @return boolean
413 function get_original_filename() {
414 if (!empty($this->inputname) && count($this->files) == 1) {
415 return $this->files[$this->inputname]['originalname'];
417 return false;
421 * If we're only handling on file (if inputname was given in the constructor) this will return the size of the file.
423 function get_filesize() {
424 if (!empty($this->inputname) && count($this->files) == 1) {
425 return $this->files[$this->inputname]['size'];
430 /**
431 * This function returns any errors wrapped up in red.
432 * @return string
434 function get_errors() {
435 return $this->notify ;
439 /**************************************************************************************
440 THESE FUNCTIONS ARE OUTSIDE THE CLASS BECAUSE THEY NEED TO BE CALLED FROM OTHER PLACES.
441 FOR EXAMPLE CLAM_HANDLE_INFECTED_FILE AND CLAM_REPLACE_INFECTED_FILE USED FROM CRON
442 UPLOAD_PRINT_FORM_FRAGMENT DOESN'T REALLY BELONG IN THE CLASS BUT CERTAINLY IN THIS FILE
443 ***************************************************************************************/
447 * This function prints out a number of upload form elements.
449 * @param int $numfiles The number of elements required (optional, defaults to 1)
450 * @param array $names Array of element names to use (optional, defaults to FILE_n)
451 * @param array $descriptions Array of strings to be printed out before each file bit.
452 * @param boolean $uselabels -Whether to output text fields for file descriptions or not (optional, defaults to false)
453 * @param array $labelnames Array of element names to use for labels (optional, defaults to LABEL_n)
454 * @param int $maxbytes used to calculate upload max size ( using {@link get_max_upload_file_size})
455 * @param boolean $return -Whether to return the string (defaults to false - string is echoed)
456 * @return string Form returned as string if $return is true
458 function upload_print_form_fragment($numfiles=1, $names=null, $descriptions=null, $uselabels=false, $labelnames=null, $maxbytes=0, $return=false) {
459 global $CFG;
460 $maxbytes = get_max_upload_file_size($CFG->maxbytes, $coursebytes, $maxbytes);
461 $str = '<input type="hidden" name="MAX_FILE_SIZE" value="'. $maxbytes .'" />'."\n";
462 for ($i = 0; $i < $numfiles; $i++) {
463 if (is_array($descriptions) && !empty($descriptions[$i])) {
464 $str .= '<strong>'. $descriptions[$i] .'</strong><br />';
466 $name = ((is_array($names) && !empty($names[$i])) ? $names[$i] : 'FILE_'.$i);
467 $str .= '<input type="file" size="50" name="'. $name .'" alt="'. $name .'" /><br />'."\n";
468 if ($uselabels) {
469 $lname = ((is_array($labelnames) && !empty($labelnames[$i])) ? $labelnames[$i] : 'LABEL_'.$i);
470 $str .= __gettext('Title:').' <input type="text" size="50" name="'. $lname .'" alt="'. $lname
471 .'" /><br /><br />'."\n";
474 if ($return) {
475 return $str;
477 else {
478 echo $str;
484 * Deals with an infected file - either moves it to a quarantinedir
485 * (specified in CFG->quarantinedir) or deletes it.
487 * If moving it fails, it deletes it.
489 *@uses $CFG
490 * @uses $USER
491 * @param string $file Full path to the file
492 * @param int $userid If not used, defaults to $USER->id (there in case called from cron)
493 * @param boolean $basiconly Admin level reporting or user level reporting.
494 * @return string Details of what the function did.
496 function clam_handle_infected_file($file, $userid=0, $basiconly=false) {
498 global $CFG, $USER;
499 if ($USER && !$userid) {
500 $userid = $USER->ident;
502 $delete = true;
503 if (file_exists($CFG->quarantinedir) && is_dir($CFG->quarantinedir) && is_writable($CFG->quarantinedir)) {
504 $now = date('YmdHis');
505 if (rename($file, $CFG->quarantinedir .'/'. $now .'-user-'. $userid .'-infected')) {
506 $delete = false;
507 clam_log_infected($file, $CFG->quarantinedir.'/'. $now .'-user-'. $userid .'-infected', $userid);
508 if ($basiconly) {
509 $notice .= "\n". __gettext('The file has been moved to a quarantine directory.');
511 else {
512 $notice .= "\n". sprintf(__gettext('The file has been moved to your specified quarantine directory, the new location is %s'), $CFG->quarantinedir.'/'. $now .'-user-'. $userid .'-infected');
515 else {
516 if ($basiconly) {
517 $notice .= "\n". __gettext('The file has been deleted');
519 else {
520 $notice .= "\n". sprintf(__gettext('Could not move the file into your specified quarantine directory, %s. You need to fix this as files are being deleted if they\'re found to be infected.'), $CFG->quarantinedir);
524 else {
525 if ($basiconly) {
526 $notice .= "\n". __gettext('The file has been deleted');
528 else {
529 $notice .= "\n". sprintf(__gettext('Could not move the file into your specified quarantine directory, %s. You need to fix this as files are being deleted if they\'re found to be infected.'), $CFG->quarantinedir);
532 if ($delete) {
533 if (unlink($file)) {
534 clam_log_infected($file, '', $userid);
535 $notice .= "\n". __gettext('The file has been deleted');
537 else {
538 if ($basiconly) {
539 // still tell the user the file has been deleted. this is only for admins.
540 $notice .= "\n". __gettext('The file has been deleted');
542 else {
543 $notice .= "\n". __gettext('The file could not be deleted');
547 return $notice;
551 * Replaces the given file with a string.
553 * The replacement string is used to notify that the original file had a virus
554 * This is to avoid missing files but could result in the wrong content-type.
555 * @param string $file Full path to the file.
556 * @return boolean
558 function clam_replace_infected_file($file) {
559 $newcontents = __gettext('This file that has been uploaded was found to contain a virus and has been moved or delted and the user notified.');
560 if (!$f = fopen($file, 'w')) {
561 return false;
563 if (!fwrite($f, $newcontents)) {
564 return false;
566 return true;
571 * If $CFG->runclamonupload is set, we scan a given file. (called from {@link preprocess_files()})
573 * This function will add on a uploadlog index in $file.
574 * @param mixed $file The file to scan from $files. or an absolute path to a file.
575 * @return int 1 if good, 0 if something goes wrong (opposite from actual error code from clam)
577 function clam_scan_file(&$file) {
578 global $CFG, $USER;
580 if (is_array($file) && is_uploaded_file($file['tmp_name'])) { // it's from $_FILES
581 $appendlog = true;
582 $fullpath = $file['tmp_name'];
584 else if (file_exists($file)) { // it's a path to somewhere on the filesystem!
585 $fullpath = $file;
587 else {
588 return false; // erm, what is this supposed to be then, huh?
591 $CFG->pathtoclam = trim($CFG->pathtoclam);
593 if (!$CFG->pathtoclam || !file_exists($CFG->pathtoclam) || !is_executable($CFG->pathtoclam)) {
594 $newreturn = 1;
595 $notice = sprintf(__gettext('Elgg is configured to run clam on file upload, but the path supplied to Clam AV, %s, is invalid.'), $CFG->pathtoclam);
596 if ($CFG->clamfailureonupload == 'actlikevirus') {
597 $notice .= "\n". __gettext('In addition, Elgg is configured so that if clam fails to run, files are treated like viruses. This essentially means that no student can upload a file successfully until you fix this.');
598 $notice .= "\n". clam_handle_infected_file($fullpath);
599 $newreturn = false;
601 clam_mail_admins($notice);
602 if ($appendlog) {
603 $file['uploadlog'] .= "\n". __gettext('Your administrator has enabled virus checking for file uploads but has misconfigured something.<br />Your file upload was NOT successful. Your administrator has been emailed to notify them so they can fix it.<br />Maybe try uploading this file later.');
604 $file['clam'] = 1;
606 return $newreturn; // return 1 if we're allowing clam failures
609 $cmd = $CFG->pathtoclam .' '. $fullpath ." 2>&1";
611 // before we do anything we need to change perms so that clamscan can read the file (clamdscan won't work otherwise)
612 chmod($fullpath,0644);
614 exec($cmd, $output, $return);
616 switch ($return) {
617 case 0: // glee! we're ok.
618 return 1; // translate clam return code into reasonable return code consistent with everything else.
619 case 1: // bad wicked evil, we have a virus.
620 $info->user = $USER->name;
621 $notice = sprintf(__gettext('Attention administrator! Clam AV has found a virus in a file uploaded by %s. Here is the output of clamscan:'), $info->user);
622 $notice .= "\n\n". implode("\n", $output);
623 $notice .= "\n\n". clam_handle_infected_file($fullpath);
624 clam_mail_admins($notice);
625 if ($appendlog) {
626 $info->filename = $file['originalname'];
627 $file['uploadlog'] .= "\n". sprintf(__gettext('The file you have uploaded, %s, has been scanned by a virus checker and found to be infected! Your file upload was NOT successful.'), $info->filename);
628 $file['virus'] = 1;
630 return false; // in this case, 0 means bad.
631 default:
632 // error - clam failed to run or something went wrong
633 $notice .= sprintf(__gettext('Clam AV has failed to run. The return error message was %s. Here is the output from Clam:'), get_clam_error_code($return));
634 $notice .= "\n\n". implode("\n", $output);
635 $newreturn = true;
636 if ($CFG->clamfailureonupload == 'actlikevirus') {
637 $notice .= "\n". clam_handle_infected_file($fullpath);
638 $newreturn = false;
640 clam_mail_admins($notice);
641 if ($appendlog) {
642 $file['uploadlog'] .= "\n". __gettext('Your administrator has enabled virus checking for file uploads but has misconfigured something.<br />Your file upload was NOT successful. Your administrator has been emailed to notify them so they can fix it.<br />Maybe try uploading this file later.');
643 $file['clam'] = 1;
645 return $newreturn; // return 1 if we're allowing failures.
650 * Emails admins about a clam outcome
652 * @param string $notice The body of the email to be sent.
654 function clam_mail_admins($notice) {
656 global $CFG;
658 $subject = sprintf(__gettext('%s :: Clam AV notification'), $CFG->sitename);
659 $user = new StdClass;
660 $user->email = $CFG->sysadminemail;
661 $user->name = $CFG->sitename.' '.__gettext('Administrator');
662 email_to_user($user,$user,$subject,$notice);
664 $admins = get_admins();
665 foreach ($admins as $admin) {
666 email_to_user($admin, get_admin(), $subject, $notice);
673 * Returns the string equivalent of a numeric clam error code
675 * @param int $returncode The numeric error code in question.
676 * return string The definition of the error code
678 function get_clam_error_code($returncode) {
679 $returncodes = array();
680 $returncodes[0] = 'No virus found.';
681 $returncodes[1] = 'Virus(es) found.';
682 $returncodes[2] = ' An error occured'; // specific to clamdscan
683 // all after here are specific to clamscan
684 $returncodes[40] = 'Unknown option passed.';
685 $returncodes[50] = 'Database initialization error.';
686 $returncodes[52] = 'Not supported file type.';
687 $returncodes[53] = 'Can\'t open directory.';
688 $returncodes[54] = 'Can\'t open file. (ofm)';
689 $returncodes[55] = 'Error reading file. (ofm)';
690 $returncodes[56] = 'Can\'t stat input file / directory.';
691 $returncodes[57] = 'Can\'t get absolute path name of current working directory.';
692 $returncodes[58] = 'I/O error, please check your filesystem.';
693 $returncodes[59] = 'Can\'t get information about current user from /etc/passwd.';
694 $returncodes[60] = 'Can\'t get information about user \'clamav\' (default name) from /etc/passwd.';
695 $returncodes[61] = 'Can\'t fork.';
696 $returncodes[63] = 'Can\'t create temporary files/directories (check permissions).';
697 $returncodes[64] = 'Can\'t write to temporary directory (please specify another one).';
698 $returncodes[70] = 'Can\'t allocate and clear memory (calloc).';
699 $returncodes[71] = 'Can\'t allocate memory (malloc).';
700 if ($returncodes[$returncode])
701 return $returncodes[$returncode];
702 return __gettext('There was an unknown error with clam.');
707 * Adds a file upload to the log table so that clam can resolve the filename to the user later if necessary
709 * @uses $CFG
710 * @uses $USER
711 * @param string $newfilepath ?
712 * @param boolean $nourl ?
713 * @todo Finish documenting this function
715 function clam_log_upload($newfilepath, $nourl=false) {
716 global $CFG, $USER;
717 // get rid of any double // that might have appeared
718 $newfilepath = preg_replace('/\/\//', '/', $newfilepath);
719 if (strpos($newfilepath, $CFG->dataroot) === false) {
720 $newfilepath = $CFG->dataroot . $newfilepath;
722 $courseid = 0;
723 //TODO fixme no course (Penny)
724 // add_to_log($courseid, 'upload', 'upload', ((!$nourl) ? substr($_SERVER['HTTP_REFERER'], 0, 100) : ''), $newfilepath);
728 * This function logs to error_log and to the log table that an infected file has been found and what's happened to it.
730 * @param string $oldfilepath Full path to the infected file before it was moved.
731 * @param string $newfilepath Full path to the infected file since it was moved to the quarantine directory (if the file was deleted, leave empty).
732 * @param int $userid The user id of the user who uploaded the file.
734 function clam_log_infected($oldfilepath='', $newfilepath='', $userid=0) {
736 // add_to_log(0, 'upload', 'infected', $_SERVER['HTTP_REFERER'], $oldfilepath, 0, $userid);//TODO fixme (Penny)
738 $user = get_record('users', 'ident', $userid);
740 $errorstr = 'Clam AV has found a file that is infected with a virus. It was uploaded by '
741 . ((empty($user)) ? ' an unknown user ' : $user->name)
742 . ((empty($oldfilepath)) ? '. The infected file was caught on upload ('.$oldfilepath.')'
743 : '. The original file path of the infected file was '. $oldfilepath)
744 . ((empty($newfilepath)) ? '. The file has been deleted ' : '. The file has been moved to a quarantine directory and the new path is '. $newfilepath);
746 error_log($errorstr);
751 * Create a directory.
753 * @uses $CFG
754 * @param string $directory a string of directory names under $CFG->dataroot eg stuff/assignment/1
755 * param boolean $shownotices If true then notification messages will be printed out on error.
756 * @return string|false Returns full path to directory if successful, false if not
758 function make_upload_directory($directory, $shownotices=true) {
760 global $CFG;
762 $currdir = $CFG->dataroot;
764 umask(0000);
766 if (!file_exists($currdir)) {
767 if (! mkdir($currdir, $CFG->directorypermissions)) {
768 if ($shownotices) {
769 notify('ERROR: You need to create the directory '. $currdir .' with web server write access');
771 return false;
773 if ($handle = fopen($currdir.'/.htaccess', 'w')) { // For safety
774 @fwrite($handle, "deny from all\r\n");
775 @fclose($handle);
779 $dirarray = explode('/', $directory);
781 foreach ($dirarray as $dir) {
782 $currdir = $currdir .'/'. $dir;
783 if (! file_exists($currdir)) {
784 if (! mkdir($currdir, $CFG->directorypermissions)) {
785 if ($shownotices) {
786 notify('ERROR: Could not find or create a directory ('. $currdir .')');
788 return false;
790 //@chmod($currdir, $CFG->directorypermissions); // Just in case mkdir didn't do it
794 return $currdir;