Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/script/ScriptLoadHandler.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "ScriptLoadHandler.h"
8
#include "ScriptLoader.h"
9
#include "ScriptTrace.h"
10
11
#include "nsContentUtils.h"
12
#include "nsIEncodedChannel.h"
13
#include "nsIStringEnumerator.h"
14
#include "nsMimeTypes.h"
15
16
#include "mozilla/Telemetry.h"
17
18
namespace mozilla {
19
namespace dom {
20
21
#undef LOG
22
#define LOG(args) \
23
0
  MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
24
25
#define LOG_ENABLED() \
26
0
  MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
27
28
ScriptLoadHandler::ScriptLoadHandler(ScriptLoader* aScriptLoader,
29
                                     ScriptLoadRequest* aRequest,
30
                                     SRICheckDataVerifier* aSRIDataVerifier)
31
  : mScriptLoader(aScriptLoader),
32
    mRequest(aRequest),
33
    mSRIDataVerifier(aSRIDataVerifier),
34
    mSRIStatus(NS_OK),
35
    mDecoder()
36
0
{
37
0
  MOZ_ASSERT(mRequest->IsUnknownDataType());
38
0
  MOZ_ASSERT(mRequest->IsLoading());
39
0
}
40
41
ScriptLoadHandler::~ScriptLoadHandler()
42
0
{}
43
44
NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
45
46
NS_IMETHODIMP
47
ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
48
                                     nsISupports* aContext,
49
                                     uint32_t aDataLength,
50
                                     const uint8_t* aData,
51
                                     uint32_t* aConsumedLength)
52
0
{
53
0
  if (mRequest->IsCanceled()) {
54
0
    // If request cancelled, ignore any incoming data.
55
0
    *aConsumedLength = aDataLength;
56
0
    return NS_OK;
57
0
  }
58
0
59
0
  nsresult rv = NS_OK;
60
0
  if (mRequest->IsUnknownDataType()) {
61
0
    rv = EnsureKnownDataType(aLoader);
62
0
    NS_ENSURE_SUCCESS(rv, rv);
63
0
  }
64
0
65
0
  if (mRequest->IsTextSource()) {
66
0
    if (!EnsureDecoder(aLoader, aData, aDataLength,
67
0
                       /* aEndOfStream = */ false)) {
68
0
      return NS_OK;
69
0
    }
70
0
71
0
    // Below we will/shall consume entire data chunk.
72
0
    *aConsumedLength = aDataLength;
73
0
74
0
    // Decoder has already been initialized. -- trying to decode all loaded bytes.
75
0
    rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ false);
76
0
    NS_ENSURE_SUCCESS(rv, rv);
77
0
78
0
    // If SRI is required for this load, appending new bytes to the hash.
79
0
    if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
80
0
      mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
81
0
    }
82
0
  } else if (mRequest->IsBinASTSource()) {
83
0
    if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
84
0
      return NS_ERROR_OUT_OF_MEMORY;
85
0
    }
86
0
87
0
    // Below we will/shall consume entire data chunk.
88
0
    *aConsumedLength = aDataLength;
89
0
90
0
    // If SRI is required for this load, appending new bytes to the hash.
91
0
    if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
92
0
      mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
93
0
    }
94
0
  } else {
95
0
    MOZ_ASSERT(mRequest->IsBytecode());
96
0
    if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
97
0
      return NS_ERROR_OUT_OF_MEMORY;
98
0
    }
99
0
100
0
    *aConsumedLength = aDataLength;
101
0
    rv = MaybeDecodeSRI();
102
0
    if (NS_FAILED(rv)) {
103
0
      nsCOMPtr<nsIRequest> channelRequest;
104
0
      aLoader->GetRequest(getter_AddRefs(channelRequest));
105
0
      return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
106
0
    }
107
0
  }
108
0
109
0
  return rv;
110
0
}
111
112
nsresult
113
ScriptLoadHandler::DecodeRawData(const uint8_t* aData,
114
                                 uint32_t aDataLength,
115
                                 bool aEndOfStream)
