Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/streamconv/converters/nsMultiMixedConv.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "nsMultiMixedConv.h"
7
#include "plstr.h"
8
#include "nsIHttpChannel.h"
9
#include "nsNetCID.h"
10
#include "nsMimeTypes.h"
11
#include "nsIStringStream.h"
12
#include "nsCRT.h"
13
#include "nsIHttpChannelInternal.h"
14
#include "nsURLHelper.h"
15
#include "nsIStreamConverterService.h"
16
#include "nsICacheInfoChannel.h"
17
#include <algorithm>
18
#include "nsContentSecurityManager.h"
19
#include "nsHttp.h"
20
#include "nsNetUtil.h"
21
#include "nsIURI.h"
22
#include "nsHttpHeaderArray.h"
23
#include "mozilla/AutoRestore.h"
24
25
nsPartChannel::nsPartChannel(nsIChannel *aMultipartChannel, uint32_t aPartID,
26
                             nsIStreamListener* aListener) :
27
  mMultipartChannel(aMultipartChannel),
28
  mListener(aListener),
29
  mStatus(NS_OK),
30
  mLoadFlags(0),
31
  mContentDisposition(0),
32
  mContentLength(UINT64_MAX),
33
  mIsByteRangeRequest(false),
34
  mByteRangeStart(0),
35
  mByteRangeEnd(0),
36
  mPartID(aPartID),
37
  mIsLastPart(false)
38
0
{
39
0
    // Inherit the load flags from the original channel...
40
0
    mMultipartChannel->GetLoadFlags(&mLoadFlags);
41
0
42
0
    mMultipartChannel->GetLoadGroup(getter_AddRefs(mLoadGroup));
43
0
}
44
45
void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd)
46
0
{
47
0
    mIsByteRangeRequest = true;
48
0
49
0
    mByteRangeStart = aStart;
50
0
    mByteRangeEnd   = aEnd;
51
0
}
52
53
nsresult nsPartChannel::SendOnStartRequest(nsISupports* aContext)
54
0
{
55
0
    return mListener->OnStartRequest(this, aContext);
56
0
}
57
58
nsresult nsPartChannel::SendOnDataAvailable(nsISupports* aContext,
59
                                            nsIInputStream* aStream,
60
                                            uint64_t aOffset, uint32_t aLen)
61
0
{
62
0
    return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aLen);
63
0
}
64
65
nsresult nsPartChannel::SendOnStopRequest(nsISupports* aContext,
66
                                          nsresult aStatus)
67
0
{
68
0
    // Drop the listener
69
0
    nsCOMPtr<nsIStreamListener> listener;
70
0
    listener.swap(mListener);
71
0
    return listener->OnStopRequest(this, aContext, aStatus);
72
0
}
73
74
void nsPartChannel::SetContentDisposition(const nsACString& aContentDispositionHeader)
75
0
{
76
0
    mContentDispositionHeader = aContentDispositionHeader;
77
0
    nsCOMPtr<nsIURI> uri;
78
0
    GetURI(getter_AddRefs(uri));
79
0
    NS_GetFilenameFromDisposition(mContentDispositionFilename,
80
0
                                  mContentDispositionHeader, uri);
81
0
    mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
82
0
}
83
84
//
85
// nsISupports implementation...
86
//
87
88
NS_IMPL_ADDREF(nsPartChannel)
89
NS_IMPL_RELEASE(nsPartChannel)
90
91
0
NS_INTERFACE_MAP_BEGIN(nsPartChannel)
92
0
    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
93
0
    NS_INTERFACE_MAP_ENTRY(nsIRequest)
94
0
    NS_INTERFACE_MAP_ENTRY(nsIChannel)
95
0
    NS_INTERFACE_MAP_ENTRY(nsIByteRangeRequest)
96
0
    NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannel)
