Skip to content

Commit 52a857e

Browse files
Merge pull request #1 from codeigniter4/develop
Update from parent repo.
2 parents f5e372b + bbe67ee commit 52a857e

File tree

28 files changed

+325
-102
lines changed

28 files changed

+325
-102
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
- `CodeIgniter\Config\Config` is now deprecated in favor of `CodeIgniter\Config\Factories::config()`
1111
- HTTP Layer Refactor: Numerous deprecations have been made towards a transition to a PSR-compliant HTTP layer. [See the User Guide](user_guide_src/source/installation/upgrade_405.rst)
1212

13+
**Mime Type Detection**
14+
15+
- `Config\Mimes::guessExtensionFromType` now only reverse searches the `$mimes` array if no extension is proposed (i.e., usually not for uploaded files).
16+
- The fallback values of `UploadedFile->getExtension()` and `UploadedFile->guessExtension()` have been changed. `UploadedFile->getExtension()` now returns `$this->getClientExtension()` instead of `''`; `UploadedFile->guessExtension()` now returns `''` instead of `$this->getClientExtension()`.
17+
These changes increase security when handling uploaded files as the client can no longer force a wrong mime type on the application. However, these might affect how file extensions are detected in your application.
18+
1319
## [v4.0.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.0.4) (2020-07-15)
1420

1521
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.0.3...v4.0.4)

