PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/fUpload.php

https://bitbucket.org/wbond/flourish/
PHP | 700 lines | 370 code | 91 blank | 239 comment | 97 complexity | c204d9f66c48a3425fbeee5ec45e89b9 MD5 | raw file
  1. <?php
  2. /**
  3. * Provides validation and movement of uploaded files
  4. *
  5. * @copyright Copyright (c) 2007-2011 Will Bond, others
  6. * @author Will Bond [wb] <will@flourishlib.com>
  7. * @author Will Bond, iMarc LLC [wb-imarc] <will@imarc.net>
  8. * @license http://flourishlib.com/license
  9. *
  10. * @package Flourish
  11. * @link http://flourishlib.com/fUpload
  12. *
  13. * @version 1.0.0b14
  14. * @changes 1.0.0b14 Fixed some method signatures [wb, 2011-08-24]
  15. * @changes 1.0.0b13 Changed the class to throw fValidationException objects instead of fProgrammerException objects when the form is improperly configured - this is to prevent error logs when bad requests are sent by scanners/hackers [wb, 2011-08-24]
  16. * @changes 1.0.0b12 Fixed the ::filter() callback constant [wb, 2010-11-24]
  17. * @changes 1.0.0b11 Added ::setImageDimensions() and ::setImageRatio() [wb-imarc, 2010-11-11]
  18. * @changes 1.0.0b10 BackwardsCompatibilityBreak - renamed ::setMaxFilesize() to ::setMaxSize() to be consistent with fFile::getSize() [wb, 2010-05-30]
  19. * @changes 1.0.0b9 BackwardsCompatibilityBreak - the class no longer accepts uploaded files that start with a `.` unless ::allowDotFiles() is called - added ::setOptional() [wb, 2010-05-30]
  20. * @changes 1.0.0b8 BackwardsCompatibilityBreak - ::validate() no longer returns the `$_FILES` array for the file being validated - added `$return_message` parameter to ::validate(), fixed a bug with detection of mime type for text files [wb, 2010-05-26]
  21. * @changes 1.0.0b7 Added ::filter() to allow for ignoring array file upload field entries that did not have a file uploaded [wb, 2009-10-06]
  22. * @changes 1.0.0b6 Updated ::move() to use the new fFilesystem::createObject() method [wb, 2009-01-21]
  23. * @changes 1.0.0b5 Removed some unnecessary error suppression operators from ::move() [wb, 2009-01-05]
  24. * @changes 1.0.0b4 Updated ::validate() so it properly handles upload max filesize specified in human-readable notation [wb, 2009-01-05]
  25. * @changes 1.0.0b3 Removed the dependency on fRequest [wb, 2009-01-05]
  26. * @changes 1.0.0b2 Fixed a bug with validating filesizes [wb, 2008-11-25]
  27. * @changes 1.0.0b The initial implementation [wb, 2007-06-14]
  28. */
  29. class fUpload
  30. {
  31. // The following constants allow for nice looking callbacks to static methods
  32. const check = 'fUpload::check';
  33. const count = 'fUpload::count';
  34. const filter = 'fUpload::filter';
  35. /**
  36. * Checks to see if the field specified is a valid file upload field
  37. *
  38. * @throws fValidationException If `$throw_exception` is `TRUE` and the request was not a POST or the content type is not multipart/form-data
  39. *
  40. * @param string $field The field to check
  41. * @param boolean $throw_exception If an exception should be thrown when there are issues with the form
  42. * @return boolean If the field is a valid file upload field
  43. */
  44. static public function check($field, $throw_exception=TRUE)
  45. {
  46. if (isset($_GET[$field]) && $_SERVER['REQUEST_METHOD'] != 'POST') {
  47. if ($throw_exception) {
  48. throw new fValidationException(
  49. 'Missing method="post" attribute in form tag'
  50. );
  51. }
  52. return FALSE;
  53. }
  54. if (isset($_POST[$field]) && (!isset($_SERVER['CONTENT_TYPE']) || stripos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') === FALSE)) {
  55. if ($throw_exception) {
  56. throw new fValidationException(
  57. 'Missing enctype="multipart/form-data" attribute in form tag'
  58. );
  59. }
  60. return FALSE;
  61. }
  62. return isset($_FILES) && isset($_FILES[$field]) && is_array($_FILES[$field]);
  63. }
  64. /**
  65. * Returns the number of files uploaded to a file upload array field
  66. *
  67. * @throws fValidationException If the form is not properly configured for file uploads
  68. *
  69. * @param string $field The field to get the number of files for
  70. * @return integer The number of uploaded files
  71. */
  72. static public function count($field)
  73. {
  74. if (!self::check($field)) {
  75. throw new fValidationException(
  76. 'The field specified, %s, does not appear to be a file upload field',
  77. $field
  78. );
  79. }
  80. if (!is_array($_FILES[$field]['name'])) {
  81. throw new fValidationException(
  82. 'The field specified, %s, does not appear to be an array file upload field',
  83. $field
  84. );
  85. }
  86. return sizeof($_FILES[$field]['name']);
  87. }
  88. /**
  89. * Composes text using fText if loaded
  90. *
  91. * @param string $message The message to compose
  92. * @param mixed $component A string or number to insert into the message
  93. * @param mixed ...
  94. * @return string The composed and possible translated message
  95. */
  96. static protected function compose($message)
  97. {
  98. $args = array_slice(func_get_args(), 1);
  99. if (class_exists('fText', FALSE)) {
  100. return call_user_func_array(
  101. array('fText', 'compose'),
  102. array($message, $args)
  103. );
  104. } else {
  105. return vsprintf($message, $args);
  106. }
  107. }
  108. /**
  109. * Removes individual file upload entries from an array of file inputs in `$_FILES` when no file was uploaded
  110. *
  111. * @throws fValidationException If the form is not properly configured for file uploads
  112. *
  113. * @param string $field The field to filter
  114. * @return array The indexes of the files that were uploaded
  115. */
  116. static public function filter($field)
  117. {
  118. $indexes = array();
  119. $columns = array('name', 'type', 'tmp_name', 'error', 'size');
  120. if (!self::count($field)) {
  121. return;
  122. }
  123. foreach (array_keys($_FILES[$field]['name']) as $index) {
  124. if ($_FILES[$field]['error'][$index] == UPLOAD_ERR_NO_FILE) {
  125. foreach ($columns as $column) {
  126. unset($_FILES[$field][$column][$index]);
  127. }
  128. } else {
  129. $indexes[] = $index;
  130. }
  131. }
  132. return $indexes;
  133. }
  134. /**
  135. * If files starting with `.` can be uploaded
  136. *
  137. * @var boolean
  138. */
  139. private $allow_dot_files = FALSE;
  140. /**
  141. * If PHP files can be uploaded
  142. *
  143. * @var boolean
  144. */
  145. private $allow_php = FALSE;
  146. /**
  147. * The dimension restrictions for uploaded images
  148. *
  149. * @var array
  150. */
  151. private $image_dimensions = array();
  152. /**
  153. * The dimension ratio restriction for uploaded images
  154. *
  155. * @var array
  156. */
  157. private $image_ratio = array();
  158. /**
  159. * If existing files of the same name should be overwritten
  160. *
  161. * @var boolean
  162. */
  163. private $enable_overwrite = FALSE;
  164. /**
  165. * The maximum file size in bytes
  166. *
  167. * @var integer
  168. */
  169. private $max_size = 0;
  170. /**
  171. * The error message to display if the mime types do not match
  172. *
  173. * @var string
  174. */
  175. private $mime_type_message = NULL;
  176. /**
  177. * The mime types of files accepted
  178. *
  179. * @var array
  180. */
  181. private $mime_types = array();
  182. /**
  183. * If the file upload is required
  184. *
  185. * @var boolean
  186. */
  187. private $required = TRUE;
  188. /**
  189. * All requests that hit this method should be requests for callbacks
  190. *
  191. * @internal
  192. *
  193. * @param string $method The method to create a callback for
  194. * @return callback The callback for the method requested
  195. */
  196. public function __get($method)
  197. {
  198. return array($this, $method);
  199. }
  200. /**
  201. * Sets the upload class to allow files starting with a `.`
  202. *
  203. * Files starting with `.` may change the behaviour of web servers,
  204. * for instance `.htaccess` files for Apache.
  205. *
  206. * @return void
  207. */
  208. public function allowDotFiles()
  209. {
  210. $this->allow_dot_files = TRUE;
  211. }
  212. /**
  213. * Sets the upload class to allow PHP files
  214. *
  215. * @return void
  216. */
  217. public function allowPHP()
  218. {
  219. $this->allow_php = TRUE;
  220. }
  221. /**
  222. * Set the class to overwrite existing files in the destination directory instead of renaming the file
  223. *
  224. * @return void
  225. */
  226. public function enableOverwrite()
  227. {
  228. $this->enable_overwrite = TRUE;
  229. }
  230. /**
  231. * Returns the `$_FILES` array for the field specified.
  232. *
  233. * @param string $field The field to get the file array for
  234. * @param mixed $index If the field is an array file upload field, use this to specify which array index to return
  235. * @return array The file info array from `$_FILES`
  236. */
  237. private function extractFileUploadArray($field, $index=NULL)
  238. {
  239. if ($index === NULL) {
  240. return $_FILES[$field];
  241. }
  242. if (!is_array($_FILES[$field]['name'])) {
  243. throw new fValidationException(
  244. 'The field specified, %s, does not appear to be an array file upload field',
  245. $field
  246. );
  247. }
  248. if (!isset($_FILES[$field]['name'][$index])) {
  249. throw new fValidationException(
  250. 'The index specified, %1$s, is invalid for the field %2$s',
  251. $index,
  252. $field
  253. );
  254. }
  255. $file_array = array();
  256. $file_array['name'] = $_FILES[$field]['name'][$index];
  257. $file_array['type'] = $_FILES[$field]['type'][$index];
  258. $file_array['tmp_name'] = $_FILES[$field]['tmp_name'][$index];
  259. $file_array['error'] = $_FILES[$field]['error'][$index];
  260. $file_array['size'] = $_FILES[$field]['size'][$index];
  261. return $file_array;
  262. }
  263. /**
  264. * Moves an uploaded file from the temp directory to a permanent location
  265. *
  266. * @throws fValidationException When the form is not setup for file uploads, the `$directory` is somehow invalid or ::validate() thows an exception
  267. *
  268. * @param string|fDirectory $directory The directory to upload the file to
  269. * @param string $field The file upload field to get the file from
  270. * @param mixed $index If the field was an array file upload field, upload the file corresponding to this index
  271. * @return fFile|NULL An fFile (or fImage) object, or `NULL` if no file was uploaded
  272. */
  273. public function move($directory, $field, $index=NULL)
  274. {
  275. if (!is_object($directory)) {
  276. $directory = new fDirectory($directory);
  277. }
  278. if (!$directory->isWritable()) {
  279. throw new fEnvironmentException(
  280. 'The directory specified, %s, is not writable',
  281. $directory->getPath()
  282. );
  283. }
  284. if (!self::check($field)) {
  285. throw new fValidationException(
  286. 'The field specified, %s, does not appear to be a file upload field',
  287. $field
  288. );
  289. }
  290. $file_array = $this->extractFileUploadArray($field, $index);
  291. $error = $this->validateField($file_array);
  292. if ($error) {
  293. throw new fValidationException($error);
  294. }
  295. // This will only ever be true if the file is optional
  296. if ($file_array['name'] == '' || $file_array['tmp_name'] == '' || $file_array['size'] == 0) {
  297. return NULL;
  298. }
  299. $file_name = fFilesystem::makeURLSafe($file_array['name']);
  300. $file_name = $directory->getPath() . $file_name;
  301. if (!$this->enable_overwrite) {
  302. $file_name = fFilesystem::makeUniqueName($file_name);
  303. }
  304. if (!move_uploaded_file($file_array['tmp_name'], $file_name)) {
  305. throw new fEnvironmentException('There was an error moving the uploaded file');
  306. }
  307. if (!chmod($file_name, 0644)) {
  308. throw new fEnvironmentException('Unable to change permissions on the uploaded file');
  309. }
  310. return fFilesystem::createObject($file_name);
  311. }
  312. /**
  313. * Sets the allowable dimensions for an uploaded image
  314. *
  315. * @param integer $min_width The minimum width - `0` for no minimum
  316. * @param integer $min_height The minimum height - `0` for no minimum
  317. * @param integer $max_width The maximum width - `0` for no maximum
  318. * @param integer $max_height The maximum height - `0` for no maximum
  319. * @return void
  320. */
  321. public function setImageDimensions($min_width, $min_height, $max_width=0, $max_height=0)
  322. {
  323. if (!is_numeric($min_width) || $min_width < 0) {
  324. throw new fProgrammerException(
  325. 'The minimum width specified, %s, is not an integer, or is less than 0',
  326. $min_width
  327. );
  328. }
  329. if (!is_numeric($min_height) || $min_height < 0) {
  330. throw new fProgrammerException(
  331. 'The minimum height specified, %s, is not an integer, or is less than 0',
  332. $min_height
  333. );
  334. }
  335. if (!is_numeric($max_width) || $max_width < 0) {
  336. throw new fProgrammerException(
  337. 'The maximum width specified, %s, is not an integer, or is less than 0',
  338. $max_width
  339. );
  340. }
  341. if (!is_numeric($max_height) || $max_height < 0) {
  342. throw new fProgrammerException(
  343. 'The maximum height specified, %s, is not an integer, or is less than 0',
  344. $max_height
  345. );
  346. }
  347. settype($min_width, 'int');
  348. settype($min_height, 'int');
  349. settype($max_width, 'int');
  350. settype($max_height, 'int');
  351. // If everything is 0 then there are no restrictions
  352. if (!$min_width && !$min_height && !$max_width && !$max_height) {
  353. $this->image_dimensions = array();
  354. return;
  355. }
  356. $this->image_dimensions = array(
  357. 'min_width' => $min_width,
  358. 'min_height' => $min_height,
  359. 'max_width' => $max_width,
  360. 'max_height' => $max_height
  361. );
  362. }
  363. /**
  364. * Sets the allowable dimensions for an uploaded image
  365. *
  366. * @param numeric $width The minimum ratio width
  367. * @param numeric $height The minimum ratio height
  368. * @param string $allow_excess_dimension The dimension that should allow for excess pixels
  369. * @return void
  370. */
  371. public function setImageRatio($width, $height, $allow_excess_dimension)
  372. {
  373. if (!is_numeric($width) || $width <= 0) {
  374. throw new fProgrammerException(
  375. 'The width specified, %s, is not a number, or is less than or equal to 0',
  376. $width
  377. );
  378. }
  379. if (!is_numeric($height) || $height <= 0) {
  380. throw new fProgrammerException(
  381. 'The height specified, %s, is not a number, or is less than or equal to 0',
  382. $height
  383. );
  384. }
  385. $valid_dimensions = array('width', 'height');
  386. if (!in_array($allow_excess_dimension, $valid_dimensions)) {
  387. throw new fProgrammerException(
  388. 'The allow excess dimension specified, %1$s, is not valid. Must be one of: %2$s.',
  389. $allow_excess_dimension,
  390. $valid_dimensions
  391. );
  392. }
  393. $this->image_ratio = array(
  394. 'width' => $width,
  395. 'height' => $height,
  396. 'allow_excess_dimension' => $allow_excess_dimension
  397. );
  398. }
  399. /**
  400. * Sets the maximum size the uploaded file may be
  401. *
  402. * This method should be used with the
  403. * [http://php.net/file-upload.post-method `MAX_FILE_SIZE`] hidden form
  404. * input since the hidden form input will reject a file that is too large
  405. * before the file completely uploads, while this method will wait until the
  406. * whole file has been uploaded. This method should always be used since it
  407. * is very easy for the `MAX_FILE_SIZE` post field to be manipulated on the
  408. * client side.
  409. *
  410. * This method can only further restrict the
  411. * [http://php.net/upload_max_filesize `upload_max_filesize` ini setting],
  412. * it can not increase that setting. `upload_max_filesize` must be set
  413. * in the php.ini (or an Apache configuration) since file uploads are
  414. * handled before the request is handed off to PHP.
  415. *
  416. * @param string $size The maximum file size (e.g. `1MB`, `200K`, `10.5M`) - `0` for no limit
  417. * @return void
  418. */
  419. public function setMaxSize($size)
  420. {
  421. $ini_max_size = ini_get('upload_max_filesize');
  422. $ini_max_size = (!is_numeric($ini_max_size)) ? fFilesystem::convertToBytes($ini_max_size) : $ini_max_size;
  423. $size = fFilesystem::convertToBytes($size);
  424. if ($size && $size > $ini_max_size) {
  425. throw new fEnvironmentException(
  426. 'The requested max file upload size, %1$s, is larger than the %2$s ini setting, which is currently set at %3$s. The ini setting must be increased to allow files of this size.',
  427. $max_size,
  428. 'upload_max_filesize',
  429. $ini_max_size
  430. );
  431. }
  432. $this->max_size = $size;
  433. }
  434. /**
  435. * Sets the file mime types accepted
  436. *
  437. * @param array $mime_types The mime types to accept
  438. * @param string $message The message to display if the uploaded file is not one of the mime type specified
  439. * @return void
  440. */
  441. public function setMIMETypes($mime_types, $message)
  442. {
  443. $this->mime_types = $mime_types;
  444. $this->mime_type_message = $message;
  445. }
  446. /**
  447. * Sets the file upload to be optional instead of required
  448. *
  449. * @return void
  450. */
  451. public function setOptional()
  452. {
  453. $this->required = FALSE;
  454. }
  455. /**
  456. * Validates the uploaded file, ensuring a file was actually uploaded and that is matched the restrictions put in place
  457. *
  458. * @throws fValidationException When the form is not configured for file uploads, no file is uploaded or the uploaded file violates the options set for this object
  459. *
  460. * @param string $field The field the file was uploaded through
  461. * @param mixed $index If the field was an array of file uploads, this specifies which one to validate
  462. * @param boolean $return_message If any validation error should be returned as a string instead of being thrown as an fValidationException
  463. * @param string |$field
  464. * @param boolean |$return_message
  465. * @return NULL|string If `$return_message` is not `TRUE` or if no error occurs, `NULL`, otherwise a string error message
  466. */
  467. public function validate($field, $index=NULL, $return_message=NULL)
  468. {
  469. if (is_bool($index) && $return_message === NULL) {
  470. $return_message = $index;
  471. $index = NULL;
  472. }
  473. if (!self::check($field)) {
  474. throw new fValidationException(
  475. 'The field specified, %s, does not appear to be a file upload field',
  476. $field
  477. );
  478. }
  479. $file_array = $this->extractFileUploadArray($field, $index);
  480. $error = $this->validateField($file_array);
  481. if ($error) {
  482. if ($return_message) {
  483. return $error;
  484. }
  485. throw new fValidationException($error);
  486. }
  487. }
  488. /**
  489. * Validates a $_FILES array against the upload configuration
  490. *
  491. * @param array $file_array The $_FILES array for a single file
  492. * @return string The validation error message
  493. */
  494. private function validateField($file_array)
  495. {
  496. if (empty($file_array['name'])) {
  497. if ($this->required) {
  498. return self::compose('Please upload a file');
  499. }
  500. return NULL;
  501. }
  502. if ($file_array['error'] == UPLOAD_ERR_FORM_SIZE || $file_array['error'] == UPLOAD_ERR_INI_SIZE) {
  503. $max_size = (!empty($_POST['MAX_FILE_SIZE'])) ? $_POST['MAX_FILE_SIZE'] : ini_get('upload_max_filesize');
  504. $max_size = (!is_numeric($max_size)) ? fFilesystem::convertToBytes($max_size) : $max_size;
  505. return self::compose(
  506. 'The file uploaded is over the limit of %s',
  507. fFilesystem::formatFilesize($max_size)
  508. );
  509. }
  510. if ($this->max_size && $file_array['size'] > $this->max_size) {
  511. return self::compose(
  512. 'The file uploaded is over the limit of %s',
  513. fFilesystem::formatFilesize($this->max_size)
  514. );
  515. }
  516. if (empty($file_array['tmp_name']) || empty($file_array['size'])) {
  517. if ($this->required) {
  518. return self::compose('Please upload a file');
  519. }
  520. return NULL;
  521. }
  522. if (!empty($this->mime_types) && file_exists($file_array['tmp_name'])) {
  523. $contents = file_get_contents($file_array['tmp_name'], FALSE, NULL, 0, 4096);
  524. if (!in_array(fFile::determineMimeType($file_array['name'], $contents), $this->mime_types)) {
  525. return self::compose($this->mime_type_message);
  526. }
  527. }
  528. if (!$this->allow_php) {
  529. $file_info = fFilesystem::getPathInfo($file_array['name']);
  530. if (in_array(strtolower($file_info['extension']), array('php', 'php4', 'php5'))) {
  531. return self::compose('The file uploaded is a PHP file, but those are not permitted');
  532. }
  533. }
  534. if (!$this->allow_dot_files) {
  535. if (substr($file_array['name'], 0, 1) == '.') {
  536. return self::compose('The name of the uploaded file may not being with a .');
  537. }
  538. }
  539. if ($this->image_dimensions && file_exists($file_array['tmp_name'])) {
  540. if (fImage::isImageCompatible($file_array['tmp_name'])) {
  541. list($width, $height, $other) = getimagesize($file_array['tmp_name']);
  542. if ($this->image_dimensions['min_width'] && $width < $this->image_dimensions['min_width']) {
  543. return self::compose(
  544. 'The uploaded image is narrower than the minimum width of %spx',
  545. $this->image_dimensions['min_width']
  546. );
  547. }
  548. if ($this->image_dimensions['min_height'] && $height < $this->image_dimensions['min_height']) {
  549. return self::compose(
  550. 'The uploaded image is shorter than the minimum height of %spx',
  551. $this->image_dimensions['min_height']
  552. );
  553. }
  554. if ($this->image_dimensions['max_width'] && $width > $this->image_dimensions['max_width']) {
  555. return self::compose(
  556. 'The uploaded image is wider than the maximum width of %spx',
  557. $this->image_dimensions['max_width']
  558. );
  559. }
  560. if ($this->image_dimensions['max_height'] && $height > $this->image_dimensions['max_height']) {
  561. return self::compose(
  562. 'The uploaded image is taller than the maximum height of %spx',
  563. $this->image_dimensions['max_height']
  564. );
  565. }
  566. }
  567. }
  568. if ($this->image_ratio && file_exists($file_array['tmp_name'])) {
  569. if (fImage::isImageCompatible($file_array['tmp_name'])) {
  570. list($width, $height, $other) = getimagesize($file_array['tmp_name']);
  571. if ($this->image_ratio['allow_excess_dimension'] == 'width' && $width/$height < $this->image_ratio['width']/$this->image_ratio['height']) {
  572. return self::compose(
  573. 'The uploaded image is too narrow for its height. The required ratio is %1$sx%2$s or wider.',
  574. $this->image_ratio['width'],
  575. $this->image_ratio['height']
  576. );
  577. }
  578. if ($this->image_ratio['allow_excess_dimension'] == 'height' && $width/$height > $this->image_ratio['width']/$this->image_ratio['height']) {
  579. return self::compose(
  580. 'The uploaded image is too short for its width. The required ratio is %1$sx%2$s or taller.',
  581. $this->image_ratio['width'],
  582. $this->image_ratio['height']
  583. );
  584. }
  585. }
  586. }
  587. }
  588. }
  589. /**
  590. * Copyright (c) 2007-2011 Will Bond <will@flourishlib.com>, others
  591. *
  592. * Permission is hereby granted, free of charge, to any person obtaining a copy
  593. * of this software and associated documentation files (the "Software"), to deal
  594. * in the Software without restriction, including without limitation the rights
  595. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  596. * copies of the Software, and to permit persons to whom the Software is
  597. * furnished to do so, subject to the following conditions:
  598. *
  599. * The above copyright notice and this permission notice shall be included in
  600. * all copies or substantial portions of the Software.
  601. *
  602. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  603. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  604. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  605. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  606. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  607. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  608. * THE SOFTWARE.
  609. */