該文是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_file
、file
命令或者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.+)?$/';
-
優先先會嘗試使用 `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; } } }
-
其次是
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; } } } } }
-
然后
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; } }
-
最后才會考慮用
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;
處理步驟
- 調整內存
memory_limit
為讀入文件保留足夠的內存 - 如果是圖片文件,要求文件不能有任何html標簽(
a|body|head|html|img|plaintext|pre|script|table|title
) - 如果不是圖片,只要不為空即可。
生成縮略圖算法
使用
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
右下角對齊,裁剪左上角
實現
內部支持GD
或ImageMagick
來處理圖片,支持jpg/png/gif
三種格式的圖片處理。
處理步驟:
-
根據圖片的寬窄比計算目標縮放區域(優先按長邊縮放)
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; }
-
根據裁剪需求,設定目標區域
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;
-
根據縮放設置,計算出目標圖片的真實尺寸
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; } }
按目標區域裁剪圖片
縮放圖片到目標尺寸
輸出圖片
具體到圖片處理記得,根據庫的不同,略有不同
** 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 **
通過 imagecopyresampled
或imagecopyresized
可以一步作為裁剪和縮放
$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);