admin/framework/composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"predis/predis": "^1.1",
2323
"squizlabs/php_codesniffer": "^3.3"
2424
},
25+
"suggest": {
26+
"ext-fileinfo": "Improves mime type detection for files"
27+
},
2528
"autoload": {
2629
"psr-4": {
2730
"CodeIgniter\\": "system/"

admin/module/composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
"mikey179/vfsstream": "^1.6",
1313
"phpunit/phpunit": "^8.5"
1414
},
15+
"suggest": {
16+
"ext-fileinfo": "Improves mime type detection for files"
17+
},
1518
"autoload-dev": {
1619
"psr-4": {
1720
"Tests\\Support\\": "tests/_support"

admin/starter/composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
"mikey179/vfsstream": "^1.6",
1414
"phpunit/phpunit": "^8.5"
1515
},
16+
"suggest": {
17+
"ext-fileinfo": "Improves mime type detection for files"
18+
},
1619
"autoload": {
1720
"psr-4": {
1821
"App\\": "app",

app/Config/Mimes.php

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
* the most common one should be first in the array to aid the guess*
1313
* methods. The same applies when more than one mime-type exists for a
1414
* single extension.
15+
*
16+
* When working with mime types, please make sure you have the ´fileinfo´
17+
* extension enabled to reliably detect the media types.
1518
*/
1619

1720
class Mimes
@@ -33,7 +36,6 @@ class Mimes
3336
'text/csv',
3437
'text/x-comma-separated-values',
3538
'text/comma-separated-values',
36-
'application/octet-stream',
3739
'application/vnd.ms-excel',
3840
'application/x-csv',
3941
'text/x-csv',
@@ -69,7 +71,6 @@ class Mimes
6971
'application/pdf',
7072
'application/force-download',
7173
'application/x-download',
72-
'binary/octet-stream',
7374
],
7475
'ai' => [
7576
'application/pdf',
@@ -462,12 +463,10 @@ class Mimes
462463
'srt' => [
463464
'text/srt',
464465
'text/plain',
465-
'application/octet-stream',
466466
],
467467
'vtt' => [
468468
'text/vtt',
469469
'text/plain',
470-
'application/octet-stream',
471470
],
472471
'ico' => [
473472
'image/x-icon',
@@ -509,11 +508,20 @@ public static function guessExtensionFromType(string $type, string $proposedExte
509508

510509
$proposedExtension = trim(strtolower($proposedExtension));
511510

512-
if ($proposedExtension !== '' && array_key_exists($proposedExtension, static::$mimes) && in_array($type, is_string(static::$mimes[$proposedExtension]) ? [static::$mimes[$proposedExtension]] : static::$mimes[$proposedExtension], true))
511+
if ($proposedExtension !== '')
513512
{
514-
return $proposedExtension;
513+
if(array_key_exists($proposedExtension, static::$mimes) && in_array($type, is_string(static::$mimes[$proposedExtension]) ? [static::$mimes[$proposedExtension]] : static::$mimes[$proposedExtension], true))
514+
{
515+
// The detected mime type matches with the proposed extension.
516+
return $proposedExtension;
517+
}
518+
519+
// An extension was proposed, but the media type does not match the mime type list.
520+
return null;
515521
}
516522

523+
// Reverse check the mime type list if no extension was proposed.
524+
// This search is order sensitive!
517525
foreach (static::$mimes as $ext => $types)
518526
{
519527
if ((is_string($types) && $types === $type) || (is_array($types) && in_array($type, $types, true)))

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
"codeigniter4/codeigniter4-standard": "^1.0",
1919
"fakerphp/faker": "^1.9",
2020
"mikey179/vfsstream": "^1.6",
21-
"phpstan/phpstan": "^0.12",
21+
"phpstan/phpstan": "0.12.65",
2222
"phpunit/phpunit": "^8.5 || ^9.1",
2323
"predis/predis": "^1.1",
2424
"rector/rector": "^0.8",
2525
"squizlabs/php_codesniffer": "^3.3"
2626
},
27+
"suggest": {
28+
"ext-fileinfo": "Improves mime type detection for files"
29+
},
2730
"config": {
2831
"optimize-autoloader": true,
2932
"preferred-install": "dist",

system/BaseModel.php

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use CodeIgniter\Pager\Pager;
2121
use CodeIgniter\Validation\ValidationInterface;
2222
use Config\Services;
23+
use InvalidArgumentException;
2324
use ReflectionClass;
2425
use ReflectionException;
2526
use ReflectionProperty;
@@ -715,31 +716,7 @@ public function insert($data = null, bool $returnID = true)
715716
{
716717
$this->insertID = 0;
717718

718-
if (empty($data))
719-
{
720-
throw DataException::forEmptyDataset('insert');
721-
}
722-
723-
// If $data is using a custom class with public or protected
724-
// properties representing the collection elements, we need to grab
725-
// them as an array.
726-
if (is_object($data) && ! $data instanceof stdClass)
727-
{
728-
$data = $this->objectToArray($data, false, true);
729-
}
730-
731-
// If it's still a stdClass, go ahead and convert to
732-
// an array so doProtectFields and other model methods
733-
// don't have to do special checks.
734-
if (is_object($data))
735-
{
736-
$data = (array) $data;
737-
}
738-
739-
if (empty($data))
740-
{
741-
throw DataException::forEmptyDataset('insert');
742-
}
719+
$data = $this->transformDataToArray($data, 'insert');
743720

744721
// Validate data before saving.
745722
if (! $this->skipValidation && ! $this->cleanRules()->validate($data))
@@ -877,32 +854,7 @@ public function update($id = null, $data = null): bool
877854
$id = [$id];
878855
}
879856

880-
if (empty($data))
881-
{
882-
throw DataException::forEmptyDataset('update');
883-
}
884-
885-
// If $data is using a custom class with public or protected
886-
// properties representing the collection elements, we need to grab
887-
// them as an array.
888-
if (is_object($data) && ! $data instanceof stdClass)
889-
{
890-
$data = $this->objectToArray($data, true, true);
891-
}
892-
893-
// If it's still a stdClass, go ahead and convert to
894-
// an array so doProtectFields and other model methods
895-
// don't have to do special checks.
896-
if (is_object($data))
897-
{
898-
$data = (array) $data;
899-
}
900-
901-
// If it's still empty here, means $data is no change or is empty object
902-
if (empty($data))
903-
{
904-
throw DataException::forEmptyDataset('update');
905-
}
857+
$data = $this->transformDataToArray($data, 'update');
906858

907859
// Validate data before saving.
908860
if (! $this->skipValidation && ! $this->cleanRules(true)->validate($data))
@@ -1692,6 +1644,55 @@ protected function objectToRawArray($data, bool $onlyChanged = true, bool $recur
16921644
return $properties;
16931645
}
16941646

1647+
/**
1648+
* Transform data to array
1649+
*
1650+
* @param array|object|null $data Data
1651+
* @param string $type Type of data (insert|update)
1652+
*
1653+
* @return array
1654+
*
1655+
* @throws DataException
1656+
* @throws InvalidArgumentException
1657+
* @throws ReflectionException
1658+
*/
1659+
protected function transformDataToArray($data, string $type): array
1660+
{
1661+
if (! in_array($type, ['insert', 'update'], true))
1662+
{
1663+
throw new InvalidArgumentException(sprintf('Invalid type "%s" used upon transforming data to array.', $type));
1664+
}
1665+
1666+
if (empty($data))
1667+
{
1668+
throw DataException::forEmptyDataset($type);
1669+
}
1670+
1671+
// If $data is using a custom class with public or protected
1672+
// properties representing the collection elements, we need to grab
1673+
// them as an array.
1674+
if (is_object($data) && ! $data instanceof stdClass)
1675+
{
1676+
$data = $this->objectToArray($data, true, true);
1677+
}
1678+
1679+
// If it's still a stdClass, go ahead and convert to
1680+
// an array so doProtectFields and other model methods
1681+
// don't have to do special checks.
1682+
if (is_object($data))
1683+
{
1684+
$data = (array) $data;
1685+
}
1686+
1687+
// If it's still empty here, means $data is no change or is empty object
1688+
if (empty($data))
1689+
{
1690+
throw DataException::forEmptyDataset($type);
1691+
}
1692+
1693+
return $data;
1694+
}
1695+
16951696
// endregion
16961697

16971698
// region Magic

system/Database/BaseConnection.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,13 @@ abstract class BaseConnection implements ConnectionInterface
289289
*/
290290
protected $aliasedTables = [];
291291

292+
/**
293+
* Query Class
294+
*
295+
* @var string
296+
*/
297+
protected $queryClass = 'CodeIgniter\\Database\\Query';
298+
292299
//--------------------------------------------------------------------
293300

294301
/**
@@ -302,6 +309,13 @@ public function __construct(array $params)
302309
{
303310
$this->$key = $value;
304311
}
312+
313+
$queryClass = str_replace('Connection', 'Query', static::class);
314+
315+
if (class_exists($queryClass))
316+
{
317+
$this->queryClass = $queryClass;
318+
}
305319
}
306320

307321
//--------------------------------------------------------------------
@@ -594,9 +608,13 @@ abstract protected function execute(string $sql);
594608
* @param string $queryClass
595609
*
596610
* @return BaseResult|Query|false
611+
*
612+
* @todo BC set $queryClass default as null in 4.1
597613
*/
598-
public function query(string $sql, $binds = null, bool $setEscapeFlags = true, string $queryClass = 'CodeIgniter\\Database\\Query')
614+
public function query(string $sql, $binds = null, bool $setEscapeFlags = true, string $queryClass = '')
599615
{
616+
$queryClass = $queryClass ?: $this->queryClass;
617+
600618
if (empty($this->connID))
601619
{
602620
$this->initialize();

system/HTTP/Files/UploadedFile.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,24 +308,29 @@ public function getTempName(): string
308308
* Overrides SPLFileInfo's to work with uploaded files, since
309309
* the temp file that's been uploaded doesn't have an extension.
310310
*
311-
* Is simply an alias for guessExtension for a safer method
312-
* than simply relying on the provided extension.
313-
* Additionally it will return clientExtension in case if there are
314-
* other extensions with the same mime type.
311+
* This method tries to guess the extension from the files mime
312+
* type but will return the clientExtension if it fails to do so.
313+
*
314+
* This method will always return a more or less helpfull extension
315+
* but might be insecure if the mime type is not machted. Consider
316+
* using guessExtension for a more safe version.
315317
*/
316318
public function getExtension(): string
317319
{
318-
return $this->guessExtension();
320+
return $this->guessExtension() ?: $this->getClientExtension();
319321
}
320322

321323
/**
322-
* Attempts to determine the best file extension.
324+
* Attempts to determine the best file extension from the file's
325+
* mime type. In contrast to getExtension, this method will return
326+
* an empty string if it fails to determine an extension instead of
327+
* falling back to the unsecure clientExtension.
323328
*
324329
* @return string
325330
*/
326331
public function guessExtension(): string
327332
{
328-
return Mimes::guessExtensionFromType($this->getClientMimeType(), $this->getClientExtension()) ?? $this->getClientExtension();
333+
return Mimes::guessExtensionFromType($this->getMimeType(), $this->getClientExtension()) ?? '';
329334
}
330335

331336
//--------------------------------------------------------------------

system/I18n/Exceptions/I18nException.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
*/
1919
class I18nException extends FrameworkException
2020
{
21+
/**
22+
* Thrown when createFromFormat fails to receive a valid
23+
* DateTime back from DateTime::createFromFormat.
24+
*
25+
* @param string $format
26+
*
27+
* @return static
28+
*/
29+
public static function forInvalidFormat(string $format)
30+
{
31+
return new static(lang('Time.invalidFormat', [$format]));
32+
}
33+
2134
/**
2235
* Thrown when the numeric representation of the month falls
2336
* outside the range of allowed months.

0 commit comments

Comments
 (0)