2011-04-06 11 views
9

Estoy buscando un drop-in include script/class que disecciona multipart/form-data y completa $_POST (+ raw) y $_FILES desde él. Por lo general, PHP lo hace por sí mismo. Pero debido a la manipulación automática no es suficiente para mí y hace inaccesible php://input[1] probablemente voy a utilizar algo como esto para evitar que:userland multipart/form-data handler

RewriteRule .* - [E=CONTENT_TYPE:noparsing/for-you-php]
Does not work. Actual solution requires mod_headers and RequestHeader set...

El procedimiento de extracción no podría ser tan complejo. Pero prefiero usar una solución bien probada. Y ante todo preferiría una implementación que utiliza fgets para dividir, e imita el manejo $_FILES de cerca y de manera eficiente. Encontrar el final de las cargas binarias me parece bastante complicado, en particular cuando tiene que quitarse \r\n, pero puede encontrar clientes que solo envían \n (no permitido, pero posible).

Estoy seguro de que algo así existe. Pero estoy teniendo dificultades para buscar en Google. ¿Alguien sabe una implementación? (PEAR :: mimeDecode puede ser pirateado para obtener un tipo de trabajo para datos de formulario, pero es un bloqueador de memoria.)

El caso de uso en resumen: necesita conservar los nombres de campo sin procesar (incluidos espacios en blanco y caracteres especiales), para el registro, pero no puede evitar las cargas de archivos siempre.


con fines decorativos, que es como una solicitud POST se ve:

POST/HTTP/1.1 
Host: localhost:8000 
Content-Length: 17717 
Content-Type: multipart/form-data; boundary=----------3wCuBwquE9P7A4OEylndVx 

Y después de una secuencia \r\n\r\n la multipart/carga sigue así:

------------3wCuBwquE9P7A4OEylndVx 
Content-Disposition: form-data; name="_charset_" 

windows-1252 
------------3wCuBwquE9P7A4OEylndVx 
Content-Disposition: form-data; name=" text field \\ 1 \";inject=1" 

text1 te twj sakfkl 
------------3wCuBwquE9P7A4OEylndVx 
Content-Disposition: form-data; name="file"; filename="dial.png" 
Content-Type: image/png 

