Analise manualmente dados em bruto de multipart / form-data com PHP

Não consigo encontrar uma resposta real para este problema, então aqui eu vou:

Como você analisa dados de solicitação HTTP em formato multipart/form-data em PHP? Eu sei que o POST bruto é automaticamente analisado se formatado corretamente, mas os dados a que me refiro são provenientes de uma solicitação PUT, que não está sendo analisada automaticamente pelo PHP. Os dados são múltiplos e tem algo parecido com:

 ------------------------------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 

Estou enviando os dados com o libcurl como (pseudo-código):

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

Se eu soltar o bit CURLOPT_CUSTOMREQUEST, a solicitação é tratada como um POST no servidor e tudo é analisado bem.

Existe uma maneira de invocar manualmente o analisador de dados HTTP do PHP ou alguma outra boa maneira de fazer isso? E sim, eu tenho que enviar o pedido como PUT 🙂

Ok, então com as sugestões de Dave e Everts eu decidi analisar manualmente os dados da solicitação bruta. Não encontrei outra maneira de fazer isso depois de pesquisar por aproximadamente um dia.

Recebi alguma ajuda desse tópico . Eu não tive nenhuma sorte adulterando os dados brutos como eles fazem no tópico referenciado, pois isso irá quebrar os arquivos que estão sendo carregados. Então é tudo regex. Isso não foi testado muito bem, mas parece estar funcionando para o meu caso de trabalho. Sem mais delongas e com a esperança de que isso possa ajudar alguém algum dia:

 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]; } } 

Uso por referência (para não copiar muito os dados):

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

Editar: veja a resposta de Jas abaixo, ele adicionou suporte para vários arquivos e algumas outras funcionalidades.

Estou surpreso porque ninguém mencionou 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

Eu suspeitava que a melhor maneira de ir sobre isso é “fazer você mesmo”, embora você possa encontrar inspiração em analisadores de e-mails de várias partes que usam um formato similar (se não exatamente o mesmo).

Pegue o limite do header HTTP Content-Type e use isso para explodir as várias partes da solicitação. Se o pedido for muito grande, tenha em mente que você pode armazenar todo o pedido na memory, possivelmente até várias vezes.

O RFC relacionado é RFC2388 , que felizmente é bastante curto.

Utilizei a function de exemplo de Chris e adicionei algumas funcionalidades necessárias, como a necessidade de R Porter de array de $ _FILES. Espero que ajude algumas pessoas.

Aqui está o uso de class e exemplo

 < ?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']); } } } 

Eu não lidei muito com http, mas encontrei esse código que poderia ajudar

 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; } 

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

Você olhou para fopen("php://input") para analisar o conteúdo?

Os headers também podem ser encontrados como $_SERVER['HTTP_*'] , os nomes são sempre maiúsculos e os traços se tornam sublinhados, por exemplo $_SERVER['HTTP_ACCEPT_LANGUAGE'] .