Bug 1185439 - Add preamble to nsIMultiPartChannel for MIME type application/package. r=valentin

This commit is contained in:
Henry Chang 2015-08-03 11:17:01 +08:00
parent eb8e021e09
commit f5137c494d
5 changed files with 137 additions and 17 deletions

View File

@ -12,7 +12,7 @@ interface nsIChannel;
* associated with a MultiPartChannel.
*/
[scriptable, uuid(4b04e835-d131-42af-8bf0-74289f99374f)]
[scriptable, uuid(3c329c90-2ee0-11e5-a2cb-0800200c9a66)]
interface nsIMultiPartChannel : nsISupports
{
/**
@ -32,4 +32,10 @@ interface nsIMultiPartChannel : nsISupports
* whether more data can be expected.
*/
readonly attribute boolean isLastPart;
/**
* ASCII-encoding content prior to the first resource. Only valid for
* content-type=application/package.
*/
readonly attribute ACString preamble;
};

View File

@ -459,6 +459,18 @@ nsPartChannel::GetBaseChannel(nsIChannel ** aReturn)
return NS_OK;
}
NS_IMETHODIMP
nsPartChannel::GetPreamble(nsACString & aPreamble)
{
aPreamble = mPreamble;
return NS_OK;
}
void
nsPartChannel::SetPreamble(const nsACString& aPreamble)
{
mPreamble = aPreamble;
}
// nsISupports implementation
NS_IMPL_ISUPPORTS(nsMultiMixedConv,
@ -522,6 +534,49 @@ private:
char *mBuffer;
};
char*
nsMultiMixedConv::ProbeToken(char* aBuffer, uint32_t& aTokenLen)
{
// To sign a packaged web app in the new security model, we need
// to add the signature to the package header. The header is the
// data before the first token and the header format is
//
// [field-name]: [field-value] CR LF
//
// So the package may look like:
//
// manifest-signature: MRjdkly...
// --gc0pJq0M:08jU534c0p
// Content-Location: /someapp.webmanifest
// Content-Type: application/manifest
//
// {
// "name": "My App",
// "description":"A great app!"
// ...
//
//
// We search for the first '\r\n--' and assign the subsquent chars
// to the token until another '\r\n'. '--' will be included in the
// token we probed. If the second '\r\n' is not found, we still treat
// the token is not found and more data will be requested.
char* posCRLFDashDash = PL_strstr(aBuffer, "\r\n--");
if (!posCRLFDashDash) {
return nullptr;
}
char* tokenStart = posCRLFDashDash + 2; // Skip "\r\n".
char* tokenEnd = PL_strstr(tokenStart, "\r\n");
if (!tokenEnd) {
return nullptr;
}
aTokenLen = tokenEnd - tokenStart;
return tokenStart;
}
// nsIStreamListener implementation
NS_IMETHODIMP
nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context,
@ -584,24 +639,36 @@ nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context,
} else if (mPackagedApp) {
// We need to check the line starts with --
if (!StringBeginsWith(firstBuffer, NS_LITERAL_CSTRING("--"))) {
return NS_ERROR_FAILURE;
}
char* tokenPos = ProbeToken(buffer, mTokenLen);
if (!tokenPos) {
// No token is found. We need more data.
mFirstOnData = true;
} else {
// Token is probed.
mToken = Substring(tokenPos, mTokenLen);
mPreamble = nsCString(Substring(buffer, tokenPos));
// If the boundary was set in the header,
// we need to check it matches with the one in the file.
if (mTokenLen &&
!StringBeginsWith(Substring(firstBuffer, 2), mToken)) {
return NS_ERROR_FAILURE;
}
// Push the cursor to the token so that the while loop below will
// find token from the right position.
cursor = tokenPos;
}
} else {
// If the boundary was set in the header,
// we need to check it matches with the one in the file.
if (mTokenLen &&
!StringBeginsWith(Substring(firstBuffer, 2), mToken)) {
return NS_ERROR_FAILURE;
}
// Save the token.
if (!mTokenLen) {
mToken = nsCString(Substring(firstBuffer, 2).BeginReading(),
posCR - 2);
mTokenLen = mToken.Length();
}
// Save the token.
if (!mTokenLen) {
mToken = nsCString(Substring(firstBuffer, 2).BeginReading(),
posCR - 2);
mTokenLen = mToken.Length();
}
cursor = buffer;
cursor = buffer;
}
} else if (!PL_strnstr(cursor, token, mTokenLen + 2)) {
char *newBuffer = (char *) realloc(buffer, bufLen + mTokenLen + 1);
if (!newBuffer)
@ -965,6 +1032,9 @@ nsMultiMixedConv::SendStart(nsIChannel *aChannel) {
// Set up the new part channel...
mPartChannel = newChannel;
// Pass preamble to the channel.
mPartChannel->SetPreamble(mPreamble);
// We pass the headers to the nsPartChannel
mPartChannel->SetResponseHead(mResponseHead.forget());

View File

@ -43,6 +43,7 @@ public:
void InitializeByteRange(int64_t aStart, int64_t aEnd);
void SetIsLastPart() { mIsLastPart = true; }
void SetPreamble(const nsACString& aPreamble);
nsresult SendOnStartRequest(nsISupports* aContext);
nsresult SendOnDataAvailable(nsISupports* aContext, nsIInputStream* aStream,
uint64_t aOffset, uint32_t aLen);
@ -86,6 +87,8 @@ protected:
uint32_t mPartID; // unique ID that can be used to identify
// this part of the multipart document
bool mIsLastPart;
nsCString mPreamble;
};
// The nsMultiMixedConv stream converter converts a stream of type "multipart/x-mixed-replace"
@ -145,6 +148,7 @@ protected:
int32_t PushOverLine(char *&aPtr, uint32_t &aLen);
char *FindToken(char *aCursor, uint32_t aLen);
nsresult BufferData(char *aData, uint32_t aLen);
char* ProbeToken(char* aBuffer, uint32_t& aTokenLen);
// member data
bool mNewPart; // Are we processing the beginning of a part?
@ -187,6 +191,11 @@ protected:
// for packaged apps, in the case that only metadata is saved in the cache
// entry and OnDataAvailable never gets called.
bool mIsFromCache;
// Preamble is defined as the ASCII-encoding string which appears before the
// first boundary. It's only supported by 'application/package' content type
// and requires the boundary is defined in the HTTP header.
nsCString mPreamble;
};
#endif /* __nsmultimixedconv__h__ */