97
0
NS_INTERFACE_MAP_END
98
99
//
100
// nsIRequest implementation...
101
//
102
103
NS_IMETHODIMP
104
nsPartChannel::GetName(nsACString &aResult)
105
0
{
106
0
    return mMultipartChannel->GetName(aResult);
107
0
}
108
109
NS_IMETHODIMP
110
nsPartChannel::IsPending(bool *aResult)
111
0
{
112
0
    // For now, consider the active lifetime of each part the same as
113
0
    // the underlying multipart channel...  This is not exactly right,
114
0
    // but it is good enough :-)
115
0
    return mMultipartChannel->IsPending(aResult);
116
0
}
117
118
NS_IMETHODIMP
119
nsPartChannel::GetStatus(nsresult *aResult)
120
0
{
121
0
    nsresult rv = NS_OK;
122
0
123
0
    if (NS_FAILED(mStatus)) {
124
0
        *aResult = mStatus;
125
0
    } else {
126
0
        rv = mMultipartChannel->GetStatus(aResult);
127
0
    }
128
0
129
0
    return rv;
130
0
}
131
132
NS_IMETHODIMP
133
nsPartChannel::Cancel(nsresult aStatus)
134
0
{
135
0
    // Cancelling an individual part must not cancel the underlying
136
0
    // multipart channel...
137
0
    // XXX but we should stop sending data for _this_ part channel!
138
0
    mStatus = aStatus;
139
0
    return NS_OK;
140
0
}
141
142
NS_IMETHODIMP
143
nsPartChannel::Suspend(void)
144
0
{
145
0
    // Suspending an individual part must not suspend the underlying
146
0
    // multipart channel...
147
0
    // XXX why not?
148
0
    return NS_OK;
149
0
}
150
151
NS_IMETHODIMP
152
nsPartChannel::Resume(void)
153
0
{
154
0
    // Resuming an individual part must not resume the underlying
155
0
    // multipart channel...
156
0
    // XXX why not?
157
0
    return NS_OK;
158
0
}
159
160
//
161
// nsIChannel implementation
162
//
163
164
NS_IMETHODIMP
165
nsPartChannel::GetOriginalURI(nsIURI * *aURI)
166
0
{
167
0
    return mMultipartChannel->GetOriginalURI(aURI);
168
0
}
169
170
NS_IMETHODIMP
171
nsPartChannel::SetOriginalURI(nsIURI *aURI)
172
0
{
173
0
    return mMultipartChannel->SetOriginalURI(aURI);
174
0
}
175
176
NS_IMETHODIMP
177
nsPartChannel::GetURI(nsIURI * *aURI)
178
0
{
179
0
    return mMultipartChannel->GetURI(aURI);
180
0
}
181
182
NS_IMETHODIMP
183
nsPartChannel::Open(nsIInputStream **result)
184
0
{
185
0
    // This channel cannot be opened!
186
0
    return NS_ERROR_FAILURE;
187
0
}
188
189
NS_IMETHODIMP
190
nsPartChannel::Open2(nsIInputStream** aStream)
191
0
{
192
0
    nsCOMPtr<nsIStreamListener> listener;
193
0
    nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
194
0
    NS_ENSURE_SUCCESS(rv, rv);
195
0
    return Open(aStream);
196
0
}
197
198
NS_IMETHODIMP
199
nsPartChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
200
0
{
201
0
    // This channel cannot be opened!
202
0
    return NS_ERROR_FAILURE;
203
0
}
204
205
NS_IMETHODIMP
206
nsPartChannel::AsyncOpen2(nsIStreamListener *aListener)
207
0
{
208
0
  nsCOMPtr<nsIStreamListener> listener = aListener;
209
0
  nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
210
0
  NS_ENSURE_SUCCESS(rv, rv);
211
0
  return AsyncOpen(listener, nullptr);
212
0
}
213
214
NS_IMETHODIMP
215
nsPartChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
216
0
{
217
0
    *aLoadFlags = mLoadFlags;
218
0
    return NS_OK;
219
0
}
220
221
NS_IMETHODIMP
222
nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
223
0
{
224
0
    mLoadFlags = aLoadFlags;
225
0
    return NS_OK;
226
0
}
227
228
NS_IMETHODIMP
229
nsPartChannel::GetIsDocument(bool *aIsDocument)
230
0
{
231
0
    return NS_GetIsDocumentChannel(this, aIsDocument);
232
0
}
233
234
NS_IMETHODIMP
235
nsPartChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
236
0
{
237
0
    *aLoadGroup = mLoadGroup;
238
0
    NS_IF_ADDREF(*aLoadGroup);
239
0
240
0
    return NS_OK;
241
0
}
242
243
NS_IMETHODIMP
244
nsPartChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
245
0
{
246
0
    mLoadGroup = aLoadGroup;
247
0
248
0
    return NS_OK;
249
0
}
250
251
NS_IMETHODIMP
252
nsPartChannel::GetOwner(nsISupports* *aOwner)
253
0
{
254
0
    return mMultipartChannel->GetOwner(aOwner);
255
0
}
256
257
NS_IMETHODIMP
258
nsPartChannel::SetOwner(nsISupports* aOwner)
259
0
{
260
0
    return mMultipartChannel->SetOwner(aOwner);
261
0
}
262
263
NS_IMETHODIMP
264
nsPartChannel::GetLoadInfo(nsILoadInfo* *aLoadInfo)
265
0
{
266
0
    return mMultipartChannel->GetLoadInfo(aLoadInfo);
267
0
}
268
269
NS_IMETHODIMP
270
nsPartChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
271
0
{
272
0
    return mMultipartChannel->SetLoadInfo(aLoadInfo);
273
0
}
274
275
NS_IMETHODIMP
276
nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks)
277
0
{
278
0
    return mMultipartChannel->GetNotificationCallbacks(aCallbacks);
279
0
}
280
281
NS_IMETHODIMP
282
nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
283
0
{
284
0
    return mMultipartChannel->SetNotificationCallbacks(aCallbacks);
285
0
}
286
287
NS_IMETHODIMP
288
nsPartChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
289
0
{
290
0
    return mMultipartChannel->GetSecurityInfo(aSecurityInfo);
291
0
}
292
293
NS_IMETHODIMP
294
nsPartChannel::GetContentType(nsACString &aContentType)
295
0
{
296
0
    aContentType = mContentType;
297
0
    return NS_OK;
298
0
}
299
300
NS_IMETHODIMP
301
nsPartChannel::SetContentType(const nsACString &aContentType)
302
0
{
303
0
    bool dummy;
304
0
    net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
305
0
    return NS_OK;
306
0
}
307
308
NS_IMETHODIMP
309
nsPartChannel::GetContentCharset(nsACString &aContentCharset)
310
0
{
311
0
    aContentCharset = mContentCharset;
312
0
    return NS_OK;
313
0
}
314
315
NS_IMETHODIMP
316
nsPartChannel::SetContentCharset(const nsACString &aContentCharset)
317
0
{
318
0
    mContentCharset = aContentCharset;
319
0
    return NS_OK;
320
0
}
321
322
NS_IMETHODIMP
323
nsPartChannel::GetContentLength(int64_t *aContentLength)
324
0
{
325
0
    *aContentLength = mContentLength;
326
0
    return NS_OK;
327
0
}
328
329
NS_IMETHODIMP
330
nsPartChannel::SetContentLength(int64_t aContentLength)
331
0
{
332
0
    mContentLength = aContentLength;
333
0
    return NS_OK;
334
0
}
335
336
NS_IMETHODIMP
337
nsPartChannel::GetContentDisposition(uint32_t *aContentDisposition)
338
0
{
339
0
    if (mContentDispositionHeader.IsEmpty())
340
0
        return NS_ERROR_NOT_AVAILABLE;
341
0
342
0
    *aContentDisposition = mContentDisposition;
343
0
    return NS_OK;
344
0
}
345
346
NS_IMETHODIMP
347
nsPartChannel::SetContentDisposition(uint32_t aContentDisposition)
348
0
{
349
0
    return NS_ERROR_NOT_AVAILABLE;
350
0
}
351
352
NS_IMETHODIMP
353
nsPartChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
354
0
{
355
0
    if (mContentDispositionFilename.IsEmpty())
356
0
        return NS_ERROR_NOT_AVAILABLE;
357
0
358
0
    aContentDispositionFilename = mContentDispositionFilename;
359
0
    return NS_OK;
360
0
}
361
362
NS_IMETHODIMP
363
nsPartChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
364
0
{
365
0
    return NS_ERROR_NOT_AVAILABLE;
366
0
}
367
368
369
NS_IMETHODIMP
370
nsPartChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
371
0
{
372
0
    if (mContentDispositionHeader.IsEmpty())
373
0
        return NS_ERROR_NOT_AVAILABLE;
374
0
375
0
    aContentDispositionHeader = mContentDispositionHeader;
376
0
    return NS_OK;
377
0
}
378
379
NS_IMETHODIMP
380
nsPartChannel::GetPartID(uint32_t *aPartID)
381
0
{
382
0
    *aPartID = mPartID;
383
0
    return NS_OK;
384
0
}
385
386
NS_IMETHODIMP
387
nsPartChannel::GetIsLastPart(bool *aIsLastPart)
388
0
{
389
0
    *aIsLastPart = mIsLastPart;
390
0
    return NS_OK;
391
0
}
392
393
//
394
// nsIByteRangeRequest implementation...
395
//
396
397
NS_IMETHODIMP
398
nsPartChannel::GetIsByteRangeRequest(bool *aIsByteRangeRequest)
399
0
{
400
0
    *aIsByteRangeRequest = mIsByteRangeRequest;
401
0
402
0
    return NS_OK;
403
0
}
404
405
406
NS_IMETHODIMP
407
nsPartChannel::GetStartRange(int64_t *aStartRange)
408
0
{
409
0
    *aStartRange = mByteRangeStart;
410
0
411
0
    return NS_OK;
412
0
}
413
414
NS_IMETHODIMP
415
nsPartChannel::GetEndRange(int64_t *aEndRange)
416
0
{
417
0
    *aEndRange = mByteRangeEnd;
418
0
    return NS_OK;
419
0
}
420
421
NS_IMETHODIMP
422
nsPartChannel::GetBaseChannel(nsIChannel ** aReturn)
423
0
{
424
0
    NS_ENSURE_ARG_POINTER(aReturn);
425
0
426
0
    *aReturn = mMultipartChannel;
427
0
    NS_IF_ADDREF(*aReturn);
428
0
    return NS_OK;
429
0
}
430
431
// nsISupports implementation
432
NS_IMPL_ISUPPORTS(nsMultiMixedConv,
433
                  nsIStreamConverter,
434
                  nsIStreamListener,
435
                  nsIRequestObserver)
