installation bug fix
[openemr.git] / library / edihistory / edih_uploads.php
blob5711601fa0c8b2f807c7c065fa91611a4aa255f9
1 <?PHP
2 /**
3 * edih_uploads.php
4 * Copyright 2012 Kevin McCormick
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; version 3 or later. You should have
14 * received a copy of the GNU General Public License along with this program;
15 * if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * <http://opensource.org/licenses/gpl-license.php>
21 * @author Kevin McCormick
22 * @link: http://www.open-emr.org
23 * @package OpenEMR
24 * @subpackage ediHistory
28 /**
29 * Rearrange the multi-file upload array
31 * @param array $_files -- the $_FILES array
32 * @param bool $top -- do not change, for internal use in function recursion
33 * @return array
35 function edih_upload_reindex(array $_files, $top = true) {
36 // blatantly copied from BigShark666 at gmail dot com 22-Nov-2011 06:51
37 // from php documentation for $_FILES predefined variable
39 $files = array();
40 foreach($_files as $name=>$file){
41 if($top) $sub_name = $file['name'];
42 else $sub_name = $name;
44 if(is_array($sub_name)){
45 foreach(array_keys($sub_name) as $key){
46 $files[$name][$key] = array(
47 'name' => $file['name'][$key],
48 'type' => $file['type'][$key],
49 'tmp_name' => $file['tmp_name'][$key],
50 'error' => $file['error'][$key],
51 'size' => $file['size'][$key],
53 $files[$name] = edih_upload_reindex($files[$name], false);
56 }else{
57 $files[$name] = $file;
60 return $files;
63 /**
64 * select error message in case of $_FILES error
66 * @param int
67 * @return string
69 function edih_upload_err_message($code) {
71 switch ($code) {
72 case UPLOAD_ERR_INI_SIZE:
73 $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini";
74 break;
75 case UPLOAD_ERR_FORM_SIZE:
76 $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form";
77 break;
78 case UPLOAD_ERR_PARTIAL:
79 $message = "The uploaded file was only partially uploaded";
80 break;
81 case UPLOAD_ERR_NO_FILE:
82 $message = "No file was uploaded";
83 break;
84 case UPLOAD_ERR_NO_TMP_DIR:
85 $message = "Missing a temporary folder";
86 break;
87 case UPLOAD_ERR_CANT_WRITE:
88 $message = "Failed to write file to disk";
89 break;
90 case UPLOAD_ERR_EXTENSION:
91 $message = "File upload stopped by extension";
92 break;
94 default:
95 $message = "Unknown upload error";
96 break;
98 return $message;
102 * Categorize and check uploaded files
104 * Files are typed and scanned, if they pass scan, then the file is moved
105 * to the edi_history temp dir and an array ['type'] ['name'] is returned
106 * If an error occurs, false is returned
108 * @uses edih_x12_file()
109 * @uses csv_file_type()
110 * @uses csv_edih_tmpdir()
111 * @param array $param_ar -- the csv_parameters("ALL") array (so we don't have to keep creating it)
112 * @param array $fidx -- individual file array from $files[$i] array
114 * @return array|bool
116 function edih_upload_match_file($param_ar, $fidx) {
118 // =============
119 $edih_upldir = csv_edih_tmpdir();
120 // =============
121 $ar_fn = array();
122 $ftype = '';
124 if (is_array($fidx) && isset($fidx['name']) ) {
125 $fn = basename($fidx['name']);
126 $ftmp = $fidx['tmp_name'];
127 } else {
128 csv_edihist_log('edih_upload_match_file: Error: invalid file argument');
129 return false;
131 //csv_check_x12_obj($filepath, $type='') {
132 $x12obj = new edih_x12_file($ftmp, false);
134 if ( $x12obj->edih_hasGS() ) {
135 $ftype = csv_file_type( $x12obj->edih_type() );
136 } elseif ( $x12obj->edih_valid() ) {
137 if ( is_array($param_ar) && count($param_ar)) {
138 // csv_parameters("ALL");
139 foreach ($param_ar as $ky=>$par) {
140 if ( !isset($param_ar[$ky]['regex']) ) { continue; }
141 if ( preg_match($param_ar[$ky]['regex'], $fn) ) {
142 $ftype = $ky;
143 break;
146 } else {
147 csv_edihist_log('edih_upload_match_file: invalid parameters');
148 return false;
150 } else {
151 // failed valdity test: unwanted characters or unmatched mime-type
152 csv_edihist_log('edih_upload_match_file: invalid x12_file '.strip_tags($x12obj->edih_message()));
153 return false;
156 if ( !$ftype ) {
157 csv_edihist_log( 'edih_upload_match_file: unable to classify file '.$fn );
158 $ar_fn['reject'] = array('name'=>$fn, 'comment'=>'unable to classify');
159 return $ar_fn;
162 $newname = $edih_upldir.DS.$fn;
164 if ( rename($ftmp, $newname) ) {
165 if ( chmod($newname, 0400) ) {
166 $ar_fn['type'] = $ftype;
167 $ar_fn['name'] = $newname;
168 } else {
169 csv_edihist_log( 'edih_upload_match_file: failed to set permissions for '.$fn );
170 $ar_fn['reject'] = array('name'=>$fn, 'comment'=>'failed to set permissions');
171 unlink($newname);
172 return false;
174 } else {
175 csv_edihist_log( "edih_upload_match_file: unable to move $fn to uploads directory");
176 return false;
179 return $ar_fn;
185 * Function to deal with zip archives of uploaded files
187 * This function examines the zip archive, checks for unwanted files, unpacks
188 * the files to the IBR_UPLOAD_DIR, and creates an array of file paths by the
189 * type of file
191 * @uses ibr_upload_match_file()
192 * @param string $zipfilename -- the $_FILES['file']['tmp_name'];
193 * @param array $param_ar -- the parameters array, so we don't have to create it here
194 * @param string &$html_str -- passed by reference for appending
195 * @return array $f_ar -- paths to unpacked files accepted by this function
197 function edih_ziptoarray($zipfilename, $param_ar, $single=false) {
198 // note that this function moves files and set permissions, so platform issues may occur
200 $html_str = '';
201 $edih_upldir = csv_edih_tmpdir();
203 $zip_obj = new ZipArchive();
204 // open archive (ZIPARCHIVE::CHECKCONS the ZIPARCHIVE::CREATE is supposedly necessary for microsoft)
205 if ($zip_obj->open($zipfilename, ZIPARCHIVE::CHECKCONS) !== true) {
206 //$html_str .= "Error: Could not open archive $zipfilename <br />" . PHP_EOL;
207 csv_edihist_log('edih_ziptoarray: Error: Could not open archive '.$zipfilename);
208 $f_zr['reject'][] = array('name'=>$zipfilename, 'comment'=>'Error: Could not open archive '.$zipfilename);
209 return $f_zr;
211 if ($zip_obj->status != 0) {
212 $err .= "Error code: " . $zip_obj->status ." ". $zip_obj->getStatusString() . "<br />" . PHP_EOL;
213 csv_edihist_log('edih_ziptoarray: '.$zipfilename.' '.$err);
214 $f_zr['reject'][] = array('name'=>$zipfilename, 'comment'=>$err);
215 return $f_zr;
217 // initialize output array and counter
218 $f_zr = array();
219 $p_ct = 0;
220 // get number of files
221 $f_ct = $zip_obj->numFiles;
222 if ($single && $f_ct > 1) {
223 csv_edihist_log('edih_ziptoarray: Usage: only single zipped file accepted through this input');
224 $f_zr['reject'][] = array('name'=>$zipfilename, 'comment'=>'Usage: only single zipped file accepted through this input');
225 return $f_zr;
227 // get the file names
228 for ($i=0; $i<$f_ct; $i++) {
230 $isOK = true;
231 $fstr = "";
232 $file = $zip_obj->statIndex($i);
233 $name = $file['name'];
234 $oldCrc = $file['crc'];
235 // get file contents
236 $fstr = stream_get_contents($zip_obj->getStream($name));
237 if ($fstr) {
238 // use only the file name
239 $bnm = basename($name);
240 $newname = tempnam($edih_upldir, 'edi');
242 // extract the file to unzip tmp dir with read/write access
243 $chrs = file_put_contents($newname, $fstr);
244 // test crc
245 $newCrc = hexdec(hash_file("crc32b", $newname) );
247 if($newCrc !== $oldCrc && ($oldCrc + 4294967296) !== $newCrc) {
248 // failure case, mismatched crc file integrity values
249 $html_str .= "CRC error: The files don't match! Removing file $bnm <br />" . PHP_EOL;
250 $isGone = unlink($newname);
251 if ($isGone) {
252 $is_tmpzip = false;
253 $html_str .= "File Removed $bnm<br />".PHP_EOL;
254 } else {
255 $html_str .= "Failed to removed file $bnm<br />".PHP_EOL;
257 } else {
258 // passed the CRC test, now type and verify file
259 $fzp['name'] = $bnm;
260 $fzp['tmp_name'] = $newname;
261 // verification checks special to our application
262 $f_uplz = edih_upload_match_file($param_ar, $fzp, $html_str);
264 if (is_array($f_uplz) && count($f_uplz)) {
265 if ( isset($f_uplz['reject']) ) {
266 $f_zr['reject'][] = $f_uplz['reject'];
267 } elseif ( isset($f_uplz['name']) ) {
268 $f_zr[$f_uplz['type']][] = $f_uplz['name'];
269 $p_ct++;
271 } else {
272 // verification failed
273 $f_zr['reject'][] = array('name'=>$fzp['name'], 'comment'=>'verification failed');
277 } else {
278 csv_edihist_log("Did not get file contents $name");
279 $isOK = false;
281 } // end for ($i=0; $i<$numFiles; $i++)
283 csv_edihist_log("Accepted $p_ct of $f_ct files from $zipfilename");
285 return $f_zr;
289 * Main function that handles the upload files array
291 * The return array has keys 'type' and subarray of file names
292 * relies on global $_POST and $_FILES variables
294 * @uses edih_upload_reindex()
295 * @uses edih_ziptoarray()
296 * @uses ibr_upload_match_file()
297 * @uses csv_parameters()
299 * @param string &$html_str referenced and appended to in this function
300 * @return array array of files that pass the checks and scans
302 function edih_upload_files() {
304 $html_str = '';
306 // from php manual ling 03-Nov-2010 08:35
307 if (empty($_FILES) && empty($_POST) && isset($_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
308 $pmax = ini_get('post_max_size');
310 csv_edihist_log('edih_upload_files: Error: upload too large, max size is '.$pmax);
311 return false;
313 if (empty($_FILES) ) {
314 csv_edihist_log('Error: upload files indicated, but none received.');
315 return false;
317 // only one $_FILES array key is expected
318 $uplkey = '';
319 $uplkey = array_key_exists("fileUplMulti", $_FILES) ? "fileUplMulti" : $uplkey;
320 $uplkey = array_key_exists("fileUplx12", $_FILES) ? "fileUplx12" : $uplkey;
322 if (!$uplkey) {
323 csv_edihist_log('edih_upload_files: Error: file array name error');
324 return false;
327 if ($uplkey != "fileUplMulti") {
328 // Undefined | Multiple Files | $_FILES Corruption Attack
329 // If this request falls under any of them, treat it invalid.
330 if ( !isset($_FILES[$uplkey]['error']) || is_array($_FILES[$uplkey]['error']) ) {
331 csv_edihist_log('edih_upload_files: Error: file array keys error');
332 return false;
336 // these are the mime-types that we will accept -- however, mime-type is not reliable
337 // for linux, system("file -bi -- ".escapeshellarg($uploadedfile)) gives mime-type and character encoding
338 $m_types = array('application/octet-stream', 'text/plain', 'application/zip', 'application/x-zip-compressed');
340 // some unwanted file extensions that might be accidentally included in upload files
341 $ext_types = 'sh|asp|html|htm|cm|js|xml|jpg|png|tif|xpm|pdf|php|py|pl|tcl|doc|pub|ppt|xls|xla|vsd|rtf|odt|ods|odp';
342 // we get the parameters here to send to ibr_upload_match_file()
343 $param_ar = csv_parameters("ALL");
345 // initialize retained files array and counter
346 $f_ar = array();
347 $p_ct = 0;
349 // here send the $_FILES array to edih_upload_reindex for "fileUplMulti"
350 // instead of $_FILES[$uplkey] ["name"][$i] ["tmp_name"][$i] ["type"][$i] ["error"][$i] ["size"][$i]
351 // we will have $files[$uplkey][$i] ["name"]["tmp_name"]["type"]["error"]["size"]
352 if ($uplkey == "fileUplMulti") {
353 $files = edih_upload_reindex($_FILES);
354 } else {
355 $files[$uplkey][] = $_FILES[$uplkey];
358 $f_ct = count($files[$uplkey]);
359 //begin the check and processing loop
360 foreach($files[$uplkey] as $idx=>$fa) {
361 // basic php verification checks
362 if ($fa['error'] !== UPLOAD_ERR_OK ) {
363 //$html_str .= "Error: [{$fa['name']}] " . edih_upload_err_message($fa['error']) . "<br />" . PHP_EOL;
364 $err = edih_upload_err_message($fa['error']);
365 $f_ar['reject'][] = array('name'=>$fa['name'],'comment'=>$err);
366 csv_edihist_log('edih_upload_files: _FILES error '.$fa['name'].' '.$err);
367 unset($files[$uplkey][$idx]);
368 continue;
370 if ( !is_uploaded_file($fa['tmp_name']) ) {
371 //$html_str .= "Error: uploaded_file error for {$fa['name']}<br />". PHP_EOL;
372 $f_ar['reject'][] = array('name'=>$fa['name'],'comment'=>'php uploaded file error');
373 csv_edihist_log('edih_upload_files: _FILES error tmp_name '.$fa['name']);
374 unset($files[$uplkey][$idx]);
375 continue;
377 if ( !in_array($fa['type'], $m_types) ) {
378 //$html_str .= "Error: mime-type {$fa['type']} not accepted for {$fa['name']} <br />" . PHP_EOL;
379 $f_ar['reject'][] = array('name'=>$fa['name'],'comment'=>'mime-type '.$fa['type']);
380 csv_edihist_log('edih_upload_files: _FILES error mime-type '.$fa['name'].' mime-type '.$fa['type']);
381 unset($files[$uplkey][$idx]);
382 continue;
384 // verify that we have a usable name
385 $fext = ( strpos($fa['name'], '.') ) ? pathinfo($fa['name'], PATHINFO_EXTENSION) : '';
386 if( $fext && preg_match('/'.$ext_types.'\?/i',$fext) ) {
387 //$html_str .= 'Error: uploaded_file error for '.$fa['name'].' extension '.$fext.'<br />'. PHP_EOL;
388 $f_ar['reject'][] = array('name'=>$fa['name'],'comment'=>'extension '.$fext);
389 csv_edihist_log('edih_upload_files: _FILES error name '.$fa['name'].' extension '.$fext);
390 unset($files[$uplkey][$idx]);
391 continue;
393 if (is_string($fa['name'])) {
394 // check for null byte in file name, linux hidden file, directory
395 if (strpos($fa['name'], '.') === 0 || strpos($fa['name'], "\0") !== false || strpos($fa['name'], "./") !== false ) {
396 //$html_str .= "Error: uploaded_file error for " . $fa['name'] . "<br />". PHP_EOL;
397 $fname = preg_replace( "/[^a-zA-Z0-9_.-]/","_", $fa['name'] );
398 $f_ar['reject'][] = array('name'=>$fname,'comment'=>'null byte, hidden, invalid');
399 csv_edihist_log('edih_upload_files: null byte, hidden, invalid '.$fname);
400 unset($files[$uplkey][$idx]);
401 continue;
403 // replace spaces in file names -- should not happen, but response files from payers might have spaces
404 // $fname = preg_replace("/[^a-zA-Z0-9_.-]/","_",$fname);
405 $fa['name'] = str_replace(' ', '_', $fa['name']);
406 } else {
407 // name is not a string
408 //$html_str .= "Error: uploaded_file error for " . $fa['tmp_name'] . "<br />". PHP_EOL;
409 $f_ar['reject'][] = array('name'=>(string)$fa['name'],'comment'=>'invalid name');
410 unset($files[$uplkey][$idx]);
411 continue;
413 if ( !$fa['tmp_name'] || !$fa['size'] ) {
414 //$html_str .= "Error: file name or size error <br />" . PHP_EOL;
415 $f_ar['reject'][] = array('name'=>(string)$fa['name'],'comment'=>'php file upload error');
416 unset($files[$uplkey][$idx]);
417 continue;
419 // verification checks special to our application
421 //////////////////////////////////
422 // check for zip file archive -- sent to edih_ziptoarray
424 if ( strpos(strtolower($fa['name']), '.zip') || strpos($fa['type'], 'zip') ) {
426 // this is a bit involved since we cannot predict how many files will be returned
427 // get an array of files from the zip unpack function"fileUplx12"
429 //if ($uplkey != "fileUplmulti") {
430 //$f_upl = edih_ziptoarray($fa['tmp_name'], $param_ar, false);
431 //} else {
432 //$f_upl = edih_ziptoarray($fa['tmp_name'], $param_ar, true);
434 $f_upl = edih_ziptoarray($fa['tmp_name'], $param_ar);
436 // put them in the correct type array
437 // expect fupl in form [type] = array(fn1, fn2, fn3, ...)
438 if (is_array($f_upl) && count($f_upl)) {
439 // $tp is file type, fz is file name
440 foreach($f_upl as $tp=>$fz) {
441 if ( $tp == 'reject' ) {
442 if ( isset($f_ar['reject']) && is_array($fz) ) {
443 array_merge($f_ar['reject'], $fz);
444 } else {
445 $f_ar['reject'] = (is_array($fz)) ? $fz : array();
447 } else {
448 // expect $fz to be an array of file names
449 foreach($fz as $zf) {
450 $f_ar[$tp][] = $zf;
451 $p_ct ++;
455 } else {
456 // nothing good from edih_ziptoarray()
457 // $html_str .= "error with zip file or no files accepted for " . $fa['name'] . "<br />" .PHP_EOL;
458 $f_ar['reject'][] = array('name'=>$fa['name'],'comment'=>'error with zip archive');
459 unset($files[$uplkey][$idx]);
461 // continue, since we have done everything that would happen below
462 continue;
464 //////////
465 // at this point, since we have come through all the if statements
466 // then we have:
467 // a single file under "fileUplEra"
468 // a single file under "fileUplx12"
469 // or one of possibly several files under "fileUplMulti"
470 //////////
471 $f_upl = edih_upload_match_file($param_ar, $fa);
473 if (is_array($f_upl) && count($f_upl) > 0 ) {
474 $f_ar[$f_upl['type']][] = $f_upl['name'];
475 $p_ct++;
476 } else {
477 // verification failed
478 csv_edihist_log('edih_upload_file: verification failed for '. $fa['name']);
479 $f_ar['reject'][] = array('name'=>$fa['name'], 'comment'=>'verification failed');
480 unset($files[$uplkey][$idx]);
482 } // end foreach($files[$uplkey] as $idx=>$fa)
484 $f_ar['remark'][] = "Received $f_ct files, accepted $p_ct" . PHP_EOL;
485 return $f_ar;
489 * Save the uploaded files array to the correct directory
491 * If a matching filename file is already in the directory it is not overwritten.
492 * The uploaded file will just be discarded.
494 * @uses csv_parameters()
495 * @see edih_upload_files()
496 * @param array $files_array files array created by edih_upload_files()
497 * @param bool -- whether to return html output
498 * @param bool -- whether to only report errors (ignored)
499 * @return string html formatted messages
501 function edih_sort_upload($files_array, $html_out=true, $err_only=true) {
503 $prc_htm = '';
504 $rmk_htm = '';
505 $dirpath = csv_edih_basedir();
507 if ( is_array($files_array) && count($files_array) ) {
508 // we have some files
509 $p_ar = csv_parameters($type="ALL");
511 $prc_htm .= "<p><em>Received Files</em></p>".PHP_EOL;
512 foreach ($files_array as $key=>$val) {
514 $prc_htm .= "<ul class='fupl'>".PHP_EOL;
515 if ( isset($p_ar[$key]) ) {
516 $tp_dir = $p_ar[$key]['directory'];
517 $tp_base = basename($tp_dir);
518 $idx = 0;
519 $prc_htm .= "<li>type $key</li>".PHP_EOL;
520 if ( !is_array($val) || !count($val) ) {
521 $prc_htm .= "<li>no new files</li>" . PHP_EOL;
522 continue;
524 foreach($val as $idx=>$nf) {
525 // check if the file has already been stored
526 // a matching file name will not be replaced
527 $nfb = basename($nf);
528 $testname = $tp_dir.DS.$nfb;
529 $prc_htm .= "<li>$nfb</li>".PHP_EOL;
530 if ( is_file($testname) ) {
531 $prc_htm .= "<li> -- file exists</li>" .PHP_EOL;
532 } elseif (rename($nf, $testname) ) {
533 $iscm = chmod($testname, 0400);
534 if (!$iscm) {
535 // if we could write, we should be able to set permissions
536 $prc_htm .= "<li> -- file save error</li>" .PHP_EOL;
537 unlink($testname);
539 } else {
540 $prc_htm .= "<li> -- file save error</li>" .PHP_EOL;
544 } elseif ($key == 'reject') {
545 $prc_htm .= "<li><bd>Reject:</bd></li>".PHP_EOL;
546 foreach($val as $idx=>$nf) {
547 $prc_htm .= "<li>".$nf['name']."</li>".PHP_EOL;
548 $prc_htm .= "<li> --".$nf['comment']."</li>".PHP_EOL;
550 } elseif ($key == 'remark') {
551 $rmk_htm .= "<p><bd>Remarks:</bd><br>".PHP_EOL;
552 foreach($val as $idx=>$r) {
553 $rmk_htm .= $r."<br>".PHP_EOL;
555 $rmk_htm .= "</p>".PHP_EOL;
556 } else {
557 $prc_htm .= "<li>$key type not stored</li>".PHP_EOL;
558 foreach($val as $idx=>$nf) {
559 $prc_htm .= "<li>".basename($nf)."</li>".PHP_EOL;
562 $prc_htm .= "</ul>".PHP_EOL;
563 $prc_htm .= $rmk_htm.PHP_EOL;
565 } else {
566 // should not happen since this function should not be called unless there are new files
567 $prc_htm .= "<ul><li>No files submitted</li></ul>" . PHP_EOL;
570 return $prc_htm;