2010-02-04 45 views
44

Tôi đang cố giả mạo tệp tải lên mà không thực sự sử dụng tệp nhập từ người dùng. Nội dung của tệp sẽ được tạo động từ một chuỗi.Javascript: Tải lên tệp ... mà không cần tệp

Điều này có khả thi không? Có ai từng làm điều này trước đây không? Có các ví dụ/lý thuyết có sẵn không?

Để làm rõ, tôi biết cách tải lên tệp bằng kỹ thuật AJAX bằng khung nội tuyến và bạn bè ẩn - sự cố đang tải lên tệp không có trong biểu mẫu.

Tôi đang sử dụng ExtJS, nhưng jQuery cũng khả thi vì ExtJS có thể cắm vào nó (ext-jquery-base).

+1

tôi nghi ngờ đó là có thể, nhưng câu hỏi thú vị, 1 –

+0

Điều này có vẻ như là giải pháp sai lầm khi vấn đề của bạn (nếu bạn có quyền kiểm soát phía máy chủ). Nếu nội dung của tập tin sẽ được tạo ra từ một chuỗi, tại sao không chỉ POST chuỗi đó và tạo ra các tập tin trên máy chủ (bằng cách sử dụng PHP hoặc bất cứ điều gì)? Nếu bạn đang tải tệp lên đích của bên thứ 3, thì hãy bỏ qua nhận xét này. –

+0

@JonathanJulian, dù thế nào đi nữa, cái này cũng có mùi của hack-value thực sự -), một mẹo tuyệt vời! – nemesisfixx

Trả lời

30

Tại sao không chỉ sử dụng XMLHttpRequest() bằng 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)); 
} 

Các kịch bản xử lý tại máy chủ chỉ ghi dữ liệu tập tin vào một tập tin.

EDIT
Tải lên tệp vẫn là bài đăng http có loại nội dung khác. Bạn có thể sử dụng loại nội dung này và tách nội dung của bạn có ranh giới:

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

Nếu bạn muốn gửi dữ liệu bổ sung, bạn chỉ cần tách riêng từng phần với một ranh giới và mô tả các tiêu đề nội dung bố trí và loại nội dung cho từng phần . Mỗi tiêu đề được phân tách bằng một dòng mới và phần thân được tách biệt với các tiêu đề bằng một dòng mới bổ sung. Đương nhiên, tải lên dữ liệu nhị phân trong thời trang này sẽ hơi khó khăn hơn :-)

Chỉnh sửa thêm: quên đề cập, đảm bảo chuỗi ranh giới không nằm trong "tệp" văn bản bạn đang gửi, nếu không nó sẽ được coi là ranh giới.

+0

Bởi vì máy chủ sẽ không nhận ra nó dưới dạng tệp 'đã tải lên'. – LiraNuna

+0

Tôi nghĩ anh ấy muốn biết cách tạo 'dữ liệu'. –

+0

@LiraNuna: Tại sao điều đó lại quan trọng nếu bạn tạo nội dung từ một chuỗi? Nó không thể nhận ra nó như là một chuỗi và viết nó? –

6

Tải lên tệp chỉ là yêu cầu POST với nội dung tệp được mã hóa đúng cách và với tiêu đề multipart/formdata đặc biệt. Bạn cần sử dụng số <input type=file /> đó vì bảo mật trình duyệt của bạn cấm bạn truy cập trực tiếp vào đĩa người dùng.

Vì bạn không cần đọc đĩa người dùng, , bạn có thể giả mạo nó bằng Javascript. Nó sẽ chỉ là một XMLHttpRequest. Để giả mạo yêu cầu tải lên "đích thực", bạn có thể cài đặt Fiddler và kiểm tra yêu cầu gửi đi của mình.

Bạn sẽ cần để mã hóa tập tin đó một cách chính xác, vì vậy liên kết này có thể rất hữu ích: RFC 2388: Returning Values from Forms: multipart/form-data

+0

Điều gì sẽ xảy ra trong yêu cầu đó? giao thức đó được xác định như thế nào? làm thế nào để giả mạo nó? – LiraNuna

+0