436
437
438
// nsIStreamConverter implementation
439
440
// No syncronous conversion at this time.
441
NS_IMETHODIMP
442
nsMultiMixedConv::Convert(nsIInputStream *aFromStream,
443
                          const char *aFromType,
444
                          const char *aToType,
445
0
                          nsISupports *aCtxt, nsIInputStream **_retval) {
446
0
    return NS_ERROR_NOT_IMPLEMENTED;
447
0
}
448
449
// Stream converter service calls this to initialize the actual stream converter (us).
450
NS_IMETHODIMP
451
nsMultiMixedConv::AsyncConvertData(const char *aFromType, const char *aToType,
452
0
                                   nsIStreamListener *aListener, nsISupports *aCtxt) {
453
0
    NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into multi mixed converter");
454
0
455
0
    // hook up our final listener. this guy gets the various On*() calls we want to throw
456
0
    // at him.
457
0
    //
458
0
    // WARNING: this listener must be able to handle multiple OnStartRequest, OnDataAvail()
459
0
    //  and OnStopRequest() call combinations. We call of series of these for each sub-part
460
0
    //  in the raw stream.
461
0
    mFinalListener = aListener;
462
0
463
0
    return NS_OK;
464
0
}
465
466
// nsIRequestObserver implementation
467
NS_IMETHODIMP
468
nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
469
0
{
470
0
    // we're assuming the content-type is available at this stage
471
0
    NS_ASSERTION(mBoundary.IsEmpty(), "a second on start???");
472
0
473
0
    nsresult rv;
474
0
475
0
    mContext = ctxt;
476
0
    mTotalSent   = 0;
477
0
    mChannel = do_QueryInterface(request, &rv);
478
0
    if (NS_FAILED(rv)) return rv;
479
0
480
0
    nsAutoCString contentType;
481
0
482
0
    // ask the HTTP channel for the content-type and extract the boundary from it.
483
0
    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
484
0
    if (NS_SUCCEEDED(rv)) {
485
0
        rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-type"), contentType);
486
0
        if (NS_FAILED(rv)) {
487
0
            return rv;
488
0
        }
489
0
        nsCString csp;
490
0
        rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-security-policy"),
491
0
                                            csp);
492
0
        if (NS_SUCCEEDED(rv)) {
493
0
          mRootContentSecurityPolicy = csp;
494
0
        }
495
0
    } else {
496
0
        // try asking the channel directly
497
0
        rv = mChannel->GetContentType(contentType);
498
0
        if (NS_FAILED(rv)) {
499
0
            return NS_ERROR_FAILURE;
500
0
        }
501
0
    }
