美文网首页
wecenter学习笔记-上传图片并生成缩略图

wecenter学习笔记-上传图片并生成缩略图

作者: imhaiyang | 来源:发表于2016-07-11 18:44 被阅读582次

    该文是wecenter学习笔记的一部分

    上传图片并生成缩略图

    这部分主要由两个模块合力完成

    • core_upload
    • core_image

    上传图片

    使用

    AWS_APP::upload()->initialize(array(
        'allowed_types' => get_setting('allowed_upload_types'),
        'upload_path' => get_setting('upload_dir') . '/' . $item_type . '/' . gmdate('Ymd'),
        'is_image' => FALSE,
        'max_size' => get_setting('upload_size_limit')
    ));
    
    ...
    
    AWS_APP::upload()->do_upload('qqfile');
    
    ...
    
    if (AWS_APP::upload()->get_error())
    {
        switch (AWS_APP::upload()->get_error())
        {
            default:
                H::ajax_json_output(AWS_APP::RSM(null, -1, AWS_APP::lang()->_t('错误代码: '.AWS_APP::upload()->get_error())));
            break;
    
            case 'upload_invalid_filetype':
                H::ajax_json_output(AWS_APP::RSM(null, -1, AWS_APP::lang()->_t('文件类型无效')));
            break;
    
            case 'upload_invalid_filesize':
               H::ajax_json_output(AWS_APP::RSM(null, -1, AWS_APP::lang()->_t("文件尺寸过大,最大允许尺寸为 ".get_setting('upload_size_limit')." KB")));
            break;
        }
    }
    
    if (! $upload_data = AWS_APP::upload()->data())
    {
        H::ajax_json_output(AWS_APP::RSM(null, -1, AWS_APP::lang()->_t( '上传失败, 请与管理员联系' )));
    }
    
    

    步骤

    • 初始化(接受的文件类型、保存路径、是否必须是图片、最大文件尺寸)
    • 接受上传文件
    • 错误处理

    实现

    core_upload处理文件上传,涵盖了

    文件存储和访问暴露

    先会将文件存储到临时文件中,在完成后面的检查后再移动到目标目录

    文件类型判定并只允许上传制定类型的文件(mimes)

    根据上传文件的扩展名判定是否是allowed_types,
    如果是图片文件,还会尝试获取图片尺寸(利用获取图片大小会解析图片头部信息的副作用)
    另外还是通过函数finfo_filefile命令或者mime_content_type来分析文件的mimes type

    system/core/upload.php#file_mime_type

    // We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii)
       $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';
    
    
    1. 优先先会尝试使用 `finfo_file`

      /* Fileinfo extension - most reliable method
       *
       * Unfortunately, prior to PHP 5.3 - it's only available as a PECL extension and the
       * more convenient FILEINFO_MIME_TYPE flag doesn't exist.
       */
      if (function_exists('finfo_file'))
      {
          $finfo = finfo_open(FILEINFO_MIME);
          if (is_resource($finfo)) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system
          {
              $mime = @finfo_file($finfo, $file['tmp_name']);
              finfo_close($finfo);
      
              /* According to the comments section of the PHP manual page,
               * it is possible that this function returns an empty string
               * for some files (e.g. if they don't exist in the magic MIME database)
               */
              if (is_string($mime) && preg_match($regexp, $mime, $matches))
              {
                  $this->file_type = $matches[1];
                  return;
              }
          }
      }
      
    2. 其次是file --brief --mime命令

      /* This is an ugly hack, but UNIX-type systems provide a "native" way to detect the file type,
       * which is still more secure than depending on the value of $_FILES[$field]['type'], and as it
       * was reported in issue #750 (https://github.com/EllisLab/CodeIgniter/issues/750) - it's better
       * than mime_content_type() as well, hence the attempts to try calling the command line with
       * three different functions.
       *
       * Notes:
       *  - the DIRECTORY_SEPARATOR comparison ensures that we're not on a Windows system
       *  - many system admins would disable the exec(), shell_exec(), popen() and similar functions
       *    due to security concerns, hence the function_exists() checks
       */
      if (DIRECTORY_SEPARATOR !== '\\')
      {
          $cmd = 'file --brief --mime ' . @escapeshellarg($file['tmp_name']) . ' 2>&1';
      
          if (function_exists('exec'))
          {
              /* This might look confusing, as $mime is being populated with all of the output when set in the second parameter.
               * However, we only neeed the last line, which is the actual return value of exec(), and as such - it overwrites
               * anything that could already be set for $mime previously. This effectively makes the second parameter a dummy
               * value, which is only put to allow us to get the return status code.
               */
              $mime = @exec($cmd, $mime, $return_status);
              if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches))
              {
                  $this->file_type = $matches[1];
                  return;
              }
          }
      
          if ( (bool) @ini_get('safe_mode') === FALSE && function_exists('shell_exec'))
          {
              $mime = @shell_exec($cmd);
              if (strlen($mime) > 0)
              {
                  $mime = explode("\n", trim($mime));
                  if (preg_match($regexp, $mime[(count($mime) - 1)], $matches))
                  {
                      $this->file_type = $matches[1];
                      return;
                  }
              }
          }
      
          if (function_exists('popen'))
          {
              $proc = @popen($cmd, 'r');
              if (is_resource($proc))
              {
                  $mime = @fread($proc, 512);
                  @pclose($proc);
                  if ($mime !== FALSE)
                  {
                      $mime = explode("\n", trim($mime));
                      if (preg_match($regexp, $mime[(count($mime) - 1)], $matches))
                      {
                          $this->file_type = $matches[1];
                          return;
                      }
                  }
              }
          }
      }
      
    3. 然后 mime_content_type

      // Fall back to the deprecated mime_content_type(), if available (still better than $_FILES[$field]['type'])
      if (function_exists('mime_content_type'))
      {
          $this->file_type = @mime_content_type($file['tmp_name']);
          if (strlen($this->file_type) > 0) // It's possible that mime_content_type() returns FALSE or an empty string
          {
              return;
          }
      }
      
    4. 最后才会考虑用 getimagesize

      if (function_exists('getimagesize'))
      {
          $imageinfo = @getimagesize($file['tmp_name']);
      
          if ($imageinfo['mime'])
          {
              $this->file_type = $imageinfo['mime'];
      
              return;
          }
      }
      
      $this->file_type = $file['type'];
      

    文件大小检查

    图片尺寸检查

    文件名和文件内容安全校验(xss clean)

    会去掉文件名中的各类非法字符

    system/core/upload.php#clean_file_name

        $bad = array(
                       "<!--",
                       "-->",
                       "'",
                       "<",
                       ">",
                       '"',
                       '&',
                       '$',
                       '=',
                       ';',
                       '?',
                       '/',
                       "%20",
                       "%22",
                       "%3c",      // <
                       "%253c",    // <
                       "%3e",      // >
                       "%0e",      // >
                       "%28",      // (
                       "%29",      // )
                       "%2528",    // (
                       "%26",      // &
                       "%24",      // $
                       "%3f",      // ?
                       "%3b",      // ;
                       "%3d"       // =
                   );
    
       $filename = str_replace($bad, '', $filename);
    

    ** 文件内容的xss clean **

    system/core/upload.php#do_xss_clean

    if (function_exists('memory_get_usage') && memory_get_usage() && ini_get('memory_limit') != '')
    {
        $current = ini_get('memory_limit') * 1024 * 1024;
    
        // There was a bug/behavioural change in PHP 5.2, where numbers over one million get output
        // into scientific notation.  number_format() ensures this number is an integer
        // http://bugs.php.net/bug.php?id=43053
    
        $new_memory = number_format(ceil(filesize($file) + $current), 0, '.', '');
    
        @ini_set('memory_limit', $new_memory); // When an integer is used, the value is measured in bytes. - PHP.net
    }
    
    // If the file being uploaded is an image, then we should have no problem with XSS attacks (in theory), but
    // IE can be fooled into mime-type detecting a malformed image as an html file, thus executing an XSS attack on anyone
    // using IE who looks at the image.  It does this by inspecting the first 255 bytes of an image.  To get around this
    // CI will itself look at the first 255 bytes of an image to determine its relative safety.  This can save a lot of
    // processor power and time if it is actually a clean image, as it will be in nearly all instances _except_ an
    // attempted XSS attack.
    
    if (function_exists('getimagesize') && @getimagesize($file) !== FALSE)
    {
        if (($file = @fopen($file, 'rb')) === FALSE) // "b" to force binary
        {
            return FALSE; // Couldn't open the file, return FALSE
        }
    
        $opening_bytes = fread($file, 256);
        fclose($file);
    
        // These are known to throw IE into mime-type detection chaos
        // <a, <body, <head, <html, <img, <plaintext, <pre, <script, <table, <title
        // title is basically just in SVG, but we filter it anyhow
    
        if ( ! preg_match('/<(a|body|head|html|img|plaintext|pre|script|table|title)[\s>]/i', $opening_bytes))
        {
            return TRUE; // its an image, no "triggers" detected in the first 256 bytes, we're good
        }
        else
        {
            return FALSE;
        }
    }
    
    if (($data = @file_get_contents($file)) === FALSE)
    {
        return FALSE;
    }
    
    return $data;
    

    处理步骤

    1. 调整内存memory_limit为读入文件保留足够的内存
    2. 如果是图片文件,要求文件不能有任何html标签(a|body|head|html|img|plaintext|pre|script|table|title
    3. 如果不是图片,只要不为空即可。

    生成缩略图算法

    使用

    AWS_APP::image()->initialize(array(
        'quality' => 90,
        'source_image' => $upload_data['full_path'],
        'new_image' => $thumb_file[$key],
        'width' => $val['w'],
        'height' => $val['h']
    ))->resize();
    

    core_image负责缩略图的生成

    • 生成指定大小的缩略图(resize)
    • 可以选择输出到文件或者浏览器

    resize的算法支持缩放和裁剪,并支持压缩到一定清晰度

    ** 缩放 **

    • IMAGE_CORE_SC_NOT_KEEP_SCALE

      直接缩放或拉伸,不考虑比例

    • IMAGE_CORE_SC_BEST_RESIZE_WIDTH

      优先匹配缩放后的目标宽度

    • IMAGE_CORE_SC_BEST_RESIZE_HEIGHT

      高度优先匹配

    ** 裁剪 **

    • IMAGE_CORE_CM_DEFAULT

      不裁剪

    • IMAGE_CORE_CM_LEFT_OR_TOP

      对齐左上角,裁剪右下角

    • IMAGE_CORE_CM_MIDDLE

      居中对齐,裁剪四周

    • IMAGE_CORE_CM_RIGHT_OR_BOTTOM

      右下角对齐,裁剪左上角

    实现

    内部支持GDImageMagick来处理图片,支持jpg/png/gif三种格式的图片处理。

    处理步骤:

    1. 根据图片的宽窄比计算目标缩放区域(优先按长边缩放)

      system/core/image.php#imageProcessImageMagick

      if ($this->source_image_w * $this->height > $this->source_image_h * $this->width)
      {
          $match_w = round($this->width * $this->source_image_h / $this->height);
          $match_h = $this->source_image_h;
      }
      else
      {
          $match_h = round($this->height * $this->source_image_w / $this->width);
          $match_w = $this->source_image_w;
      }
      
    2. 根据裁剪需求,设定目标区域

      system/core/image.php#imageProcessImageMagick

      switch ($this->clipping)
      {
          case IMAGE_CORE_CM_LEFT_OR_TOP:
              $this->source_image_x = 0;
              $this->source_image_y = 0;
          break;
      
          case IMAGE_CORE_CM_MIDDLE:
              $this->source_image_x = round(($this->source_image_w - $match_w) / 2);
              $this->source_image_y = round(($this->source_image_h - $match_h) / 2);
          break;
      
          case IMAGE_CORE_CM_RIGHT_OR_BOTTOM:
              $this->source_image_x = $this->source_image_w - $match_w;
              $this->source_image_y = $this->source_image_h - $match_h;
          break;
      }
      
      $this->source_image_w = $match_w;
      $this->source_image_h = $match_h;
      $this->source_image_x += $this->start_x;
      $this->source_image_y += $this->start_y;
      
    3. 根据缩放设置,计算出目标图片的真实尺寸

      system/core/image.php#imageProcessImageMagick

      $resize_height = $this->height;
      $resize_width = $this->width;
      
      if ($this->scale != IMAGE_CORE_SC_NOT_KEEP_SCALE)
      {
          if ($this->scale == IMAGE_CORE_SC_BEST_RESIZE_WIDTH)
          {
              $resize_height = round($this->width * $this->source_image_h / $this->source_image_w);
              $resize_width = $this->width;
          }
          else if ($this->scale == IMAGE_CORE_SC_BEST_RESIZE_HEIGHT)
          {
              $resize_width = round($this->height * $this->source_image_w / $this->source_image_h);
              $resize_height = $this->height;
          }
      }
      
    4. 按目标区域裁剪图片

    5. 缩放图片到目标尺寸

    6. 输出图片

    具体到图片处理记得,根据库的不同,略有不同

    ** imagemagick **

    $im = new Imagick();
    
    $im->readimageblob(file_get_contents($this->source_image));
    
    $im->setCompressionQuality($this->quality);
    
    if ($this->source_image_x OR $this->source_image_y)
    {
        $im->cropImage($this->source_image_w, $this->source_image_h, $this->source_image_x, $this->source_image_y);
    }
    
    $im->thumbnailImage($resize_width, $resize_height, true);
    
    if ($this->option == IMAGE_CORE_OP_TO_FILE AND $this->new_image)
    {
        file_put_contents($this->new_image, $im->getimageblob());
    }
    else if ($this->option == IMAGE_CORE_OP_OUTPUT)
    {
        $output = $im->getimageblob();
                $outputtype = $im->getFormat();
    
        header("Content-type: $outputtype");
        echo $output;
        die;
    }
    

    ** gd **

    通过 imagecopyresampledimagecopyresized可以一步作为裁剪和缩放

    $func_resize($dst_img, $im, $dst_x, $dst_y, $this->source_image_x, $this->source_image_y, $fdst_w, $fdst_h, $this->source_image_w, $this->source_image_h);
    
    

    表单防CSRF(Cross-site request forgery)的实现 ←o→ 对称加密

    相关文章

      网友评论

          本文标题:wecenter学习笔记-上传图片并生成缩略图

          本文链接:https://www.haomeiwen.com/subject/wksjjttx.html