không phải là giao thức, nó chỉ là một yêu cầu HTTP thông thường; Tôi đã cập nhật câu trả lời của mình –

+0

Tôi không sử dụng Fiddler (người dùng Linux ở đây), nhưng Firebug không cho thấy nó trông như thế nào. Điều này mang lại cho tôi một bước gần hơn. Tôi đang upvoting vì nó là hữu ích, nhưng chưa chọn câu trả lời. – LiraNuna

3

Tôi chỉ bắt chuỗi POST_DATA này với các addon Firefox TamperData. Tôi đã gửi biểu mẫu có một trường type="file" có tên "myfile" và nút gửi có tên "btn-submit" có giá trị "Tải lên". Nội dung của tập tin tải lên là

Line One 
Line Two 
Line Three 

Vì vậy, đây là chuỗi 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 

Tôi không chắc chắn những gì các số có nghĩa (192642264827446), nhưng điều đó không nên quá khó để tìm ngoài.

+0

Tôi đã định dạng lại POST_DATA để dễ đọc hơn, 192642264827446 trông giống như một điểm đánh dấu ranh giới –

+0

Cảm ơn, gnibbler. Vâng, tôi nghĩ nó có thể giống như một điểm đánh dấu ranh giới, có lẽ chỉ là một số ngẫu nhiên. –

+1

Vâng, đó là điểm đánh dấu ranh giới. Nếu bạn kiểm tra tiêu đề 'multipart/form-data', ranh giới sẽ theo sau nó. Số ngẫu nhiên ở cuối là để tránh bất kỳ xung đột nào với dữ liệu được gửi đi. –

13

Chỉ cần chia sẻ kết quả cuối cùng, hoạt động - và có cách xóa/xóa thông số rõ ràng mà không cần mã hóa bất cứ thứ gì.

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

này đã được thử nghiệm để làm việc trên tất cả các trình duyệt hiện đại, bao gồm nhưng không giới hạn:

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

+1 Giải pháp hay.Nhưng tôi nghĩ có gì đó sai với thuật toán của bạn. Tại sao bạn sử dụng 'for in' cho đối tượng params? Nó seams như nó được chuẩn bị cho nhiều hơn một tập tin nhưng tập tin thứ hai như thế nào sẽ được đặt tên trong đối tượng? 'Action',' overwrite' và 'destination' được sử dụng ở đâu? và làm thế nào họ không phá vỡ mã bên trong 'cho in'? –

+0

@Protron: Lý do tôi sử dụng 'for (in)' là lấy các khóa từ đối tượng mô tả. Mã sẽ phát hiện nếu 'filename' được đặt trên một đối tượng lồng nhau (mô tả một tệp để tải lên). Các tham số khác ('overwrite',' action', 'destination') chỉ là các tham số phụ được truyền như thể bạn đã sử dụng một biểu mẫu. – LiraNuna

+2

@LiraNuna, tôi thấy tất cả các bạn nhận được tất cả các phép thuật về '----------------------------- ', yêu cầu duy nhất của thông số MIME (xem RFC 1341, sec 7.2.1) là ranh giới bắt đầu bằng '--' được theo sau bởi một mã thông báo hợp lệ (xem RFC 1341 sec.4). Hy vọng điều này sẽ giúp người khác biết tự do của họ quá :-) – nemesisfixx

2

https://stackoverflow.com/a/2198524/2914587 làm việc cho tôi, sau khi tôi bổ sung thêm một '--' trước trận chung kết boundary trong payload:

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

Nếu bạn không cần sự hỗ trợ cho các trình duyệt cũ hơn, bạn có thể sử dụng Object FormData, mà là một phần của tập tin API:

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

tập tin Api được hỗ trợ bởi tất cả các trình duyệt hiện nay (IE10 +)

+0

Điều này phù hợp nhất với tôi. cảm ơn! – trudesign

+2

Tôi tránh viết XMLHttpRequests của riêng mình. Điều này chắc chắn là câu trả lời ưa thích của tôi! – Chris

+0

Điều này làm việc cho tôi. Cám ơn vì đã chia sẻ. –

0

Cách dễ dàng để bắt chước "giả" upload file với 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 
}); 
Các vấn đề liên quan