502
0
503
0
    Tokenizer p(contentType);
504
0
    p.SkipUntil(Token::Char(';'));
505
0
    if (!p.CheckChar(';')) {
506
0
        return NS_ERROR_CORRUPTED_CONTENT;
507
0
    }
508
0
    p.SkipWhites();
509
0
    if (!p.CheckWord("boundary")) {
510
0
        return NS_ERROR_CORRUPTED_CONTENT;
511
0
    }
512
0
    p.SkipWhites();
513
0
    if (!p.CheckChar('=')) {
514
0
        return NS_ERROR_CORRUPTED_CONTENT;
515
0
    }
516
0
    p.SkipWhites();
517
0
    Unused << p.ReadUntil(Token::Char(';'), mBoundary);
518
0
    mBoundary.Trim(" \""); // ignoring potential quoted string formatting violations
519
0
    if (mBoundary.IsEmpty()) {
520
0
        return NS_ERROR_CORRUPTED_CONTENT;
521
0
    }
522
0
523
0
    mHeaderTokens[HEADER_CONTENT_TYPE] =
524
0
      mTokenizer.AddCustomToken("content-type", mTokenizer.CASE_INSENSITIVE, false);
525
0
    mHeaderTokens[HEADER_CONTENT_LENGTH] =
526
0
      mTokenizer.AddCustomToken("content-length", mTokenizer.CASE_INSENSITIVE, false);
527
0
    mHeaderTokens[HEADER_CONTENT_DISPOSITION] =
528
0
      mTokenizer.AddCustomToken("content-disposition", mTokenizer.CASE_INSENSITIVE, false);
529
0
    mHeaderTokens[HEADER_SET_COOKIE] =
530
0
      mTokenizer.AddCustomToken("set-cookie", mTokenizer.CASE_INSENSITIVE, false);
531
0
    mHeaderTokens[HEADER_CONTENT_RANGE] =
532
0
      mTokenizer.AddCustomToken("content-range", mTokenizer.CASE_INSENSITIVE, false);
533
0
    mHeaderTokens[HEADER_RANGE] =
534
0
      mTokenizer.AddCustomToken("range", mTokenizer.CASE_INSENSITIVE, false);
535
0
    mHeaderTokens[HEADER_CONTENT_SECURITY_POLICY] =
536
0
      mTokenizer.AddCustomToken("content-security-policy",
537
0
                                mTokenizer.CASE_INSENSITIVE,
538
0
                                false);
539
0
540
0
    mLFToken = mTokenizer.AddCustomToken("\n", mTokenizer.CASE_SENSITIVE, false);
541
0
    mCRLFToken = mTokenizer.AddCustomToken("\r\n", mTokenizer.CASE_SENSITIVE, false);
542
0
543
0
    SwitchToControlParsing();
544
0
545
0
    mBoundaryToken =
546
0
      mTokenizer.AddCustomToken(mBoundary, mTokenizer.CASE_SENSITIVE);
547
0
    mBoundaryTokenWithDashes =
548
0
      mTokenizer.AddCustomToken(NS_LITERAL_CSTRING("--") + mBoundary, mTokenizer.CASE_SENSITIVE);
549
0
550
0
    return NS_OK;
551
0
}
552
553
// nsIStreamListener implementation
554
NS_IMETHODIMP
555
nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context,
556
                                  nsIInputStream *inStr, uint64_t sourceOffset,
557
                                  uint32_t count)
558
0
{
559
0
    // Failing these assertions may indicate that some of the target listeners of this converter
560
0
    // is looping the thead queue, which is harmful to how we collect the raw (content) data.
561
0
    MOZ_DIAGNOSTIC_ASSERT(!mInOnDataAvailable, "nsMultiMixedConv::OnDataAvailable reentered!");
562
0
    MOZ_DIAGNOSTIC_ASSERT(!mRawData, "There are unsent data from the previous tokenizer feed!");
563
0
564
0
    if (mInOnDataAvailable) {
565
0
        // The multipart logic is incapable of being reentered.
566
0
        return NS_ERROR_UNEXPECTED;
567
0
    }
568
0
569
0
    mozilla::AutoRestore<bool> restore(mInOnDataAvailable);
570
0
    mInOnDataAvailable = true;
571
0
572
0
    nsresult rv_feed = mTokenizer.FeedInput(inStr, count);
573
0
    // We must do this every time.  Regardless if something has failed during the
574
0
    // parsing process.  Otherwise the raw data reference would not be thrown away.
575
0
    nsresult rv_send = SendData();
576
0
577
0
    return NS_FAILED(rv_send) ? rv_send : rv_feed;
578
0
}
579
580
NS_IMETHODIMP
581
nsMultiMixedConv::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
582
                                nsresult aStatus)
