Fixing bug 491201. Adding ability for XMLHttpRequest.send() to accept a File object. r+sr=jonas@sicking.cc, r=cbiesinger@gmail.com

This commit is contained in:
Matin Movassate 2009-09-08 16:29:41 -07:00
Родитель 2122c22619
Коммит 9648a313b0
11 изменённых файлов: 379 добавлений и 41 удалений

Просмотреть файл

@ -93,6 +93,7 @@ XPIDLSRCS = \
nsIContentPolicy.idl \
nsIDocumentEncoder.idl \
nsIDOMFile.idl \
nsIDOMFileInternal.idl \
nsIDOMFileList.idl \
nsIDOMFileException.idl \
nsIDOMParser.idl \

Просмотреть файл

@ -41,6 +41,7 @@
#include "nsICharsetDetectionObserver.h"
#include "nsIDOMFile.h"
#include "nsIDOMFileInternal.h"
#include "nsIDOMFileList.h"
#include "nsIInputStream.h"
#include "nsCOMArray.h"
@ -52,11 +53,13 @@ class nsIFile;
class nsIInputStream;
class nsDOMFile : public nsIDOMFile,
public nsIDOMFileInternal,
public nsICharsetDetectionObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMFILE
NS_DECL_NSIDOMFILEINTERNAL
nsDOMFile(nsIFile *aFile)
: mFile(aFile)

Просмотреть файл

@ -0,0 +1,46 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "domstubs.idl"
interface nsIFile;
[scriptable, uuid(047CA6C4-52B3-46F1-8976-E198B724F72F)]
interface nsIDOMFileInternal : nsISupports
{
attribute nsIFile internalFile;
};

Просмотреть файл

@ -68,6 +68,7 @@
NS_INTERFACE_MAP_BEGIN(nsDOMFile)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFile)
NS_INTERFACE_MAP_ENTRY(nsIDOMFile)
NS_INTERFACE_MAP_ENTRY(nsIDOMFileInternal)
NS_INTERFACE_MAP_ENTRY(nsICharsetDetectionObserver)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(File)
NS_INTERFACE_MAP_END
@ -146,6 +147,20 @@ nsDOMFile::GetAsText(const nsAString &aCharset, nsAString &aResult)
return ConvertStream(stream, charset.get(), aResult);
}
NS_IMETHODIMP
nsDOMFile::GetInternalFile(nsIFile **aFile)
{
NS_IF_ADDREF(*aFile = mFile);
return NS_OK;
}
NS_IMETHODIMP
nsDOMFile::SetInternalFile(nsIFile *aFile)
{
mFile = aFile;
return NS_OK;
}
NS_IMETHODIMP
nsDOMFile::GetAsDataURL(nsAString &aResult)
{

Просмотреть файл

@ -45,8 +45,10 @@
#include "nsIURI.h"
#include "nsILoadGroup.h"
#include "nsNetUtil.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "nsIUploadChannel.h"
#include "nsIUploadChannel2.h"
#include "nsIDOMSerializer.h"
#include "nsXPCOM.h"
#include "nsISupportsPrimitives.h"
@ -62,7 +64,10 @@
#include "nsIScriptGlobalObject.h"
#include "nsIDOMClassInfo.h"
#include "nsIDOMElement.h"
#include "nsIDOMFileInternal.h"
#include "nsIDOMWindow.h"
#include "nsIMIMEService.h"
#include "nsCExternalHandlerService.h"
#include "nsIVariant.h"
#include "nsVariant.h"
#include "nsIParser.h"
@ -2358,6 +2363,37 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
postDataStream = stream;
charset.Truncate();
}
else {
// nsIDOMFile?
nsCOMPtr<nsIDOMFileInternal> file(do_QueryInterface(supports));
if (file) {
nsCOMPtr<nsIFile> internalFile;
rv = file->GetInternalFile(getter_AddRefs(internalFile));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> stream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), internalFile);
NS_ENSURE_SUCCESS(rv, rv);
// Feed local file input stream into our upload channel
if (stream) {
postDataStream = stream;
charset.Truncate();
defaultContentType.Truncate();
nsCOMPtr<nsIMIMEService> mimeService =
do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString mediaType;
rv = mimeService->GetTypeFromFile(internalFile, mediaType);
if (NS_SUCCEEDED(rv)) {
defaultContentType = mediaType;
}
}
}
}
}
}
}
@ -2388,7 +2424,7 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
}
if (postDataStream) {
nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
nsCOMPtr<nsIUploadChannel2> uploadChannel(do_QueryInterface(httpChannel));
NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
// If no content type header was set by the client, we set it to
@ -2427,15 +2463,26 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
}
}
// If necessary, wrap the stream in a buffered stream so as to guarantee
// support for our upload when calling ExplicitSetUploadStream.
if (!NS_InputStreamIsBuffered(postDataStream)) {
nsCOMPtr<nsIInputStream> bufferedStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
postDataStream,
4096);
NS_ENSURE_SUCCESS(rv, rv);
postDataStream = bufferedStream;
}
mUploadComplete = PR_FALSE;
PRUint32 uploadTotal = 0;
postDataStream->Available(&uploadTotal);
mUploadTotal = uploadTotal;
rv = uploadChannel->SetUploadStream(postDataStream, contentType, -1);
// Reset the method to its original value
if (httpChannel) {
httpChannel->SetRequestMethod(method);
}
// We want to use a newer version of the upload channel that won't
// ignore the necessary headers for an empty Content-Type.
rv = uploadChannel->ExplicitSetUploadStream(postDataStream, contentType, -1, method, PR_FALSE);
}
}