116
0
{
117
0
  CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(aDataLength);
118
0
  if (!needed.isValid()) {
119
0
    return NS_ERROR_OUT_OF_MEMORY;
120
0
  }
121
0
122
0
  // Reference to the script source buffer which we will update.
123
0
  ScriptLoadRequest::ScriptTextBuffer& scriptText = mRequest->ScriptText();
124
0
125
0
  uint32_t haveRead = scriptText.length();
126
0
127
0
  CheckedInt<uint32_t> capacity = haveRead;
128
0
  capacity += needed.value();
129
0
130
0
  if (!capacity.isValid() || !scriptText.resize(capacity.value())) {
131
0
    return NS_ERROR_OUT_OF_MEMORY;
132
0
  }
133
0
134
0
  uint32_t result;
135
0
  size_t read;
136
0
  size_t written;
137
0
  bool hadErrors;
138
0
  Tie(result, read, written, hadErrors) = mDecoder->DecodeToUTF16(
139
0
    MakeSpan(aData, aDataLength),
140
0
    MakeSpan(scriptText.begin() + haveRead, needed.value()),
141
0
    aEndOfStream);
142
0
  MOZ_ASSERT(result == kInputEmpty);
143
0
  MOZ_ASSERT(read == aDataLength);
144
0
  MOZ_ASSERT(written <= needed.value());
145
0
  Unused << hadErrors;
146
0
147
0
  haveRead += written;
148
0
  MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
149
0
  MOZ_ALWAYS_TRUE(scriptText.resize(haveRead));
150
0
  mRequest->mScriptTextLength = scriptText.length();
151
0
152
0
  return NS_OK;
153
0
}
154
155
bool
156
ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
157
                                 const uint8_t* aData,
158
                                 uint32_t aDataLength,
159
                                 bool aEndOfStream)
160
0
{
161
0
  // Check if decoder has already been created.
162
0
  if (mDecoder) {
163
0
    return true;
164
0
  }
165
0
166
0
  nsAutoCString charset;
167
0
  if (!EnsureDecoder(aLoader, aData, aDataLength, aEndOfStream, charset)) {
168
0
    return false;
169
0
  }
170
0
  return true;
171
0
}
172
173
bool
174
ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
175
                                 const uint8_t* aData,
176
                                 uint32_t aDataLength,
177
                                 bool aEndOfStream,
178
                                 nsCString& oCharset)
179
0
{
180
0
  // JavaScript modules are always UTF-8.
181
0
  if (mRequest->IsModuleRequest()) {
182
0
    oCharset = "UTF-8";
183
0
    mDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
184
0
    return true;
185
0
  }
186
0
187
0
  // Determine if BOM check should be done.  This occurs either
188
0
  // if end-of-stream has been reached, or at least 3 bytes have
189
0
  // been read from input.
190
0
  if (!aEndOfStream && (aDataLength < 3)) {
191
0
    return false;
192
0
  }
193
0
194
0
  // Do BOM detection.
195
0
  const Encoding* encoding;
196
0
  size_t bomLength;
197
0
  Tie(encoding, bomLength) = Encoding::ForBOM(MakeSpan(aData, aDataLength));
198
0
  if (encoding) {
199
0
    mDecoder = encoding->NewDecoderWithBOMRemoval();
200
0
    encoding->Name(oCharset);
201
0
    return true;
202
0
  }
203
0
204
0
  // BOM detection failed, check content stream for charset.
205
0
  nsCOMPtr<nsIRequest> req;
206
0
  nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
207
0
  NS_ASSERTION(req, "StreamLoader's request went away prematurely");
208
0
  NS_ENSURE_SUCCESS(rv, false);
209
0
210
0
  nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
211
0
212
0
  if (channel) {
213
0
    nsAutoCString label;
214
0
    if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
215
0
        (encoding = Encoding::ForLabel(label))) {
216
0
      mDecoder = encoding->NewDecoderWithoutBOMHandling();
217
0
      encoding->Name(oCharset);
218
0
      return true;
219
0
    }
220
0
  }
221
0
222
0
  // Check the hint charset from the script element or preload
223
0
  // request.
224
0
  nsAutoString hintCharset;
225
0
  if (!mRequest->IsPreload()) {
226
0
    mRequest->Element()->GetScriptCharset(hintCharset);
227
0
  } else {
228
0
    nsTArray<ScriptLoader::PreloadInfo>::index_type i =
229
0
      mScriptLoader->mPreloads.IndexOf(mRequest, 0,
230
0
            ScriptLoader::PreloadRequestComparator());
231
0
232
0
    NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
233
0
                 "Incorrect preload bookkeeping");
