2010-02-04 22 views
44

Estoy tratando de falsificar una carga de archivo sin utilizar realmente una entrada de archivo del usuario. El contenido del archivo se generará dinámicamente a partir de una cadena.Javascript: cargando un archivo ... sin un archivo

¿Esto es posible? ¿Alguien ha hecho esto antes? ¿Hay ejemplos/teoría disponible?

Para aclarar, sé cómo cargar un archivo utilizando técnicas AJAX utilizando un iframe oculto y amigos: el problema es cargar un archivo que no está en el formulario.

Estoy usando ExtJS, pero jQuery también es factible ya que ExtJS puede conectarse (ext-jquery-base).

+1

Dudo que sea posible, pero pregunta interesante, 1 –

+0

Esta parece ser la solución equivocada a su problema (si tienes control del lado del servidor). Si el contenido del archivo se generará a partir de una cadena, ¿por qué no simplemente POSTAR esa cadena y crear el archivo en el servidor (usando PHP o lo que sea)? Si está cargando un archivo a un destino de terceros, entonces ignore este comentario. –

+0

@JonathanJulian, no importa qué, este uso huele a verdadero valor de hack -), truco impresionante! – nemesisfixx

Respuesta

30

¿Por qué no usar XMLHttpRequest() con POST?

function beginQuoteFileUnquoteUpload(data) 
{ 
    var xhr = new XMLHttpRequest(); 
    xhr.open("POST", "http://www.mysite.com/myuploadhandler.php", true); 
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 
    xhr.onreadystatechange = function() 
    { 
     if (xhr.readyState == 4 && xhr.status == 200) 
      alert("File uploaded!"); 
    } 
    xhr.send("filedata="+encodeURIComponent(data)); 
} 

La secuencia de comandos del manejador en el servidor solo escribe los datos del archivo en un archivo.

EDITAR
carga de archivos sigue siendo un HTTP POST con un tipo de contenido diferente. Puede utilizar este tipo de contenido y separar su contenido con límites:

function beginQuoteFileUnquoteUpload(data) 
{ 
    // Define a boundary, I stole this from IE but you can use any string AFAIK 
    var boundary = "---------------------------7da24f2e50046"; 
    var xhr = new XMLHttpRequest(); 
    var body = '--' + boundary + '\r\n' 
      // Parameter name is "file" and local filename is "temp.txt" 
      + 'Content-Disposition: form-data; name="file";' 
      + 'filename="temp.txt"\r\n' 
      // Add the file's mime-type 
      + 'Content-type: plain/text\r\n\r\n' 
      + data + '\r\n' 
      + boundary + '--'; 

    xhr.open("POST", "http://www.mysite.com/myuploadhandler.php", true); 
    xhr.setRequestHeader(
     "Content-type", "multipart/form-data; boundary="+boundary 

    ); 
    xhr.onreadystatechange = function() 
    { 
     if (xhr.readyState == 4 && xhr.status == 200) 
      alert("File uploaded!"); 
    } 
    xhr.send(body); 
} 

Si desea enviar datos adicionales, que acaba de separar cada sección con un límite y describir las cabeceras Content-Disposition y de tipo de contenido para cada sección . Cada encabezado está separado por una nueva línea y el cuerpo está separado de los encabezados por una nueva línea adicional. Naturalmente, subir los datos binarios de esta manera sería un poco más difícil :-)

Editar más: se olvidó de mencionar, asegúrese de que la cadena de límite no está en el "archivo" de texto que está enviando, de lo contrario lo hará ser tratado como un límite.

+0

Porque el servidor no lo reconocerá como un 'archivo' cargado. – LiraNuna

+0

Creo que quiere saber cómo generar 'datos'. –

+0

@LiraNuna: ¿Por qué importa si estás generando el contenido de una cadena? ¿No puede simplemente reconocerlo como una cadena y escribirlo? –

6

Un archivo cargado es solo una solicitud POST con ese contenido de archivo codificado correctamente y con un encabezado especial multipart/formdata. Debe usar ese <input type=file /> porque la seguridad de su navegador le prohíbe acceder directamente al disco del usuario.

Como no necesita leer el disco de usuario, , puede falsificarlo utilizando Javascript. Será solo un XMLHttpRequest. Para falsificar una solicitud de carga "auténtica", puede instalar Fiddler e inspeccionar su solicitud de salida.

Tendrá que codificar el archivo correctamente, por lo que este enlace puede ser muy útil: RFC 2388: Returning Values from Forms: multipart/form-data

+0

¿Qué debería ir en esa solicitud, entonces? ¿cómo se define ese protocolo? cómo fingirlo? – LiraNuna

+0

que no es un protocolo, es solo una solicitud HTTP regular; Actualicé mi respuesta –

+0

No usé Fiddler (usuario de Linux aquí), pero Firebug muestra cómo debería verse. Esto me lleva un paso más cerca. Estoy votando, ya que es útil, pero aún no he seleccionado la respuesta. – LiraNuna

3

acabo atrapado esta cadena POST_DATA con el complemento de Firefox TamperData. Envié un formulario con un campo type="file" llamado "myfile" y un botón de envío llamado "btn-submit" con el valor "Upload". El contenido del archivo subido son

Line One 
Line Two 
Line Three 

Así que aquí es la cadena POST_DATA:

