Source code

Revision control

Other Tools

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 "nsEscape.h"
8
9
#include "mozilla/ArrayUtils.h"
10
#include "mozilla/BinarySearch.h"
11
#include "mozilla/CheckedInt.h"
12
#include "mozilla/TextUtils.h"
13
#include "nsTArray.h"
14
#include "nsCRT.h"
15
#include "plstr.h"
16
#include "nsASCIIMask.h"
17
18
static const char hexCharsUpper[] = "0123456789ABCDEF";
19
static const char hexCharsUpperLower[] = "0123456789ABCDEFabcdef";
20
21
static const int netCharType[256] =
22
// clang-format off
23
/* Bit 0 xalpha -- the alphas
24
** Bit 1 xpalpha -- as xalpha but
25
** converts spaces to plus and plus to %2B
26
** Bit 3 ... path -- as xalphas but doesn't escape '/'
27
*/
28
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
29
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */
30
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */
31
0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */
32
7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */
33
0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */
34
/* bits for '@' changed from 7 to 0 so '@' can be escaped */
35
/* in usernames and passwords in publishing. */
36
7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */
37
0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */
38
7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */
39
0,
40
};
41
42
/* decode % escaped hex codes into character values
43
*/
44
#define UNHEX(C) \
45
((C >= '0' && C <= '9') ? C - '0' : \
46
((C >= 'A' && C <= 'F') ? C - 'A' + 10 : \
47
((C >= 'a' && C <= 'f') ? C - 'a' + 10 : 0)))
48
// clang-format on
49
50
#define IS_OK(C) (netCharType[((unsigned int)(C))] & (aFlags))
51
#define HEX_ESCAPE '%'
52
53
static const uint32_t ENCODE_MAX_LEN = 6; // %uABCD
54
55
static uint32_t AppendPercentHex(char* aBuffer, unsigned char aChar) {
56
uint32_t i = 0;
57
aBuffer[i++] = '%';
58
aBuffer[i++] = hexCharsUpper[aChar >> 4]; // high nibble
59
aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low nibble
60
return i;
61
}
62
63
static uint32_t AppendPercentHex(char16_t* aBuffer, char16_t aChar) {
64
uint32_t i = 0;
65
aBuffer[i++] = '%';
66
if (aChar & 0xff00) {
67
aBuffer[i++] = 'u';
68
aBuffer[i++] = hexCharsUpper[aChar >> 12]; // high-byte high nibble
69
aBuffer[i++] = hexCharsUpper[(aChar >> 8) & 0xF]; // high-byte low nibble
70
}
71
aBuffer[i++] = hexCharsUpper[(aChar >> 4) & 0xF]; // low-byte high nibble
72
aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low-byte low nibble
73
return i;
74
}
75
76
//----------------------------------------------------------------------------------------
77
char* nsEscape(const char* aStr, size_t aLength, size_t* aOutputLength,
78
nsEscapeMask aFlags)
79
//----------------------------------------------------------------------------------------
80
{
81
if (!aStr) {
82
return nullptr;
83
}
84
85
size_t charsToEscape = 0;
86
87
const unsigned char* src = (const unsigned char*)aStr;
88
for (size_t i = 0; i < aLength; ++i) {
89
if (!IS_OK(src[i])) {
90
charsToEscape++;
91
}
92
}
93
94
// calculate how much memory should be allocated
95
// original length + 2 bytes for each escaped character + terminating '\0'
96
// do the sum in steps to check for overflow
97
size_t dstSize = aLength + 1 + charsToEscape;
98
if (dstSize <= aLength) {
99
return nullptr;
100
}
101
dstSize += charsToEscape;
102
if (dstSize < aLength) {
103
return nullptr;
104
}
105
106
// fail if we need more than 4GB
107
if (dstSize > UINT32_MAX) {
108
return nullptr;
109
}
110
111
char* result = (char*)moz_xmalloc(dstSize);
112
113
unsigned char* dst = (unsigned char*)result;
114
src = (const unsigned char*)aStr;
115
if (aFlags == url_XPAlphas) {
116
for (size_t i = 0; i < aLength; ++i) {
117
unsigned char c = *src++;
118
if (IS_OK(c)) {
119
*dst++ = c;
120
} else if (c == ' ') {
121
*dst++ = '+'; /* convert spaces to pluses */
122
} else {
123
*dst++ = HEX_ESCAPE;
124
*dst++ = hexCharsUpper[c >> 4]; /* high nibble */
125
*dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */
126
}
127
}
128
} else {
129
for (size_t i = 0; i < aLength; ++i) {
130
unsigned char c = *src++;
131
if (IS_OK(c)) {
132
*dst++ = c;
133
} else {
134
*dst++ = HEX_ESCAPE;
135
*dst++ = hexCharsUpper[c >> 4]; /* high nibble */
136
*dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */
137
}
138
}
139
}
140
141
*dst = '\0'; /* tack on eos */
142
if (aOutputLength) {
143
*aOutputLength = dst - (unsigned char*)result;
144
}
145
146
return result;
147
}
148
149
//----------------------------------------------------------------------------------------
150
char* nsUnescape(char* aStr)
151
//----------------------------------------------------------------------------------------
152
{
153
nsUnescapeCount(aStr);
154
return aStr;
155
}
156
157
//----------------------------------------------------------------------------------------
158
int32_t nsUnescapeCount(char* aStr)
159
//----------------------------------------------------------------------------------------
160
{
161
char* src = aStr;
162
char* dst = aStr;
163
164
char c1[] = " ";
165
char c2[] = " ";
166
char* const pc1 = c1;
167
char* const pc2 = c2;
168
169
if (!*src) {
170
// A null string was passed in. Nothing to escape.
171
// Returns early as the string might not actually be mutable with
172
// length 0.
173
return 0;
174
}
175
176
while (*src) {
177
c1[0] = *(src + 1);
178
if (*(src + 1) == '\0') {
179
c2[0] = '\0';
180
} else {
181
c2[0] = *(src + 2);
182
}
183
184
if (*src != HEX_ESCAPE || PL_strpbrk(pc1, hexCharsUpperLower) == 0 ||
185
PL_strpbrk(pc2, hexCharsUpperLower) == 0) {
186
*dst++ = *src++;
187
} else {
188
src++; /* walk over escape */
189
if (*src) {
190
*dst = UNHEX(*src) << 4;
191
src++;
192
}
193
if (*src) {
194
*dst = (*dst + UNHEX(*src));
195
src++;
196
}
197
dst++;
198
}
199
}
200
201
*dst = 0;
202
return (int)(dst - aStr);
203
204
} /* NET_UnEscapeCnt */
205
206
void nsAppendEscapedHTML(const nsACString& aSrc, nsACString& aDst) {
207
// Preparation: aDst's length will increase by at least aSrc's length. If the
208
// addition overflows, we skip this, which is fine, and we'll likely abort
209
// while (infallibly) appending due to aDst becoming too large.
210
mozilla::CheckedInt<nsACString::size_type> newCapacity = aDst.Length();
211
newCapacity += aSrc.Length();
212
if (newCapacity.isValid()) {
213
aDst.SetCapacity(newCapacity.value());
214
}
215
216
for (auto cur = aSrc.BeginReading(); cur != aSrc.EndReading(); cur++) {
217
if (*cur == '<') {
218
aDst.AppendLiteral("&lt;");
219
} else if (*cur == '>') {
220
aDst.AppendLiteral("&gt;");
221
} else if (*cur == '&') {
222
aDst.AppendLiteral("&amp;");
223
} else if (*cur == '"') {
224
aDst.AppendLiteral("&quot;");
225
} else if (*cur == '\'') {
226
aDst.AppendLiteral("&#39;");
227
} else {
228
aDst.Append(*cur);
229
}
230
}
231
}
232
233
//----------------------------------------------------------------------------------------
234
//
235
// The following table encodes which characters needs to be escaped for which
236
// parts of an URL. The bits are the "url components" in the enum EscapeMask,
237
// see nsEscape.h.
238
//
239
// esc_Scheme = 1
240
// esc_Username = 2
241
// esc_Password = 4
242
// esc_Host = 8
243
// esc_Directory = 16
244
// esc_FileBaseName = 32
245
// esc_FileExtension = 64
246
// esc_Param = 128
247
// esc_Query = 256
248
// esc_Ref = 512
249
250
static const uint32_t EscapeChars[256] =
251
// clang-format off
252
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
253
{
254
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x
255
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
256
0,1023, 0, 512,1023, 0,1023, 624,1023,1023,1023,1023,1023,1023, 953, 784, // 2x !"#$%&'()*+,-./
257
1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008,1008, 0,1008, 0, 768, // 3x 0123456789:;<=>?
258
1008,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, // 4x @ABCDEFGHIJKLMNO
259
1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008, 896,1008, 896,1023, // 5x PQRSTUVWXYZ[\]^_
260
384,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, // 6x `abcdefghijklmno
261
1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, 896,1012, 896,1023, 0, // 7x pqrstuvwxyz{|}~ DEL
262
0 // 80 to FF are zero
263
};
264
// clang-format on
265
266
static uint16_t dontNeedEscape(unsigned char aChar, uint32_t aFlags) {
267
return EscapeChars[(uint32_t)aChar] & aFlags;
268
}
269
static uint16_t dontNeedEscape(uint16_t aChar, uint32_t aFlags) {
270
return aChar < mozilla::ArrayLength(EscapeChars)
271
? (EscapeChars[(uint32_t)aChar] & aFlags)
272
: 0;
273
}
274
275
//----------------------------------------------------------------------------------------
276
277
/**
278
* Templated helper for URL escaping a portion of a string.
279
*
280
* @param aPart The pointer to the beginning of the portion of the string to
281
* escape.
282
* @param aPartLen The length of the string to escape.
283
* @param aFlags Flags used to configure escaping. @see EscapeMask
284
* @param aResult String that has the URL escaped portion appended to. Only
285
* altered if the string is URL escaped or |esc_AlwaysCopy| is specified.
286
* @param aDidAppend Indicates whether or not data was appended to |aResult|.
287
* @return NS_ERROR_INVALID_ARG, NS_ERROR_OUT_OF_MEMORY on failure.
288
*/
289
template <class T>
290
static nsresult T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen,
291
uint32_t aFlags, const ASCIIMaskArray* aFilterMask,
292
T& aResult, bool& aDidAppend) {
293
typedef nsCharTraits<typename T::char_type> traits;
294
typedef typename traits::unsigned_char_type unsigned_char_type;
295
static_assert(sizeof(*aPart) == 1 || sizeof(*aPart) == 2,
296
"unexpected char type");
297
298
if (!aPart) {
299
MOZ_ASSERT_UNREACHABLE("null pointer");
300
return NS_ERROR_INVALID_ARG;
301
}
302
303
bool forced = !!(aFlags & esc_Forced);
304
bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII);
305
bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII);
306
bool writing = !!(aFlags & esc_AlwaysCopy);
307
bool colon = !!(aFlags & esc_Colon);
308
bool spaces = !!(aFlags & esc_Spaces);
309
310
auto src = reinterpret_cast<const unsigned_char_type*>(aPart);
311
312
typename T::char_type tempBuffer[100];
313
unsigned int tempBufferPos = 0;
314
315
bool previousIsNonASCII = false;
316
for (size_t i = 0; i < aPartLen; ++i) {
317
unsigned_char_type c = *src++;
318
319
// If there is a filter, we wish to skip any characters which match it.
320
// This is needed so we don't perform an extra pass just to extract the
321
// filtered characters.
322
if (aFilterMask && mozilla::ASCIIMask::IsMasked(*aFilterMask, c)) {
323
if (!writing) {
324
if (!aResult.Append(aPart, i, mozilla::fallible)) {
325
return NS_ERROR_OUT_OF_MEMORY;
326
}
327
writing = true;
328
}
329
continue;
330
}
331
332
// if the char has not to be escaped or whatever follows % is
333
// a valid escaped string, just copy the char.
334
//
335
// Also the % will not be escaped until forced
336
// See bugzilla bug 61269 for details why we changed this
337
//
338
// And, we will not escape non-ascii characters if requested.
339
// On special request we will also escape the colon even when
340
// not covered by the matrix.
341
// ignoreAscii is not honored for control characters (C0 and DEL)
342
//
343
// And, we should escape the '|' character when it occurs after any
344
// non-ASCII character as it may be aPart of a multi-byte character.
345
//
346
// 0x20..0x7e are the valid ASCII characters.
347
if ((dontNeedEscape(c, aFlags) || (c == HEX_ESCAPE && !forced) ||
348
(c > 0x7f && ignoreNonAscii) ||
349
(c >= 0x20 && c < 0x7f && ignoreAscii)) &&
350
!(c == ':' && colon) && !(c == ' ' && spaces) &&
351
!(previousIsNonASCII && c == '|' && !ignoreNonAscii)) {
352
if (writing) {
353
tempBuffer[tempBufferPos++] = c;
354
}
355
} else { /* do the escape magic */
356
if (!writing) {
357
if (!aResult.Append(aPart, i, mozilla::fallible)) {
358
return NS_ERROR_OUT_OF_MEMORY;
359
}
360
writing = true;
361
}
362
uint32_t len = ::AppendPercentHex(tempBuffer + tempBufferPos, c);
363
tempBufferPos += len;
364
MOZ_ASSERT(len <= ENCODE_MAX_LEN, "potential buffer overflow");
365
}
366
367
// Flush the temp buffer if it doesnt't have room for another encoded char.
368
if (tempBufferPos >= mozilla::ArrayLength(tempBuffer) - ENCODE_MAX_LEN) {
369
NS_ASSERTION(writing, "should be writing");
370
if (!aResult.Append(tempBuffer, tempBufferPos, mozilla::fallible)) {
371
return NS_ERROR_OUT_OF_MEMORY;
372
}
373
tempBufferPos = 0;
374
}
375
376
previousIsNonASCII = (c > 0x7f);
377
}
378
if (writing) {
379
if (!aResult.Append(tempBuffer, tempBufferPos, mozilla::fallible)) {
380
return NS_ERROR_OUT_OF_MEMORY;
381
}
382
}
383
aDidAppend = writing;
384
return NS_OK;
385
}
386
387
bool NS_EscapeURL(const char* aPart, int32_t aPartLen, uint32_t aFlags,
388
nsACString& aResult) {
389
size_t partLen;
390
if (aPartLen < 0) {
391
partLen = strlen(aPart);
392
} else {
393
partLen = aPartLen;
394
}
395
396
return NS_EscapeURLSpan(MakeSpan(aPart, partLen), aFlags, aResult);
397
}
398
399
bool NS_EscapeURLSpan(mozilla::Span<const char> aStr, uint32_t aFlags,
400
nsACString& aResult) {
401
bool appended = false;
402
nsresult rv = T_EscapeURL(aStr.Elements(), aStr.Length(), aFlags, nullptr,
403
aResult, appended);
404
if (NS_FAILED(rv)) {
405
::NS_ABORT_OOM(aResult.Length() * sizeof(nsACString::char_type));
406
}
407
408
return appended;
409
}
410
411
nsresult NS_EscapeURL(const nsACString& aStr, uint32_t aFlags,
412
nsACString& aResult, const mozilla::fallible_t&) {
413
bool appended = false;
414
nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, nullptr,
415
aResult, appended);
416
if (NS_FAILED(rv)) {
417
aResult.Truncate();
418
return rv;
419
}
420
421
if (!appended) {
422
aResult = aStr;
423
}
424
425
return rv;
426
}
427
428
nsresult NS_EscapeAndFilterURL(const nsACString& aStr, uint32_t aFlags,
429
const ASCIIMaskArray* aFilterMask,
430
nsACString& aResult,
431
const mozilla::fallible_t&) {
432
bool appended = false;
433
nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aFilterMask,
434
aResult, appended);
435
if (NS_FAILED(rv)) {
436
aResult.Truncate();
437
return rv;
438
}
439
440
if (!appended) {
441
if (!aResult.Assign(aStr, fallible)) {
442
return NS_ERROR_OUT_OF_MEMORY;
443
}
444
}
445
446
return rv;
447
}
448
449
const nsAString& NS_EscapeURL(const nsAString& aStr, uint32_t aFlags,
450
nsAString& aResult) {
451
bool result = false;
452
nsresult rv = T_EscapeURL<nsAString>(aStr.Data(), aStr.Length(), aFlags,
453
nullptr, aResult, result);
454
455
if (NS_FAILED(rv)) {
456
::NS_ABORT_OOM(aResult.Length() * sizeof(nsAString::char_type));
457
}
458
459
if (result) {
460
return aResult;
461
}
462
return aStr;
463
}
464
465
// Starting at aStr[aStart] find the first index in aStr that matches any
466
// character that is forbidden by aFunction. Return false if not found.
467
static bool FindFirstMatchFrom(const nsString& aStr, size_t aStart,
468
const std::function<bool(char16_t)>& aFunction,
469
size_t* aIndex) {
470
for (size_t j = aStart, l = aStr.Length(); j < l; ++j) {
471
if (aFunction(aStr[j])) {
472
*aIndex = j;
473
return true;
474
}
475
}
476
return false;
477
}
478
479
const nsAString& NS_EscapeURL(const nsString& aStr,
480
const std::function<bool(char16_t)>& aFunction,
481
nsAString& aResult) {
482
bool didEscape = false;
483
for (size_t i = 0, strLen = aStr.Length(); i < strLen;) {
484
size_t j;
485
if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aFunction, &j))) {
486
if (i == 0) {
487
didEscape = true;
488
aResult.Truncate();
489
aResult.SetCapacity(aStr.Length());
490
}
491
if (j != i) {
492
// The substring from 'i' up to 'j' that needs no escaping.
493
aResult.Append(nsDependentSubstring(aStr, i, j - i));
494
}
495
char16_t buffer[ENCODE_MAX_LEN];
496
uint32_t bufferLen = ::AppendPercentHex(buffer, aStr[j]);
497
MOZ_ASSERT(bufferLen <= ENCODE_MAX_LEN, "buffer overflow");
498
aResult.Append(buffer, bufferLen);
499
i = j + 1;
500
} else {
501
if (MOZ_UNLIKELY(didEscape)) {
502
// The tail of the string that needs no escaping.
503
aResult.Append(nsDependentSubstring(aStr, i, strLen - i));
504
}
505
break;
506
}
507
}
508
if (MOZ_UNLIKELY(didEscape)) {
509
return aResult;
510
}
511
return aStr;
512
}
513
514
bool NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags,
515
nsACString& aResult) {
516
bool didAppend = false;
517
nsresult rv =
518
NS_UnescapeURL(aStr, aLen, aFlags, aResult, didAppend, mozilla::fallible);
519
if (rv == NS_ERROR_OUT_OF_MEMORY) {
520
::NS_ABORT_OOM(aLen * sizeof(nsACString::char_type));
521
}
522
523
return didAppend;
524
}
525
526
nsresult NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags,
527
nsACString& aResult, bool& aDidAppend,
528
const mozilla::fallible_t&) {
529
if (!aStr) {
530
MOZ_ASSERT_UNREACHABLE("null pointer");
531
return NS_ERROR_INVALID_ARG;
532
}
533
534
MOZ_ASSERT(aResult.IsEmpty(),
535
"Passing a non-empty string as an out parameter!");
536
537
uint32_t len;
538
if (aLen < 0) {
539
size_t stringLength = strlen(aStr);
540
if (stringLength >= UINT32_MAX) {
541
return NS_ERROR_OUT_OF_MEMORY;
542
}
543
len = stringLength;
544
} else {
545
len = aLen;
546
}
547
548
bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII);
549
bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII);
550
bool writing = !!(aFlags & esc_AlwaysCopy);
551
bool skipControl = !!(aFlags & esc_SkipControl);
552
bool skipInvalidHostChar = !!(aFlags & esc_Host);
553
554
unsigned char* destPtr;
555
uint32_t destPos;
556
557
if (writing) {
558
if (!aResult.SetLength(len, mozilla::fallible)) {
559
return NS_ERROR_OUT_OF_MEMORY;
560
}
561
destPos = 0;
562
destPtr = reinterpret_cast<unsigned char*>(aResult.BeginWriting());
563
}
564
565
const char* last = aStr;
566
const char* end = aStr + len;
567
568
for (const char* p = aStr; p < end; ++p) {
569
if (*p == HEX_ESCAPE && p + 2 < end) {
570
unsigned char c1 = *((unsigned char*)p + 1);
571
unsigned char c2 = *((unsigned char*)p + 2);
572
unsigned char u = (UNHEX(c1) << 4) + UNHEX(c2);
573
if (mozilla::IsAsciiHexDigit(c1) && mozilla::IsAsciiHexDigit(c2) &&
574
(!skipInvalidHostChar || dontNeedEscape(u, aFlags) || c1 >= '8') &&
575
((c1 < '8' && !ignoreAscii) || (c1 >= '8' && !ignoreNonAscii)) &&
576
!(skipControl &&
577
(c1 < '2' || (c1 == '7' && (c2 == 'f' || c2 == 'F'))))) {
578
if (MOZ_UNLIKELY(!writing)) {
579
writing = true;
580
if (!aResult.SetLength(len, mozilla::fallible)) {
581
return NS_ERROR_OUT_OF_MEMORY;
582
}
583
destPos = 0;
584
destPtr = reinterpret_cast<unsigned char*>(aResult.BeginWriting());
585
}
586
if (p > last) {
587
auto toCopy = p - last;
588
memcpy(destPtr + destPos, last, toCopy);
589
destPos += toCopy;
590
MOZ_ASSERT(destPos <= len);
591
last = p;
592
}
593
destPtr[destPos] = u;
594
destPos += 1;
595
MOZ_ASSERT(destPos <= len);
596
p += 2;
597
last += 3;
598
}
599
}
600
}
601
if (writing && last < end) {
602
auto toCopy = end - last;
603
memcpy(destPtr + destPos, last, toCopy);
604
destPos += toCopy;
605
MOZ_ASSERT(destPos <= len);
606
}
607
608
if (writing) {
609
aResult.Truncate(destPos);
610
}
611
612
aDidAppend = writing;
613
return NS_OK;
614
}