234
0
    hintCharset = mScriptLoader->mPreloads[i].mCharset;
235
0
  }
236
0
237
0
  if ((encoding = Encoding::ForLabel(hintCharset))) {
238
0
    mDecoder = encoding->NewDecoderWithoutBOMHandling();
239
0
    encoding->Name(oCharset);
240
0
    return true;
241
0
  }
242
0
243
0
  // Get the charset from the charset of the document.
244
0
  if (mScriptLoader->mDocument) {
245
0
    encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
246
0
    mDecoder = encoding->NewDecoderWithoutBOMHandling();
247
0
    encoding->Name(oCharset);
248
0
    return true;
249
0
  }
250
0
251
0
  // Curiously, there are various callers that don't pass aDocument. The
252
0
  // fallback in the old code was ISO-8859-1, which behaved like
253
0
  // windows-1252.
254
0
  oCharset = "windows-1252";
255
0
  mDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
256
0
  return true;
257
0
}
258
259
nsresult
260
ScriptLoadHandler::MaybeDecodeSRI()
261
0
{
262
0
  if (!mSRIDataVerifier || mSRIDataVerifier->IsComplete() || NS_FAILED(mSRIStatus)) {
263
0
    return NS_OK;
264
0
  }
265
0
266
0
  // Skip until the content is large enough to be decoded.
267
0
  if (mRequest->mScriptBytecode.length() <= mSRIDataVerifier->DataSummaryLength()) {
268
0
    return NS_OK;
269
0
  }
270
0
271
0
  mSRIStatus = mSRIDataVerifier->ImportDataSummary(
272
0
    mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin());
273
0
274
0
  if (NS_FAILED(mSRIStatus)) {
275
0
    // We are unable to decode the hash contained in the alternate data which
276
0
    // contains the bytecode, or it does not use the same algorithm.
277
0
    LOG(("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart request"));
278
0
    return mSRIStatus;
279
0
  }
280
0
281
0
  mRequest->mBytecodeOffset = mSRIDataVerifier->DataSummaryLength();
282
0
  return NS_OK;
283
0
}
284
285
nsresult
286
ScriptLoadHandler::EnsureKnownDataType(nsIIncrementalStreamLoader* aLoader)
287
0
{
288
0
  MOZ_ASSERT(mRequest->IsUnknownDataType());
289
0
  MOZ_ASSERT(mRequest->IsLoading());
290
0
291
0
  nsCOMPtr<nsIRequest> req;
292
0
  nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
293
0
  MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
294
0
  NS_ENSURE_SUCCESS(rv, rv);
295
0
296
0
  if (ScriptLoader::BinASTEncodingEnabled()) {
297
0
    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
298
0
    if (httpChannel) {
299
0
      nsAutoCString mimeType;
300
0
      httpChannel->GetContentType(mimeType);
301
0
      if (mimeType.LowerCaseEqualsASCII(APPLICATION_JAVASCRIPT_BINAST)) {
302
0
        if (mRequest->ShouldAcceptBinASTEncoding()) {
303
0
          mRequest->SetBinASTSource();
304
0
          TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
305
0
          return NS_OK;
306
0
        } else {
307
0
          return NS_ERROR_FAILURE;
308
0
        }
309
0
      }
310
0
    }
311
0
  }
312
0
313
0
  if (mRequest->IsLoadingSource()) {
314
0
    mRequest->SetTextSource();
315
0
    TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
316
0
    return NS_OK;
317
0
  }
318
0
319
0
  nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
320
0
  if (cic) {
321
0
    nsAutoCString altDataType;
322
0
    cic->GetAlternativeDataType(altDataType);
323
0
    if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
324
0
      mRequest->SetBytecode();
325
0
      TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_bytecode");
326
0
    } else {
327
0
      MOZ_ASSERT(altDataType.IsEmpty());
328
0
      mRequest->SetTextSource();
329
0
      TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
330
0
    }
331
0
  } else {
332
0
    mRequest->SetTextSource();
333
0
    TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
334
0
  }
335
0
336
0
  MOZ_ASSERT(!mRequest->IsUnknownDataType());
337
0
  MOZ_ASSERT(mRequest->IsLoading());