-----------------------------192642264827446\r\n 
Content-Disposition: form-data; \n 
name="myfile"; filename="local-file-name.txt"\r\n 
Content-Type: text/plain\r\n 
\r\n 
Line \n 
One\r\n 
Line Two\r\n 
Line Three\r\n 
\r\n 
-----------------------------192642264827446\n 
\r\n 
Content-Disposition: form-data; name="btn-submit"\r\n 
\r\n 
Upload\n 
\r\n 
-----------------------------192642264827446--\r\n 

No estoy seguro de lo que significa el número (192642264827446), pero eso no debería ser demasiado difícil de encontrar fuera.

+0

He reformateado POST_DATA para que sea más fácil de leer, el 192642264827446 parece un marcador de límite –

+0

Gracias, gnibbler. Sí, pensé que podría ser algo así como un marcador de límites, probablemente solo un número aleatorio. –

+1

Sí, es un marcador de límite. Si marca el encabezado 'multipart/form-data', el límite lo seguirá. El número aleatorio al final es para evitar cualquier conflicto con los datos que se envían. –

13

Simplemente compartiendo el resultado final, que funciona, y tiene una forma clara de agregar/eliminar parámetros sin ningún tipo de codificación.

var boundary = '-----------------------------' + 
      Math.floor(Math.random() * Math.pow(10, 8)); 

    /* Parameters go here */ 
var params = { 
    file: { 
     type: 'text/plain', 
     filename: Path.utils.basename(currentTab.id), 
     content: GET_CONTENT() /* File content goes here */ 
    }, 
    action: 'upload', 
    overwrite: 'true', 
    destination: '/' 
}; 

var content = []; 
for(var i in params) { 
    content.push('--' + boundary); 

    var mimeHeader = 'Content-Disposition: form-data; name="'+i+'"; '; 
    if(params[i].filename) 
     mimeHeader += 'filename="'+ params[i].filename +'";'; 
    content.push(mimeHeader); 

    if(params[i].type) 
     content.push('Content-Type: ' + params[i].type); 

    content.push(''); 
    content.push(params[i].content || params[i]); 
}; 

    /* Use your favorite toolkit here */ 
    /* it should still work if you can control headers and POST raw data */ 
Ext.Ajax.request({ 
    method: 'POST', 
    url: 'www.example.com/upload.php', 
    jsonData: content.join('\r\n'), 
    headers: { 
     'Content-Type': 'multipart/form-data; boundary=' + boundary, 
     'Content-Length': content.length 
    } 
}); 

Esto fue probado para trabajar en todos los navegadores modernos, incluyendo, pero no limitado a:

  • IE6 +
  • FF 1.5+
  • Opera 9+
  • Chrome 1.0+
  • Safari 3.0+
+0

+1 Buena solución.Pero creo que algo está mal con tu algoritmo. ¿Por qué usas un 'for in' para el objeto params? Parece que está preparado para más de un archivo, pero el segundo archivo ¿cómo se nombrará en el objeto? ¿Dónde se usan 'action',' overwrite', y 'destination'? y cómo no rompen el código dentro del 'for in'? –

+0

@Protron: La razón por la que uso 'for (in)' es obtener las claves del objeto de descripción. El código detectará si 'filename' está establecido en un objeto anidado (que describe un archivo para cargar). Los otros parámetros ('sobrescribir',' acción', 'destino') son solo parámetros adicionales pasados ​​como si usara un formulario. – LiraNuna

+2

@LiraNuna, veo a todos ustedes haciendo magia sobre el '-----------------------------', el único requisito por la especificación MIME (ver RFC 1341, sec 7.2.1) es que el límite comienza con '-' seguido por un token válido (ver RFC 1341 sec.4). Espero que esto ayude a otros a conocer su libertad también :-) – nemesisfixx

2

https://stackoverflow.com/a/2198524/2914587 trabajó para mí, después de añadir un extra '--' antes de la final boundary en la carga útil:

var body = '--' + boundary + '\r\n' 
     // Parameter name is "file" and local filename is "temp.txt" 
     + 'Content-Disposition: form-data; name="file";' 
     + 'filename="temp.txt"\r\n' 
     // Add the file's mime-type 
     + 'Content-type: plain/text\r\n\r\n' 
     + data + '\r\n' 
     + '--' + boundary + '--'; 
15

Si no necesita soporte para navegadores antiguos puede utilizar el objeto FormData, que forma parte de la API del archivo:

var formData = new FormData(); 
var blob = new Blob(['Lorem ipsum'], { type: 'plain/text' }); 
formData.append('file', blob,'readme.txt'); 

var request = new XMLHttpRequest(); 
request.open('POST', 'http://example.org/upload'); 
request.send(formData); 

Api archivo es compatible con todos los navegadores actuales (IE10) +

+0

Esto funcionó mejor para mí. ¡Gracias! – trudesign

+2

Evito escribir mis propias XMLHttpRequests. ¡Esta es definitivamente mi respuesta preferida! – Chris

+0

Esto funcionó para mí. Gracias por compartir. –

0

manera fácil de imitar a la carga de archivos "falso" con jQuery:

var fd = new FormData(); 
var file = new Blob(['file contents'], {type: 'plain/text'}); 

fd.append('formFieldName', file, 'fileName.txt'); 

$.ajax({ 
    url: 'http://example.com/yourAddress', 
    method: 'post', 
    data: fd, 
    processData: false,  //this... 
    contentType: false   //and this is for formData type 
}); 
Cuestiones relacionadas