Uploader.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <?php
  2. /**
  3. * Simple Ajax Uploader
  4. * Version 2.6.2
  5. * https://github.com/LPology/Simple-Ajax-Uploader
  6. *
  7. * Copyright 2012-2017 LPology, LLC
  8. * Released under the MIT license
  9. *
  10. * View the documentation for an example of how to use this class.
  11. */
  12. class FileUpload {
  13. private $fileName; // Filename of the uploaded file
  14. private $fileSize; // Size of uploaded file in bytes
  15. private $fileExtension; // File extension of uploaded file
  16. private $fileNameWithoutExt;
  17. private $savedFile; // Path to newly uploaded file (after upload completed)
  18. private $errorMsg; // Error message if handleUpload() returns false (use getErrorMsg() to retrieve)
  19. private $isXhr;
  20. public $uploadDir; // File upload directory (include trailing slash)
  21. public $allowedExtensions; // Array of permitted file extensions
  22. public $sizeLimit = 10485760; // Max file upload size in bytes (default 10MB)
  23. public $newFileName; // Optionally save uploaded files with a new name by setting this
  24. public $corsInputName = 'XHR_CORS_TARGETORIGIN';
  25. public $uploadName = 'uploadfile';
  26. function __construct($uploadName = null) {
  27. if ($uploadName !== null) {
  28. $this->uploadName = $uploadName;
  29. }
  30. if (isset($_FILES[$this->uploadName])) {
  31. $this->isXhr = false;
  32. if ($_FILES[$this->uploadName]['error'] === UPLOAD_ERR_OK) {
  33. $this->fileName = $_FILES[$this->uploadName]['name'];
  34. $this->fileSize = $_FILES[$this->uploadName]['size'];
  35. } else {
  36. $this->setErrorMsg($this->errorCodeToMsg($_FILES[$this->uploadName]['error']));
  37. }
  38. } elseif (isset($_SERVER['HTTP_X_FILE_NAME']) || isset($_GET[$this->uploadName])) {
  39. $this->isXhr = true;
  40. $this->fileName = isset($_SERVER['HTTP_X_FILE_NAME']) ?
  41. $_SERVER['HTTP_X_FILE_NAME'] : $_GET[$this->uploadName];
  42. if (isset($_SERVER['CONTENT_LENGTH'])) {
  43. $this->fileSize = (int)$_SERVER['CONTENT_LENGTH'];
  44. } else {
  45. throw new Exception('Content length is empty.');
  46. }
  47. }
  48. if ($this->fileName) {
  49. $this->fileName = $this->sanitizeFilename($this->fileName);
  50. $pathinfo = pathinfo($this->fileName);
  51. if (isset($pathinfo['extension']) &&
  52. isset($pathinfo['filename']))
  53. {
  54. $this->fileExtension = strtolower($pathinfo['extension']);
  55. $this->fileNameWithoutExt = $pathinfo['filename'];
  56. }
  57. }
  58. }
  59. private function sanitizeFilename($name) {
  60. $name = trim($this->basename(stripslashes($name)), ".\x00..\x20");
  61. // Use timestamp for empty filenames
  62. if (!$name) {
  63. $name = str_replace('.', '-', microtime(true));
  64. }
  65. return $name;
  66. }
  67. private function basename($filepath, $suffix = null) {
  68. $splited = preg_split('/\//', rtrim($filepath, '/ '));
  69. return substr(basename('X'.$splited[count($splited)-1], $suffix), 1);
  70. }
  71. public function getFileName() {
  72. return $this->fileName;
  73. }
  74. public function getFileSize() {
  75. return $this->fileSize;
  76. }
  77. public function getFileNameWithoutExt() {
  78. return $this->fileNameWithoutExt;
  79. }
  80. public function getExtension() {
  81. return $this->fileExtension;
  82. }
  83. public function getErrorMsg() {
  84. return $this->errorMsg;
  85. }
  86. public function getSavedFile() {
  87. return $this->savedFile;
  88. }
  89. private function errorCodeToMsg($code) {
  90. switch($code) {
  91. case UPLOAD_ERR_INI_SIZE:
  92. $message = '文件大小超出限制。';
  93. break;
  94. case UPLOAD_ERR_PARTIAL:
  95. $message = '上传的文件只是部分上传。';
  96. break;
  97. case UPLOAD_ERR_NO_FILE:
  98. $message = '没有上传文件。';
  99. break;
  100. case UPLOAD_ERR_NO_TMP_DIR:
  101. $message = '缺少临时文件夹。';
  102. break;
  103. case UPLOAD_ERR_CANT_WRITE:
  104. $message = '无法将文件写入磁盘。';
  105. break;
  106. case UPLOAD_ERR_EXTENSION:
  107. $message = '通过扩展停止文件上传。';
  108. break;
  109. default:
  110. $message = '未知上传错误。';
  111. break;
  112. }
  113. return $message;
  114. }
  115. private function checkExtension($ext, $allowedExtensions) {
  116. if (!is_array($allowedExtensions))
  117. return false;
  118. if (!in_array(strtolower($ext), array_map('strtolower', $allowedExtensions)))
  119. return false;
  120. return true;
  121. }
  122. private function setErrorMsg($msg) {
  123. if (empty($this->errorMsg))
  124. $this->errorMsg = $msg;
  125. }
  126. private function fixDir($dir) {
  127. if (empty($dir))
  128. return $dir;
  129. $slash = DIRECTORY_SEPARATOR;
  130. $dir = str_replace('/', $slash, $dir);
  131. $dir = str_replace('\\', $slash, $dir);
  132. return substr($dir, -1) == $slash ? $dir : $dir . $slash;
  133. }
  134. // escapeJS and jsMatcher are adapted from the Escaper component of
  135. // Zend Framework, Copyright (c) 2005-2013, Zend Technologies USA, Inc.
  136. // https://github.com/zendframework/zf2/tree/master/library/Zend/Escaper
  137. private function escapeJS($string) {
  138. return preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, $string);
  139. }
  140. private function jsMatcher($matches) {
  141. $chr = $matches[0];
  142. if (strlen($chr) == 1)
  143. return sprintf('\\x%02X', ord($chr));
  144. if (function_exists('iconv'))
  145. $chr = iconv('UTF-16BE', 'UTF-8', $chr);
  146. elseif (function_exists('mb_convert_encoding'))
  147. $chr = mb_convert_encoding($chr, 'UTF-8', 'UTF-16BE');
  148. return sprintf('\\u%04s', strtoupper(bin2hex($chr)));
  149. }
  150. public function corsResponse($data) {
  151. if (isset($_REQUEST[$this->corsInputName])) {
  152. $targetOrigin = $this->escapeJS($_REQUEST[$this->corsInputName]);
  153. $targetOrigin = htmlspecialchars($targetOrigin, ENT_QUOTES, 'UTF-8');
  154. return "<script>window.parent.postMessage('$data','$targetOrigin');</script>";
  155. }
  156. return $data;
  157. }
  158. public function getMimeType($path) {
  159. $finfo = new finfo(FILEINFO_MIME_TYPE);
  160. $fileContents = file_get_contents($path);
  161. $mime = $finfo->buffer($fileContents);
  162. $fileContents = null;
  163. return $mime;
  164. }
  165. public function isWebImage($path) {
  166. $pathinfo = pathinfo($path);
  167. if (isset($pathinfo['extension'])) {
  168. if (!in_array(strtolower($pathinfo['extension']), array('gif', 'png', 'jpg', 'jpeg')))
  169. return false;
  170. }
  171. $type = exif_imagetype($path);
  172. if (!$type)
  173. return false;
  174. return ($type == IMAGETYPE_GIF || $type == IMAGETYPE_JPEG || $type == IMAGETYPE_PNG);
  175. }
  176. private function saveXhr($path) {
  177. if (false !== file_put_contents($path, fopen('php://input', 'r')))
  178. return true;
  179. return false;
  180. }
  181. private function saveForm($path) {
  182. if (move_uploaded_file($_FILES[$this->uploadName]['tmp_name'], $path))
  183. return true;
  184. return false;
  185. }
  186. private function save($path) {
  187. if (true === $this->isXhr)
  188. return $this->saveXhr($path);
  189. return $this->saveForm($path);
  190. }
  191. public function handleUpload($uploadDir = null, $allowedExtensions = null) {
  192. if (!$this->fileName) {
  193. $this->setErrorMsg('上传文件不正确或没有上传文件');
  194. return false;
  195. }
  196. if ($this->fileSize == 0) {
  197. $this->setErrorMsg('文件是空的');
  198. return false;
  199. }
  200. if ($this->fileSize > $this->sizeLimit) {
  201. $this->setErrorMsg('文件大小超出限制');
  202. return false;
  203. }
  204. if (!empty($uploadDir))
  205. $this->uploadDir = $uploadDir;
  206. $this->uploadDir = $this->fixDir($this->uploadDir);
  207. if (!file_exists($this->uploadDir)) {
  208. $this->setErrorMsg('上传目录不存在');
  209. return false;
  210. } else if (!is_writable($this->uploadDir)) {
  211. $this->setErrorMsg('上载目录存在,但不可写。');
  212. return false;
  213. }
  214. if (is_array($allowedExtensions))
  215. $this->allowedExtensions = $allowedExtensions;
  216. if (!empty($this->allowedExtensions)) {
  217. if (!$this->checkExtension($this->fileExtension, $this->allowedExtensions)) {
  218. $this->setErrorMsg('无效的文件类型');
  219. return false;
  220. }
  221. }
  222. $this->savedFile = $this->uploadDir . $this->fileName;
  223. if (!empty($this->newFileName)) {
  224. $this->fileName = $this->newFileName;
  225. $this->savedFile = $this->uploadDir . $this->fileName;
  226. $this->fileNameWithoutExt = null;
  227. $this->fileExtension = null;
  228. $pathinfo = pathinfo($this->fileName);
  229. if (isset($pathinfo['filename']))
  230. $this->fileNameWithoutExt = $pathinfo['filename'];
  231. if (isset($pathinfo['extension']))
  232. $this->fileExtension = strtolower($pathinfo['extension']);
  233. }
  234. if (!$this->save($this->savedFile)) {
  235. $this->setErrorMsg('文件无法保存');
  236. return false;
  237. }
  238. return true;
  239. }
  240. }