0

I searched for solution to this the better part of the day and I'm still in the dark. For a client of mine I created a simple web gallery for uploading images and I'm using valums file uploader. Till now I didn't have any problems with it on any other site I created, except miss-configuration which I solved some time ago.

So, what is the problem?

When I upload a image with filesize below 260KB it works fine. But, when I'm uploading a bigger image, it doesn't throw me back any error. I just get an empty thumbnail, because image wasn't uploaded.

When I open Chrome Console, this is what I see:

POST http://designflowstudio.com/gallery2/include/upload.php?url=uploads%2Fe558c4dfc2a0f0d60f5ebff474c97ffc&fid=7&qqfile=316267269718409738080100.jpg 403 (Forbidden) fileuploader.js:1463
POST http://designflowstudio.com/gallery2/include/upload.php?url=uploads%2Fe558c4dfc2a0f0d60f5ebff474c97ffc&fid=7&qqfile=3.jpg 403 (Forbidden) fileuploader.js:1463

On some occasions I got 413 error "Request entity too large".

Let me say that this problem really annoys me because the script works well on my local web server (apache) and on my web server. Here I can upload as may and as big images as I can find, but on the client's web server...

If anyone have time and will to help me, here is the JavaScript that I use to call the valums uploader:

     function createUploader(){
 var uploader = new qq.FileUploaderBasic({
  debug: true,
  multiple: true,
  allowedExtensions: [<?php $tmp=".";foreach($allowedExt as $ext){$tmp.="'$ext', ";}echo substr($tmp,1,strlen($tmp)-3);?>],
  button: document.getElementById('uploadDiv'),
  action: '<?php echo$home;?>include/upload.php',
  sizeLimit: <?php echo$sizeLimit;?>,
  forceMultipart: true,
  params: {'url':'uploads/<?php echo$g['path'];?>','fid':'<?php echo$g['id'];?>'},
  onSubmit: function(id, fName){$('#upload-list').append('<div id="upload-list-'+id+'" class="gallery" rel="'+fName+'"><div class="progress'+id+'"></div>');$('.progress'+id).progressbar({value:0})},
  onProgress: function(id, fName, loaded, total){
   var p = 0;
   p = parseFloat(loaded/total*100);
   if(isNaN(p)) p = '';
   $('.progress'+id).progressbar("option","value",p);
  },
  onComplete: function(id,fName,json){
   if(json.error){
    $('#upload-list-'+id).html(fName+'<br />'+json.error);
   }else{
    $('#upload-list-'+id)
     .attr("rel",json.fname)
     .html('<img class="cmd" id="deleteImg" rel="'+json.id+'" src="<?php echo$home;?>images/delete.png" title="Delete picture" /><img class="img" src="<?php echo$home;?>uploads/<?php echo$g['path'];?>/th_'+json.fname+'" /><input class="ut" type="text" name="name" value="'+json.name+'" /><input class="ud" type="text" name="desc" /><div class="cl"></div>')
     .attr("id",json.id);
   }
  },
  onError: function(id,fName,error){
   console.log(id+' '+fName+' '+error);
  }
 });
}
window.onload = createUploader;

And here is my PHP for server processing the file:

require("config.php");
require("SimpleImage.php");
ini_set("log_errors" , "1");
ini_set("error_log" , "php-errors-upload.log");
ini_set("display_errors" , "1");
/**
 * Handle file uploads via XMLHttpRequest
 */
class qqUploadedFileXhr {
    /**
     * Save the file to the specified path
     * @return boolean TRUE on success
     */
    function save($path) {
        $input = fopen("php://input", "r");
        $temp = tmpfile();
        $realSize = stream_copy_to_stream($input, $temp);
        fclose($input);

        if ($realSize != $this->getSize()){
            return false;
        }

        $target = fopen($path, "w");
        fseek($temp, 0, SEEK_SET);
        stream_copy_to_stream($temp, $target);
        fclose($target);

        return true;
    }
    function getName() {
        return $_GET['qqfile'];
    }
    function getSize() {
        if (isset($_SERVER["CONTENT_LENGTH"])){
            return (int)$_SERVER["CONTENT_LENGTH"];            
        } else {
            throw new Exception('Getting content length is not supported.');
        }
    }   
}

/**
 * Handle file uploads via regular form post (uses the $_FILES array)
 */
class qqUploadedFileForm {
    /**
     * Save the file to the specified path
     * @return boolean TRUE on success
     */
    function save($path) {
        if(!move_uploaded_file($_FILES['qqfile']['tmp_name'], $path)){
            return false;
        }
        return true;
    }
    function getName() {
        return $_FILES['qqfile']['name'];
    }
    function getSize() {
        return $_FILES['qqfile']['size'];
    }
}

class qqFileUploader {
    private $allowedExtensions = array();
    private $sizeLimit = 20485760;
    private $file;

    function __construct(array $allowedExtensions = array(), $sizeLimit = 20485760){
        $allowedExtensions = array_map("strtolower", $allowedExtensions);

        $this->allowedExtensions = $allowedExtensions;        
        $this->sizeLimit = $sizeLimit;

        $this->checkServerSettings();

        if (isset($_GET['qqfile'])) {
            $this->file = new qqUploadedFileXhr();
        } elseif (isset($_FILES['qqfile'])) {
            $this->file = new qqUploadedFileForm();
        } else {
            $this->file = false;
        }
    }

    private function checkServerSettings(){        
        $postSize = $this->toBytes(ini_get('post_max_size'));
        $uploadSize = $this->toBytes(ini_get('upload_max_filesize'));

        if ($postSize < $this->sizeLimit || $uploadSize < $this->sizeLimit){
            $size = max(1, $this->sizeLimit / 1024 / 1024) . 'M';             
            die("{'error':'increase post_max_size and upload_max_filesize to $size'}");    
        }        
    }