583
0
{
584
0
    nsresult rv;
585
0
586
0
    if (mBoundary.IsEmpty()) { // no token, no love.
587
0
        return NS_ERROR_FAILURE;
588
0
    }
589
0
590
0
    if (mPartChannel) {
591
0
        mPartChannel->SetIsLastPart();
592
0
593
0
        MOZ_DIAGNOSTIC_ASSERT(!mRawData, "There are unsent data from the previous tokenizer feed!");
594
0
595
0
        rv = mTokenizer.FinishInput();
596
0
        if (NS_SUCCEEDED(aStatus)) {
597
0
            aStatus = rv;
598
0
        }
599
0
        rv = SendData();
600
0
        if (NS_SUCCEEDED(aStatus)) {
601
0
            aStatus = rv;
602
0
        }
603
0
604
0
        (void) SendStop(aStatus);
605
0
    } else if (NS_FAILED(aStatus) && !mRequestListenerNotified) {
606
0
        // underlying data production problem. we should not be in
607
0
        // the middle of sending data. if we were, mPartChannel,
608
0
        // above, would have been non-null.
609
0
610
0
        (void) mFinalListener->OnStartRequest(request, ctxt);
611
0
        (void) mFinalListener->OnStopRequest(request, ctxt, aStatus);
612
0
    }
613
0
614
0
    return NS_OK;
615
0
}
616
617
nsresult
618
nsMultiMixedConv::ConsumeToken(Token const & token)
619
0
{
620
0
  nsresult rv;
621
0
622
0
  switch (mParserState) {
623
0
    case PREAMBLE:
624
0
      if (token.Equals(mBoundaryTokenWithDashes)) {
625
0
        // The server first used boundary '--boundary'.  Hence, we no longer
626
0
        // accept plain 'boundary' token as a delimiter.
627
0
        mTokenizer.RemoveCustomToken(mBoundaryToken);
628
0
        mParserState = BOUNDARY_CRLF;
629
0
        break;
630
0
      }
631
0
      if (token.Equals(mBoundaryToken)) {
632
0
        // And here the opposite from the just above block...
633
0
        mTokenizer.RemoveCustomToken(mBoundaryTokenWithDashes);
634
0
        mParserState = BOUNDARY_CRLF;
635
0
        break;
636
0
      }
637
0
638
0
      // This is a preamble, just ignore it and wait for the boundary.
639
0
      break;
640
0
641
0
    case BOUNDARY_CRLF:
642
0
      if (token.Equals(Token::NewLine())) {
643
0
        mParserState = HEADER_NAME;
644
0
        mResponseHeader = HEADER_UNKNOWN;
645
0
        HeadersToDefault();
646
0
        SetHeaderTokensEnabled(true);
647
0
        break;
648
0
      }
649
0
      return NS_ERROR_CORRUPTED_CONTENT;
650
0
651
0
    case HEADER_NAME:
652
0
      SetHeaderTokensEnabled(false);
653
0
      if (token.Equals(Token::NewLine())) {
654
0
        mParserState = BODY_INIT;
655
0
        SwitchToBodyParsing();
656
0
        break;
657
0
      }
658
0
      for (uint32_t h = HEADER_CONTENT_TYPE; h < HEADER_UNKNOWN; ++h) {
659
0
        if (token.Equals(mHeaderTokens[h])) {
660
0
          mResponseHeader = static_cast<EHeader>(h);
661
0
          break;
662
0
        }
663
0
      }
664
0
      mParserState = HEADER_SEP;
665
0
      break;
666
0
667
0
    case HEADER_SEP:
668
0
      if (token.Equals(Token::Char(':'))) {
669
0
        mParserState = HEADER_VALUE;
670
0
        mResponseHeaderValue.Truncate();
671
0
        break;
672
0
      }
673
0
      if (mResponseHeader == HEADER_UNKNOWN) {
674
0
        // If the header is not of any we understand, just pass everything till ':'
675
0
        break;
676
0
      }
677
0
      if (token.Equals(Token::Whitespace())) {
678
0
        // Accept only header-name traling whitespaces after known headers
679
0
        break;
680
0
      }
681
0
      return NS_ERROR_CORRUPTED_CONTENT;
682
0
683
0
    case HEADER_VALUE:
684
0
      if (token.Equals(Token::Whitespace()) && mResponseHeaderValue.IsEmpty()) {
685
0
        // Eat leading whitespaces
686
0
        break;
687
0
      }
688
0
      if (token.Equals(Token::NewLine())) {
689
0
        nsresult rv = ProcessHeader();
690
0
        if (NS_FAILED(rv)) {
691
0
          return rv;
692
0
        }
693
0
        mParserState = HEADER_NAME;
694
0
        mResponseHeader = HEADER_UNKNOWN;
695
0
        SetHeaderTokensEnabled(true);
696
0
      } else {
697
0
        mResponseHeaderValue.Append(token.Fragment());
698
0
      }
699
0
      break;
700
0
701
0
    case BODY_INIT:
702
0
      rv = SendStart();
703
0
      if (NS_FAILED(rv)) {
704
0
        return rv;
705
0
      }
706
0
      mParserState = BODY;
707
0
      MOZ_FALLTHROUGH;
708
0
709
0
    case BODY: {
710
0
      if (!token.Equals(mLFToken) && !token.Equals(mCRLFToken)) {
711
0
        if (token.Equals(mBoundaryTokenWithDashes) ||
712
0
            token.Equals(mBoundaryToken)) {
713
0
          // Allow CRLF to NOT be part of the boundary as well
714
0
          SwitchToControlParsing();
715
0
          mParserState = TRAIL_DASH1;
716
0
          break;
717
0
        }
718
0
        AccumulateData(token);
719
0
        break;
720
0
      }
721
0
722
0
      // After CRLF we must explicitly check for boundary.  If found,
723
0
      // that CRLF is part of the boundary and must not be send to the
724
0
      // data listener.
725
0
      Token token2;
726
0
      if (!mTokenizer.Next(token2)) {
727
0
        // Note: this will give us the CRLF token again when more data
728
0
        // or OnStopRequest arrive.  I.e. we will enter BODY case in
729
0
        // the very same state as we are now and start this block over.
730
0
        mTokenizer.NeedMoreInput();
731
0
        break;
732
0
      }
733
0
      if (token2.Equals(mBoundaryTokenWithDashes) ||
734
0
          token2.Equals(mBoundaryToken)) {
735
0
        SwitchToControlParsing();
736
0
        mParserState = TRAIL_DASH1;
737
0
        break;
738
0
      }
739
0
740
0
      AccumulateData(token);
741
0
      AccumulateData(token2);
742
0
      break;
743
0
    }
744
0
745
0
    case TRAIL_DASH1:
746
0
      if (token.Equals(Token::NewLine())) {
747
0
        rv = SendStop(NS_OK);
748
0
        if (NS_FAILED(rv)) {
749
0
          return rv;
750
0
        }
751
0
        mParserState = BOUNDARY_CRLF;
752
0
        mTokenizer.Rollback();
753
0
        break;
754
0
      }
755
0
      if (token.Equals(Token::Char('-'))) {
756
0
        mParserState = TRAIL_DASH2;
757
0
        break;
758
0
      }
759
0
      return NS_ERROR_CORRUPTED_CONTENT;
760
0
761
0
    case TRAIL_DASH2:
762
0
      if (token.Equals(Token::Char('-'))) {
763
0
        mPartChannel->SetIsLastPart();
764
0
        // SendStop calls SendData first.
765
0
        rv = SendStop(NS_OK);
766
0
        if (NS_FAILED(rv)) {
767
0
          return rv;
768
0
        }
769
0
        mParserState = EPILOGUE;
770
0
        break;
771
0
      }
772
0
      return NS_ERROR_CORRUPTED_CONTENT;
773
0
774
0
    case EPILOGUE:
775
0
      // Just ignore
776
0
      break;
777
0
778
0
    default:
779
0
      MOZ_ASSERT(false, "Missing parser state handling branch");
780
0
      break;
781
0
  } // switch
782
0
783
0
  return NS_OK;
784
0
}
785
786
void
787
nsMultiMixedConv::SetHeaderTokensEnabled(bool aEnable)
788
0
{
789
0
    for (uint32_t h = HEADER_FIRST; h < HEADER_UNKNOWN; ++h) {
790
0
        mTokenizer.EnableCustomToken(mHeaderTokens[h], aEnable);
791
0
    }
792
0
}
793
794
void
795
nsMultiMixedConv::SwitchToBodyParsing()
796
0
{
797
0
    mTokenizer.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
798
0
    mTokenizer.EnableCustomToken(mLFToken, true);
799
0
    mTokenizer.EnableCustomToken(mCRLFToken, true);
800
0
    mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, true);
