Skip to content
Permalink
Browse files

Fix $_FILES parsing

The old tests were just straight up wrong. I built them wrong when
converting the code from 2.x to now. In 2.x all files were preceded by
the `data` name which made files parsing much simpler as we had already
stripped off the first name element.

The new method feels better to me as well. It doesn't use reference
arguments and I think is a tiny bit simpler.

Refs #3999
  • Loading branch information...
markstory committed Jul 18, 2014
1 parent 96a1f36 commit 45725b870dfff5a5d82fabd8079fe6ccd355d46e
Showing with 85 additions and 243 deletions.
  1. +20 −19 src/Network/Request.php
  2. +65 −224 tests/TestCase/Network/RequestTest.php
@@ -402,10 +402,10 @@ protected static function _base() {
protected function _processFiles($post, $files) {
if (is_array($files)) {
foreach ($files as $key => $data) {
if (!is_numeric($key)) {
$this->_processFileData($post, '', $data, $key);
} else {
if (isset($data['tmp_name']) && is_string($data['tmp_name'])) {
$post[$key] = $data;
} else {
$post[$key] = $this->_processFileData([], $data);
}
}
}
@@ -414,31 +414,32 @@ protected function _processFiles($post, $files) {
/**
* Recursively walks the FILES array restructuring the data
* into something sane and useable.
* into something sane and usable.
*
* @param array &$post The post data having files inserted into
* @param array $data The data being built
* @param array $post The post data being traversed
* @param string $path The dot separated path to insert $data into.
* @param array $data The data to traverse/insert.
* @param string $field The terminal field name, which is the top level key in $_FILES.
* @param string $field The terminal field in the path. This is one of the
* $_FILES properties e.g. name, tmp_name, size, error
* @return void
*/
protected function _processFileData(&$post, $path, $data, $field) {
foreach ($data as $key => $fields) {
$newPath = $key;
if (!empty($path)) {
$newPath = $path . '.' . $key;
protected function _processFileData($data, $post, $path = '', $field = '') {
foreach ($post as $key => $fields) {
$newField = $field;
if ($path === '' && $newField === '') {
$newField = $key;
}
if ($field === $newField) {
$path .= '.' . $key;
}
if (is_array($fields)) {
$this->_processFileData($post, $newPath, $fields, $field);
$data = $this->_processFileData($data, $fields, $path, $newField);
} else {
if (strpos($newPath, '.') === false) {
$newPath = $field . '.' . $key;
} else {
$newPath .= '.' . $field;
}
$post = Hash::insert($post, $newPath, $fields);
$path = trim($path . '.' . $field, '.');
$data = Hash::insert($data, $path, $fields);
}
}
return $data;
}
/**
@@ -259,235 +259,76 @@ public function testPutParsingJSON() {
}
/**
* Test parsing of FILES array
* Test processing files with `file` field names.
*
* @return void
*/
public function testFilesParsing() {
$files = array(
'name' => array(
'File' => array(
array('data' => 'cake_sqlserver_patch.patch'),
array('data' => 'controller.diff'),
array('data' => ''),
array('data' => ''),
),
'Post' => array('attachment' => 'jquery-1.2.1.js'),
),
'type' => array(
'File' => array(
array('data' => ''),
array('data' => ''),
array('data' => ''),
array('data' => ''),
),
'Post' => array('attachment' => 'application/x-javascript'),
),
'tmp_name' => array(
'File' => array(
array('data' => '/private/var/tmp/phpy05Ywj'),
array('data' => '/private/var/tmp/php7MBztY'),
array('data' => ''),
array('data' => ''),
),
'Post' => array('attachment' => '/private/var/tmp/phpEwlrIo'),
),
'error' => array(
'File' => array(
array('data' => 0),
array('data' => 0),
array('data' => 4),
array('data' => 4)
),
'Post' => array('attachment' => 0)
),
'size' => array(
'File' => array(
array('data' => 6271),
array('data' => 350),
array('data' => 0),
array('data' => 0),
),
'Post' => array('attachment' => 80469)
),
);
public function testProcessFilesNested() {
$files = [
'image_main' => [
'name' => ['file' => 'born on.txt'],
'type' => ['file' => 'text/plain'],
'tmp_name' => ['file' => '/private/var/tmp/php'],
'error' => ['file' => 0],
'size' => ['file' => 17178]
],
0 => [
'name' => ['image' => 'scratch.text'],
'type' => ['image' => 'text/plain'],
'tmp_name' => ['image' => '/private/var/tmp/phpChIZPb'],
'error' => ['image' => 0],
'size' => ['image' => 1490]
],
'pictures' => [
'name' => [
0 => ['file' => 'a-file.png']
],
'type' => [
0 => ['file' => 'image/png']
],
'tmp_name' => [
0 => ['file' => '/tmp/file123']
],
'error' => [
0 => ['file' => '0']
],
'size' => [
0 => ['file' => 17188]
],
]
];
$request = new Request(compact('files'));
$expected = array(
'File' => array(
array(
'data' => array(
'name' => 'cake_sqlserver_patch.patch',
'type' => '',
'tmp_name' => '/private/var/tmp/phpy05Ywj',
'error' => 0,
'size' => 6271,
)
),
array(
'data' => array(
'name' => 'controller.diff',
'type' => '',
'tmp_name' => '/private/var/tmp/php7MBztY',
'error' => 0,
'size' => 350,
)
),
array(
'data' => array(
'name' => '',
'type' => '',
'tmp_name' => '',
'error' => 4,
'size' => 0,
)
),
array(
'data' => array(
'name' => '',
'type' => '',
'tmp_name' => '',
'error' => 4,
'size' => 0,
)
),
),
'Post' => array(
'attachment' => array(
'name' => 'jquery-1.2.1.js',
'type' => 'application/x-javascript',
'tmp_name' => '/private/var/tmp/phpEwlrIo',
$expected = [
'image_main' => [
'file' => [
'name' => 'born on.txt',
'type' => 'text/plain',
'tmp_name' => '/private/var/tmp/php',
'error' => 0,
'size' => 80469,
)
)
);
$this->assertEquals($expected, $request->data);
$files = array(
'name' => array(
'Document' => array(
1 => array(
'birth_cert' => 'born on.txt',
'passport' => 'passport.txt',
'drivers_license' => 'ugly pic.jpg'
),
2 => array(
'birth_cert' => 'aunt betty.txt',
'passport' => 'betty-passport.txt',
'drivers_license' => 'betty-photo.jpg'
),
),
),
'type' => array(
'Document' => array(
1 => array(
'birth_cert' => 'application/octet-stream',
'passport' => 'application/octet-stream',
'drivers_license' => 'application/octet-stream',
),
2 => array(
'birth_cert' => 'application/octet-stream',
'passport' => 'application/octet-stream',
'drivers_license' => 'application/octet-stream',
)
)
),
'tmp_name' => array(
'Document' => array(
1 => array(
'birth_cert' => '/private/var/tmp/phpbsUWfH',
'passport' => '/private/var/tmp/php7f5zLt',
'drivers_license' => '/private/var/tmp/phpMXpZgT',
),
2 => array(
'birth_cert' => '/private/var/tmp/php5kHZt0',
'passport' => '/private/var/tmp/phpnYkOuM',
'drivers_license' => '/private/var/tmp/php9Rq0P3',
)
)
),
'error' => array(
'Document' => array(
1 => array(
'birth_cert' => 0,
'passport' => 0,
'drivers_license' => 0,
),
2 => array(
'birth_cert' => 0,
'passport' => 0,
'drivers_license' => 0,
)
)
),
'size' => array(
'Document' => array(
1 => array(
'birth_cert' => 123,
'passport' => 458,
'drivers_license' => 875,
),
2 => array(
'birth_cert' => 876,
'passport' => 976,
'drivers_license' => 9783,
)
)
)
);
$request = new Request(compact('files'));
$expected = array(
'Document' => array(
1 => array(
'birth_cert' => array(
'name' => 'born on.txt',
'tmp_name' => '/private/var/tmp/phpbsUWfH',
'error' => 0,
'size' => 123,
'type' => 'application/octet-stream',
),
'passport' => array(
'name' => 'passport.txt',
'tmp_name' => '/private/var/tmp/php7f5zLt',
'error' => 0,
'size' => 458,
'type' => 'application/octet-stream',
),
'drivers_license' => array(
'name' => 'ugly pic.jpg',
'tmp_name' => '/private/var/tmp/phpMXpZgT',
'error' => 0,
'size' => 875,
'type' => 'application/octet-stream',
),
),
2 => array(
'birth_cert' => array(
'name' => 'aunt betty.txt',
'tmp_name' => '/private/var/tmp/php5kHZt0',
'error' => 0,
'size' => 876,
'type' => 'application/octet-stream',
),
'passport' => array(
'name' => 'betty-passport.txt',
'tmp_name' => '/private/var/tmp/phpnYkOuM',
'error' => 0,
'size' => 976,
'type' => 'application/octet-stream',
),
'drivers_license' => array(
'name' => 'betty-photo.jpg',
'tmp_name' => '/private/var/tmp/php9Rq0P3',
'error' => 0,
'size' => 9783,
'type' => 'application/octet-stream',
),
),
)
);
'size' => 17178,
]
],
'pictures' => [
0 => [
'file' => [
'name' => 'a-file.png',
'type' => 'image/png',
'tmp_name' => '/tmp/file123',
'error' => '0',
'size' => 17188,
]
]
],
0 => [
'image' => [
'name' => 'scratch.text',
'type' => 'text/plain',
'tmp_name' => '/private/var/tmp/phpChIZPb',
'error' => 0,
'size' => 1490
]
]
];
$this->assertEquals($expected, $request->data);
}

13 comments on commit 45725b8

@chinpei215

This comment has been minimized.

Copy link
Member

chinpei215 replied Aug 22, 2014

It's a nice fix!
I encountered this issue a few days ago in 2.x.
Do you have a plan to backport these changes to 2.x?

@markstory

This comment has been minimized.

Copy link
Member Author

markstory replied Aug 22, 2014

I didn't think this issue existed in 2.x as the files are generally always prefixed with data which should mitigate this issue.

@chinpei215

This comment has been minimized.

Copy link
Member

chinpei215 replied Aug 22, 2014

Generally, it doesn't occur.
I have created a kind of bulk update action.
So I created a view like this.

echo $this->Form->input('0.Photo.file', array('type' => 'file'));
echo $this->Form->input('1.Photo.file', array('type' => 'file'));

In this case, $this->request->data will be like this.

Array
(
    [Photo] => Array
        (
            [file] => Array
                (
                    [name] => 1.jpg
                    [type] => image/jpeg
                    [tmp_name] => C:\xampp\tmp\phpD795.tmp
                    [error] => 0
                    [size] => 21391
                )

        )

    [1] => Array
        (
            [Photo] => Array
                (
                    [file] => Array
                        (
                            [name] => 2.jpg
                            [type] => image/jpeg
                            [tmp_name] => C:\xampp\tmp\phpD796.tmp
                            [error] => 0
                            [size] => 8096
                        )

                )

        )
)
@markstory

This comment has been minimized.

Copy link
Member Author

markstory replied Aug 22, 2014

Interesting, and does backporting this fix to 2.x solve the issue?

@chinpei215

This comment has been minimized.

Copy link
Member

chinpei215 replied Aug 23, 2014

Sorry, I didn't look closely into this issue.
It may be only related this check.

$newPath = $key;
if (!empty($path)) {
    $newPath = $path . '.' . $key;
}

If $path is 0, it doesn't work.

So, now I think that we have only to fix it.

There might be no need to backport to 2.x.

@markstory

This comment has been minimized.

Copy link
Member Author

markstory replied Aug 23, 2014

Ok, If you could share with me the $_FILES array that is troublesome, I can work on a fix.

@chinpei215

This comment has been minimized.

Copy link
Member

chinpei215 replied Aug 23, 2014

Sorry, I wanted to tell you that we didn't need all changes in this commit.

@markstory

This comment has been minimized.

Copy link
Member Author

markstory replied Aug 23, 2014

Ok, I can change the path check to not use empty.

@markstory

This comment has been minimized.

Copy link
Member Author

markstory replied Aug 23, 2014

Fixed in cd7438d

@chinpei215

This comment has been minimized.

Copy link
Member

chinpei215 replied Aug 23, 2014

Thanks!

@chinpei215

This comment has been minimized.

Copy link
Member

chinpei215 replied Aug 27, 2014

I am very sorry ...
Now I understand what you mean.
But it's too late 😢

$_FILES array is such as the following .

array(
  'data' => array(
    'name' => array(
      'Photo' => array(
        0 => array(
          'file' => '1.jpg',
        ),
        1 => array(
          'file' => '2.jpg',
        ),
      ),
    ),
    'type' => array(
      'Photo' => array(
        0 => array(
          'file' => 'image/jpeg',
        ),
        1 => array(
          'file' => 'image/jpeg',
        ),
      ),
    ),
    'tmp_name' => array(
      'Photo' => array(
        0 => array(
          'file' => 'C:\\xampp\\tmp\\php5603.tmp',
        ),
        1 => array(
          'file' => 'C:\\xampp\\tmp\\php5604.tmp',
        ),
      ),
    ),
    'error' => array(
      'Photo' => array(
        0 => array(
          'file' => 0,
        ),
        1 => array(
          'file' => 0,
        ),
      ),
    ),
    'size' => array(
      'Photo' => array(
        0 => array(
          'file' => 21391,
        ),
        1 => array(
          'file' => 8096,
        ),
      ),
    ),
  ),
)
@markstory

This comment has been minimized.

Copy link
Member Author

markstory replied Aug 27, 2014

Thanks, I think I've gotten the issue fixed - I figured it out by reading your earlier comments more carefully.

@chinpei215

This comment has been minimized.

Copy link
Member

chinpei215 replied Aug 27, 2014

Yes, it works now. Thanks.

Please sign in to comment.
You can’t perform that action at this time.