netwerk/streamconv/converters/nsMultiMixedConv.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "nsMultiMixedConv.h"
michael@0 7 #include "plstr.h"
michael@0 8 #include "nsIHttpChannel.h"
michael@0 9 #include "nsNetUtil.h"
michael@0 10 #include "nsMimeTypes.h"
michael@0 11 #include "nsIStringStream.h"
michael@0 12 #include "nsCRT.h"
michael@0 13 #include "nsIHttpChannelInternal.h"
michael@0 14 #include "nsURLHelper.h"
michael@0 15 #include "nsIStreamConverterService.h"
michael@0 16 #include <algorithm>
michael@0 17
michael@0 18 //
michael@0 19 // Helper function for determining the length of data bytes up to
michael@0 20 // the next multipart token. A token is usually preceded by a LF
michael@0 21 // or CRLF delimiter.
michael@0 22 //
michael@0 23 static uint32_t
michael@0 24 LengthToToken(const char *cursor, const char *token)
michael@0 25 {
michael@0 26 uint32_t len = token - cursor;
michael@0 27 // Trim off any LF or CRLF preceding the token
michael@0 28 if (len && *(token-1) == '\n') {
michael@0 29 --len;
michael@0 30 if (len && *(token-2) == '\r')
michael@0 31 --len;
michael@0 32 }
michael@0 33 return len;
michael@0 34 }
michael@0 35
michael@0 36 nsPartChannel::nsPartChannel(nsIChannel *aMultipartChannel, uint32_t aPartID,
michael@0 37 nsIStreamListener* aListener) :
michael@0 38 mMultipartChannel(aMultipartChannel),
michael@0 39 mListener(aListener),
michael@0 40 mStatus(NS_OK),
michael@0 41 mContentLength(UINT64_MAX),
michael@0 42 mIsByteRangeRequest(false),
michael@0 43 mByteRangeStart(0),
michael@0 44 mByteRangeEnd(0),
michael@0 45 mPartID(aPartID),
michael@0 46 mIsLastPart(false)
michael@0 47 {
michael@0 48 mMultipartChannel = aMultipartChannel;
michael@0 49
michael@0 50 // Inherit the load flags from the original channel...
michael@0 51 mMultipartChannel->GetLoadFlags(&mLoadFlags);
michael@0 52
michael@0 53 mMultipartChannel->GetLoadGroup(getter_AddRefs(mLoadGroup));
michael@0 54 }
michael@0 55
michael@0 56 nsPartChannel::~nsPartChannel()
michael@0 57 {
michael@0 58 }
michael@0 59
michael@0 60 void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd)
michael@0 61 {
michael@0 62 mIsByteRangeRequest = true;
michael@0 63
michael@0 64 mByteRangeStart = aStart;
michael@0 65 mByteRangeEnd = aEnd;
michael@0 66 }
michael@0 67
michael@0 68 nsresult nsPartChannel::SendOnStartRequest(nsISupports* aContext)
michael@0 69 {
michael@0 70 return mListener->OnStartRequest(this, aContext);
michael@0 71 }
michael@0 72
michael@0 73 nsresult nsPartChannel::SendOnDataAvailable(nsISupports* aContext,
michael@0 74 nsIInputStream* aStream,
michael@0 75 uint64_t aOffset, uint32_t aLen)
michael@0 76 {
michael@0 77 return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aLen);
michael@0 78 }
michael@0 79
michael@0 80 nsresult nsPartChannel::SendOnStopRequest(nsISupports* aContext,
michael@0 81 nsresult aStatus)
michael@0 82 {
michael@0 83 // Drop the listener
michael@0 84 nsCOMPtr<nsIStreamListener> listener;
michael@0 85 listener.swap(mListener);
michael@0 86 return listener->OnStopRequest(this, aContext, aStatus);
michael@0 87 }
michael@0 88
michael@0 89 void nsPartChannel::SetContentDisposition(const nsACString& aContentDispositionHeader)
michael@0 90 {
michael@0 91 mContentDispositionHeader = aContentDispositionHeader;
michael@0 92 nsCOMPtr<nsIURI> uri;
michael@0 93 GetURI(getter_AddRefs(uri));
michael@0 94 NS_GetFilenameFromDisposition(mContentDispositionFilename,
michael@0 95 mContentDispositionHeader, uri);
michael@0 96 mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
michael@0 97 }
michael@0 98
michael@0 99 //
michael@0 100 // nsISupports implementation...
michael@0 101 //
michael@0 102
michael@0 103 NS_IMPL_ADDREF(nsPartChannel)
michael@0 104 NS_IMPL_RELEASE(nsPartChannel)
michael@0 105
michael@0 106 NS_INTERFACE_MAP_BEGIN(nsPartChannel)
michael@0 107 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
michael@0 108 NS_INTERFACE_MAP_ENTRY(nsIRequest)
michael@0 109 NS_INTERFACE_MAP_ENTRY(nsIChannel)
michael@0 110 NS_INTERFACE_MAP_ENTRY(nsIByteRangeRequest)
michael@0 111 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannel)
michael@0 112 NS_INTERFACE_MAP_END
michael@0 113
michael@0 114 //
michael@0 115 // nsIRequest implementation...
michael@0 116 //
michael@0 117
michael@0 118 NS_IMETHODIMP
michael@0 119 nsPartChannel::GetName(nsACString &aResult)
michael@0 120 {
michael@0 121 return mMultipartChannel->GetName(aResult);
michael@0 122 }
michael@0 123
michael@0 124 NS_IMETHODIMP
michael@0 125 nsPartChannel::IsPending(bool *aResult)
michael@0 126 {
michael@0 127 // For now, consider the active lifetime of each part the same as
michael@0 128 // the underlying multipart channel... This is not exactly right,
michael@0 129 // but it is good enough :-)
michael@0 130 return mMultipartChannel->IsPending(aResult);
michael@0 131 }
michael@0 132
michael@0 133 NS_IMETHODIMP
michael@0 134 nsPartChannel::GetStatus(nsresult *aResult)
michael@0 135 {
michael@0 136 nsresult rv = NS_OK;
michael@0 137
michael@0 138 if (NS_FAILED(mStatus)) {
michael@0 139 *aResult = mStatus;
michael@0 140 } else {
michael@0 141 rv = mMultipartChannel->GetStatus(aResult);
michael@0 142 }
michael@0 143
michael@0 144 return rv;
michael@0 145 }
michael@0 146
michael@0 147 NS_IMETHODIMP
michael@0 148 nsPartChannel::Cancel(nsresult aStatus)
michael@0 149 {
michael@0 150 // Cancelling an individual part must not cancel the underlying
michael@0 151 // multipart channel...
michael@0 152 // XXX but we should stop sending data for _this_ part channel!
michael@0 153 mStatus = aStatus;
michael@0 154 return NS_OK;
michael@0 155 }
michael@0 156
michael@0 157 NS_IMETHODIMP
michael@0 158 nsPartChannel::Suspend(void)
michael@0 159 {
michael@0 160 // Suspending an individual part must not suspend the underlying
michael@0 161 // multipart channel...
michael@0 162 // XXX why not?
michael@0 163 return NS_OK;
michael@0 164 }
michael@0 165
michael@0 166 NS_IMETHODIMP
michael@0 167 nsPartChannel::Resume(void)
michael@0 168 {
michael@0 169 // Resuming an individual part must not resume the underlying
michael@0 170 // multipart channel...
michael@0 171 // XXX why not?
michael@0 172 return NS_OK;
michael@0 173 }
michael@0 174
michael@0 175 //
michael@0 176 // nsIChannel implementation
michael@0 177 //
michael@0 178
michael@0 179 NS_IMETHODIMP
michael@0 180 nsPartChannel::GetOriginalURI(nsIURI * *aURI)
michael@0 181 {
michael@0 182 return mMultipartChannel->GetOriginalURI(aURI);
michael@0 183 }
michael@0 184
michael@0 185 NS_IMETHODIMP
michael@0 186 nsPartChannel::SetOriginalURI(nsIURI *aURI)
michael@0 187 {
michael@0 188 return mMultipartChannel->SetOriginalURI(aURI);
michael@0 189 }
michael@0 190
michael@0 191 NS_IMETHODIMP
michael@0 192 nsPartChannel::GetURI(nsIURI * *aURI)
michael@0 193 {
michael@0 194 return mMultipartChannel->GetURI(aURI);
michael@0 195 }
michael@0 196
michael@0 197 NS_IMETHODIMP
michael@0 198 nsPartChannel::Open(nsIInputStream **result)
michael@0 199 {
michael@0 200 // This channel cannot be opened!
michael@0 201 return NS_ERROR_FAILURE;
michael@0 202 }
michael@0 203
michael@0 204 NS_IMETHODIMP
michael@0 205 nsPartChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
michael@0 206 {
michael@0 207 // This channel cannot be opened!
michael@0 208 return NS_ERROR_FAILURE;
michael@0 209 }
michael@0 210
michael@0 211 NS_IMETHODIMP
michael@0 212 nsPartChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
michael@0 213 {
michael@0 214 *aLoadFlags = mLoadFlags;
michael@0 215 return NS_OK;
michael@0 216 }
michael@0 217
michael@0 218 NS_IMETHODIMP
michael@0 219 nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
michael@0 220 {
michael@0 221 mLoadFlags = aLoadFlags;
michael@0 222 return NS_OK;
michael@0 223 }
michael@0 224
michael@0 225 NS_IMETHODIMP
michael@0 226 nsPartChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
michael@0 227 {
michael@0 228 *aLoadGroup = mLoadGroup;
michael@0 229 NS_IF_ADDREF(*aLoadGroup);
michael@0 230
michael@0 231 return NS_OK;
michael@0 232 }
michael@0 233
michael@0 234 NS_IMETHODIMP
michael@0 235 nsPartChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
michael@0 236 {
michael@0 237 mLoadGroup = aLoadGroup;
michael@0 238
michael@0 239 return NS_OK;
michael@0 240 }
michael@0 241
michael@0 242 NS_IMETHODIMP
michael@0 243 nsPartChannel::GetOwner(nsISupports* *aOwner)
michael@0 244 {
michael@0 245 return mMultipartChannel->GetOwner(aOwner);
michael@0 246 }
michael@0 247
michael@0 248 NS_IMETHODIMP
michael@0 249 nsPartChannel::SetOwner(nsISupports* aOwner)
michael@0 250 {
michael@0 251 return mMultipartChannel->SetOwner(aOwner);
michael@0 252 }
michael@0 253
michael@0 254 NS_IMETHODIMP
michael@0 255 nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
michael@0 256 {
michael@0 257 return mMultipartChannel->GetNotificationCallbacks(aCallbacks);
michael@0 258 }
michael@0 259
michael@0 260 NS_IMETHODIMP
michael@0 261 nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
michael@0 262 {
michael@0 263 return mMultipartChannel->SetNotificationCallbacks(aCallbacks);
michael@0 264 }
michael@0 265
michael@0 266 NS_IMETHODIMP
michael@0 267 nsPartChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
michael@0 268 {
michael@0 269 return mMultipartChannel->GetSecurityInfo(aSecurityInfo);
michael@0 270 }
michael@0 271
michael@0 272 NS_IMETHODIMP
michael@0 273 nsPartChannel::GetContentType(nsACString &aContentType)
michael@0 274 {
michael@0 275 aContentType = mContentType;
michael@0 276 return NS_OK;
michael@0 277 }
michael@0 278
michael@0 279 NS_IMETHODIMP
michael@0 280 nsPartChannel::SetContentType(const nsACString &aContentType)
michael@0 281 {
michael@0 282 bool dummy;
michael@0 283 net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
michael@0 284 return NS_OK;
michael@0 285 }
michael@0 286
michael@0 287 NS_IMETHODIMP
michael@0 288 nsPartChannel::GetContentCharset(nsACString &aContentCharset)
michael@0 289 {
michael@0 290 aContentCharset = mContentCharset;
michael@0 291 return NS_OK;
michael@0 292 }
michael@0 293
michael@0 294 NS_IMETHODIMP
michael@0 295 nsPartChannel::SetContentCharset(const nsACString &aContentCharset)
michael@0 296 {
michael@0 297 mContentCharset = aContentCharset;
michael@0 298 return NS_OK;
michael@0 299 }
michael@0 300
michael@0 301 NS_IMETHODIMP
michael@0 302 nsPartChannel::GetContentLength(int64_t *aContentLength)
michael@0 303 {
michael@0 304 *aContentLength = mContentLength;
michael@0 305 return NS_OK;
michael@0 306 }
michael@0 307
michael@0 308 NS_IMETHODIMP
michael@0 309 nsPartChannel::SetContentLength(int64_t aContentLength)
michael@0 310 {
michael@0 311 mContentLength = aContentLength;
michael@0 312 return NS_OK;
michael@0 313 }
michael@0 314
michael@0 315 NS_IMETHODIMP
michael@0 316 nsPartChannel::GetContentDisposition(uint32_t *aContentDisposition)
michael@0 317 {
michael@0 318 if (mContentDispositionHeader.IsEmpty())
michael@0 319 return NS_ERROR_NOT_AVAILABLE;
michael@0 320
michael@0 321 *aContentDisposition = mContentDisposition;
michael@0 322 return NS_OK;
michael@0 323 }
michael@0 324
michael@0 325 NS_IMETHODIMP
michael@0 326 nsPartChannel::SetContentDisposition(uint32_t aContentDisposition)
michael@0 327 {
michael@0 328 return NS_ERROR_NOT_AVAILABLE;
michael@0 329 }
michael@0 330
michael@0 331 NS_IMETHODIMP
michael@0 332 nsPartChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
michael@0 333 {
michael@0 334 if (mContentDispositionFilename.IsEmpty())
michael@0 335 return NS_ERROR_NOT_AVAILABLE;
michael@0 336
michael@0 337 aContentDispositionFilename = mContentDispositionFilename;
michael@0 338 return NS_OK;
michael@0 339 }
michael@0 340
michael@0 341 NS_IMETHODIMP
michael@0 342 nsPartChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
michael@0 343 {
michael@0 344 return NS_ERROR_NOT_AVAILABLE;
michael@0 345 }
michael@0 346
michael@0 347
michael@0 348 NS_IMETHODIMP
michael@0 349 nsPartChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
michael@0 350 {
michael@0 351 if (mContentDispositionHeader.IsEmpty())
michael@0 352 return NS_ERROR_NOT_AVAILABLE;
michael@0 353
michael@0 354 aContentDispositionHeader = mContentDispositionHeader;
michael@0 355 return NS_OK;
michael@0 356 }
michael@0 357
michael@0 358 NS_IMETHODIMP
michael@0 359 nsPartChannel::GetPartID(uint32_t *aPartID)
michael@0 360 {
michael@0 361 *aPartID = mPartID;
michael@0 362 return NS_OK;
michael@0 363 }
michael@0 364
michael@0 365 NS_IMETHODIMP
michael@0 366 nsPartChannel::GetIsLastPart(bool *aIsLastPart)
michael@0 367 {
michael@0 368 *aIsLastPart = mIsLastPart;
michael@0 369 return NS_OK;
michael@0 370 }
michael@0 371
michael@0 372 //
michael@0 373 // nsIByteRangeRequest implementation...
michael@0 374 //
michael@0 375
michael@0 376 NS_IMETHODIMP
michael@0 377 nsPartChannel::GetIsByteRangeRequest(bool *aIsByteRangeRequest)
michael@0 378 {
michael@0 379 *aIsByteRangeRequest = mIsByteRangeRequest;
michael@0 380
michael@0 381 return NS_OK;
michael@0 382 }
michael@0 383
michael@0 384
michael@0 385 NS_IMETHODIMP
michael@0 386 nsPartChannel::GetStartRange(int64_t *aStartRange)
michael@0 387 {
michael@0 388 *aStartRange = mByteRangeStart;
michael@0 389
michael@0 390 return NS_OK;
michael@0 391 }
michael@0 392
michael@0 393 NS_IMETHODIMP
michael@0 394 nsPartChannel::GetEndRange(int64_t *aEndRange)
michael@0 395 {
michael@0 396 *aEndRange = mByteRangeEnd;
michael@0 397 return NS_OK;
michael@0 398 }
michael@0 399
michael@0 400 NS_IMETHODIMP
michael@0 401 nsPartChannel::GetBaseChannel(nsIChannel ** aReturn)
michael@0 402 {
michael@0 403 NS_ENSURE_ARG_POINTER(aReturn);
michael@0 404
michael@0 405 *aReturn = mMultipartChannel;
michael@0 406 NS_IF_ADDREF(*aReturn);
michael@0 407 return NS_OK;
michael@0 408 }
michael@0 409
michael@0 410
michael@0 411 // nsISupports implementation
michael@0 412 NS_IMPL_ISUPPORTS(nsMultiMixedConv,
michael@0 413 nsIStreamConverter,
michael@0 414 nsIStreamListener,
michael@0 415 nsIRequestObserver)
michael@0 416
michael@0 417
michael@0 418 // nsIStreamConverter implementation
michael@0 419
michael@0 420 // No syncronous conversion at this time.
michael@0 421 NS_IMETHODIMP
michael@0 422 nsMultiMixedConv::Convert(nsIInputStream *aFromStream,
michael@0 423 const char *aFromType,
michael@0 424 const char *aToType,
michael@0 425 nsISupports *aCtxt, nsIInputStream **_retval) {
michael@0 426 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 427 }
michael@0 428
michael@0 429 // Stream converter service calls this to initialize the actual stream converter (us).
michael@0 430 NS_IMETHODIMP
michael@0 431 nsMultiMixedConv::AsyncConvertData(const char *aFromType, const char *aToType,
michael@0 432 nsIStreamListener *aListener, nsISupports *aCtxt) {
michael@0 433 NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into multi mixed converter");
michael@0 434
michael@0 435 // hook up our final listener. this guy gets the various On*() calls we want to throw
michael@0 436 // at him.
michael@0 437 //
michael@0 438 // WARNING: this listener must be able to handle multiple OnStartRequest, OnDataAvail()
michael@0 439 // and OnStopRequest() call combinations. We call of series of these for each sub-part
michael@0 440 // in the raw stream.
michael@0 441 mFinalListener = aListener;
michael@0 442 return NS_OK;
michael@0 443 }
michael@0 444
michael@0 445 // AutoFree implementation to prevent memory leaks
michael@0 446 class AutoFree
michael@0 447 {
michael@0 448 public:
michael@0 449 AutoFree() : mBuffer(nullptr) {}
michael@0 450
michael@0 451 AutoFree(char *buffer) : mBuffer(buffer) {}
michael@0 452
michael@0 453 ~AutoFree() {
michael@0 454 free(mBuffer);
michael@0 455 }
michael@0 456
michael@0 457 AutoFree& operator=(char *buffer) {
michael@0 458 mBuffer = buffer;
michael@0 459 return *this;
michael@0 460 }
michael@0 461
michael@0 462 operator char*() const {
michael@0 463 return mBuffer;
michael@0 464 }
michael@0 465 private:
michael@0 466 char *mBuffer;
michael@0 467 };
michael@0 468
michael@0 469 // nsIStreamListener implementation
michael@0 470 NS_IMETHODIMP
michael@0 471 nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context,
michael@0 472 nsIInputStream *inStr, uint64_t sourceOffset,
michael@0 473 uint32_t count) {
michael@0 474
michael@0 475 if (mToken.IsEmpty()) // no token, no love.
michael@0 476 return NS_ERROR_FAILURE;
michael@0 477
michael@0 478 nsresult rv = NS_OK;
michael@0 479 AutoFree buffer = nullptr;
michael@0 480 uint32_t bufLen = 0, read = 0;
michael@0 481
michael@0 482 NS_ASSERTION(request, "multimixed converter needs a request");
michael@0 483
michael@0 484 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
michael@0 485 if (NS_FAILED(rv)) return rv;
michael@0 486
michael@0 487 // fill buffer
michael@0 488 {
michael@0 489 bufLen = count + mBufLen;
michael@0 490 NS_ENSURE_TRUE((bufLen >= count) && (bufLen >= mBufLen),
michael@0 491 NS_ERROR_FAILURE);
michael@0 492 buffer = (char *) malloc(bufLen);
michael@0 493 if (!buffer)
michael@0 494 return NS_ERROR_OUT_OF_MEMORY;
michael@0 495
michael@0 496 if (mBufLen) {
michael@0 497 // incorporate any buffered data into the parsing
michael@0 498 memcpy(buffer, mBuffer, mBufLen);
michael@0 499 free(mBuffer);
michael@0 500 mBuffer = 0;
michael@0 501 mBufLen = 0;
michael@0 502 }
michael@0 503
michael@0 504 rv = inStr->Read(buffer + (bufLen - count), count, &read);
michael@0 505
michael@0 506 if (NS_FAILED(rv) || read == 0) return rv;
michael@0 507 NS_ASSERTION(read == count, "poor data size assumption");
michael@0 508 }
michael@0 509
michael@0 510 char *cursor = buffer;
michael@0 511
michael@0 512 if (mFirstOnData) {
michael@0 513 // this is the first OnData() for this request. some servers
michael@0 514 // don't bother sending a token in the first "part." This is
michael@0 515 // illegal, but we'll handle the case anyway by shoving the
michael@0 516 // boundary token in for the server.
michael@0 517 mFirstOnData = false;
michael@0 518 NS_ASSERTION(!mBufLen, "this is our first time through, we can't have buffered data");
michael@0 519 const char * token = mToken.get();
michael@0 520
michael@0 521 PushOverLine(cursor, bufLen);
michael@0 522
michael@0 523 if (bufLen < mTokenLen+2) {
michael@0 524 // we don't have enough data yet to make this comparison.
michael@0 525 // skip this check, and try again the next time OnData()
michael@0 526 // is called.
michael@0 527 mFirstOnData = true;
michael@0 528 }
michael@0 529 else if (!PL_strnstr(cursor, token, mTokenLen+2)) {
michael@0 530 buffer = (char *) realloc(buffer, bufLen + mTokenLen + 1);
michael@0 531 if (!buffer)
michael@0 532 return NS_ERROR_OUT_OF_MEMORY;
michael@0 533
michael@0 534 memmove(buffer + mTokenLen + 1, buffer, bufLen);
michael@0 535 memcpy(buffer, token, mTokenLen);
michael@0 536 buffer[mTokenLen] = '\n';
michael@0 537
michael@0 538 bufLen += (mTokenLen + 1);
michael@0 539
michael@0 540 // need to reset cursor to the buffer again (bug 100595)
michael@0 541 cursor = buffer;
michael@0 542 }
michael@0 543 }
michael@0 544
michael@0 545 char *token = nullptr;
michael@0 546
michael@0 547 if (mProcessingHeaders) {
michael@0 548 // we were not able to process all the headers
michael@0 549 // for this "part" given the previous buffer given to
michael@0 550 // us in the previous OnDataAvailable callback.
michael@0 551 bool done = false;
michael@0 552 rv = ParseHeaders(channel, cursor, bufLen, &done);
michael@0 553 if (NS_FAILED(rv)) return rv;
michael@0 554
michael@0 555 if (done) {
michael@0 556 mProcessingHeaders = false;
michael@0 557 rv = SendStart(channel);
michael@0 558 if (NS_FAILED(rv)) return rv;
michael@0 559 }
michael@0 560 }
michael@0 561
michael@0 562 int32_t tokenLinefeed = 1;
michael@0 563 while ( (token = FindToken(cursor, bufLen)) ) {
michael@0 564
michael@0 565 if (((token + mTokenLen) < (cursor + bufLen)) &&
michael@0 566 (*(token + mTokenLen + 1) == '-')) {
michael@0 567 // This was the last delimiter so we can stop processing
michael@0 568 rv = SendData(cursor, LengthToToken(cursor, token));
michael@0 569 if (NS_FAILED(rv)) return rv;
michael@0 570 return SendStop(NS_OK);
michael@0 571 }
michael@0 572
michael@0 573 if (!mNewPart && token > cursor) {
michael@0 574 // headers are processed, we're pushing data now.
michael@0 575 NS_ASSERTION(!mProcessingHeaders, "we should be pushing raw data");
michael@0 576 rv = SendData(cursor, LengthToToken(cursor, token));
michael@0 577 bufLen -= token - cursor;
michael@0 578 if (NS_FAILED(rv)) return rv;
michael@0 579 }
michael@0 580 // XXX else NS_ASSERTION(token == cursor, "?");
michael@0 581 token += mTokenLen;
michael@0 582 bufLen -= mTokenLen;
michael@0 583 tokenLinefeed = PushOverLine(token, bufLen);
michael@0 584
michael@0 585 if (mNewPart) {
michael@0 586 // parse headers
michael@0 587 mNewPart = false;
michael@0 588 cursor = token;
michael@0 589 bool done = false;
michael@0 590 rv = ParseHeaders(channel, cursor, bufLen, &done);
michael@0 591 if (NS_FAILED(rv)) return rv;
michael@0 592 if (done) {
michael@0 593 rv = SendStart(channel);
michael@0 594 if (NS_FAILED(rv)) return rv;
michael@0 595 }
michael@0 596 else {
michael@0 597 // we haven't finished processing header info.
michael@0 598 // we'll break out and try to process later.
michael@0 599 mProcessingHeaders = true;
michael@0 600 break;
michael@0 601 }
michael@0 602 }
michael@0 603 else {
michael@0 604 mNewPart = true;
michael@0 605 // Reset state so we don't carry it over from part to part
michael@0 606 mContentType.Truncate();
michael@0 607 mContentLength = UINT64_MAX;
michael@0 608 mContentDisposition.Truncate();
michael@0 609 mIsByteRangeRequest = false;
michael@0 610 mByteRangeStart = 0;
michael@0 611 mByteRangeEnd = 0;
michael@0 612
michael@0 613 rv = SendStop(NS_OK);
michael@0 614 if (NS_FAILED(rv)) return rv;
michael@0 615 // reset the token to front. this allows us to treat
michael@0 616 // the token as a starting token.
michael@0 617 token -= mTokenLen + tokenLinefeed;
michael@0 618 bufLen += mTokenLen + tokenLinefeed;
michael@0 619 cursor = token;
michael@0 620 }
michael@0 621 }
michael@0 622
michael@0 623 // at this point, we want to buffer up whatever amount (bufLen)
michael@0 624 // we have leftover. However, we *always* want to ensure that
michael@0 625 // we buffer enough data to handle a broken token.
michael@0 626
michael@0 627 // carry over
michael@0 628 uint32_t bufAmt = 0;
michael@0 629 if (mProcessingHeaders)
michael@0 630 bufAmt = bufLen;
michael@0 631 else if (bufLen) {
michael@0 632 // if the data ends in a linefeed, and we're in the middle
michael@0 633 // of a "part" (ie. mPartChannel exists) don't bother
michael@0 634 // buffering, go ahead and send the data we have. Otherwise
michael@0 635 // if we don't have a channel already, then we don't even
michael@0 636 // have enough info to start a part, go ahead and buffer
michael@0 637 // enough to collect a boundary token.
michael@0 638 if (!mPartChannel || !(cursor[bufLen-1] == nsCRT::LF) )
michael@0 639 bufAmt = std::min(mTokenLen - 1, bufLen);
michael@0 640 }
michael@0 641
michael@0 642 if (bufAmt) {
michael@0 643 rv = BufferData(cursor + (bufLen - bufAmt), bufAmt);
michael@0 644 if (NS_FAILED(rv)) return rv;
michael@0 645 bufLen -= bufAmt;
michael@0 646 }
michael@0 647
michael@0 648 if (bufLen) {
michael@0 649 rv = SendData(cursor, bufLen);
michael@0 650 if (NS_FAILED(rv)) return rv;
michael@0 651 }
michael@0 652
michael@0 653 return rv;
michael@0 654 }
michael@0 655
michael@0 656
michael@0 657 // nsIRequestObserver implementation
michael@0 658 NS_IMETHODIMP
michael@0 659 nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
michael@0 660 // we're assuming the content-type is available at this stage
michael@0 661 NS_ASSERTION(mToken.IsEmpty(), "a second on start???");
michael@0 662 const char *bndry = nullptr;
michael@0 663 nsAutoCString delimiter;
michael@0 664 nsresult rv = NS_OK;
michael@0 665 mContext = ctxt;
michael@0 666
michael@0 667 mFirstOnData = true;
michael@0 668 mTotalSent = 0;
michael@0 669
michael@0 670 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
michael@0 671 if (NS_FAILED(rv)) return rv;
michael@0 672
michael@0 673 // ask the HTTP channel for the content-type and extract the boundary from it.
michael@0 674 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel, &rv);
michael@0 675 if (NS_SUCCEEDED(rv)) {
michael@0 676 rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-type"), delimiter);
michael@0 677 if (NS_FAILED(rv)) return rv;
michael@0 678 } else {
michael@0 679 // try asking the channel directly
michael@0 680 rv = channel->GetContentType(delimiter);
michael@0 681 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
michael@0 682 }
michael@0 683
michael@0 684 bndry = strstr(delimiter.BeginWriting(), "boundary");
michael@0 685 if (!bndry) return NS_ERROR_FAILURE;
michael@0 686
michael@0 687 bndry = strchr(bndry, '=');
michael@0 688 if (!bndry) return NS_ERROR_FAILURE;
michael@0 689
michael@0 690 bndry++; // move past the equals sign
michael@0 691
michael@0 692 char *attrib = (char *) strchr(bndry, ';');
michael@0 693 if (attrib) *attrib = '\0';
michael@0 694
michael@0 695 nsAutoCString boundaryString(bndry);
michael@0 696 if (attrib) *attrib = ';';
michael@0 697
michael@0 698 boundaryString.Trim(" \"");
michael@0 699
michael@0 700 mToken = boundaryString;
michael@0 701 mTokenLen = boundaryString.Length();
michael@0 702
michael@0 703 if (mTokenLen == 0)
michael@0 704 return NS_ERROR_FAILURE;
michael@0 705
michael@0 706 return NS_OK;
michael@0 707 }
michael@0 708
michael@0 709 NS_IMETHODIMP
michael@0 710 nsMultiMixedConv::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
michael@0 711 nsresult aStatus) {
michael@0 712
michael@0 713 if (mToken.IsEmpty()) // no token, no love.
michael@0 714 return NS_ERROR_FAILURE;
michael@0 715
michael@0 716 if (mPartChannel) {
michael@0 717 mPartChannel->SetIsLastPart();
michael@0 718
michael@0 719 // we've already called SendStart() (which sets up the mPartChannel,
michael@0 720 // and fires an OnStart()) send any data left over, and then fire the stop.
michael@0 721 if (mBufLen > 0 && mBuffer) {
michael@0 722 (void) SendData(mBuffer, mBufLen);
michael@0 723 // don't bother checking the return value here, if the send failed
michael@0 724 // we're done anyway as we're in the OnStop() callback.
michael@0 725 free(mBuffer);
michael@0 726 mBuffer = nullptr;
michael@0 727 mBufLen = 0;
michael@0 728 }
michael@0 729 (void) SendStop(aStatus);
michael@0 730 } else if (NS_FAILED(aStatus)) {
michael@0 731 // underlying data production problem. we should not be in
michael@0 732 // the middle of sending data. if we were, mPartChannel,
michael@0 733 // above, would have been true.
michael@0 734
michael@0 735 // if we send the start, the URI Loader's m_targetStreamListener, may
michael@0 736 // be pointing at us causing a nice stack overflow. So, don't call
michael@0 737 // OnStartRequest! - This breaks necko's semantecs.
michael@0 738 //(void) mFinalListener->OnStartRequest(request, ctxt);
michael@0 739
michael@0 740 (void) mFinalListener->OnStopRequest(request, ctxt, aStatus);
michael@0 741 }
michael@0 742
michael@0 743 return NS_OK;
michael@0 744 }
michael@0 745
michael@0 746
michael@0 747 // nsMultiMixedConv methods
michael@0 748 nsMultiMixedConv::nsMultiMixedConv() :
michael@0 749 mCurrentPartID(0)
michael@0 750 {
michael@0 751 mTokenLen = 0;
michael@0 752 mNewPart = true;
michael@0 753 mContentLength = UINT64_MAX;
michael@0 754 mBuffer = nullptr;
michael@0 755 mBufLen = 0;
michael@0 756 mProcessingHeaders = false;
michael@0 757 mByteRangeStart = 0;
michael@0 758 mByteRangeEnd = 0;
michael@0 759 mTotalSent = 0;
michael@0 760 mIsByteRangeRequest = false;
michael@0 761 }
michael@0 762
michael@0 763 nsMultiMixedConv::~nsMultiMixedConv() {
michael@0 764 NS_ASSERTION(!mBuffer, "all buffered data should be gone");
michael@0 765 if (mBuffer) {
michael@0 766 free(mBuffer);
michael@0 767 mBuffer = nullptr;
michael@0 768 }
michael@0 769 }
michael@0 770
michael@0 771 nsresult
michael@0 772 nsMultiMixedConv::BufferData(char *aData, uint32_t aLen) {
michael@0 773 NS_ASSERTION(!mBuffer, "trying to over-write buffer");
michael@0 774
michael@0 775 char *buffer = (char *) malloc(aLen);
michael@0 776 if (!buffer) return NS_ERROR_OUT_OF_MEMORY;
michael@0 777
michael@0 778 memcpy(buffer, aData, aLen);
michael@0 779 mBuffer = buffer;
michael@0 780 mBufLen = aLen;
michael@0 781 return NS_OK;
michael@0 782 }
michael@0 783
michael@0 784
michael@0 785 nsresult
michael@0 786 nsMultiMixedConv::SendStart(nsIChannel *aChannel) {
michael@0 787 nsresult rv = NS_OK;
michael@0 788
michael@0 789 nsCOMPtr<nsIStreamListener> partListener(mFinalListener);
michael@0 790 if (mContentType.IsEmpty()) {
michael@0 791 mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
michael@0 792 nsCOMPtr<nsIStreamConverterService> serv =
michael@0 793 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
michael@0 794 if (NS_SUCCEEDED(rv)) {
michael@0 795 nsCOMPtr<nsIStreamListener> converter;
michael@0 796 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
michael@0 797 "*/*",
michael@0 798 mFinalListener,
michael@0 799 mContext,
michael@0 800 getter_AddRefs(converter));
michael@0 801 if (NS_SUCCEEDED(rv)) {
michael@0 802 partListener = converter;
michael@0 803 }
michael@0 804 }
michael@0 805 }
michael@0 806
michael@0 807 // if we already have an mPartChannel, that means we never sent a Stop()
michael@0 808 // before starting up another "part." that would be bad.
michael@0 809 NS_ASSERTION(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel");
michael@0 810
michael@0 811 nsPartChannel *newChannel;
michael@0 812 newChannel = new nsPartChannel(aChannel, mCurrentPartID++, partListener);
michael@0 813 if (!newChannel)
michael@0 814 return NS_ERROR_OUT_OF_MEMORY;
michael@0 815
michael@0 816 if (mIsByteRangeRequest) {
michael@0 817 newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd);
michael@0 818 }
michael@0 819
michael@0 820 mTotalSent = 0;
michael@0 821
michael@0 822 // Set up the new part channel...
michael@0 823 mPartChannel = newChannel;
michael@0 824
michael@0 825 rv = mPartChannel->SetContentType(mContentType);
michael@0 826 if (NS_FAILED(rv)) return rv;
michael@0 827
michael@0 828 rv = mPartChannel->SetContentLength(mContentLength);
michael@0 829 if (NS_FAILED(rv)) return rv;
michael@0 830
michael@0 831 mPartChannel->SetContentDisposition(mContentDisposition);
michael@0 832
michael@0 833 nsLoadFlags loadFlags = 0;
michael@0 834 mPartChannel->GetLoadFlags(&loadFlags);
michael@0 835 loadFlags |= nsIChannel::LOAD_REPLACE;
michael@0 836 mPartChannel->SetLoadFlags(loadFlags);
michael@0 837
michael@0 838 nsCOMPtr<nsILoadGroup> loadGroup;
michael@0 839 (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
michael@0 840
michael@0 841 // Add the new channel to the load group (if any)
michael@0 842 if (loadGroup) {
michael@0 843 rv = loadGroup->AddRequest(mPartChannel, nullptr);
michael@0 844 if (NS_FAILED(rv)) return rv;
michael@0 845 }
michael@0 846
michael@0 847 // Let's start off the load. NOTE: we don't forward on the channel passed
michael@0 848 // into our OnDataAvailable() as it's the root channel for the raw stream.
michael@0 849 return mPartChannel->SendOnStartRequest(mContext);
michael@0 850 }
michael@0 851
michael@0 852
michael@0 853 nsresult
michael@0 854 nsMultiMixedConv::SendStop(nsresult aStatus) {
michael@0 855
michael@0 856 nsresult rv = NS_OK;
michael@0 857 if (mPartChannel) {
michael@0 858 rv = mPartChannel->SendOnStopRequest(mContext, aStatus);
michael@0 859 // don't check for failure here, we need to remove the channel from
michael@0 860 // the loadgroup.
michael@0 861
michael@0 862 // Remove the channel from its load group (if any)
michael@0 863 nsCOMPtr<nsILoadGroup> loadGroup;
michael@0 864 (void) mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
michael@0 865 if (loadGroup)
michael@0 866 (void) loadGroup->RemoveRequest(mPartChannel, mContext, aStatus);
michael@0 867 }
michael@0 868
michael@0 869 mPartChannel = 0;
michael@0 870 return rv;
michael@0 871 }
michael@0 872
michael@0 873 nsresult
michael@0 874 nsMultiMixedConv::SendData(char *aBuffer, uint32_t aLen) {
michael@0 875
michael@0 876 nsresult rv = NS_OK;
michael@0 877
michael@0 878 if (!mPartChannel) return NS_ERROR_FAILURE; // something went wrong w/ processing
michael@0 879
michael@0 880 if (mContentLength != UINT64_MAX) {
michael@0 881 // make sure that we don't send more than the mContentLength
michael@0 882 // XXX why? perhaps the Content-Length header was actually wrong!!
michael@0 883 if ((uint64_t(aLen) + mTotalSent) > mContentLength)
michael@0 884 aLen = static_cast<uint32_t>(mContentLength - mTotalSent);
michael@0 885
michael@0 886 if (aLen == 0)
michael@0 887 return NS_OK;
michael@0 888 }
michael@0 889
michael@0 890 uint64_t offset = mTotalSent;
michael@0 891 mTotalSent += aLen;
michael@0 892
michael@0 893 nsCOMPtr<nsIStringInputStream> ss(
michael@0 894 do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
michael@0 895 if (NS_FAILED(rv))
michael@0 896 return rv;
michael@0 897
michael@0 898 rv = ss->ShareData(aBuffer, aLen);
michael@0 899 if (NS_FAILED(rv))
michael@0 900 return rv;
michael@0 901
michael@0 902 nsCOMPtr<nsIInputStream> inStream(do_QueryInterface(ss, &rv));
michael@0 903 if (NS_FAILED(rv)) return rv;
michael@0 904
michael@0 905 return mPartChannel->SendOnDataAvailable(mContext, inStream, offset, aLen);
michael@0 906 }
michael@0 907
michael@0 908 int32_t
michael@0 909 nsMultiMixedConv::PushOverLine(char *&aPtr, uint32_t &aLen) {
michael@0 910 int32_t chars = 0;
michael@0 911 if ((aLen > 0) && (*aPtr == nsCRT::CR || *aPtr == nsCRT::LF)) {
michael@0 912 if ((aLen > 1) && (aPtr[1] == nsCRT::LF))
michael@0 913 chars++;
michael@0 914 chars++;
michael@0 915 aPtr += chars;
michael@0 916 aLen -= chars;
michael@0 917 }
michael@0 918 return chars;
michael@0 919 }
michael@0 920
michael@0 921 nsresult
michael@0 922 nsMultiMixedConv::ParseHeaders(nsIChannel *aChannel, char *&aPtr,
michael@0 923 uint32_t &aLen, bool *_retval) {
michael@0 924 // NOTE: this data must be ascii.
michael@0 925 // NOTE: aPtr is NOT null terminated!
michael@0 926 nsresult rv = NS_OK;
michael@0 927 char *cursor = aPtr, *newLine = nullptr;
michael@0 928 uint32_t cursorLen = aLen;
michael@0 929 bool done = false;
michael@0 930 uint32_t lineFeedIncrement = 1;
michael@0 931
michael@0 932 mContentLength = UINT64_MAX; // XXX what if we were already called?
michael@0 933 while (cursorLen && (newLine = (char *) memchr(cursor, nsCRT::LF, cursorLen))) {
michael@0 934 // adjust for linefeeds
michael@0 935 if ((newLine > cursor) && (newLine[-1] == nsCRT::CR) ) { // CRLF
michael@0 936 lineFeedIncrement = 2;
michael@0 937 newLine--;
michael@0 938 }
michael@0 939 else
michael@0 940 lineFeedIncrement = 1; // reset
michael@0 941
michael@0 942 if (newLine == cursor) {
michael@0 943 // move the newLine beyond the linefeed marker
michael@0 944 NS_ASSERTION(cursorLen >= lineFeedIncrement, "oops!");
michael@0 945
michael@0 946 cursor += lineFeedIncrement;
michael@0 947 cursorLen -= lineFeedIncrement;
michael@0 948
michael@0 949 done = true;
michael@0 950 break;
michael@0 951 }
michael@0 952
michael@0 953 char tmpChar = *newLine;
michael@0 954 *newLine = '\0'; // cursor is now null terminated
michael@0 955 char *colon = (char *) strchr(cursor, ':');
michael@0 956 if (colon) {
michael@0 957 *colon = '\0';
michael@0 958 nsAutoCString headerStr(cursor);
michael@0 959 headerStr.CompressWhitespace();
michael@0 960 *colon = ':';
michael@0 961
michael@0 962 nsAutoCString headerVal(colon + 1);
michael@0 963 headerVal.CompressWhitespace();
michael@0 964
michael@0 965 // examine header
michael@0 966 if (headerStr.LowerCaseEqualsLiteral("content-type")) {
michael@0 967 mContentType = headerVal;
michael@0 968 } else if (headerStr.LowerCaseEqualsLiteral("content-length")) {
michael@0 969 mContentLength = nsCRT::atoll(headerVal.get());
michael@0 970 } else if (headerStr.LowerCaseEqualsLiteral("content-disposition")) {
michael@0 971 mContentDisposition = headerVal;
michael@0 972 } else if (headerStr.LowerCaseEqualsLiteral("set-cookie")) {
michael@0 973 nsCOMPtr<nsIHttpChannelInternal> httpInternal =
michael@0 974 do_QueryInterface(aChannel);
michael@0 975 if (httpInternal) {
michael@0 976 httpInternal->SetCookie(headerVal.get());
michael@0 977 }
michael@0 978 } else if (headerStr.LowerCaseEqualsLiteral("content-range") ||
michael@0 979 headerStr.LowerCaseEqualsLiteral("range") ) {
michael@0 980 // something like: Content-range: bytes 7000-7999/8000
michael@0 981 char* tmpPtr;
michael@0 982
michael@0 983 tmpPtr = (char *) strchr(colon + 1, '/');
michael@0 984 if (tmpPtr)
michael@0 985 *tmpPtr = '\0';
michael@0 986
michael@0 987 // pass the bytes-unit and the SP
michael@0 988 char *range = (char *) strchr(colon + 2, ' ');
michael@0 989 if (!range)
michael@0 990 return NS_ERROR_FAILURE;
michael@0 991
michael@0 992 do {
michael@0 993 range++;
michael@0 994 } while (*range == ' ');
michael@0 995
michael@0 996 if (range[0] == '*'){
michael@0 997 mByteRangeStart = mByteRangeEnd = 0;
michael@0 998 }
michael@0 999 else {
michael@0 1000 tmpPtr = (char *) strchr(range, '-');
michael@0 1001 if (!tmpPtr)
michael@0 1002 return NS_ERROR_FAILURE;
michael@0 1003
michael@0 1004 tmpPtr[0] = '\0';
michael@0 1005
michael@0 1006 mByteRangeStart = nsCRT::atoll(range);
michael@0 1007 tmpPtr++;
michael@0 1008 mByteRangeEnd = nsCRT::atoll(tmpPtr);
michael@0 1009 }
michael@0 1010
michael@0 1011 mIsByteRangeRequest = true;
michael@0 1012 if (mContentLength == UINT64_MAX)
michael@0 1013 mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1);
michael@0 1014 }
michael@0 1015 }
michael@0 1016 *newLine = tmpChar;
michael@0 1017 newLine += lineFeedIncrement;
michael@0 1018 cursorLen -= (newLine - cursor);
michael@0 1019 cursor = newLine;
michael@0 1020 }
michael@0 1021
michael@0 1022 aPtr = cursor;
michael@0 1023 aLen = cursorLen;
michael@0 1024
michael@0 1025 *_retval = done;
michael@0 1026 return rv;
michael@0 1027 }
michael@0 1028
michael@0 1029 char *
michael@0 1030 nsMultiMixedConv::FindToken(char *aCursor, uint32_t aLen) {
michael@0 1031 // strnstr without looking for null termination
michael@0 1032 const char *token = mToken.get();
michael@0 1033 char *cur = aCursor;
michael@0 1034
michael@0 1035 if (!(token && aCursor && *token)) {
michael@0 1036 NS_WARNING("bad data");
michael@0 1037 return nullptr;
michael@0 1038 }
michael@0 1039
michael@0 1040 for (; aLen >= mTokenLen; aCursor++, aLen--) {
michael@0 1041 if (!memcmp(aCursor, token, mTokenLen) ) {
michael@0 1042 if ((aCursor - cur) >= 2) {
michael@0 1043 // back the cursor up over a double dash for backwards compat.
michael@0 1044 if ((*(aCursor-1) == '-') && (*(aCursor-2) == '-')) {
michael@0 1045 aCursor -= 2;
michael@0 1046 aLen += 2;
michael@0 1047
michael@0 1048 // we're playing w/ double dash tokens, adjust.
michael@0 1049 mToken.Assign(aCursor, mTokenLen + 2);
michael@0 1050 mTokenLen = mToken.Length();
michael@0 1051 }
michael@0 1052 }
michael@0 1053 return aCursor;
michael@0 1054 }
michael@0 1055 }
michael@0 1056
michael@0 1057 return nullptr;
michael@0 1058 }
michael@0 1059
michael@0 1060 nsresult
michael@0 1061 NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv)
michael@0 1062 {
michael@0 1063 NS_PRECONDITION(aMultiMixedConv != nullptr, "null ptr");
michael@0 1064 if (! aMultiMixedConv)
michael@0 1065 return NS_ERROR_NULL_POINTER;
michael@0 1066
michael@0 1067 *aMultiMixedConv = new nsMultiMixedConv();
michael@0 1068 if (! *aMultiMixedConv)
michael@0 1069 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1070
michael@0 1071 NS_ADDREF(*aMultiMixedConv);
michael@0 1072 return NS_OK;
michael@0 1073 }
michael@0 1074

mercurial