801
0
    mTokenizer.EnableCustomToken(mBoundaryToken, true);
802
0
}
803
804
void
805
nsMultiMixedConv::SwitchToControlParsing()
806
0
{
807
0
    mTokenizer.SetTokenizingMode(Tokenizer::Mode::FULL);
808
0
    mTokenizer.EnableCustomToken(mLFToken, false);
809
0
    mTokenizer.EnableCustomToken(mCRLFToken, false);
810
0
    mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, false);
811
0
    mTokenizer.EnableCustomToken(mBoundaryToken, false);
812
0
}
813
814
// nsMultiMixedConv methods
815
nsMultiMixedConv::nsMultiMixedConv() :
816
    mCurrentPartID(0),
817
    mInOnDataAvailable(false),
818
    mResponseHeader(HEADER_UNKNOWN),
819
    // XXX: This is a hack to bypass the raw pointer to refcounted object in
820
    // lambda analysis. It should be removed and replaced when the
821
    // IncrementalTokenizer API is improved to avoid the need for such
822
    // workarounds.
823
    //
824
    // This is safe because `mTokenizer` will not outlive `this`, meaning that
825
    // this std::bind object will be destroyed before `this` dies.
826
    mTokenizer(std::bind(&nsMultiMixedConv::ConsumeToken, this, std::placeholders::_1))
827
0
{
828
0
    mContentLength      = UINT64_MAX;
829
0
    mByteRangeStart     = 0;
830
0
    mByteRangeEnd       = 0;
831
0
    mTotalSent          = 0;
832
0
    mIsByteRangeRequest = false;
833
0
    mParserState        = INIT;
834
0
    mRawData            = nullptr;
835
0
    mRequestListenerNotified = false;
836
0
}
837
838
nsresult
839
nsMultiMixedConv::SendStart()
840
0
{
841
0
    nsresult rv = NS_OK;
842
0
843
0
    nsCOMPtr<nsIStreamListener> partListener(mFinalListener);
844
0
    if (mContentType.IsEmpty()) {
845
0
        mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
846
0
        nsCOMPtr<nsIStreamConverterService> serv =
847
0
            do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
848
0
        if (NS_SUCCEEDED(rv)) {
849
0
            nsCOMPtr<nsIStreamListener> converter;
850
0
            rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
851
0
                                        "*/*",
852
0
                                        mFinalListener,
853
0
                                        mContext,
854
0
                                        getter_AddRefs(converter));
855
0
            if (NS_SUCCEEDED(rv)) {
856
0
                partListener = converter;
857
0
            }
858
0
        }
859
0
    }
