web-dev-qa-db-fra.com

Analyser manuellement les données brutes multipart / form-data avec PHP

Je n'arrive pas à trouver une vraie réponse à ce problème alors je vais ici:

Comment analyser les données brutes des requêtes HTTP dans multipart/form-data format en PHP? Je sais que raw POST est automatiquement analysé s'il est formaté correctement, mais les données auxquelles je fais référence proviennent d'une demande PUT, qui n'est pas analysée automatiquement par PHP. Les données sont en plusieurs parties et ressemble à quelque chose comme:

------------------------------b2449e94a11c
Content-Disposition: form-data; name="user_id"

3
------------------------------b2449e94a11c
Content-Disposition: form-data; name="post_id"

5
------------------------------b2449e94a11c
Content-Disposition: form-data; name="image"; filename="/tmp/current_file"
Content-Type: application/octet-stream

�����JFIF���������... a bunch of binary data

J'envoie les données avec libcurl comme ça (pseudo code):

curl_setopt_array(
  CURLOPT_POSTFIELDS => array(
    'user_id' => 3, 
    'post_id' => 5, 
    'image' => '@/tmp/current_file'),
  CURLOPT_CUSTOMREQUEST => 'PUT'
  );

Si je laisse tomber le bit CURLOPT_CUSTOMREQUEST, la demande est traitée comme un POST sur le serveur et tout est analysé très bien.

Existe-t-il un moyen d'invoquer manuellement l'analyseur de données HTTP PHP ou une autre manière intéressante de le faire? Et oui, je dois envoyer la demande en PUT :)

32
Christof

Modifier - veuillez lire d'abord: cette réponse reçoit toujours des hits réguliers 7 ans plus tard. Je n'ai jamais utilisé ce code depuis et je ne sais pas s'il existe une meilleure façon de le faire de nos jours. Veuillez consulter les commentaires ci-dessous et sachez qu'il existe de nombreux scénarios où ce code ne fonctionnera pas. À utiliser à vos risques et périls.

-

Ok, donc avec les suggestions de Dave et Everts, j'ai décidé d'analyser manuellement les données de demande brutes. Je n'ai pas trouvé d'autre moyen de le faire après avoir cherché environ une journée.

J'ai obtenu de l'aide de cette thread . Je n'ai pas eu de chance de falsifier les données brutes comme elles le font dans le thread référencé, car cela briserait les fichiers en cours de téléchargement. C'est donc tout regex. Cela n'a pas été très bien testé, mais semble fonctionner pour mon cas de travail. Sans plus tarder et dans l'espoir que cela puisse aider quelqu'un d'autre un jour:

function parse_raw_http_request(array &$a_data)
{
  // read incoming data
  $input = file_get_contents('php://input');

  // grab multipart boundary from content type header
  preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches);
  $boundary = $matches[1];

  // split content by boundary and get rid of last -- element
  $a_blocks = preg_split("/-+$boundary/", $input);
  array_pop($a_blocks);

  // loop data blocks
  foreach ($a_blocks as $id => $block)
  {
    if (empty($block))
      continue;

    // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char

    // parse uploaded files
    if (strpos($block, 'application/octet-stream') !== FALSE)
    {
      // match "name", then everything after "stream" (optional) except for prepending newlines 
      preg_match("/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s", $block, $matches);
    }
    // parse all other fields
    else
    {
      // match "name" and optional value in between newline sequences
      preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $block, $matches);
    }
    $a_data[$matches[1]] = $matches[2];
  }        
}

Utilisation par référence (afin de ne pas trop copier autour des données):

$a_data = array();
parse_raw_http_request($a_data);
var_dump($a_data);
28
Christof

Je suis surpris que personne n'ait mentionné parse_str ou mb_parse_str:

$result = [];
$rawPost = file_get_contents('php://input');
mb_parse_str($rawPost, $result);
var_dump($result);

http://php.net/manual/en/function.mb-parse-str.php

6
Mahn

J'ai utilisé l'exemple de fonction de Chris et ajouté certaines fonctionnalités nécessaires, telles que le besoin de R Porter pour un tableau de $ _FILES. J'espère que cela aide certaines personnes.

Voici le classe & exemple sage

<?php
include_once('class.stream.php');

$data = array();

new stream($data);

$_PUT = $data['post'];
$_FILES = $data['file'];

/* Handle moving the file(s) */
if (count($_FILES) > 0) {
    foreach($_FILES as $key => $value) {
        if (!is_uploaded_file($value['tmp_name'])) {
            /* Use getimagesize() or fileinfo() to validate file prior to moving here */
            rename($value['tmp_name'], '/path/to/uploads/'.$value['name']);
        } else {
            move_uploaded_file($value['tmp_name'], '/path/to/uploads/'.$value['name']);
        }
    }
}
5
jas-

Je soupçonne que la meilleure façon de procéder est de "le faire vous-même", bien que vous puissiez trouver l'inspiration dans les analyseurs de courrier électronique en plusieurs parties qui utilisent un format similaire (sinon le même).

Saisissez la limite de l'en-tête HTTP Content-Type et utilisez-la pour exploser les différentes parties de la demande. Si la demande est très volumineuse, n'oubliez pas que vous pouvez stocker l'intégralité de la demande en mémoire, peut-être même plusieurs fois.

La RFC associée est RFC2388 , ce qui est heureusement assez court.

2
Evert

Avez-vous regardé fopen("php://input") pour analyser le contenu?

Les en-têtes peuvent également être trouvés sous la forme $_SERVER['HTTP_*'], les noms sont toujours en majuscules et les tirets deviennent des traits de soulignement, par exemple $_SERVER['HTTP_ACCEPT_LANGUAGE'].

1
Dave Kok

Je n'ai pas beaucoup traité les en-têtes http, mais j'ai trouvé ce morceau de code qui pourrait aider

function http_parse_headers( $header )
{
    $retVal = array();
    $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));
    foreach( $fields as $field ) {
        if( preg_match('/([^:]+): (.+)/m', $field, $match) ) {
            $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1])));
            if( isset($retVal[$match[1]]) ) {
                $retVal[$match[1]] = array($retVal[$match[1]], $match[2]);
            } else {
                $retVal[$match[1]] = trim($match[2]);
            }
        }
    }
    return $retVal;
}

Depuis http://php.net/manual/en/function.http-parse-headers.php

1
Ben