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
24 * @subpackage ediHistory
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
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
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);
57 $files[$name] = $file;
64 * select error message in case of $_FILES error
69 function edih_upload_err_message($code) {
72 case UPLOAD_ERR_INI_SIZE
:
73 $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini";
75 case UPLOAD_ERR_FORM_SIZE
:
76 $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form";
78 case UPLOAD_ERR_PARTIAL
:
79 $message = "The uploaded file was only partially uploaded";
81 case UPLOAD_ERR_NO_FILE
:
82 $message = "No file was uploaded";
84 case UPLOAD_ERR_NO_TMP_DIR
:
85 $message = "Missing a temporary folder";
87 case UPLOAD_ERR_CANT_WRITE
:
88 $message = "Failed to write file to disk";
90 case UPLOAD_ERR_EXTENSION
:
91 $message = "File upload stopped by extension";
95 $message = "Unknown upload error";
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
116 function edih_upload_match_file($param_ar, $fidx) {
119 $edih_upldir = csv_edih_tmpdir();
124 if (is_array($fidx) && isset($fidx['name']) ) {
125 $fn = basename($fidx['name']);
126 $ftmp = $fidx['tmp_name'];
128 csv_edihist_log('edih_upload_match_file: Error: invalid file argument');
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) ) {
147 csv_edihist_log('edih_upload_match_file: invalid parameters');
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()));
157 csv_edihist_log( 'edih_upload_match_file: unable to classify file '.$fn );
158 $ar_fn['reject'] = array('name'=>$fn, 'comment'=>'unable to classify');
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;
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');
175 csv_edihist_log( "edih_upload_match_file: unable to move $fn to uploads directory");
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
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
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);
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);
217 // initialize output array and counter
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');
227 // get the file names
228 for ($i=0; $i<$f_ct; $i++
) {
232 $file = $zip_obj->statIndex($i);
233 $name = $file['name'];
234 $oldCrc = $file['crc'];
236 $fstr = stream_get_contents($zip_obj->getStream($name));
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);
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);
253 $html_str .= "File Removed $bnm<br />".PHP_EOL
;
255 $html_str .= "Failed to removed file $bnm<br />".PHP_EOL
;
258 // passed the CRC test, now type and verify file
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'];
272 // verification failed
273 $f_zr['reject'][] = array('name'=>$fzp['name'], 'comment'=>'verification failed');
278 csv_edihist_log("Did not get file contents $name");
281 } // end for ($i=0; $i<$numFiles; $i++)
283 csv_edihist_log("Accepted $p_ct of $f_ct files from $zipfilename");
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() {
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);
313 if (empty($_FILES) ) {
314 csv_edihist_log('Error: upload files indicated, but none received.');
317 // only one $_FILES array key is expected
319 $uplkey = array_key_exists("fileUplMulti", $_FILES) ?
"fileUplMulti" : $uplkey;
320 $uplkey = array_key_exists("fileUplx12", $_FILES) ?
"fileUplx12" : $uplkey;
323 csv_edihist_log('edih_upload_files: Error: file array name error');
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');
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
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);
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]);
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]);
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]);
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]);
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]);
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']);
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]);
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]);
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);
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);
445 $f_ar['reject'] = (is_array($fz)) ?
$fz : array();
448 // expect $fz to be an array of file names
449 foreach($fz as $zf) {
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
465 // at this point, since we have come through all the if statements
467 // a single file under "fileUplEra"
468 // a single file under "fileUplx12"
469 // or one of possibly several files under "fileUplMulti"
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'];
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
;
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) {
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);
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
;
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);
535 // if we could write, we should be able to set permissions
536 $prc_htm .= "<li> -- file save error</li>" .PHP_EOL
;
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
;
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
;
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
;