860
0
861
0
    // if we already have an mPartChannel, that means we never sent a Stop()
862
0
    // before starting up another "part." that would be bad.
863
0
    MOZ_ASSERT(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel");
864
0
865
0
    nsPartChannel *newChannel;
866
0
    newChannel = new nsPartChannel(mChannel, mCurrentPartID++, partListener);
867
0
    if (!newChannel)
868
0
        return NS_ERROR_OUT_OF_MEMORY;
869
0
870
0
    if (mIsByteRangeRequest) {
871
0
        newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd);
872
0
    }
873
0
874
0
    mTotalSent = 0;
875
0
876
0
    // Set up the new part channel...
877
0
    mPartChannel = newChannel;
878
0
879
0
    rv = mPartChannel->SetContentType(mContentType);
880
0
    if (NS_FAILED(rv)) return rv;
881
0
882
0
    rv = mPartChannel->SetContentLength(mContentLength);
883
0
    if (NS_FAILED(rv)) return rv;
884
0
885
0
    mPartChannel->SetContentDisposition(mContentDisposition);
886
0
887
0
    // Each part of a multipart/replace response can be used
888
0
    // for the top level document.  We must inform upper layers
889
0
    // about this by setting the LOAD_REPLACE flag so that certain
890
0
    // state assertions are evaluated as positive.
891
0
    nsLoadFlags loadFlags = 0;
892
0
    mPartChannel->GetLoadFlags(&loadFlags);
893
0
    loadFlags |= nsIChannel::LOAD_REPLACE;
894
0
    mPartChannel->SetLoadFlags(loadFlags);
895
0
896
0
    nsCOMPtr<nsILoadGroup> loadGroup;