View File

@ -76,7 +76,15 @@ function contentHandler_type_missing(metadata, response)
response.bodyOutputStream.write(body, body.length);
}
function contentHandler_with_package_header(metadata, response)
{
response.setHeader("Content-Type", 'application/package');
var body = testData.packageHeader + testData.getData();
response.bodyOutputStream.write(body, body.length);
}
var testData = {
packageHeader: 'manifest-signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\r\n',
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" },
{ headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
@ -99,7 +107,7 @@ var testData = {
}
}
function multipartListener(test, badContentType) {
function multipartListener(test, badContentType, shouldVerifyPackageHeader) {
this._buffer = "";
this.testNum = 0;
this.test = test;
@ -108,6 +116,7 @@ function multipartListener(test, badContentType) {
// content type. If so, resources will have their content type set to
// application/octet-stream
this.badContentType = badContentType == undefined ? false : badContentType;
this.shouldVerifyPackageHeader = shouldVerifyPackageHeader;
}
multipartListener.prototype.responseHandler = function(request, buffer) {
@ -128,6 +137,12 @@ multipartListener.prototype.QueryInterface = function(iid) {
}
multipartListener.prototype.onStartRequest = function(request, context) {
if (this.shouldVerifyPackageHeader) {
let partChannel = request.QueryInterface(Ci.nsIMultiPartChannel);
ok(!!partChannel, 'Should be multipart channel');
equal(partChannel.preamble, this.test.packageHeader);
}
this._buffer = "";
this.headerListener = new headerListener(this.test.content[this.testNum].headers, this.badContentType);
let headerProvider = request.QueryInterface(Ci.nsIResponseHeadProvider);
@ -222,6 +237,18 @@ function test_multipart_content_type_other() {
chan.asyncOpen(conv, null);
}
function test_multipart_package_header() {
var streamConv = Cc["@mozilla.org/streamConverters;1"]
.getService(Ci.nsIStreamConverterService);
var conv = streamConv.asyncConvertData("application/package",
"*/*",
new multipartListener(testData, false, true),
null);
var chan = make_channel(uri + "/multipart5");
chan.asyncOpen(conv, null);
}
function run_test()
{
@ -230,6 +257,7 @@ function run_test()
httpserver.registerPathHandler("/multipart2", contentHandler_with_boundary);
httpserver.registerPathHandler("/multipart3", contentHandler_chunked_headers);
httpserver.registerPathHandler("/multipart4", contentHandler_type_missing);
httpserver.registerPathHandler("/multipart5", contentHandler_with_package_header);
httpserver.start(-1);
run_next_test();
@ -239,3 +267,4 @@ add_test(test_multipart);
add_test(test_multipart_with_boundary);
add_test(test_multipart_chunked_headers);
add_test(test_multipart_content_type_other);
add_test(test_multipart_package_header);

View File

@ -493,5 +493,11 @@ ExternalHelperAppParent::GetIsLastPart(bool* aIsLastPart)
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
ExternalHelperAppParent::GetPreamble(nsACString & aPreamble)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
} // namespace dom
} // namespace mozilla