IPNG Z @@@[email protected]@B`@@B;[email protected]@@-'[email protected]@@[email protected]\[email protected]@@[email protected][email protected][email protected]? ='[email protected]@@  
@@@GtIMEGYAAU,#}[email protected]@@[email protected] with [email protected]@ @IDATxZl]w| 
+0

Esto probablemente se convierta en una pregunta de recompensa .. – mario

+0

¿Se garantizará que cada parte MIME en la multiparte tenga una longitud de contenido? No recuerdo si la especificación lo requiere o no. Me imagino que lo haría. – Charles

+0

Lo malo es que la especificación [RFC2388] (http://www.faqs.org/rfcs/rfc2388.html) no menciona 'Content-Length' en absoluto. Si bien supongo que la mayoría de los navegadores actuales lo hacen (y usan la codificación base64 al menos), en realidad estoy tratando de apoyar a los clientes más extravagantes. (Editar: No, ni siquiera Opera lo hace). – mario

Respuesta

4

Es tarde y yo puedo No lo pruebe en este momento, pero el siguiente debe hacer lo que quiera:

//$boundary = null; 

if (is_resource($input = fopen('php://input', 'rb')) === true) 
{ 

    while ((feof($input) !== true) && (($line = fgets($input)) !== false)) 
    { 
     if (isset($boundary) === true) 
     { 
      $content = null; 

      while ((feof($input) !== true) && (($line = fgets($input)) !== false)) 
      { 
       $line = trim($line); 

       if (strlen($line) > 0) 
       { 
        $content .= $line . ' '; 
       } 

       else if (empty($line) === true) 
       { 
        if (stripos($content, 'name=') !== false) 
        { 
         $name = trim(stripcslashes(preg_replace('~.*name="?(.+)"?.*~i', '$1', $content))); 

         if (stripos($content, 'Content-Type:') !== false) 
         { 
          $tmpname = tempnam(sys_get_temp_dir(), ''); 

          if (is_resource($temp = fopen($tmpname, 'wb')) === true) 
          { 
           while ((feof($input) !== true) && (($line = fgets($input)) !== false) && (strpos($line, $boundary) !== 0)) 
           { 
            fwrite($temp, preg_replace('~(?:\r\n|\n)$~', '', $line)); 
           } 

           fclose($temp); 
          } 

          $FILES[$name] = array 
          (
           'name' => trim(stripcslashes(preg_replace('~.*filename="?(.+)"?.*~i', '$1', $content))), 
           'type' => trim(preg_replace('~.*Content-Type: ([^\s]*).*~i', '$1', $content)), 
           'size' => sprintf('%u', filesize($tmpname)), 
           'tmp_name' => $tmpname, 
           'error' => UPLOAD_ERR_OK, 
          ); 
         } 

         else 
         { 
          $result = null; 

          while ((feof($input) !== true) && (($line = fgets($input)) !== false) && (strpos($line, $boundary) !== 0)) 
          { 
           $result .= preg_replace('~(?:\r\n|\n)$~', '', $line); 
          } 

          if (array_key_exists($name, $POST) === true) 
          { 
           if (is_array($POST[$name]) === true) 
           { 
            $POST[$name][] = $result; 
           } 

           else 
           { 
            $POST[$name] = array($POST[$name], $result); 
           } 
          } 

          else 
          { 
           $POST[$name] = $result; 
          } 
         } 
        } 

        if (strpos($line, $boundary) === 0) 
        { 
         //break; 
        } 
       } 
      } 
     } 

     else if ((is_null($boundary) === true) && (strpos($line, 'boundary=') !== false)) 
     { 
      $boundary = "--" . trim(preg_replace('~.*boundary="?(.+)"?.*~i', '$1', $line)); 
     } 
    } 

    fclose($input); 
} 

echo '<pre>'; 
print_r($POST); 
echo '</pre>'; 

echo '<hr />'; 

echo '<pre>'; 
print_r($FILES); 
echo '</pre>'; 
+0

Aún no se pudo probar. Parece viable sin embargo. - Olvidé algunos problemas para la tarea, nombre duplicar 'name []', 'name []', 'name []' request vars. Y mi descripción POST fue engañosa, el 'boundary =' nunca es parte del cuerpo. ¡Pero al menos el enfoque de 'fgets' parece factible de esta manera! – mario

+0

@mario: Se corrigió un error ortográfico en la expresión regular y se agregó la compatibilidad con claves duplicadas para las variables de solicitud $ POST. ¿Podrías aclarar a qué te refieres cuando dices que el límite nunca forma parte del cuerpo? –

+0

Gracias! Arreglé mi ejemplo de pregunta. El '; boundary =' aparece en '$ _SERVER [" CONTENT_TYPE "]' solamente. El cuerpo de entrada // comienza realmente en el primer '------ lo que sea '. Pero ya lo he adaptado con un pequeño preg_match() de antemano; que creo que funciona bien, ya que su código previsualiza las pruebas $ boundary con isset(). Me tomo un poco de tiempo con una prueba completa. Pero, de nuevo, creo que se ve bien, y puedo adaptarlo a mis otras necesidades extrañas. – mario

0

Al leer los comentarios, ¿qué hay de codificar los datos antes de que se publique POST? Haga que el cliente envíe los datos POST en UTF8 o incluso URLencoded, luego los caracteres ASCII que se perdieron se transmitirán sin escribir su propio controlador POST, que bien podría presentar sus propios errores ...

+0

Nah, no puedo hacer eso. Ese es mi extraño requerimiento aquí. Necesito ** interceptar ** una solicitud POST ordinaria. No tengo influencia sobre los clientes y necesito respaldar formularios estándar. Puedo escapar de todo el problema usando una POST 'application/x-www-urlencoded' en lugar de' multipart/form-data'. Pero eso frustra el propósito y hace que la carga de archivos sea imposible. Tendré que ir con la solución y todos sus problemas potenciales. – mario

3

Quizás un nuevo php. directiva ini enable_post_data_reading podría ayudar, pero parece que fue añadida en PHP 5.4, todavía tengo la versión anterior y no se podía probar que :(

de PHP Manual:

enable_post_data_reading booleano

Desactivando esta opción hace que $ _POST y $ _FILES no se llenen. La única forma de leer postdata será y luego pasará por el contenedor de flujo de entrada php: //. Esto puede ser útil para las solicitudes de proxy o para procesar los datos POST de manera eficiente en la memoria .

+0

Parece que funciona aquí, puede que necesite verificar con un rastreador para ver si realmente es ** raw **. – Pacerier