897
0
    (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
898
0
899
0
    // Add the new channel to the load group (if any)
900
0
    if (loadGroup) {
901
0
        rv = loadGroup->AddRequest(mPartChannel, nullptr);
902
0
        if (NS_FAILED(rv)) return rv;
903
0
    }
904
0
905
0
    // This prevents artificial call to OnStart/StopRequest when the root
906
0
    // channel fails.  Since now it's ensured to keep with the nsIStreamListener
907
0
    // contract every time.
908
0
    mRequestListenerNotified = true;
909
0
910
0
    // Let's start off the load. NOTE: we don't forward on the channel passed
911
0
    // into our OnDataAvailable() as it's the root channel for the raw stream.
912
0
    return mPartChannel->SendOnStartRequest(mContext);
913
0
}
914
915
nsresult
916
nsMultiMixedConv::SendStop(nsresult aStatus)
917
0
{
918
0
    // Make sure we send out all accumulcated data prior call to OnStopRequest.
919
0
    // If there is no data, this is a no-op.
920
0
    nsresult rv = SendData();
921
0
    if (NS_SUCCEEDED(aStatus)) {
922
0
        aStatus = rv;
923
0
    }
924
0
    if (mPartChannel) {
925
0
        rv = mPartChannel->SendOnStopRequest(mContext, aStatus);
926
0
        // don't check for failure here, we need to remove the channel from
927
0
        // the loadgroup.
928
0
929
0
        // Remove the channel from its load group (if any)
930
0
        nsCOMPtr<nsILoadGroup> loadGroup;
931
0
        (void) mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
932
0
        if (loadGroup)
933
0
            (void) loadGroup->RemoveRequest(mPartChannel, mContext, aStatus);
934
0
    }
935
0
936
0
    mPartChannel = nullptr;
937
0
    return rv;
938
0
}
939
940
void
941
nsMultiMixedConv::AccumulateData(Token const & aToken)
942
0
{
943
0
    if (!mRawData) {
944
0
        // This is the first read of raw data during this FeedInput loop
945
0
        // of the incremental tokenizer.  All 'raw' tokens are coming from
946
0
        // the same linear buffer, hence begining of this loop raw data
947
0
        // is begining of the first raw token.  Length of this loop raw
948
0
        // data is just sum of all 'raw' tokens we collect during this loop.
949
0
        //
950
0
        // It's ensured we flush (send to to the listener via OnDataAvailable)
951
0
        // and nullify the collected raw data right after FeedInput call.
952
0
        // Hence, the reference can't outlive the actual buffer.
953
0
        mRawData = aToken.Fragment().BeginReading();
954
0
        mRawDataLength = 0;
955
0
    }
956
0
957
0
    mRawDataLength += aToken.Fragment().Length();
958
0
}
959
960
nsresult
961
nsMultiMixedConv::SendData()
962
0
{
963
0
    nsresult rv;
964
0
965
0
    if (!mRawData) {
966
0
        return NS_OK;
967
0
    }
968
0
969
0
    nsACString::const_char_iterator rawData = mRawData;
970
0
    mRawData = nullptr;
971
0
972
0
    if (!mPartChannel) {
973
0
        return NS_ERROR_FAILURE; // something went wrong w/ processing
974
0
    }
975
0
976
0
    if (mContentLength != UINT64_MAX) {
977
0
        // make sure that we don't send more than the mContentLength
978
0
        // XXX why? perhaps the Content-Length header was actually wrong!!
979
0
        if ((uint64_t(mRawDataLength) + mTotalSent) > mContentLength)
980
0
            mRawDataLength = static_cast<uint32_t>(mContentLength - mTotalSent);
981
0
982
0
        if (mRawDataLength == 0)
983
0
            return NS_OK;
984
0
    }
985
0
986
0
    uint64_t offset = mTotalSent;
987
0
    mTotalSent += mRawDataLength;
988
0
989
0
    nsCOMPtr<nsIStringInputStream> ss(
990
0
            do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
991
0
    if (NS_FAILED(rv))
992
0
        return rv;
993
0
994
0
    rv = ss->ShareData(rawData, mRawDataLength);
995
0
    mRawData = nullptr;
996
0
    if (NS_FAILED(rv))
997
0
        return rv;
998
0
999
0
    nsCOMPtr<nsIInputStream> inStream(do_QueryInterface(ss, &rv));
1000
0
    if (NS_FAILED(rv)) return rv;
1001
0
1002
0
    return mPartChannel->SendOnDataAvailable(mContext, inStream, offset, mRawDataLength);
1003
0
}
1004
1005
void
1006
nsMultiMixedConv::HeadersToDefault()
1007
0
{
1008
0
    mContentLength = UINT64_MAX;
1009
0
    mContentType.Truncate();
1010
0
    mContentDisposition.Truncate();
1011
0
    mContentSecurityPolicy.Truncate();
1012
0
    mIsByteRangeRequest = false;
1013
0
}
1014
1015
nsresult
1016
nsMultiMixedConv::ProcessHeader()
1017
0
{
1018
0
    mozilla::Tokenizer p(mResponseHeaderValue);
1019
0
1020
0
    switch (mResponseHeader) {
1021
0
    case HEADER_CONTENT_TYPE:
1022
0
      mContentType = mResponseHeaderValue;
1023
0
      mContentType.CompressWhitespace();
1024
0
      break;
1025
0
    case HEADER_CONTENT_LENGTH:
1026
0
      p.SkipWhites();
1027
0
      if (!p.ReadInteger(&mContentLength)) {
1028
0
        return NS_ERROR_CORRUPTED_CONTENT;
1029
0
      }
1030
0
      break;
1031
0
    case HEADER_CONTENT_DISPOSITION:
1032
0
      mContentDisposition = mResponseHeaderValue;
1033
0
      mContentDisposition.CompressWhitespace();
1034
0
      break;
1035
0
    case HEADER_SET_COOKIE: {
1036
0
      nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(mChannel);
1037
0
      mResponseHeaderValue.CompressWhitespace();
1038
0
      if (httpInternal) {
1039
0
        DebugOnly<nsresult> rv = httpInternal->SetCookie(mResponseHeaderValue.get());
1040
0
        MOZ_ASSERT(NS_SUCCEEDED(rv));
1041
0
      }
1042
0
      break;
1043
0
    }
1044
0
    case HEADER_RANGE:
1045
0
    case HEADER_CONTENT_RANGE: {
1046
0
      if (!p.CheckWord("bytes") ||
1047
0
          !p.CheckWhite()) {
1048
0
        return NS_ERROR_CORRUPTED_CONTENT;
1049
0
      }
1050
0
      p.SkipWhites();
1051
0
      if (p.CheckChar('*')) {
1052
0
        mByteRangeStart = mByteRangeEnd = 0;
1053
0
      } else if (!p.ReadInteger(&mByteRangeStart) ||
1054
0
                 !p.CheckChar('-') ||
1055
0
                 !p.ReadInteger(&mByteRangeEnd)) {
1056
0
        return NS_ERROR_CORRUPTED_CONTENT;
1057
0
      }
1058
0
      mIsByteRangeRequest = true;
1059
0
      if (mContentLength == UINT64_MAX) {
1060
0
        mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1);
1061
0
      }
1062
0
      break;
1063
0
    }
1064
0
    case HEADER_CONTENT_SECURITY_POLICY: {
1065
0
      mContentSecurityPolicy = mResponseHeaderValue;
1066
0
      mContentSecurityPolicy.CompressWhitespace();
1067
0
      nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
1068
0
      if (httpChannel) {
1069
0
        nsCString resultCSP = mRootContentSecurityPolicy;
1070
0
        if (!mContentSecurityPolicy.IsEmpty()) {
1071
0
          // We are updating the root channel CSP header respectively for
1072
0
          // each part as: CSP-root + CSP-partN, where N is the part number.
1073
0
          // Here we append current part's CSP to root CSP and reset CSP
1074
0
          // header for each part.
1075
0
          if (!resultCSP.IsEmpty()) {
1076
0
            resultCSP.Append(";");
1077
0
          }
1078
0
          resultCSP.Append(mContentSecurityPolicy);
1079
0
        }
1080
0
        nsresult rv = httpChannel->SetResponseHeader(
1081
0
                        NS_LITERAL_CSTRING("Content-Security-Policy"),
1082
0
                        resultCSP, false);
1083
0
        if (NS_FAILED(rv)) {
1084
0
          return NS_ERROR_CORRUPTED_CONTENT;
1085
0
        }
1086
0
      }
1087
0
      break;
1088
0
    }
1089
0
    case HEADER_UNKNOWN:
1090
0
      // We ignore anything else...
1091
0
      break;
1092
0
    }
1093
0
1094
0
    return NS_OK;
1095
0
}
1096
1097
nsresult
1098
NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv)
1099
0
{
1100
0
    MOZ_ASSERT(aMultiMixedConv != nullptr, "null ptr");
1101
0
    if (! aMultiMixedConv)
1102
0
        return NS_ERROR_NULL_POINTER;
1103
0
1104
0
    *aMultiMixedConv = new nsMultiMixedConv();
1105
0
1106
0
    NS_ADDREF(*aMultiMixedConv);
1107
0
    return NS_OK;
1108
0
}