    private function toBytes($str){
        $val = trim($str);
        $last = strtolower($str[strlen($str)-1]);
        switch($last) {
            case 'g': $val *= 1024;
            case 'm': $val *= 1024;
            case 'k': $val *= 1024;        
        }
        return $val;
    }

    /**
     * Returns array('success'=>true) or array('error'=>'error message')
     */
    function handleUpload($uploadDirectory, $replaceOldFile = FALSE){
        chdir("../");
        if(!file_exists($uploadDirectory)){
          foreach(explode("/",$uploadDirectory) as $val){
            if(empty($val2)){$val2=$val."/";}else{$val2.=$val."/";}
            if($val<>""){if(!file_exists($val2)){mkdir($val2);}}
          }
        }

        if (!is_writable($uploadDirectory)){
            return array('error' => "Server error. Upload directory isn't writable.");
        }

        if (!$this->file){
            return array('error' => 'No files were uploaded.');
        }

        $size = $this->file->getSize();

        if ($size == 0) {
            return array('error' => 'File is empty');
        }

        if ($size > $this->sizeLimit) {
            return array('error' => 'File is too large');
        }

        $pathinfo = pathinfo($this->file->getName());
        $filename = md5($pathinfo['filename'].mt_rand());
        //$filename = md5(uniqid());
        $ext = strtolower($pathinfo['extension']);

        if($this->allowedExtensions && !in_array(strtolower($ext), $this->allowedExtensions)){
            $these = implode(', ', $this->allowedExtensions);
            return array('error' => 'File has an invalid extension, it should be one of '. $these . '.');
        }

        if(!$replaceOldFile){
            /// don't overwrite previous files that were uploaded
            while (file_exists($uploadDirectory . $filename . '.' . $ext)) {
                $filename .= rand(10, 99);
            }
        }
        if ($this->file->save($uploadDirectory .'/'. $filename . '.' . $ext)){
            global $sql_images;
            $md=md5($filename);
            $tmp=mysql_query("SELECT `order` FROM `$sql_images` WHERE `gid`='{$_REQUEST['fid']}' ORDER BY `order` DESC");
            if($tmp&&mysql_num_rows($tmp)>0){$i=mysql_result($tmp,0);$i++;}else{$i=0;}
            mysql_query("INSERT INTO `$sql_images` (`order`,`gid`,`name`,`path`) VALUES ('$i','{$_REQUEST['fid']}','{$pathinfo['filename']}','$filename.$ext')");
            $image = new SimpleImage();
            $image->load($uploadDirectory.'/'.$filename.'.'.$ext);
            $image->resizeToWidth(150);
            $image->save($uploadDirectory.'/th_'.$filename.'.'.$ext);
            return array('success'=>true,'fname'=>$filename.".".$ext,'name'=>$pathinfo['filename'],'id'=>mysql_insert_id());
        } else {
            return array('error'=> 'Could not save uploaded file.' .
                'The upload was cancelled, or server error encountered');
        }
    }
}

// list of valid extensions, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array();
// max file size in bytes
$sizeLimit = 20*1024*1024;

$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
$result = $uploader->handleUpload($_REQUEST['url']);
// to pass data through iframe you will need to encode all html tags
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);

Let me say again, all the calls to MySQL, files with require() are valid. The scripts work on two other servers, but on this one, not.

Thank you for your time and will to help.

Ray Nicholus
  • 19,538
  • 14
  • 59
  • 82
Kristjan O.
  • 814
  • 1
  • 9
  • 33
  • Well, I figure it out. The script _fileuploader.js_ sets content type to _application/octet-stream_. Till now, all servers were good with it but not this one. This was starting to work out when I commented the part where it sets this content type. Part that I have commented: `xhr.setRequestHeader("Content-Type", "application/octet-stream");` Meanwhile I thought that there is mod_security enabled on server and corrupts POST data. I'm posting this in hope that someday someone may need this information. And because I regularly search for help here at SO. Cheers :) – Kristjan O. Oct 09 '12 at 21:59

1 Answers1

0

You have some configuration issues on your server if it is choking due to the content-type header. Instead of commenting out this line, to work around this messed up server, you should perhaps try to set the forceMultipart option to true, assuming your server can handle multipart-encoded requests correctly. This all assumes you are using the 2.1-SNAPSHOT version of the uploader, as the option I mentioned first appeared in this version.

Ray Nicholus
  • 19,538
  • 14
  • 59
  • 82
  • I'm using the options **forceMultipart: true** and it didn't change anything. At first I used the old version and then I updated to the latest. The error was still there. And after some research and Googling, I'm still suspecting that there is a problem with mod_security, but the client wasn't wiling to contact his administrator to clarify this and to change server setting. Also, uploader works with Chrome, Opera and IE, but not Firefox. Something sets the content-Type to "application/octet-stream" and of course, it's denied. Also tried with content-Type "multipart/form-data", no luck. – Kristjan O. Oct 14 '12 at 22:13
  • The content-type of the XHR POST request is hardcoded to application/octet-stream in the uploader js code. If setting forceMultipart to true doesn't fix your problem, then the issue is not the content-type. – Ray Nicholus Oct 15 '12 at 18:48
  • What I meant, in FireFox the xhr contentType was setted to "application/octet-stream" after I commented that line in uploader js, in other browsers no contentType was sent and the upload worked. Can I say to the client that this is server misconfiguration? – Kristjan O. Oct 16 '12 at 09:35
  • I would certainly say the issue is server-side. – Ray Nicholus Oct 17 '12 at 02:40