Просмотреть файл

@ -18,5 +18,13 @@ function handleRequest(request, response)
Array.prototype.push.apply(bytes, body.readByteArray(avail));
var data = String.fromCharCode.apply(null, bytes);
response.write(data);
}
response.setHeader("Result-Content-Length", "" + data.length);
if (data.indexOf("TEST_REDIRECT_STR") >= 0) {
var newURL = "http://" + data.split("&url=")[1];
response.setStatusLine(null, 307, "redirect");
response.setHeader("Location", newURL, false);
}
else {
response.write(data);
}
}

Просмотреть файл

@ -13,6 +13,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=464848
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=464848">Mozilla Bug 464848</a>
<p id="display">
<input id="fileList" type="file"></input>
</p>
<div id="content" style="display: none">
@ -32,6 +33,32 @@ testDoc2.appendChild(testDoc2.createElement("res"));
testDoc2.documentElement.appendChild(testDoc2.createTextNode("text"));
is(testDoc2.inputEncoding, null, "wrong encoding");
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var testData = "blahblahblahblahblahblahblaaaaaaaah. blah.";
testFileTxt = createFileWithDataExt(testData, ".txt");
testFilePng = createFileWithDataExt(testData, ".png");
testFileJpg = createFileWithDataExt(testData, ".jpg");
testFileGif = createFileWithDataExt(testData, ".gif");
testFilePdf = createFileWithDataExt(testData, ".pdf");
testFileXml = createFileWithDataExt(testData, ".xml");
testFileUnknown = createFileWithDataExt(testData, "noext");
function createFileWithDataExt(fileData, extension) {
var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
var testFile = dirSvc.get("ProfD", Components.interfaces.nsIFile);
testFile.append("testfile" + extension);
var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
outStream.init(testFile, 0x02 | 0x08 | 0x20, 0666, 0);
outStream.write(fileData, fileData.length);
outStream.close();
var fileList = document.getElementById('fileList');
fileList.value = testFile.path;
return fileList.files[0];
}
tests = [{ body: null,
resBody: "",
},
@ -109,30 +136,105 @@ tests = [{ body: null,
resBody: "<!-- doc 2 -->\n<res>text</res>",
resContentType: "foo/bar; charset=uTf-8",
},
{ body: testFileTxt,
resBody: testData,
resContentType: testFileTxt.mediaType,
resContentLength: testData.length,
},
{ body: testFilePng,
resBody: testData,
resContentType: testFilePng.mediaType,
resContentLength: testData.length,
},
{ body: testFileJpg,
resBody: testData,
resContentType: testFileJpg.mediaType,
resContentLength: testData.length,
},
{ body: testFileGif,
resBody: testData,
resContentType: testFileGif.mediaType,
resContentLength: testData.length,
},
{ body: testFilePdf,
resBody: testData,
resContentType: testFilePdf.mediaType,
resContentLength: testData.length,
},
{ body: testFileXml,
resBody: testData,
resContentType: testFileXml.mediaType,
resContentLength: testData.length,
},
{ body: testFileUnknown,
resBody: testData,
resContentType: "",
resContentLength: testData.length,
},
{ //will trigger a redirect test server-side
body: ("TEST_REDIRECT_STR&url=" + window.location.host + window.location.pathname),
redirect: true,
},
];
for each(test in tests) {
xhr = new XMLHttpRequest;
xhr.open("POST", "file_XHRSendData.sjs", false);
if (test.contentType)
xhr.setRequestHeader("Content-Type", test.contentType);
xhr.send(test.body);
try {
for each(test in tests) {
xhr = new XMLHttpRequest;
xhr.open("POST", "file_XHRSendData.sjs", false);
if (test.contentType)
xhr.setRequestHeader("Content-Type", test.contentType);
xhr.send(test.body);
if (test.resContentType) {
is(xhr.getResponseHeader("Result-Content-Type"), test.resContentType,
"Wrong Content-Type sent");
}
else {
is(xhr.getResponseHeader("Result-Content-Type"), null);
}
if (test.resContentType) {
is(xhr.getResponseHeader("Result-Content-Type"), test.resContentType,
"Wrong Content-Type sent");
}
else {
is(xhr.getResponseHeader("Result-Content-Type"), null);
}
if (test.body instanceof Document) {
is(xhr.responseText.replace("\r\n", "\n"), test.resBody, "Wrong body");
}
else {
is(xhr.responseText, test.resBody, "Wrong body");
if (test.resContentLength) {
is(xhr.getResponseHeader("Result-Content-Length"), test.resContentLength, "Wrong Content-Length sent");
}
if (test.body instanceof Document) {
is(xhr.responseText.replace("\r\n", "\n"), test.resBody, "Wrong body");
}
else if (!test.redirect) {
is(xhr.responseText, test.resBody, "Wrong body");
}
else {
// If we're testing redirect, determine whether the body is
// this document by looking for the relevant bug url
is(xhr.responseText.indexOf("https://bugzilla.mozilla.org/show_bug.cgi?id=464848") >= 0, true,
"Wrong page for redirect");
}
}
}
finally {
cleanUpData();
}
function cleanUpData() {
removeFileWithExt(".txt");
removeFileWithExt(".png");
removeFileWithExt(".jpg");
removeFileWithExt(".gif");
removeFileWithExt(".pdf");
removeFileWithExt(".xml");
removeFileWithExt("noext");
}
function removeFileWithExt(extension)
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
var testFile = dirSvc.get("ProfD", Components.interfaces.nsIFile);
testFile.append("testfile" + extension);
testFile.remove(false);
}
</script>
</pre>

Просмотреть файл

@ -111,6 +111,7 @@ XPIDLSRCS = \
nsISyncStreamListener.idl \
nsISystemProxySettings.idl \
nsIUnicharStreamLoader.idl \
nsIUploadChannel2.idl \
nsIStandardURL.idl \
nsINestedURI.idl \
nsIURLParser.idl \

Просмотреть файл

@ -0,0 +1,73 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsIInputStream;
[scriptable, uuid(8821E259-7252-4464-B874-A55D8EF6B222)]
interface nsIUploadChannel2 : nsISupports
{
/**
* Sets a stream to be uploaded by this channel with the specified
* Content-Type and Content-Length header values.
*
* Most implementations of this interface require that the stream:
* (1) implement threadsafe addRef and release
* (2) implement nsIInputStream::readSegments
* (3) implement nsISeekableStream::seek
*
* @param aStream
* The stream to be uploaded by this channel.
* @param aContentType
* This value will replace any existing Content-Type
* header on the HTTP request, regardless of whether
* or not its empty.
* @param aContentLength
* A value of -1 indicates that the length of the stream should be
* determined by calling the stream's |available| method.
* @param aMethod
* The HTTP request method to set on the stream.
* @param aStreamHasHeaders
* True if the stream already contains headers for the HTTP request.
*/
void explicitSetUploadStream(in nsIInputStream aStream,
in ACString aContentType,
in long long aContentLength,
in ACString aMethod,
in boolean aStreamHasHeaders);
};

Просмотреть файл

@ -2771,7 +2771,7 @@ nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
return NS_OK; // no other options to set
if (preserveMethod) {
nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
if (mUploadStream && uploadChannel) {
// rewind upload stream
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
@ -2779,19 +2779,23 @@ nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
// replicate original call to SetUploadStream...
if (mUploadStreamHasHeaders)
uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1);
else {
const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
const char *clen = mRequestHead.PeekHeader(nsHttp::Content_Length);
if (ctype && clen)
uploadChannel->SetUploadStream(mUploadStream,
nsDependentCString(ctype),
atoi(clen));
}
const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
if (!ctype)
ctype = "";
const char *clen = mRequestHead.PeekHeader(nsHttp::Content_Length);
if (clen)
uploadChannel->ExplicitSetUploadStream(
mUploadStream,
nsDependentCString(ctype),
nsCRT::atoll(clen),
nsDependentCString(mRequestHead.Method()),
mUploadStreamHasHeaders);
}
// must happen after setting upload stream since SetUploadStream
// may change the request method.
// since preserveMethod is true, we need to ensure that the appropriate
// request method gets set on the channel, regardless of whether or not
// we set the upload stream above. This means SetRequestMethod() will
// be called twice if ExplicitSetUploadStream() gets called above.
httpChannel->SetRequestMethod(nsDependentCString(mRequestHead.Method()));
}
// convey the referrer if one was used for this channel to the next one
@ -4078,6 +4082,7 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
NS_INTERFACE_MAP_ENTRY(nsICacheListener)
NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
@ -4685,7 +4690,9 @@ nsHttpChannel::GetUploadStream(nsIInputStream **stream)
}
NS_IMETHODIMP
nsHttpChannel::SetUploadStream(nsIInputStream *stream, const nsACString &contentType, PRInt32 contentLength)
nsHttpChannel::SetUploadStream(nsIInputStream *stream,
const nsACString &contentType,
PRInt32 contentLength)
{
// NOTE: for backwards compatibility and for compatibility with old style
// plugins, |stream| may include headers, specifically Content-Type and
@ -4703,7 +4710,8 @@ nsHttpChannel::SetUploadStream(nsIInputStream *stream, const nsACString &content
return NS_ERROR_FAILURE;
}
}
mRequestHead.SetHeader(nsHttp::Content_Length, nsPrintfCString("%d", contentLength));
mRequestHead.SetHeader(nsHttp::Content_Length,
nsPrintfCString("%d", contentLength));
mRequestHead.SetHeader(nsHttp::Content_Type, contentType);
mUploadStreamHasHeaders = PR_FALSE;
mRequestHead.SetMethod(nsHttp::Put); // PUT request
@ -4721,6 +4729,37 @@ nsHttpChannel::SetUploadStream(nsIInputStream *stream, const nsACString &content
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::ExplicitSetUploadStream(nsIInputStream *aStream,
const nsACString &aContentType,
PRInt64 aContentLength,
const nsACString &aMethod,
PRBool aStreamHasHeaders)
{
// Ensure stream is set and method is valid
NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
if (aContentLength < 0) {
PRUint32 streamLength;
aStream->Available(&streamLength);
aContentLength = streamLength;
if (aContentLength < 0) {
NS_ERROR("unable to determine content length");
return NS_ERROR_FAILURE;
}
}
nsresult rv = SetRequestMethod(aMethod);
NS_ENSURE_SUCCESS(rv, rv);
mRequestHead.SetHeader(nsHttp::Content_Length, nsPrintfCString("%lld", aContentLength));
mRequestHead.SetHeader(nsHttp::Content_Type, aContentType);
mUploadStreamHasHeaders = aStreamHasHeaders;
mUploadStream = aStream;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetResponseStatus(PRUint32 *value)
{

Просмотреть файл

@ -74,6 +74,7 @@
#include "nsIEncodedChannel.h"
#include "nsITransport.h"
#include "nsIUploadChannel.h"
#include "nsIUploadChannel2.h"
#include "nsIStringEnumerator.h"
#include "nsIOutputStream.h"
#include "nsIAsyncInputStream.h"
@ -101,6 +102,7 @@ class nsHttpChannel : public nsHashPropertyBag
, public nsIStreamListener
, public nsICachingChannel
, public nsIUploadChannel
, public nsIUploadChannel2
, public nsICacheListener
, public nsIEncodedChannel
, public nsITransportEventSink
@ -121,6 +123,7 @@ public:
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSICACHINGCHANNEL
NS_DECL_NSIUPLOADCHANNEL
NS_DECL_NSIUPLOADCHANNEL2
NS_DECL_NSICACHELISTENER
NS_DECL_NSIENCODEDCHANNEL
NS_DECL_NSIHTTPCHANNELINTERNAL