338
0
  return NS_OK;
339
0
}
340
341
NS_IMETHODIMP
342
ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
343
                                    nsISupports* aContext,
344
                                    nsresult aStatus,
345
                                    uint32_t aDataLength,
346
                                    const uint8_t* aData)
347
0
{
348
0
  nsresult rv = NS_OK;
349
0
  if (LOG_ENABLED()) {
350
0
    nsAutoCString url;
351
0
    mRequest->mURI->GetAsciiSpec(url);
352
0
    LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)",
353
0
         mRequest.get(), url.get()));
354
0
  }
355
0
356
0
  nsCOMPtr<nsIRequest> channelRequest;
357
0
  aLoader->GetRequest(getter_AddRefs(channelRequest));
358
0
359
0
  if (!mRequest->IsCanceled()) {
360
0
    if (mRequest->IsUnknownDataType()) {
361
0
      rv = EnsureKnownDataType(aLoader);
362
0
      NS_ENSURE_SUCCESS(rv, rv);
363
0
    }
364
0
365
0
    if (mRequest->IsTextSource()) {
366
0
      DebugOnly<bool> encoderSet =
367
0
        EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
368
0
      MOZ_ASSERT(encoderSet);
369
0
      rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ true);
370
0
      NS_ENSURE_SUCCESS(rv, rv);
371
0
372
0
      LOG(("ScriptLoadRequest (%p): Source length = %u",
373
0
           mRequest.get(), unsigned(mRequest->ScriptText().length())));
374
0
375
0
      // If SRI is required for this load, appending new bytes to the hash.
376
0
      if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
377
0
        mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
378
0
      }
379
0
    } else if (mRequest->IsBinASTSource()) {
380
0
      if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
381
0
        return NS_ERROR_OUT_OF_MEMORY;
382
0
      }
383
0
384
0
      // If SRI is required for this load, appending new bytes to the hash.
385
0
      if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
386
0
        mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
387
0
      }
388
0
    } else {
389
0
      MOZ_ASSERT(mRequest->IsBytecode());
390
0
      if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
391
0
        return NS_ERROR_OUT_OF_MEMORY;
392
0
      }
393
0
394
0
      LOG(("ScriptLoadRequest (%p): Bytecode length = %u",
395
0
           mRequest.get(), unsigned(mRequest->mScriptBytecode.length())));
396
0
397
0
      // If we abort while decoding the SRI, we fallback on explictly requesting
398
0
      // the source. Thus, we should not continue in
399
0
      // ScriptLoader::OnStreamComplete, which removes the request from the
400
0
      // waiting lists.
401
0
      rv = MaybeDecodeSRI();
402
0
      if (NS_FAILED(rv)) {
403
0
        return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
404
0
      }
405
0
406
0
      // The bytecode cache always starts with the SRI hash, thus even if there
407
0
      // is no SRI data verifier instance, we still want to skip the hash.
408
0
      rv = SRICheckDataVerifier::DataSummaryLength(mRequest->mScriptBytecode.length(),
409
0
                                                   mRequest->mScriptBytecode.begin(),
410
0
                                                   &mRequest->mBytecodeOffset);
411
0
      if (NS_FAILED(rv)) {
412
0
        return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
413
0
      }
414
0
    }
415
0
  }
416
0
417
0
  // Everything went well, keep the CacheInfoChannel alive such that we can
418
0
  // later save the bytecode on the cache entry.
419
0
  if (NS_SUCCEEDED(rv) && mRequest->IsSource() &&
420
0
      nsContentUtils::IsBytecodeCacheEnabled()) {
421
0
    mRequest->mCacheInfo = do_QueryInterface(channelRequest);
422
0
    LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p",
423
0
         mRequest.get(), mRequest->mCacheInfo.get()));
424
0
  }
425
0
426
0
  // we have to mediate and use mRequest.
427
0
  rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
428
0
                                       mSRIDataVerifier);
429
0
430
0
  // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
431
0
  if (NS_FAILED(rv)) {
432
0
    mRequest->mCacheInfo = nullptr;
433
0
  }
434
0
435
0
  return rv;
436
0
}
437
438
#undef LOG_ENABLED
439
#undef LOG
440
441
} // dom namespace
442
} // mozilla namespace