Bug 1172233 - Allow web packaged apps to be served without a mimetype r=mcmanus

This commit is contained in:
Valentin Gosu 2015-07-17 14:25:43 +02:00
parent 273814e79e
commit 5b77d96fef
4 changed files with 88 additions and 16 deletions

View File

@ -651,7 +651,8 @@ PackagedAppService::OpenNewPackageInternal(nsIURI *aURI,
}
nsCOMPtr<nsIStreamListener> mimeConverter;
rv = streamconv->AsyncConvertData("multipart/mixed", "*/*", downloader, nullptr,
// Passing `this` as context, so nsMultiMixedConv knows this is a packaged app
rv = streamconv->AsyncConvertData("multipart/mixed", "*/*", downloader, this,
getter_AddRefs(mimeConverter));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;

View File

@ -13,7 +13,9 @@
#include "nsIHttpChannelInternal.h"
#include "nsURLHelper.h"
#include "nsIStreamConverterService.h"
#include "nsIPackagedAppService.h"
#include <algorithm>
#include "nsHttp.h"
//
// Helper function for determining the length of data bytes up to
@ -470,6 +472,11 @@ nsMultiMixedConv::AsyncConvertData(const char *aFromType, const char *aToType,
// and OnStopRequest() call combinations. We call of series of these for each sub-part
// in the raw stream.
mFinalListener = aListener;
nsCOMPtr<nsIPackagedAppService> pas(do_QueryInterface(aCtxt));
if (pas) {
mPackagedApp = true;
}
return NS_OK;
}
@ -732,11 +739,15 @@ nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel, &rv);
if (NS_SUCCEEDED(rv)) {
rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-type"), delimiter);
if (NS_FAILED(rv)) return rv;
if (NS_FAILED(rv) && !mPackagedApp) {
return rv;
}
} else {
// try asking the channel directly
rv = channel->GetContentType(delimiter);
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
if (NS_FAILED(rv) && !mPackagedApp) {
return NS_ERROR_FAILURE;
}
}
// http://www.w3.org/TR/web-packaging/#streamable-package-format
@ -745,13 +756,22 @@ nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
// the content of the file.
if (delimiter.Find("application/package") != kNotFound) {
mPackagedApp = true;
mHasAppContentType = true;
mToken.Truncate();
mTokenLen = 0;
}
bndry = strstr(delimiter.BeginWriting(), "boundary");
if (!bndry && mPackagedApp) {
bool requestSucceeded = true;
if (httpChannel) {
httpChannel->GetRequestSucceeded(&requestSucceeded);
}
// If the package has the appropriate content type, or if it is a successful
// packaged app request, without the required content type, there's no need
// for a boundary to be included in this header.
if (!bndry && (mHasAppContentType || (mPackagedApp && requestSucceeded))) {
return NS_OK;
}
@ -775,9 +795,9 @@ nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
mToken = boundaryString;
mTokenLen = boundaryString.Length();
if (mTokenLen == 0 && !mPackagedApp) {
return NS_ERROR_FAILURE;
}
if (mTokenLen == 0 && !mPackagedApp) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
@ -841,6 +861,7 @@ nsMultiMixedConv::nsMultiMixedConv() :
mTotalSent = 0;
mIsByteRangeRequest = false;
mPackagedApp = false;
mHasAppContentType = false;
}
nsMultiMixedConv::~nsMultiMixedConv() {
@ -870,7 +891,9 @@ nsMultiMixedConv::SendStart(nsIChannel *aChannel) {
nsresult rv = NS_OK;
nsCOMPtr<nsIStreamListener> partListener(mFinalListener);
if (mContentType.IsEmpty()) {
// For packaged apps that don't have a content type we want to just
// go ahead and serve them with an empty content type
if (mContentType.IsEmpty() && !mPackagedApp) {
mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
nsCOMPtr<nsIStreamConverterService> serv =
do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
@ -1065,6 +1088,17 @@ nsMultiMixedConv::ParseHeaders(nsIChannel *aChannel, char *&aPtr,
// examine header
if (headerStr.LowerCaseEqualsLiteral("content-type")) {
mContentType = headerVal;
// If the HTTP channel doesn't have an application/package
// content type we still want to serve the resource, but with the
// "application/octet-stream" header, so we prevent execution of
// unsafe content
if (mPackagedApp && !mHasAppContentType) {
mContentType = APPLICATION_OCTET_STREAM;
mResponseHead->SetHeader(mozilla::net::nsHttp::Content_Type,
mContentType);
mResponseHead->SetContentType(mContentType);
}
} else if (headerStr.LowerCaseEqualsLiteral("content-length")) {
mContentLength = nsCRT::atoll(headerVal.get());
} else if (headerStr.LowerCaseEqualsLiteral("content-disposition")) {

View File

@ -177,6 +177,9 @@ protected:
uint32_t mCurrentPartID;
// If true, it means the packaged app had an "application/package" header
// Otherwise, we remove "Content-Type" headers from files in the package
bool mHasAppContentType;
// This is true if the content-type is application/package
// Streamable packages don't require the boundary in the header
// as it can be ascertained from the package file.

View File

@ -24,6 +24,8 @@ Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://gre/modules/Services.jsm");
var httpserver = null;
var gPAS = Cc["@mozilla.org/network/packaged-app-service;1"]
.getService(Ci.nsIPackagedAppService);
XPCOMUtils.defineLazyGetter(this, "uri", function() {
return "http://localhost:" + httpserver.identity.primaryPort;
@ -69,6 +71,13 @@ function contentHandler_chunked_headers(metadata, response)
});
}
function contentHandler_type_missing(metadata, response)
{
response.setHeader("Content-Type", 'text/plain');
var body = testData.getData();
response.bodyOutputStream.write(body, body.length);
}
var testData = {
content: [
{ headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n <head>\r\n <script src=\"/scripts/app.js\"></script>\r\n ...\r\n </head>\r\n ...\r\n</html>\r\n", type: "text/html" },
@ -92,16 +101,21 @@ var testData = {
}
}
function multipartListener(test) {
function multipartListener(test, badContentType) {
this._buffer = "";
this.testNum = 0;
this.test = test;
this.numTests = this.test.content.length;
// If set to true, that means the package is missing the application/package
// content type. If so, resources will have their content type set to
// application/octet-stream
this.badContentType = badContentType == undefined ? false : badContentType;
}
multipartListener.prototype.responseHandler = function(request, buffer) {
equal(buffer, this.test.content[this.testNum].data);
equal(request.QueryInterface(Ci.nsIChannel).contentType, this.test.content[this.testNum].type);
equal(request.QueryInterface(Ci.nsIChannel).contentType,
this.badContentType ? "application/octet-stream" : this.test.content[this.testNum].type);
if (++this.testNum == this.numTests) {
run_next_test();
}
@ -117,7 +131,7 @@ multipartListener.prototype.QueryInterface = function(iid) {
multipartListener.prototype.onStartRequest = function(request, context) {
this._buffer = "";
this.headerListener = new headerListener(this.test.content[this.testNum].headers);
this.headerListener = new headerListener(this.test.content[this.testNum].headers, this.badContentType);
let headerProvider = request.QueryInterface(Ci.nsIResponseHeadProvider);
if (headerProvider) {
headerProvider.visitResponseHeaders(this.headerListener);
@ -141,8 +155,9 @@ multipartListener.prototype.onStopRequest = function(request, context, status) {
}
}
function headerListener(headers) {
function headerListener(headers, badContentType) {
this.expectedHeaders = headers;
this.badContentType = badContentType;
this.index = 0;
}
@ -155,7 +170,8 @@ headerListener.prototype.QueryInterface = function(iid) {
headerListener.prototype.visitHeader = function(header, value) {
ok(this.index <= this.expectedHeaders.length);
equal(header + ": " + value, this.expectedHeaders[this.index]);
if (!this.badContentType)
equal(header + ": " + value, this.expectedHeaders[this.index]);
this.index++;
}
@ -165,7 +181,7 @@ function test_multipart() {
var conv = streamConv.asyncConvertData("multipart/mixed",
"*/*",
new multipartListener(testData),
null);
gPAS);
var chan = make_channel(uri + "/multipart");
chan.asyncOpen(conv, null);
@ -177,7 +193,7 @@ function test_multipart_with_boundary() {
var conv = streamConv.asyncConvertData("multipart/mixed",
"*/*",
new multipartListener(testData),
null);
gPAS);
var chan = make_channel(uri + "/multipart2");
chan.asyncOpen(conv, null);
@ -189,18 +205,35 @@ function test_multipart_chunked_headers() {
var conv = streamConv.asyncConvertData("multipart/mixed",
"*/*",
new multipartListener(testData),
null);
gPAS);
var chan = make_channel(uri + "/multipart3");
chan.asyncOpen(conv, null);
}
function test_multipart_content_type_other() {
var streamConv = Cc["@mozilla.org/streamConverters;1"]
.getService(Ci.nsIStreamConverterService);
// mime types other that multipart/mixed and application/package are only
// allowed if an nsIPackagedAppService is passed as context
var conv = streamConv.asyncConvertData("multipart/mixed",
"*/*",
new multipartListener(testData, true),
gPAS);
var chan = make_channel(uri + "/multipart4");
chan.asyncOpen(conv, null);
}
function run_test()
{
httpserver = new HttpServer();
httpserver.registerPathHandler("/multipart", contentHandler);
httpserver.registerPathHandler("/multipart2", contentHandler_with_boundary);
httpserver.registerPathHandler("/multipart3", contentHandler_chunked_headers);
httpserver.registerPathHandler("/multipart4", contentHandler_type_missing);
httpserver.start(-1);
run_next_test();
@ -209,3 +242,4 @@ function run_test()
add_test(test_multipart);
add_test(test_multipart_with_boundary);
add_test(test_multipart_chunked_headers);
add_test(test_multipart_content_type_other);