PageRenderTime 49ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/phpBB/includes/functions_upload.php

http://github.com/phpbb/phpbb
PHP | 1118 lines | 842 code | 130 blank | 146 comment | 97 complexity | 7143be02859bcde21bc19992663e6c92 MD5 | raw file
Possible License(s): GPL-3.0, AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * This file is part of the phpBB Forum Software package.
  5. *
  6. * @copyright (c) phpBB Limited <https://www.phpbb.com>
  7. * @license GNU General Public License, version 2 (GPL-2.0)
  8. *
  9. * For full copyright and license information, please see
  10. * the docs/CREDITS.txt file.
  11. *
  12. */
  13. /**
  14. * @ignore
  15. */
  16. if (!defined('IN_PHPBB'))
  17. {
  18. exit;
  19. }
  20. /**
  21. * Responsible for holding all file relevant information, as well as doing file-specific operations.
  22. * The {@link fileupload fileupload class} can be used to upload several files, each of them being this object to operate further on.
  23. */
  24. class filespec
  25. {
  26. var $filename = '';
  27. var $realname = '';
  28. var $uploadname = '';
  29. var $mimetype = '';
  30. var $extension = '';
  31. var $filesize = 0;
  32. var $width = 0;
  33. var $height = 0;
  34. var $image_info = array();
  35. var $destination_file = '';
  36. var $destination_path = '';
  37. var $file_moved = false;
  38. var $init_error = false;
  39. var $local = false;
  40. var $error = array();
  41. var $upload = '';
  42. /**
  43. * The plupload object
  44. * @var \phpbb\plupload\plupload
  45. */
  46. protected $plupload;
  47. /**
  48. * phpBB Mimetype guesser
  49. * @var \phpbb\mimetype\guesser
  50. */
  51. protected $mimetype_guesser;
  52. /**
  53. * File Class
  54. * @access private
  55. */
  56. function filespec($upload_ary, $upload_namespace, \phpbb\mimetype\guesser $mimetype_guesser = null, \phpbb\plupload\plupload $plupload = null)
  57. {
  58. if (!isset($upload_ary))
  59. {
  60. $this->init_error = true;
  61. return;
  62. }
  63. $this->filename = $upload_ary['tmp_name'];
  64. $this->filesize = $upload_ary['size'];
  65. $name = (STRIP) ? stripslashes($upload_ary['name']) : $upload_ary['name'];
  66. $name = trim(utf8_basename($name));
  67. $this->realname = $this->uploadname = $name;
  68. $this->mimetype = $upload_ary['type'];
  69. // Opera adds the name to the mime type
  70. $this->mimetype = (strpos($this->mimetype, '; name') !== false) ? str_replace(strstr($this->mimetype, '; name'), '', $this->mimetype) : $this->mimetype;
  71. if (!$this->mimetype)
  72. {
  73. $this->mimetype = 'application/octet-stream';
  74. }
  75. $this->extension = strtolower(self::get_extension($this->realname));
  76. // Try to get real filesize from temporary folder (not always working) ;)
  77. $this->filesize = (@filesize($this->filename)) ? @filesize($this->filename) : $this->filesize;
  78. $this->width = $this->height = 0;
  79. $this->file_moved = false;
  80. $this->local = (isset($upload_ary['local_mode'])) ? true : false;
  81. $this->upload = $upload_namespace;
  82. $this->plupload = $plupload;
  83. $this->mimetype_guesser = $mimetype_guesser;
  84. }
  85. /**
  86. * Cleans destination filename
  87. *
  88. * @param real|unique|unique_ext $mode real creates a realname, filtering some characters, lowering every character. Unique creates an unique filename
  89. * @param string $prefix Prefix applied to filename
  90. * @param string $user_id The user_id is only needed for when cleaning a user's avatar
  91. * @access public
  92. */
  93. function clean_filename($mode = 'unique', $prefix = '', $user_id = '')
  94. {
  95. if ($this->init_error)
  96. {
  97. return;
  98. }
  99. switch ($mode)
  100. {
  101. case 'real':
  102. // Remove every extension from filename (to not let the mime bug being exposed)
  103. if (strpos($this->realname, '.') !== false)
  104. {
  105. $this->realname = substr($this->realname, 0, strpos($this->realname, '.'));
  106. }
  107. // Replace any chars which may cause us problems with _
  108. $bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|');
  109. $this->realname = rawurlencode(str_replace($bad_chars, '_', strtolower($this->realname)));
  110. $this->realname = preg_replace("/%(\w{2})/", '_', $this->realname);
  111. $this->realname = $prefix . $this->realname . '.' . $this->extension;
  112. break;
  113. case 'unique':
  114. $this->realname = $prefix . md5(unique_id());
  115. break;
  116. case 'avatar':
  117. $this->extension = strtolower($this->extension);
  118. $this->realname = $prefix . $user_id . '.' . $this->extension;
  119. break;
  120. case 'unique_ext':
  121. default:
  122. $this->realname = $prefix . md5(unique_id()) . '.' . $this->extension;
  123. break;
  124. }
  125. }
  126. /**
  127. * Get property from file object
  128. */
  129. function get($property)
  130. {
  131. if ($this->init_error || !isset($this->$property))
  132. {
  133. return false;
  134. }
  135. return $this->$property;
  136. }
  137. /**
  138. * Check if file is an image (mimetype)
  139. *
  140. * @return true if it is an image, false if not
  141. */
  142. function is_image()
  143. {
  144. return (strpos($this->mimetype, 'image/') === 0);
  145. }
  146. /**
  147. * Check if the file got correctly uploaded
  148. *
  149. * @return true if it is a valid upload, false if not
  150. */
  151. function is_uploaded()
  152. {
  153. $is_plupload = $this->plupload && $this->plupload->is_active();
  154. if (!$this->local && !$is_plupload && !is_uploaded_file($this->filename))
  155. {
  156. return false;
  157. }
  158. if (($this->local || $is_plupload) && !file_exists($this->filename))
  159. {
  160. return false;
  161. }
  162. return true;
  163. }
  164. /**
  165. * Remove file
  166. */
  167. function remove()
  168. {
  169. if ($this->file_moved)
  170. {
  171. @unlink($this->destination_file);
  172. }
  173. }
  174. /**
  175. * Get file extension
  176. *
  177. * @param string Filename that needs to be checked
  178. * @return string Extension of the supplied filename
  179. */
  180. static public function get_extension($filename)
  181. {
  182. $filename = utf8_basename($filename);
  183. if (strpos($filename, '.') === false)
  184. {
  185. return '';
  186. }
  187. $filename = explode('.', $filename);
  188. return array_pop($filename);
  189. }
  190. /**
  191. * Get mimetype
  192. *
  193. * @param string $filename Filename that needs to be checked
  194. * @return string Mimetype of supplied filename
  195. */
  196. function get_mimetype($filename)
  197. {
  198. if ($this->mimetype_guesser !== null)
  199. {
  200. $mimetype = $this->mimetype_guesser->guess($filename, $this->uploadname);
  201. if ($mimetype !== 'application/octet-stream')
  202. {
  203. $this->mimetype = $mimetype;
  204. }
  205. }
  206. return $this->mimetype;
  207. }
  208. /**
  209. * Get filesize
  210. */
  211. function get_filesize($filename)
  212. {
  213. return @filesize($filename);
  214. }
  215. /**
  216. * Check the first 256 bytes for forbidden content
  217. */
  218. function check_content($disallowed_content)
  219. {
  220. if (empty($disallowed_content))
  221. {
  222. return true;
  223. }
  224. $fp = @fopen($this->filename, 'rb');
  225. if ($fp !== false)
  226. {
  227. $ie_mime_relevant = fread($fp, 256);
  228. fclose($fp);
  229. foreach ($disallowed_content as $forbidden)
  230. {
  231. if (stripos($ie_mime_relevant, '<' . $forbidden) !== false)
  232. {
  233. return false;
  234. }
  235. }
  236. }
  237. return true;
  238. }
  239. /**
  240. * Move file to destination folder
  241. * The phpbb_root_path variable will be applied to the destination path
  242. *
  243. * @param string $destination Destination path, for example $config['avatar_path']
  244. * @param bool $overwrite If set to true, an already existing file will be overwritten
  245. * @param bool $skip_image_check If set to true, the check for the file to be a valid image is skipped
  246. * @param string $chmod Permission mask for chmodding the file after a successful move. The mode entered here reflects the mode defined by {@link phpbb_chmod()}
  247. *
  248. * @access public
  249. */
  250. function move_file($destination, $overwrite = false, $skip_image_check = false, $chmod = false)
  251. {
  252. global $user, $phpbb_root_path;
  253. if (sizeof($this->error))
  254. {
  255. return false;
  256. }
  257. $chmod = ($chmod === false) ? CHMOD_READ | CHMOD_WRITE : $chmod;
  258. // We need to trust the admin in specifying valid upload directories and an attacker not being able to overwrite it...
  259. $this->destination_path = $phpbb_root_path . $destination;
  260. // Check if the destination path exist...
  261. if (!file_exists($this->destination_path))
  262. {
  263. @unlink($this->filename);
  264. return false;
  265. }
  266. $upload_mode = (@ini_get('open_basedir') || @ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == 'on') ? 'move' : 'copy';
  267. $upload_mode = ($this->local) ? 'local' : $upload_mode;
  268. $this->destination_file = $this->destination_path . '/' . utf8_basename($this->realname);
  269. // Check if the file already exist, else there is something wrong...
  270. if (file_exists($this->destination_file) && !$overwrite)
  271. {
  272. @unlink($this->filename);
  273. $this->error[] = $user->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file);
  274. $this->file_moved = false;
  275. return false;
  276. }
  277. else
  278. {
  279. if (file_exists($this->destination_file))
  280. {
  281. @unlink($this->destination_file);
  282. }
  283. switch ($upload_mode)
  284. {
  285. case 'copy':
  286. if (!@copy($this->filename, $this->destination_file))
  287. {
  288. if (!@move_uploaded_file($this->filename, $this->destination_file))
  289. {
  290. $this->error[] = sprintf($user->lang[$this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR'], $this->destination_file);
  291. }
  292. }
  293. break;
  294. case 'move':
  295. if (!@move_uploaded_file($this->filename, $this->destination_file))
  296. {
  297. if (!@copy($this->filename, $this->destination_file))
  298. {
  299. $this->error[] = sprintf($user->lang[$this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR'], $this->destination_file);
  300. }
  301. }
  302. break;
  303. case 'local':
  304. if (!@copy($this->filename, $this->destination_file))
  305. {
  306. $this->error[] = sprintf($user->lang[$this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR'], $this->destination_file);
  307. }
  308. break;
  309. }
  310. // Remove temporary filename
  311. @unlink($this->filename);
  312. if (sizeof($this->error))
  313. {
  314. return false;
  315. }
  316. phpbb_chmod($this->destination_file, $chmod);
  317. }
  318. // Try to get real filesize from destination folder
  319. $this->filesize = (@filesize($this->destination_file)) ? @filesize($this->destination_file) : $this->filesize;
  320. // Get mimetype of supplied file
  321. $this->mimetype = $this->get_mimetype($this->destination_file);
  322. if ($this->is_image() && !$skip_image_check)
  323. {
  324. $this->width = $this->height = 0;
  325. if (($this->image_info = @getimagesize($this->destination_file)) !== false)
  326. {
  327. $this->width = $this->image_info[0];
  328. $this->height = $this->image_info[1];
  329. if (!empty($this->image_info['mime']))
  330. {
  331. $this->mimetype = $this->image_info['mime'];
  332. }
  333. // Check image type
  334. $types = fileupload::image_types();
  335. if (!isset($types[$this->image_info[2]]) || !in_array($this->extension, $types[$this->image_info[2]]))
  336. {
  337. if (!isset($types[$this->image_info[2]]))
  338. {
  339. $this->error[] = sprintf($user->lang['IMAGE_FILETYPE_INVALID'], $this->image_info[2], $this->mimetype);
  340. }
  341. else
  342. {
  343. $this->error[] = sprintf($user->lang['IMAGE_FILETYPE_MISMATCH'], $types[$this->image_info[2]][0], $this->extension);
  344. }
  345. }
  346. // Make sure the dimensions match a valid image
  347. if (empty($this->width) || empty($this->height))
  348. {
  349. $this->error[] = $user->lang['ATTACHED_IMAGE_NOT_IMAGE'];
  350. }
  351. }
  352. else
  353. {
  354. $this->error[] = $user->lang['UNABLE_GET_IMAGE_SIZE'];
  355. }
  356. }
  357. $this->file_moved = true;
  358. $this->additional_checks();
  359. unset($this->upload);
  360. return true;
  361. }
  362. /**
  363. * Performing additional checks
  364. */
  365. function additional_checks()
  366. {
  367. global $user;
  368. if (!$this->file_moved)
  369. {
  370. return false;
  371. }
  372. // Filesize is too big or it's 0 if it was larger than the maxsize in the upload form
  373. if ($this->upload->max_filesize && ($this->get('filesize') > $this->upload->max_filesize || $this->filesize == 0))
  374. {
  375. $max_filesize = get_formatted_filesize($this->upload->max_filesize, false);
  376. $this->error[] = sprintf($user->lang[$this->upload->error_prefix . 'WRONG_FILESIZE'], $max_filesize['value'], $max_filesize['unit']);
  377. return false;
  378. }
  379. if (!$this->upload->valid_dimensions($this))
  380. {
  381. $this->error[] = $user->lang($this->upload->error_prefix . 'WRONG_SIZE',
  382. $user->lang('PIXELS', (int) $this->upload->min_width),
  383. $user->lang('PIXELS', (int) $this->upload->min_height),
  384. $user->lang('PIXELS', (int) $this->upload->max_width),
  385. $user->lang('PIXELS', (int) $this->upload->max_height),
  386. $user->lang('PIXELS', (int) $this->width),
  387. $user->lang('PIXELS', (int) $this->height));
  388. return false;
  389. }
  390. return true;
  391. }
  392. }
  393. /**
  394. * Class for assigning error messages before a real filespec class can be assigned
  395. */
  396. class fileerror extends filespec
  397. {
  398. function fileerror($error_msg)
  399. {
  400. $this->error[] = $error_msg;
  401. }
  402. }
  403. /**
  404. * File upload class
  405. * Init class (all parameters optional and able to be set/overwritten separately) - scope is global and valid for all uploads
  406. */
  407. class fileupload
  408. {
  409. var $allowed_extensions = array();
  410. var $disallowed_content = array('body', 'head', 'html', 'img', 'plaintext', 'a href', 'pre', 'script', 'table', 'title');
  411. var $max_filesize = 0;
  412. var $min_width = 0;
  413. var $min_height = 0;
  414. var $max_width = 0;
  415. var $max_height = 0;
  416. var $error_prefix = '';
  417. /** @var int Timeout for remote upload */
  418. var $upload_timeout = 6;
  419. /**
  420. * Init file upload class.
  421. *
  422. * @param string $error_prefix Used error messages will get prefixed by this string
  423. * @param array $allowed_extensions Array of allowed extensions, for example array('jpg', 'jpeg', 'gif', 'png')
  424. * @param int $max_filesize Maximum filesize
  425. * @param int $min_width Minimum image width (only checked for images)
  426. * @param int $min_height Minimum image height (only checked for images)
  427. * @param int $max_width Maximum image width (only checked for images)
  428. * @param int $max_height Maximum image height (only checked for images)
  429. * @param bool|array $disallowed_content If enabled, the first 256 bytes of the file must not
  430. * contain any of its values. Defaults to false.
  431. *
  432. */
  433. function fileupload($error_prefix = '', $allowed_extensions = false, $max_filesize = false, $min_width = false, $min_height = false, $max_width = false, $max_height = false, $disallowed_content = false)
  434. {
  435. $this->set_allowed_extensions($allowed_extensions);
  436. $this->set_max_filesize($max_filesize);
  437. $this->set_allowed_dimensions($min_width, $min_height, $max_width, $max_height);
  438. $this->set_error_prefix($error_prefix);
  439. $this->set_disallowed_content($disallowed_content);
  440. }
  441. /**
  442. * Reset vars
  443. */
  444. function reset_vars()
  445. {
  446. $this->max_filesize = 0;
  447. $this->min_width = $this->min_height = $this->max_width = $this->max_height = 0;
  448. $this->error_prefix = '';
  449. $this->allowed_extensions = array();
  450. $this->disallowed_content = array();
  451. }
  452. /**
  453. * Set allowed extensions
  454. */
  455. function set_allowed_extensions($allowed_extensions)
  456. {
  457. if ($allowed_extensions !== false && is_array($allowed_extensions))
  458. {
  459. $this->allowed_extensions = $allowed_extensions;
  460. }
  461. }
  462. /**
  463. * Set allowed dimensions
  464. */
  465. function set_allowed_dimensions($min_width, $min_height, $max_width, $max_height)
  466. {
  467. $this->min_width = (int) $min_width;
  468. $this->min_height = (int) $min_height;
  469. $this->max_width = (int) $max_width;
  470. $this->max_height = (int) $max_height;
  471. }
  472. /**
  473. * Set maximum allowed filesize
  474. */
  475. function set_max_filesize($max_filesize)
  476. {
  477. if ($max_filesize !== false && (int) $max_filesize)
  478. {
  479. $this->max_filesize = (int) $max_filesize;
  480. }
  481. }
  482. /**
  483. * Set disallowed strings
  484. */
  485. function set_disallowed_content($disallowed_content)
  486. {
  487. if ($disallowed_content !== false && is_array($disallowed_content))
  488. {
  489. $this->disallowed_content = array_diff($disallowed_content, array(''));
  490. }
  491. }
  492. /**
  493. * Set error prefix
  494. */
  495. function set_error_prefix($error_prefix)
  496. {
  497. $this->error_prefix = $error_prefix;
  498. }
  499. /**
  500. * Form upload method
  501. * Upload file from users harddisk
  502. *
  503. * @param string $form_name Form name assigned to the file input field (if it is an array, the key has to be specified)
  504. * @param \phpbb\mimetype\guesser $mimetype_guesser Mimetype guesser
  505. * @param \phpbb\plupload\plupload $plupload The plupload object
  506. *
  507. * @return object $file Object "filespec" is returned, all further operations can be done with this object
  508. * @access public
  509. */
  510. function form_upload($form_name, \phpbb\mimetype\guesser $mimetype_guesser = null, \phpbb\plupload\plupload $plupload = null)
  511. {
  512. global $user, $request;
  513. $upload = $request->file($form_name);
  514. unset($upload['local_mode']);
  515. if ($plupload)
  516. {
  517. $result = $plupload->handle_upload($form_name);
  518. if (is_array($result))
  519. {
  520. $upload = array_merge($upload, $result);
  521. }
  522. }
  523. $file = new filespec($upload, $this, $mimetype_guesser, $plupload);
  524. if ($file->init_error)
  525. {
  526. $file->error[] = '';
  527. return $file;
  528. }
  529. // Error array filled?
  530. if (isset($upload['error']))
  531. {
  532. $error = $this->assign_internal_error($upload['error']);
  533. if ($error !== false)
  534. {
  535. $file->error[] = $error;
  536. return $file;
  537. }
  538. }
  539. // Check if empty file got uploaded (not catched by is_uploaded_file)
  540. if (isset($upload['size']) && $upload['size'] == 0)
  541. {
  542. $file->error[] = $user->lang[$this->error_prefix . 'EMPTY_FILEUPLOAD'];
  543. return $file;
  544. }
  545. // PHP Upload filesize exceeded
  546. if ($file->get('filename') == 'none')
  547. {
  548. $max_filesize = @ini_get('upload_max_filesize');
  549. $unit = 'MB';
  550. if (!empty($max_filesize))
  551. {
  552. $unit = strtolower(substr($max_filesize, -1, 1));
  553. $max_filesize = (int) $max_filesize;
  554. $unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB');
  555. }
  556. $file->error[] = (empty($max_filesize)) ? $user->lang[$this->error_prefix . 'PHP_SIZE_NA'] : sprintf($user->lang[$this->error_prefix . 'PHP_SIZE_OVERRUN'], $max_filesize, $user->lang[$unit]);
  557. return $file;
  558. }
  559. // Not correctly uploaded
  560. if (!$file->is_uploaded())
  561. {
  562. $file->error[] = $user->lang[$this->error_prefix . 'NOT_UPLOADED'];
  563. return $file;
  564. }
  565. $this->common_checks($file);
  566. return $file;
  567. }
  568. /**
  569. * Move file from another location to phpBB
  570. */
  571. function local_upload($source_file, $filedata = false, \phpbb\mimetype\guesser $mimetype_guesser = null)
  572. {
  573. global $user, $request;
  574. $upload = array();
  575. $upload['local_mode'] = true;
  576. $upload['tmp_name'] = $source_file;
  577. if ($filedata === false)
  578. {
  579. $upload['name'] = utf8_basename($source_file);
  580. $upload['size'] = 0;
  581. }
  582. else
  583. {
  584. $upload['name'] = $filedata['realname'];
  585. $upload['size'] = $filedata['size'];
  586. $upload['type'] = $filedata['type'];
  587. }
  588. $file = new filespec($upload, $this, $mimetype_guesser);
  589. if ($file->init_error)
  590. {
  591. $file->error[] = '';
  592. return $file;
  593. }
  594. if (isset($upload['error']))
  595. {
  596. $error = $this->assign_internal_error($upload['error']);
  597. if ($error !== false)
  598. {
  599. $file->error[] = $error;
  600. return $file;
  601. }
  602. }
  603. // PHP Upload filesize exceeded
  604. if ($file->get('filename') == 'none')
  605. {
  606. $max_filesize = @ini_get('upload_max_filesize');
  607. $unit = 'MB';
  608. if (!empty($max_filesize))
  609. {
  610. $unit = strtolower(substr($max_filesize, -1, 1));
  611. $max_filesize = (int) $max_filesize;
  612. $unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB');
  613. }
  614. $file->error[] = (empty($max_filesize)) ? $user->lang[$this->error_prefix . 'PHP_SIZE_NA'] : sprintf($user->lang[$this->error_prefix . 'PHP_SIZE_OVERRUN'], $max_filesize, $user->lang[$unit]);
  615. return $file;
  616. }
  617. // Not correctly uploaded
  618. if (!$file->is_uploaded())
  619. {
  620. $file->error[] = $user->lang[$this->error_prefix . 'NOT_UPLOADED'];
  621. return $file;
  622. }
  623. $this->common_checks($file);
  624. $request->overwrite('local', $upload, \phpbb\request\request_interface::FILES);
  625. return $file;
  626. }
  627. /**
  628. * Remote upload method
  629. * Uploads file from given url
  630. *
  631. * @param string $upload_url URL pointing to file to upload, for example http://www.foobar.com/example.gif
  632. * @param \phpbb\mimetype\guesser $mimetype_guesser Mimetype guesser
  633. * @return object $file Object "filespec" is returned, all further operations can be done with this object
  634. * @access public
  635. */
  636. function remote_upload($upload_url, \phpbb\mimetype\guesser $mimetype_guesser = null)
  637. {
  638. global $user, $phpbb_root_path;
  639. $upload_ary = array();
  640. $upload_ary['local_mode'] = true;
  641. if (!preg_match('#^(https?://).*?\.(' . implode('|', $this->allowed_extensions) . ')$#i', $upload_url, $match))
  642. {
  643. $file = new fileerror($user->lang[$this->error_prefix . 'URL_INVALID']);
  644. return $file;
  645. }
  646. if (empty($match[2]))
  647. {
  648. $file = new fileerror($user->lang[$this->error_prefix . 'URL_INVALID']);
  649. return $file;
  650. }
  651. $url = parse_url($upload_url);
  652. $default_port = 80;
  653. $hostname = $url['host'];
  654. if ($url['scheme'] == 'https')
  655. {
  656. $default_port = 443;
  657. $hostname = 'tls://' . $url['host'];
  658. }
  659. $host = $url['host'];
  660. $path = $url['path'];
  661. $port = (!empty($url['port'])) ? (int) $url['port'] : $default_port;
  662. $upload_ary['type'] = 'application/octet-stream';
  663. $url['path'] = explode('.', $url['path']);
  664. $ext = array_pop($url['path']);
  665. $url['path'] = implode('', $url['path']);
  666. $upload_ary['name'] = utf8_basename($url['path']) . (($ext) ? '.' . $ext : '');
  667. $filename = $url['path'];
  668. $filesize = 0;
  669. $remote_max_filesize = $this->max_filesize;
  670. if (!$remote_max_filesize)
  671. {
  672. $max_filesize = @ini_get('upload_max_filesize');
  673. if (!empty($max_filesize))
  674. {
  675. $unit = strtolower(substr($max_filesize, -1, 1));
  676. $remote_max_filesize = (int) $max_filesize;
  677. switch ($unit)
  678. {
  679. case 'g':
  680. $remote_max_filesize *= 1024;
  681. // no break
  682. case 'm':
  683. $remote_max_filesize *= 1024;
  684. // no break
  685. case 'k':
  686. $remote_max_filesize *= 1024;
  687. // no break
  688. }
  689. }
  690. }
  691. $errno = 0;
  692. $errstr = '';
  693. if (!($fsock = @fsockopen($hostname, $port, $errno, $errstr)))
  694. {
  695. $file = new fileerror($user->lang[$this->error_prefix . 'NOT_UPLOADED']);
  696. return $file;
  697. }
  698. // Make sure $path not beginning with /
  699. if (strpos($path, '/') === 0)
  700. {
  701. $path = substr($path, 1);
  702. }
  703. fputs($fsock, 'GET /' . $path . " HTTP/1.1\r\n");
  704. fputs($fsock, "HOST: " . $host . "\r\n");
  705. fputs($fsock, "Connection: close\r\n\r\n");
  706. // Set a proper timeout for the socket
  707. socket_set_timeout($fsock, $this->upload_timeout);
  708. $get_info = false;
  709. $data = '';
  710. $length = false;
  711. $timer_stop = time() + $this->upload_timeout;
  712. while ((!$length || $filesize < $length) && !@feof($fsock))
  713. {
  714. if ($get_info)
  715. {
  716. if ($length)
  717. {
  718. // Don't attempt to read past end of file if server indicated length
  719. $block = @fread($fsock, min($length - $filesize, 1024));
  720. }
  721. else
  722. {
  723. $block = @fread($fsock, 1024);
  724. }
  725. $filesize += strlen($block);
  726. if ($remote_max_filesize && $filesize > $remote_max_filesize)
  727. {
  728. $max_filesize = get_formatted_filesize($remote_max_filesize, false);
  729. $file = new fileerror(sprintf($user->lang[$this->error_prefix . 'WRONG_FILESIZE'], $max_filesize['value'], $max_filesize['unit']));
  730. return $file;
  731. }
  732. $data .= $block;
  733. }
  734. else
  735. {
  736. $line = @fgets($fsock, 1024);
  737. if ($line == "\r\n")
  738. {
  739. $get_info = true;
  740. }
  741. else
  742. {
  743. if (stripos($line, 'content-type: ') !== false)
  744. {
  745. $upload_ary['type'] = rtrim(str_replace('content-type: ', '', strtolower($line)));
  746. }
  747. else if ($this->max_filesize && stripos($line, 'content-length: ') !== false)
  748. {
  749. $length = (int) str_replace('content-length: ', '', strtolower($line));
  750. if ($remote_max_filesize && $length && $length > $remote_max_filesize)
  751. {
  752. $max_filesize = get_formatted_filesize($remote_max_filesize, false);
  753. $file = new fileerror(sprintf($user->lang[$this->error_prefix . 'WRONG_FILESIZE'], $max_filesize['value'], $max_filesize['unit']));
  754. return $file;
  755. }
  756. }
  757. else if (stripos($line, '404 not found') !== false)
  758. {
  759. $file = new fileerror($user->lang[$this->error_prefix . 'URL_NOT_FOUND']);
  760. return $file;
  761. }
  762. }
  763. }
  764. $stream_meta_data = stream_get_meta_data($fsock);
  765. // Cancel upload if we exceed timeout
  766. if (!empty($stream_meta_data['timed_out']) || time() >= $timer_stop)
  767. {
  768. $file = new fileerror($user->lang[$this->error_prefix . 'REMOTE_UPLOAD_TIMEOUT']);
  769. return $file;
  770. }
  771. }
  772. @fclose($fsock);
  773. if (empty($data))
  774. {
  775. $file = new fileerror($user->lang[$this->error_prefix . 'EMPTY_REMOTE_DATA']);
  776. return $file;
  777. }
  778. $tmp_path = (!@ini_get('safe_mode') || strtolower(@ini_get('safe_mode')) == 'off') ? sys_get_temp_dir() : $phpbb_root_path . 'cache';
  779. $filename = tempnam($tmp_path, unique_id() . '-');
  780. if (!($fp = @fopen($filename, 'wb')))
  781. {
  782. $file = new fileerror($user->lang[$this->error_prefix . 'NOT_UPLOADED']);
  783. return $file;
  784. }
  785. $upload_ary['size'] = fwrite($fp, $data);
  786. fclose($fp);
  787. unset($data);
  788. $upload_ary['tmp_name'] = $filename;
  789. $file = new filespec($upload_ary, $this, $mimetype_guesser);
  790. $this->common_checks($file);
  791. return $file;
  792. }
  793. /**
  794. * Assign internal error
  795. * @access private
  796. */
  797. function assign_internal_error($errorcode)
  798. {
  799. global $user;
  800. switch ($errorcode)
  801. {
  802. case 1:
  803. $max_filesize = @ini_get('upload_max_filesize');
  804. $unit = 'MB';
  805. if (!empty($max_filesize))
  806. {
  807. $unit = strtolower(substr($max_filesize, -1, 1));
  808. $max_filesize = (int) $max_filesize;
  809. $unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB');
  810. }
  811. $error = (empty($max_filesize)) ? $user->lang[$this->error_prefix . 'PHP_SIZE_NA'] : sprintf($user->lang[$this->error_prefix . 'PHP_SIZE_OVERRUN'], $max_filesize, $user->lang[$unit]);
  812. break;
  813. case 2:
  814. $max_filesize = get_formatted_filesize($this->max_filesize, false);
  815. $error = sprintf($user->lang[$this->error_prefix . 'WRONG_FILESIZE'], $max_filesize['value'], $max_filesize['unit']);
  816. break;
  817. case 3:
  818. $error = $user->lang[$this->error_prefix . 'PARTIAL_UPLOAD'];
  819. break;
  820. case 4:
  821. $error = $user->lang[$this->error_prefix . 'NOT_UPLOADED'];
  822. break;
  823. case 6:
  824. $error = 'Temporary folder could not be found. Please check your PHP installation.';
  825. break;
  826. default:
  827. $error = false;
  828. break;
  829. }
  830. return $error;
  831. }
  832. /**
  833. * Perform common checks
  834. */
  835. function common_checks(&$file)
  836. {
  837. global $user;
  838. // Filesize is too big or it's 0 if it was larger than the maxsize in the upload form
  839. if ($this->max_filesize && ($file->get('filesize') > $this->max_filesize || $file->get('filesize') == 0))
  840. {
  841. $max_filesize = get_formatted_filesize($this->max_filesize, false);
  842. $file->error[] = sprintf($user->lang[$this->error_prefix . 'WRONG_FILESIZE'], $max_filesize['value'], $max_filesize['unit']);
  843. }
  844. // check Filename
  845. if (preg_match("#[\\/:*?\"<>|]#i", $file->get('realname')))
  846. {
  847. $file->error[] = sprintf($user->lang[$this->error_prefix . 'INVALID_FILENAME'], $file->get('realname'));
  848. }
  849. // Invalid Extension
  850. if (!$this->valid_extension($file))
  851. {
  852. $file->error[] = sprintf($user->lang[$this->error_prefix . 'DISALLOWED_EXTENSION'], $file->get('extension'));
  853. }
  854. // MIME Sniffing
  855. if (!$this->valid_content($file))
  856. {
  857. $file->error[] = sprintf($user->lang[$this->error_prefix . 'DISALLOWED_CONTENT']);
  858. }
  859. }
  860. /**
  861. * Check for allowed extension
  862. */
  863. function valid_extension(&$file)
  864. {
  865. return (in_array($file->get('extension'), $this->allowed_extensions)) ? true : false;
  866. }
  867. /**
  868. * Check for allowed dimension
  869. */
  870. function valid_dimensions(&$file)
  871. {
  872. if (!$this->max_width && !$this->max_height && !$this->min_width && !$this->min_height)
  873. {
  874. return true;
  875. }
  876. if (($file->get('width') > $this->max_width && $this->max_width) ||
  877. ($file->get('height') > $this->max_height && $this->max_height) ||
  878. ($file->get('width') < $this->min_width && $this->min_width) ||
  879. ($file->get('height') < $this->min_height && $this->min_height))
  880. {
  881. return false;
  882. }
  883. return true;
  884. }
  885. /**
  886. * Check if form upload is valid
  887. */
  888. function is_valid($form_name)
  889. {
  890. global $request;
  891. $upload = $request->file($form_name);
  892. return (!empty($upload) && $upload['name'] !== 'none');
  893. }
  894. /**
  895. * Check for bad content (IE mime-sniffing)
  896. */
  897. function valid_content(&$file)
  898. {
  899. return ($file->check_content($this->disallowed_content));
  900. }
  901. /**
  902. * Get image type/extension mapping
  903. *
  904. * @return array Array containing the image types and their extensions
  905. */
  906. static public function image_types()
  907. {
  908. $result = array(
  909. IMAGETYPE_GIF => array('gif'),
  910. IMAGETYPE_JPEG => array('jpg', 'jpeg'),
  911. IMAGETYPE_PNG => array('png'),
  912. IMAGETYPE_SWF => array('swf'),
  913. IMAGETYPE_PSD => array('psd'),
  914. IMAGETYPE_BMP => array('bmp'),
  915. IMAGETYPE_TIFF_II => array('tif', 'tiff'),
  916. IMAGETYPE_TIFF_MM => array('tif', 'tiff'),
  917. IMAGETYPE_JPC => array('jpg', 'jpeg'),
  918. IMAGETYPE_JP2 => array('jpg', 'jpeg'),
  919. IMAGETYPE_JPX => array('jpg', 'jpeg'),
  920. IMAGETYPE_JB2 => array('jpg', 'jpeg'),
  921. IMAGETYPE_IFF => array('iff'),
  922. IMAGETYPE_WBMP => array('wbmp'),
  923. IMAGETYPE_XBM => array('xbm'),
  924. );
  925. if (defined('IMAGETYPE_SWC'))
  926. {
  927. $result[IMAGETYPE_SWC] = array('swc');
  928. }
  929. return $result;
  930. }
  931. }