Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim:set ts=4 sw=2 sts=2 et cindent: */
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 "IPCMessageUtils.h"
8
9
#include "nsASCIIMask.h"
10
#include "nsStandardURL.h"
11
#include "nsCRT.h"
12
#include "nsEscape.h"
13
#include "nsIFile.h"
14
#include "nsIObjectInputStream.h"
15
#include "nsIObjectOutputStream.h"
16
#include "nsIIDNService.h"
17
#include "mozilla/Logging.h"
18
#include "nsAutoPtr.h"
19
#include "nsIURLParser.h"
20
#include "nsNetCID.h"
21
#include "mozilla/MemoryReporting.h"
22
#include "mozilla/ipc/URIUtils.h"
23
#include "mozilla/TextUtils.h"
24
#include <algorithm>
25
#include "nsContentUtils.h"
26
#include "prprf.h"
27
#include "nsReadableUtils.h"
28
#include "mozilla/net/MozURL_ffi.h"
29
#include "mozilla/TextUtils.h"
30
31
//
32
// setenv MOZ_LOG nsStandardURL:5
33
//
34
static LazyLogModule gStandardURLLog("nsStandardURL");
35
36
// The Chromium code defines its own LOG macro which we don't want
37
#undef LOG
38
#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
39
#undef LOG_ENABLED
40
#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
41
42
using namespace mozilla::ipc;
43
44
namespace mozilla {
45
namespace net {
46
47
static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
48
static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID);
49
50
// This will always be initialized and destroyed on the main thread, but
51
// can be safely used on other threads.
52
StaticRefPtr<nsIIDNService> nsStandardURL::gIDN;
53
54
// This value will only be updated on the main thread once. Worker threads
55
// may race when reading this values, but that's OK because in the worst
56
// case we will just dispatch a noop runnable to the main thread.
57
bool nsStandardURL::gInitialized = false;
58
59
const char nsStandardURL::gHostLimitDigits[] = {'/', '\\', '?', '#', 0};
60
bool nsStandardURL::gPunycodeHost = true;
61
62
// Invalid host characters
63
// Note that the array below will be initialized at compile time,
64
// so we do not need to "optimize" TestForInvalidHostCharacters.
65
//
66
constexpr bool TestForInvalidHostCharacters(char c) {
67
// Testing for these:
68
// CONTROL_CHARACTERS " #/:?@[\\]*<>|\"";
69
return (c > 0 && c < 32) || // The control characters are [1, 31]
70
c == ' ' || c == '#' || c == '/' || c == ':' || c == '?' || c == '@' ||
71
c == '[' || c == '\\' || c == ']' || c == '*' || c == '<' ||
72
c == '^' ||
73
#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
74
// Mailnews %-escapes file paths into URLs.
75
c == '>' || c == '|' || c == '"';
76
#else
77
c == '>' || c == '|' || c == '"' || c == '%';
78
#endif
79
}
80
constexpr ASCIIMaskArray sInvalidHostChars =
81
CreateASCIIMask(TestForInvalidHostCharacters);
82
83
//----------------------------------------------------------------------------
84
// nsStandardURL::nsSegmentEncoder
85
//----------------------------------------------------------------------------
86
87
nsStandardURL::nsSegmentEncoder::nsSegmentEncoder(const Encoding* encoding)
88
: mEncoding(encoding) {
89
if (mEncoding == UTF_8_ENCODING) {
90
mEncoding = nullptr;
91
}
92
}
93
94
int32_t nsStandardURL::nsSegmentEncoder::EncodeSegmentCount(
95
const char* aStr, const URLSegment& aSeg, int16_t aMask, nsCString& aOut,
96
bool& aAppended, uint32_t aExtraLen) {
97
// aExtraLen is characters outside the segment that will be
98
// added when the segment is not empty (like the @ following
99
// a username).
100
if (!aStr || aSeg.mLen <= 0) {
101
// Empty segment, so aExtraLen not added per above.
102
aAppended = false;
103
return 0;
104
}
105
106
uint32_t origLen = aOut.Length();
107
108
Span<const char> span = MakeSpan(aStr + aSeg.mPos, aSeg.mLen);
109
110
// first honor the origin charset if appropriate. as an optimization,
111
// only do this if the segment is non-ASCII. Further, if mEncoding is
112
// null, then the origin charset is UTF-8 and there is nothing to do.
113
if (mEncoding) {
114
size_t upTo = Encoding::ASCIIValidUpTo(AsBytes(span));
115
if (upTo != span.Length()) {
116
// we have to encode this segment
117
char bufferArr[512];
118
Span<char> buffer = MakeSpan(bufferArr);
119
120
auto encoder = mEncoding->NewEncoder();
121
122
nsAutoCString valid; // has to be declared in this scope
123
if (MOZ_UNLIKELY(!IsUTF8(span.From(upTo)))) {
124
MOZ_ASSERT_UNREACHABLE("Invalid UTF-8 passed to nsStandardURL.");
125
// It's UB to pass invalid UTF-8 to
126
// EncodeFromUTF8WithoutReplacement(), so let's make our input valid
127
// UTF-8 by replacing invalid sequences with the REPLACEMENT
128
// CHARACTER.
129
UTF_8_ENCODING->Decode(
130
nsDependentCSubstring(span.Elements(), span.Length()), valid);
131
// This assigment is OK. `span` can't be used after `valid` has
132
// been destroyed because the only way out of the scope that `valid`
133
// was declared in is via return inside the loop below. Specifically,
134
// the return is the only way out of the loop.
135
span = valid;
136
}
137
138
size_t totalRead = 0;
139
for (;;) {
140
uint32_t encoderResult;
141
size_t read;
142
size_t written;
143
Tie(encoderResult, read, written) =
144
encoder->EncodeFromUTF8WithoutReplacement(
145
AsBytes(span.From(totalRead)), AsWritableBytes(buffer), true);
146
totalRead += read;
147
auto bufferWritten = buffer.To(written);
148
if (!NS_EscapeURLSpan(bufferWritten, aMask, aOut)) {
149
aOut.Append(bufferWritten);
150
}
151
if (encoderResult == kInputEmpty) {
152
aAppended = true;
153
// Difference between original and current output
154
// string lengths plus extra length
155
return aOut.Length() - origLen + aExtraLen;
156
}
157
if (encoderResult == kOutputFull) {
158
continue;
159
}
160
aOut.AppendLiteral("%26%23");
161
aOut.AppendInt(encoderResult);
162
aOut.AppendLiteral("%3B");
163
}
164
MOZ_RELEASE_ASSERT(
165
false,
166
"There's supposed to be no way out of the above loop except return.");
167
}
168
}
169
170
if (NS_EscapeURLSpan(span, aMask, aOut)) {
171
aAppended = true;
172
// Difference between original and current output
173
// string lengths plus extra length
174
return aOut.Length() - origLen + aExtraLen;
175
}
176
aAppended = false;
177
// Original segment length plus extra length
178
return span.Length() + aExtraLen;
179
}
180
181
const nsACString& nsStandardURL::nsSegmentEncoder::EncodeSegment(
182
const nsACString& str, int16_t mask, nsCString& result) {
183
const char* text;
184
bool encoded;
185
EncodeSegmentCount(str.BeginReading(text), URLSegment(0, str.Length()), mask,
186
result, encoded);
187
if (encoded) return result;
188
return str;
189
}
190
191
//----------------------------------------------------------------------------
192
// nsStandardURL <public>
193
//----------------------------------------------------------------------------
194
195
#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
196
static StaticMutex gAllURLsMutex;
197
static LinkedList<nsStandardURL> gAllURLs;
198
#endif
199
200
nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
201
: mDefaultPort(-1),
202
mPort(-1),
203
mDisplayHost(nullptr),
204
mURLType(URLTYPE_STANDARD),
205
mSupportsFileURL(aSupportsFileURL),
206
mCheckedIfHostA(false) {
207
LOG(("Creating nsStandardURL @%p\n", this));
208
209
// gInitialized changes value only once (false->true) on the main thread.
210
// It's OK to race here because in the worst case we'll just
211
// dispatch a noop runnable to the main thread.
212
MOZ_ASSERT(gInitialized);
213
214
// default parser in case nsIStandardURL::Init is never called
215
mParser = net_GetStdURLParser();
216
217
#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
218
if (NS_IsMainThread()) {
219
if (aTrackURL) {
220
StaticMutexAutoLock lock(gAllURLsMutex);
221
gAllURLs.insertBack(this);
222
}
223
}
224
#endif
225
}
226
227
nsStandardURL::~nsStandardURL() {
228
LOG(("Destroying nsStandardURL @%p\n", this));
229
230
#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
231
{
232
StaticMutexAutoLock lock(gAllURLsMutex);
233
if (isInList()) {
234
remove();
235
}
236
}
237
#endif
238
}
239
240
#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
241
struct DumpLeakedURLs {
242
DumpLeakedURLs() = default;
243
~DumpLeakedURLs();
244
};
245
246
DumpLeakedURLs::~DumpLeakedURLs() {
247
MOZ_ASSERT(NS_IsMainThread());
248
StaticMutexAutoLock lock(gAllURLsMutex);
249
if (!gAllURLs.isEmpty()) {
250
printf("Leaked URLs:\n");
251
for (auto url : gAllURLs) {
252
url->PrintSpec();
253
}
254
gAllURLs.clear();
255
}
256
}
257
#endif
258
259
void nsStandardURL::InitGlobalObjects() {
260
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
261
262
if (gInitialized) {
263
return;
264
}
265
266
gInitialized = true;
267
268
Preferences::AddBoolVarCache(&gPunycodeHost,
269
"network.standard-url.punycode-host", true);
270
nsCOMPtr<nsIIDNService> serv(do_GetService(NS_IDNSERVICE_CONTRACTID));
271
if (serv) {
272
gIDN = serv;
273
}
274
MOZ_DIAGNOSTIC_ASSERT(gIDN);
275
276
// Make sure nsURLHelper::InitGlobals() gets called on the main thread
277
nsCOMPtr<nsIURLParser> parser = net_GetStdURLParser();
278
MOZ_DIAGNOSTIC_ASSERT(parser);
279
Unused << parser;
280
}
281
282
void nsStandardURL::ShutdownGlobalObjects() {
283
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
284
gIDN = nullptr;
285
286
#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
287
if (gInitialized) {
288
// This instanciates a dummy class, and will trigger the class
289
// destructor when libxul is unloaded. This is equivalent to atexit(),
290
// but gracefully handles dlclose().
291
StaticMutexAutoLock lock(gAllURLsMutex);
292
static DumpLeakedURLs d;
293
}
294
#endif
295
}
296
297
//----------------------------------------------------------------------------
298
// nsStandardURL <private>
299
//----------------------------------------------------------------------------
300
301
void nsStandardURL::Clear() {
302
mSpec.Truncate();
303
304
mPort = -1;
305
306
mScheme.Reset();
307
mAuthority.Reset();
308
mUsername.Reset();
309
mPassword.Reset();
310
mHost.Reset();
311
312
mPath.Reset();
313
mFilepath.Reset();
314
mDirectory.Reset();
315
mBasename.Reset();
316
317
mExtension.Reset();
318
mQuery.Reset();
319
mRef.Reset();
320
321
InvalidateCache();
322
}
323
324
void nsStandardURL::InvalidateCache(bool invalidateCachedFile) {
325
if (invalidateCachedFile) {
326
mFile = nullptr;
327
}
328
}
329
330
// Return the number of "dots" in the string, or -1 if invalid. Note that the
331
// number of relevant entries in the bases/starts/ends arrays is number of
332
// dots + 1.
333
// Since the trailing dot is allowed, we pass and adjust "length".
334
//
335
// length is assumed to be <= host.Length(); the callers is responsible for that
336
//
337
// Note that the value returned is guaranteed to be in [-1, 3] range.
338
inline int32_t ValidateIPv4Number(const nsACString& host, int32_t bases[4],
339
int32_t dotIndex[3], bool& onlyBase10,
340
int32_t& length) {
341
MOZ_ASSERT(length <= (int32_t)host.Length());
342
if (length <= 0) {
343
return -1;
344
}
345
346
bool lastWasNumber = false; // We count on this being false for i == 0
347
int32_t dotCount = 0;
348
onlyBase10 = true;
349
350
for (int32_t i = 0; i < length; i++) {
351
char current = host[i];
352
if (current == '.') {
353
if (!lastWasNumber) { // A dot should not follow an X or a dot, or be
354
// first
355
return -1;
356
}
357
358
if (dotCount > 0 &&
359
i == (length - 1)) { // Trailing dot is OK; shorten and return
360
length--;
361
return dotCount;
362
}
363
364
if (dotCount > 2) {
365
return -1;
366
}
367
lastWasNumber = false;
368
dotIndex[dotCount] = i;
369
dotCount++;
370
} else if (current == 'X' || current == 'x') {
371
if (!lastWasNumber || // An X should not follow an X or a dot or be first
372
i == (length - 1) || // No trailing Xs allowed
373
(dotCount == 0 &&
374
i != 1) || // If we had no dots, an X should be second
375
host[i - 1] != '0' || // X should always follow a 0. Guaranteed i >
376
// 0 as lastWasNumber is true
377
(dotCount > 0 &&
378
host[i - 2] != '.')) { // And that zero follows a dot if it exists
379
return -1;
380
}
381
lastWasNumber = false;
382
bases[dotCount] = 16;
383
onlyBase10 = false;
384
385
} else if (current == '0') {
386
if (i < length - 1 && // Trailing zero doesn't signal octal
387
host[i + 1] != '.' && // Lone zero is not octal
388
(i == 0 || host[i - 1] == '.')) { // Zero at start or following a dot
389
// is a candidate for octal
390
bases[dotCount] = 8; // This will turn to 16 above if X shows up
391
onlyBase10 = false;
392
}
393
lastWasNumber = true;
394
395
} else if (current >= '1' && current <= '7') {
396
lastWasNumber = true;
397
398
} else if (current >= '8' && current <= '9') {
399
if (bases[dotCount] == 8) {
400
return -1;
401
}
402
lastWasNumber = true;
403
404
} else if ((current >= 'a' && current <= 'f') ||
405
(current >= 'A' && current <= 'F')) {
406
if (bases[dotCount] != 16) {
407
return -1;
408
}
409
lastWasNumber = true;
410
411
} else {
412
return -1;
413
}
414
}
415
416
return dotCount;
417
}
418
419
inline nsresult ParseIPv4Number10(const nsACString& input, uint32_t& number,
420
uint32_t maxNumber) {
421
uint64_t value = 0;
422
const char* current = input.BeginReading();
423
const char* end = input.EndReading();
424
for (; current < end; ++current) {
425
char c = *current;
426
MOZ_ASSERT(c >= '0' && c <= '9');
427
value *= 10;
428
value += c - '0';
429
}
430
if (value <= maxNumber) {
431
number = value;
432
return NS_OK;
433
}
434
435
// The error case
436
number = 0;
437
return NS_ERROR_FAILURE;
438
}
439
440
inline nsresult ParseIPv4Number(const nsACString& input, int32_t base,
441
uint32_t& number, uint32_t maxNumber) {
442
// Accumulate in the 64-bit value
443
uint64_t value = 0;
444
const char* current = input.BeginReading();
445
const char* end = input.EndReading();
446
switch (base) {
447
case 16:
448
++current;
449
MOZ_FALLTHROUGH;
450
case 8:
451
++current;
452
break;
453
case 10:
454
default:
455
break;
456
}
457
for (; current < end; ++current) {
458
value *= base;
459
char c = *current;
460
MOZ_ASSERT((base == 10 && IsAsciiDigit(c)) ||
461
(base == 8 && c >= '0' && c <= '7') ||
462
(base == 16 && IsAsciiHexDigit(c)));
463
if (IsAsciiDigit(c)) {
464
value += c - '0';
465
} else if (c >= 'a' && c <= 'f') {
466
value += c - 'a' + 10;
467
} else if (c >= 'A' && c <= 'F') {
468
value += c - 'A' + 10;
469
}
470
}
471
472
if (value <= maxNumber) {
473
number = value;
474
return NS_OK;
475
}
476
477
// The error case
478
number = 0;
479
return NS_ERROR_FAILURE;
480
}
481
483
/* static */
484
nsresult nsStandardURL::NormalizeIPv4(const nsACString& host,
485
nsCString& result) {
486
int32_t bases[4] = {10, 10, 10, 10};
487
bool onlyBase10 = true; // Track this as a special case
488
int32_t dotIndex[3]; // The positions of the dots in the string
489
490
// The length may be adjusted by ValidateIPv4Number (ignoring the trailing
491
// period) so use "length", rather than host.Length() after that call.
492
int32_t length = static_cast<int32_t>(host.Length());
493
int32_t dotCount =
494
ValidateIPv4Number(host, bases, dotIndex, onlyBase10, length);
495
if (dotCount < 0 || length <= 0) {
496
return NS_ERROR_FAILURE;
497
}
498
499
// Max values specified by the spec
500
static const uint32_t upperBounds[] = {0xffffffffu, 0xffffffu, 0xffffu,
501
0xffu};
502
uint32_t ipv4;
503
int32_t start = (dotCount > 0 ? dotIndex[dotCount - 1] + 1 : 0);
504
505
nsresult res;
506
// Doing a special case for all items being base 10 gives ~35% speedup
507
res = (onlyBase10
508
? ParseIPv4Number10(Substring(host, start, length - start), ipv4,
509
upperBounds[dotCount])
510
: ParseIPv4Number(Substring(host, start, length - start),
511
bases[dotCount], ipv4, upperBounds[dotCount]));
512
if (NS_FAILED(res)) {
513
return NS_ERROR_FAILURE;
514
}
515
516
int32_t lastUsed = -1;
517
for (int32_t i = 0; i < dotCount; i++) {
518
uint32_t number;
519
start = lastUsed + 1;
520
lastUsed = dotIndex[i];
521
res =
522
(onlyBase10 ? ParseIPv4Number10(
523
Substring(host, start, lastUsed - start), number, 255)
524
: ParseIPv4Number(Substring(host, start, lastUsed - start),
525
bases[i], number, 255));
526
if (NS_FAILED(res)) {
527
return NS_ERROR_FAILURE;
528
}
529
ipv4 += number << (8 * (3 - i));
530
}
531
532
uint8_t ipSegments[4];
533
NetworkEndian::writeUint32(ipSegments, ipv4);
534
result = nsPrintfCString("%d.%d.%d.%d", ipSegments[0], ipSegments[1],
535
ipSegments[2], ipSegments[3]);
536
return NS_OK;
537
}
538
539
nsresult nsStandardURL::NormalizeIDN(const nsACString& host,
540
nsCString& result) {
541
// If host is ACE, then convert to UTF-8. Else, if host is already UTF-8,
542
// then make sure it is normalized per IDN.
543
544
// this function returns true if normalization succeeds.
545
546
result.Truncate();
547
nsresult rv;
548
549
if (!gIDN) {
550
return NS_ERROR_UNEXPECTED;
551
}
552
553
bool isAscii;
554
nsAutoCString normalized;
555
rv = gIDN->ConvertToDisplayIDN(host, &isAscii, normalized);
556
if (NS_FAILED(rv)) {
557
return rv;
558
}
559
560
// The result is ASCII. No need to convert to ACE.
561
if (isAscii) {
562
result = normalized;
563
mCheckedIfHostA = true;
564
mDisplayHost.Truncate();
565
return NS_OK;
566
}
567
568
rv = gIDN->ConvertUTF8toACE(normalized, result);
569
if (NS_FAILED(rv)) {
570
return rv;
571
}
572
573
mCheckedIfHostA = true;
574
mDisplayHost = normalized;
575
576
return NS_OK;
577
}
578
579
bool nsStandardURL::ValidIPv6orHostname(const char* host, uint32_t length) {
580
if (!host || !*host) {
581
// Should not be NULL or empty string
582
return false;
583
}
584
585
if (length != strlen(host)) {
586
// Embedded null
587
return false;
588
}
589
590
bool openBracket = host[0] == '[';
591
bool closeBracket = host[length - 1] == ']';
592
593
if (openBracket && closeBracket) {
594
return net_IsValidIPv6Addr(Substring(host + 1, length - 2));
595
}
596
597
if (openBracket || closeBracket) {
598
// Fail if only one of the brackets is present
599
return false;
600
}
601
602
const char* end = host + length;
603
const char* iter = host;
604
for (; iter != end && *iter; ++iter) {
605
if (ASCIIMask::IsMasked(sInvalidHostChars, *iter)) {
606
return false;
607
}
608
}
609
return true;
610
}
611
612
void nsStandardURL::CoalescePath(netCoalesceFlags coalesceFlag, char* path) {
613
net_CoalesceDirs(coalesceFlag, path);
614
int32_t newLen = strlen(path);
615
if (newLen < mPath.mLen) {
616
int32_t diff = newLen - mPath.mLen;
617
mPath.mLen = newLen;
618
mDirectory.mLen += diff;
619
mFilepath.mLen += diff;
620
ShiftFromBasename(diff);
621
}
622
}
623
624
uint32_t nsStandardURL::AppendSegmentToBuf(char* buf, uint32_t i,
625
const char* str,
626
const URLSegment& segInput,
627
URLSegment& segOutput,
628
const nsCString* escapedStr,
629
bool useEscaped, int32_t* diff) {
630
MOZ_ASSERT(segInput.mLen == segOutput.mLen);
631
632
if (diff) *diff = 0;
633
634
if (segInput.mLen > 0) {
635
if (useEscaped) {
636
MOZ_ASSERT(diff);
637
segOutput.mLen = escapedStr->Length();
638
*diff = segOutput.mLen - segInput.mLen;
639
memcpy(buf + i, escapedStr->get(), segOutput.mLen);
640
} else {
641
memcpy(buf + i, str + segInput.mPos, segInput.mLen);
642
}
643
segOutput.mPos = i;
644
i += segOutput.mLen;
645
} else {
646
segOutput.mPos = i;
647
}
648
return i;
649
}
650
651
uint32_t nsStandardURL::AppendToBuf(char* buf, uint32_t i, const char* str,
652
uint32_t len) {
653
memcpy(buf + i, str, len);
654
return i + len;
655
}
656
657
// basic algorithm:
658
// 1- escape url segments (for improved GetSpec efficiency)
659
// 2- allocate spec buffer
660
// 3- write url segments
661
// 4- update url segment positions and lengths
662
nsresult nsStandardURL::BuildNormalizedSpec(const char* spec,
663
const Encoding* encoding) {
664
// Assumptions: all member URLSegments must be relative the |spec| argument
665
// passed to this function.
666
667
// buffers for holding escaped url segments (these will remain empty unless
668
// escaping is required).
669
nsAutoCString encUsername, encPassword, encHost, encDirectory, encBasename,
670
encExtension, encQuery, encRef;
671
bool useEncUsername, useEncPassword, useEncHost = false, useEncDirectory,
672
useEncBasename, useEncExtension,
673
useEncQuery, useEncRef;
674
nsAutoCString portbuf;
675
676
//
677
// escape each URL segment, if necessary, and calculate approximate normalized
678
// spec length.
679
//
680
// [scheme://][username[:password]@]host[:port]/path[?query_string][#ref]
681
682
uint32_t approxLen = 0;
683
684
// the scheme is already ASCII
685
if (mScheme.mLen > 0)
686
approxLen +=
687
mScheme.mLen + 3; // includes room for "://", which we insert always
688
689
// encode URL segments; convert UTF-8 to origin charset and possibly escape.
690
// results written to encXXX variables only if |spec| is not already in the
691
// appropriate encoding.
692
{
693
nsSegmentEncoder encoder;
694
nsSegmentEncoder queryEncoder(encoding);
695
// Username@
696
approxLen += encoder.EncodeSegmentCount(spec, mUsername, esc_Username,
697
encUsername, useEncUsername, 0);
698
approxLen += 1; // reserve length for @
699
// :password - we insert the ':' even if there's no actual password if
700
// "user:@" was in the spec
701
if (mPassword.mLen > 0) {
702
approxLen += 1 + encoder.EncodeSegmentCount(spec, mPassword, esc_Password,
703
encPassword, useEncPassword);
704
}
705
// mHost is handled differently below due to encoding differences
706
MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
707
if (mPort != -1 && mPort != mDefaultPort) {
708
// :port
709
portbuf.AppendInt(mPort);
710
approxLen += portbuf.Length() + 1;
711
}
712
713
approxLen +=
714
1; // reserve space for possible leading '/' - may not be needed
715
// Should just use mPath? These are pessimistic, and thus waste space
716
approxLen += encoder.EncodeSegmentCount(spec, mDirectory, esc_Directory,
717
encDirectory, useEncDirectory, 1);
718
approxLen += encoder.EncodeSegmentCount(spec, mBasename, esc_FileBaseName,
719
encBasename, useEncBasename);
720
approxLen += encoder.EncodeSegmentCount(spec, mExtension, esc_FileExtension,
721
encExtension, useEncExtension, 1);
722
723
// These next ones *always* add their leading character even if length is 0
724
// Handles items like "http://#"
725
// ?query
726
if (mQuery.mLen >= 0)
727
approxLen += 1 + queryEncoder.EncodeSegmentCount(spec, mQuery, esc_Query,
728
encQuery, useEncQuery);
729
// #ref
730
731
if (mRef.mLen >= 0) {
732
approxLen += 1 + encoder.EncodeSegmentCount(spec, mRef, esc_Ref, encRef,
733
useEncRef);
734
}
735
}
736
737
// do not escape the hostname, if IPv6 address literal, mHost will
738
// already point to a [ ] delimited IPv6 address literal.
739
// However, perform Unicode normalization on it, as IDN does.
740
// Note that we don't disallow URLs without a host - file:, etc
741
if (mHost.mLen > 0) {
742
nsAutoCString tempHost;
743
NS_UnescapeURL(spec + mHost.mPos, mHost.mLen, esc_AlwaysCopy | esc_Host,
744
tempHost);
745
if (tempHost.Contains('\0'))
746
return NS_ERROR_MALFORMED_URI; // null embedded in hostname
747
if (tempHost.Contains(' '))
748
return NS_ERROR_MALFORMED_URI; // don't allow spaces in the hostname
749
nsresult rv = NormalizeIDN(tempHost, encHost);
750
if (NS_FAILED(rv)) {
751
return rv;
752
}
753
if (!SegmentIs(spec, mScheme, "resource") &&
754
!SegmentIs(spec, mScheme, "chrome")) {
755
nsAutoCString ipString;
756
if (encHost.Length() > 0 && encHost.First() == '[' &&
757
encHost.Last() == ']' &&
758
ValidIPv6orHostname(encHost.get(), encHost.Length())) {
759
rv = (nsresult)rusturl_parse_ipv6addr(&encHost, &ipString);
760
if (NS_FAILED(rv)) {
761
return rv;
762
}
763
encHost = ipString;
764
} else if (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) {
765
encHost = ipString;
766
}
767
}
768
769
// NormalizeIDN always copies, if the call was successful.
770
useEncHost = true;
771
approxLen += encHost.Length();
772
773
if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
774
return NS_ERROR_MALFORMED_URI;
775
}
776
} else {
777
// empty host means empty mDisplayHost
778
mDisplayHost.Truncate();
779
mCheckedIfHostA = true;
780
}
781
782
// We must take a copy of every single segment because they are pointing to
783
// the |spec| while we are changing their value, in case we must use
784
// encoded strings.
785
URLSegment username(mUsername);
786
URLSegment password(mPassword);
787
URLSegment host(mHost);
788
URLSegment path(mPath);
789
URLSegment directory(mDirectory);
790
URLSegment basename(mBasename);
791
URLSegment extension(mExtension);
792
URLSegment query(mQuery);
793
URLSegment ref(mRef);
794
795
// The encoded string could be longer than the original input, so we need
796
// to check the final URI isn't longer than the max length.
797
if (approxLen + 1 > (uint32_t)net_GetURLMaxLength()) {
798
return NS_ERROR_MALFORMED_URI;
799
}
800
801
//
802
// generate the normalized URL string
803
//
804
// approxLen should be correct or 1 high
805
if (!mSpec.SetLength(approxLen + 1,
806
fallible)) // buf needs a trailing '\0' below
807
return NS_ERROR_OUT_OF_MEMORY;
808
char* buf = mSpec.BeginWriting();
809
uint32_t i = 0;
810
int32_t diff = 0;
811
812
if (mScheme.mLen > 0) {
813
i = AppendSegmentToBuf(buf, i, spec, mScheme, mScheme);
814
net_ToLowerCase(buf + mScheme.mPos, mScheme.mLen);
815
i = AppendToBuf(buf, i, "://", 3);
816
}
817
818
// record authority starting position
819
mAuthority.mPos = i;
820
821
// append authority
822
if (mUsername.mLen > 0 || mPassword.mLen > 0) {
823
if (mUsername.mLen > 0) {
824
i = AppendSegmentToBuf(buf, i, spec, username, mUsername, &encUsername,
825
useEncUsername, &diff);
826
ShiftFromPassword(diff);
827
} else {
828
mUsername.mLen = -1;
829
}
830
if (password.mLen > 0) {
831
buf[i++] = ':';
832
i = AppendSegmentToBuf(buf, i, spec, password, mPassword, &encPassword,
833
useEncPassword, &diff);
834
ShiftFromHost(diff);
835
} else {
836
mPassword.mLen = -1;
837
}
838
buf[i++] = '@';
839
} else {
840
mUsername.mLen = -1;
841
mPassword.mLen = -1;
842
}
843
if (host.mLen > 0) {
844
i = AppendSegmentToBuf(buf, i, spec, host, mHost, &encHost, useEncHost,
845
&diff);
846
ShiftFromPath(diff);
847
848
net_ToLowerCase(buf + mHost.mPos, mHost.mLen);
849
MOZ_ASSERT(mPort >= -1, "Invalid negative mPort");
850
if (mPort != -1 && mPort != mDefaultPort) {
851
buf[i++] = ':';
852
// Already formatted while building approxLen
853
i = AppendToBuf(buf, i, portbuf.get(), portbuf.Length());
854
}
855
}
856
857
// record authority length
858
mAuthority.mLen = i - mAuthority.mPos;
859
860
// path must always start with a "/"
861
if (mPath.mLen <= 0) {
862
LOG(("setting path=/"));
863
mDirectory.mPos = mFilepath.mPos = mPath.mPos = i;
864
mDirectory.mLen = mFilepath.mLen = mPath.mLen = 1;
865
// basename must exist, even if empty (bug 113508)
866
mBasename.mPos = i + 1;
867
mBasename.mLen = 0;
868
buf[i++] = '/';
869
} else {
870
uint32_t leadingSlash = 0;
871
if (spec[path.mPos] != '/') {
872
LOG(("adding leading slash to path\n"));
873
leadingSlash = 1;
874
buf[i++] = '/';
875
// basename must exist, even if empty (bugs 113508, 429347)
876
if (mBasename.mLen == -1) {
877
mBasename.mPos = basename.mPos = i;
878
mBasename.mLen = basename.mLen = 0;
879
}
880
}
881
882
// record corrected (file)path starting position
883
mPath.mPos = mFilepath.mPos = i - leadingSlash;
884
885
i = AppendSegmentToBuf(buf, i, spec, directory, mDirectory, &encDirectory,
886
useEncDirectory, &diff);
887
ShiftFromBasename(diff);
888
889
// the directory must end with a '/'
890
if (buf[i - 1] != '/') {
891
buf[i++] = '/';
892
mDirectory.mLen++;
893
}
894
895
i = AppendSegmentToBuf(buf, i, spec, basename, mBasename, &encBasename,
896
useEncBasename, &diff);
897
ShiftFromExtension(diff);
898
899
// make corrections to directory segment if leadingSlash
900
if (leadingSlash) {
901
mDirectory.mPos = mPath.mPos;
902
if (mDirectory.mLen >= 0)
903
mDirectory.mLen += leadingSlash;
904
else
905
mDirectory.mLen = 1;
906
}
907
908
if (mExtension.mLen >= 0) {
909
buf[i++] = '.';
910
i = AppendSegmentToBuf(buf, i, spec, extension, mExtension, &encExtension,
911
useEncExtension, &diff);
912
ShiftFromQuery(diff);
913
}
914
// calculate corrected filepath length
915
mFilepath.mLen = i - mFilepath.mPos;
916
917
if (mQuery.mLen >= 0) {
918
buf[i++] = '?';
919
i = AppendSegmentToBuf(buf, i, spec, query, mQuery, &encQuery,
920
useEncQuery, &diff);
921
ShiftFromRef(diff);
922
}
923
if (mRef.mLen >= 0) {
924
buf[i++] = '#';
925
i = AppendSegmentToBuf(buf, i, spec, ref, mRef, &encRef, useEncRef,
926
&diff);
927
}
928
// calculate corrected path length
929
mPath.mLen = i - mPath.mPos;
930
}
931
932
buf[i] = '\0';
933
936
if (SegmentIs(buf, mScheme, "file")) {
937
char* path = &buf[mPath.mPos];
938
if (mPath.mLen >= 3 && path[0] == '/' && IsAsciiAlpha(path[1]) &&
939
path[2] == '|') {
940
buf[mPath.mPos + 2] = ':';
941
}
942
}
943
944
if (mDirectory.mLen > 1) {
945
netCoalesceFlags coalesceFlag = NET_COALESCE_NORMAL;
946
if (SegmentIs(buf, mScheme, "ftp")) {
947
coalesceFlag =
948
(netCoalesceFlags)(coalesceFlag | NET_COALESCE_ALLOW_RELATIVE_ROOT |
949
NET_COALESCE_DOUBLE_SLASH_IS_ROOT);
950
}
951
CoalescePath(coalesceFlag, buf + mDirectory.mPos);
952
}
953
mSpec.Truncate(strlen(buf));
954
NS_ASSERTION(mSpec.Length() <= approxLen,
955
"We've overflowed the mSpec buffer!");
956
MOZ_ASSERT(mSpec.Length() <= (uint32_t)net_GetURLMaxLength(),
957
"The spec should never be this long, we missed a check.");
958
959
MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
960
return NS_OK;
961
}
962
963
bool nsStandardURL::SegmentIs(const URLSegment& seg, const char* val,
964
bool ignoreCase) {
965
// one or both may be null
966
if (!val || mSpec.IsEmpty())
967
return (!val && (mSpec.IsEmpty() || seg.mLen < 0));
968
if (seg.mLen < 0) return false;
969
// if the first |seg.mLen| chars of |val| match, then |val| must
970
// also be null terminated at |seg.mLen|.
971
if (ignoreCase)
972
return !PL_strncasecmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
973
(val[seg.mLen] == '\0');
974
975
return !strncmp(mSpec.get() + seg.mPos, val, seg.mLen) &&
976
(val[seg.mLen] == '\0');
977
}
978
979
bool nsStandardURL::SegmentIs(const char* spec, const URLSegment& seg,
980
const char* val, bool ignoreCase) {
981
// one or both may be null
982
if (!val || !spec) return (!val && (!spec || seg.mLen < 0));
983
if (seg.mLen < 0) return false;
984
// if the first |seg.mLen| chars of |val| match, then |val| must
985
// also be null terminated at |seg.mLen|.
986
if (ignoreCase)
987
return !PL_strncasecmp(spec + seg.mPos, val, seg.mLen) &&
988
(val[seg.mLen] == '\0');
989
990
return !strncmp(spec + seg.mPos, val, seg.mLen) && (val[seg.mLen] == '\0');
991
}
992
993
bool nsStandardURL::SegmentIs(const URLSegment& seg1, const char* val,
994
const URLSegment& seg2, bool ignoreCase) {
995
if (seg1.mLen != seg2.mLen) return false;
996
if (seg1.mLen == -1 || (!val && mSpec.IsEmpty()))
997
return true; // both are empty
998
if (!val) return false;
999
if (ignoreCase)
1000
return !PL_strncasecmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
1001
1002
return !strncmp(mSpec.get() + seg1.mPos, val + seg2.mPos, seg1.mLen);
1003
}
1004
1005
int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
1006
const char* val, uint32_t valLen) {
1007
if (val && valLen) {
1008
if (len == 0)
1009
mSpec.Insert(val, pos, valLen);
1010
else
1011
mSpec.Replace(pos, len, nsDependentCString(val, valLen));
1012
return valLen - len;
1013
}
1014
1015
// else remove the specified segment
1016
mSpec.Cut(pos, len);
1017
return -int32_t(len);
1018
}
1019
1020
int32_t nsStandardURL::ReplaceSegment(uint32_t pos, uint32_t len,
1021
const nsACString& val) {
1022
if (len == 0)
1023
mSpec.Insert(val, pos);
1024
else
1025
mSpec.Replace(pos, len, val);
1026
return val.Length() - len;
1027
}
1028
1029
nsresult nsStandardURL::ParseURL(const char* spec, int32_t specLen) {
1030
nsresult rv;
1031
1032
if (specLen > net_GetURLMaxLength()) {
1033
return NS_ERROR_MALFORMED_URI;
1034
}
1035
1036
//
1037
// parse given URL string
1038
//
1039
rv = mParser->ParseURL(spec, specLen, &mScheme.mPos, &mScheme.mLen,
1040
&mAuthority.mPos, &mAuthority.mLen, &mPath.mPos,
1041
&mPath.mLen);
1042
if (NS_FAILED(rv)) return rv;
1043
1044
#ifdef DEBUG
1045
if (mScheme.mLen <= 0) {
1046
printf("spec=%s\n", spec);
1047
NS_WARNING("malformed url: no scheme");
1048
}
1049
#endif
1050
1051
if (mAuthority.mLen > 0) {
1052
rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
1053
&mUsername.mPos, &mUsername.mLen,
1054
&mPassword.mPos, &mPassword.mLen, &mHost.mPos,
1055
&mHost.mLen, &mPort);
1056
if (NS_FAILED(rv)) return rv;
1057
1058
// Don't allow mPort to be set to this URI's default port
1059
if (mPort == mDefaultPort) mPort = -1;
1060
1061
mUsername.mPos += mAuthority.mPos;
1062
mPassword.mPos += mAuthority.mPos;
1063
mHost.mPos += mAuthority.mPos;
1064
}
1065
1066
if (mPath.mLen > 0) rv = ParsePath(spec, mPath.mPos, mPath.mLen);
1067
1068
return rv;
1069
}
1070
1071
nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos,
1072
int32_t pathLen) {
1073
LOG(("ParsePath: %s pathpos %d len %d\n", spec, pathPos, pathLen));
1074
1075
if (pathLen > net_GetURLMaxLength()) {
1076
return NS_ERROR_MALFORMED_URI;
1077
}
1078
1079
nsresult rv = mParser->ParsePath(spec + pathPos, pathLen, &mFilepath.mPos,
1080
&mFilepath.mLen, &mQuery.mPos, &mQuery.mLen,
1081
&mRef.mPos, &mRef.mLen);
1082
if (NS_FAILED(rv)) return rv;
1083
1084
mFilepath.mPos += pathPos;
1085
mQuery.mPos += pathPos;
1086
mRef.mPos += pathPos;
1087
1088
if (mFilepath.mLen > 0) {
1089
rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
1090
&mDirectory.mPos, &mDirectory.mLen,
1091
&mBasename.mPos, &mBasename.mLen,
1092
&mExtension.mPos, &mExtension.mLen);
1093
if (NS_FAILED(rv)) return rv;
1094
1095
mDirectory.mPos += mFilepath.mPos;
1096
mBasename.mPos += mFilepath.mPos;
1097
mExtension.mPos += mFilepath.mPos;
1098
}
1099
return NS_OK;
1100
}
1101
1102
char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len,
1103
const char* tail) {
1104
// Verify pos and length are within boundaries
1105
if (pos > mSpec.Length()) return nullptr;
1106
if (len < 0) return nullptr;
1107
if ((uint32_t)len > (mSpec.Length() - pos)) return nullptr;
1108
if (!tail) return nullptr;
1109
1110
uint32_t tailLen = strlen(tail);
1111
1112
// Check for int overflow for proposed length of combined string
1113
if (UINT32_MAX - ((uint32_t)len + 1) < tailLen) return nullptr;
1114
1115
char* result = (char*)moz_xmalloc(len + tailLen + 1);
1116
memcpy(result, mSpec.get() + pos, len);
1117
memcpy(result + len, tail, tailLen);
1118
result[len + tailLen] = '\0';
1119
return result;
1120
}
1121
1122
nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream,
1123
URLSegment& seg) {
1124
nsresult rv;
1125
1126
rv = stream->Read32(&seg.mPos);
1127
if (NS_FAILED(rv)) return rv;
1128
1129
rv = stream->Read32((uint32_t*)&seg.mLen);
1130
if (NS_FAILED(rv)) return rv;
1131
1132
return NS_OK;
1133
}
1134
1135
nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream,
1136
const URLSegment& seg) {
1137
nsresult rv;
1138
1139
rv = stream->Write32(seg.mPos);
1140
if (NS_FAILED(rv)) return rv;
1141
1142
rv = stream->Write32(uint32_t(seg.mLen));
1143
if (NS_FAILED(rv)) return rv;
1144
1145
return NS_OK;
1146
}
1147
1148
#define SHIFT_FROM(name, what) \
1149
void nsStandardURL::name(int32_t diff) { \
1150
if (!diff) return; \
1151
if (what.mLen >= 0) { \
1152
CheckedInt<int32_t> pos = what.mPos; \
1153
pos += diff; \
1154
MOZ_ASSERT(pos.isValid()); \
1155
what.mPos = pos.value(); \
1156
}
1157
1158
#define SHIFT_FROM_NEXT(name, what, next) \
1159
SHIFT_FROM(name, what) \
1160
next(diff); \
1161
}
1162
1163
#define SHIFT_FROM_LAST(name, what) \
1164
SHIFT_FROM(name, what) \
1165
}
1166
1167
SHIFT_FROM_NEXT(ShiftFromAuthority, mAuthority, ShiftFromUsername)
1168
SHIFT_FROM_NEXT(ShiftFromUsername, mUsername, ShiftFromPassword)
1169
SHIFT_FROM_NEXT(ShiftFromPassword, mPassword, ShiftFromHost)
1170
SHIFT_FROM_NEXT(ShiftFromHost, mHost, ShiftFromPath)
1171
SHIFT_FROM_NEXT(ShiftFromPath, mPath, ShiftFromFilepath)
1172
SHIFT_FROM_NEXT(ShiftFromFilepath, mFilepath, ShiftFromDirectory)
1173
SHIFT_FROM_NEXT(ShiftFromDirectory, mDirectory, ShiftFromBasename)
1174
SHIFT_FROM_NEXT(ShiftFromBasename, mBasename, ShiftFromExtension)
1175
SHIFT_FROM_NEXT(ShiftFromExtension, mExtension, ShiftFromQuery)
1176
SHIFT_FROM_NEXT(ShiftFromQuery, mQuery, ShiftFromRef)
1177
SHIFT_FROM_LAST(ShiftFromRef, mRef)
1178
1179
//----------------------------------------------------------------------------
1180
// nsStandardURL::nsISupports
1181
//----------------------------------------------------------------------------
1182
1183
NS_IMPL_ADDREF(nsStandardURL)
1184
NS_IMPL_RELEASE(nsStandardURL)
1185
1186
NS_INTERFACE_MAP_BEGIN(nsStandardURL)
1187
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStandardURL)
1188
NS_INTERFACE_MAP_ENTRY(nsIURI)
1189
NS_INTERFACE_MAP_ENTRY(nsIURL)
1190
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileURL, mSupportsFileURL)
1191
NS_INTERFACE_MAP_ENTRY(nsIStandardURL)
1192
NS_INTERFACE_MAP_ENTRY(nsISerializable)
1193
NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
1194
NS_INTERFACE_MAP_ENTRY(nsISensitiveInfoHiddenURI)
1195
// see nsStandardURL::Equals
1196
if (aIID.Equals(kThisImplCID))
1197
foundInterface = static_cast<nsIURI*>(this);
1198
else
1199
NS_INTERFACE_MAP_ENTRY(nsISizeOf)
1200
NS_INTERFACE_MAP_END
1201
1202
//----------------------------------------------------------------------------
1203
// nsStandardURL::nsIURI
1204
//----------------------------------------------------------------------------
1205
1206
// result may contain unescaped UTF-8 characters
1207
NS_IMETHODIMP
1208
nsStandardURL::GetSpec(nsACString& result) {
1209
MOZ_ASSERT(mSpec.Length() <= (uint32_t)net_GetURLMaxLength(),
1210
"The spec should never be this long, we missed a check.");
1211
nsresult rv = NS_OK;
1212
if (gPunycodeHost) {
1213
result = mSpec;
1214
} else { // XXX: This code path may be slow
1215
rv = GetDisplaySpec(result);
1216
}
1217
return rv;
1218
}
1219
1220
// result may contain unescaped UTF-8 characters
1221
NS_IMETHODIMP
1222
nsStandardURL::GetSensitiveInfoHiddenSpec(nsACString& result) {
1223
nsresult rv = GetSpec(result);
1224
if (NS_FAILED(rv)) {
1225
return rv;
1226
}
1227
if (mPassword.mLen > 0) {
1228
result.ReplaceLiteral(mPassword.mPos, mPassword.mLen, "****");
1229
}
1230
return NS_OK;
1231
}
1232
1233
// result may contain unescaped UTF-8 characters
1234
NS_IMETHODIMP
1235
nsStandardURL::GetSpecIgnoringRef(nsACString& result) {
1236
// URI without ref is 0 to one char before ref
1237
if (mRef.mLen < 0) {
1238
return GetSpec(result);
1239
}
1240
1241
URLSegment noRef(0, mRef.mPos - 1);
1242
result = Segment(noRef);
1243
1244
MOZ_ASSERT(mCheckedIfHostA);
1245
if (!gPunycodeHost && !mDisplayHost.IsEmpty()) {
1246
result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
1247
}
1248
1249
return NS_OK;
1250
}
1251
1252
nsresult nsStandardURL::CheckIfHostIsAscii() {
1253
nsresult rv;
1254
if (mCheckedIfHostA) {
1255
return NS_OK;
1256
}
1257
1258
mCheckedIfHostA = true;
1259
1260
if (!gIDN) {
1261
return NS_ERROR_NOT_INITIALIZED;
1262
}
1263
1264
nsAutoCString displayHost;
1265
bool isAscii;
1266
rv = gIDN->ConvertToDisplayIDN(Host(), &isAscii, displayHost);
1267
if (NS_FAILED(rv)) {
1268
mDisplayHost.Truncate();
1269
mCheckedIfHostA = false;
1270
return rv;
1271
}
1272
1273
if (!isAscii) {
1274
mDisplayHost = displayHost;
1275
}
1276
1277
return NS_OK;
1278
}
1279
1280
NS_IMETHODIMP
1281
nsStandardURL::GetDisplaySpec(nsACString& aUnicodeSpec) {
1282
aUnicodeSpec.Assign(mSpec);
1283
MOZ_ASSERT(mCheckedIfHostA);
1284
if (!mDisplayHost.IsEmpty()) {
1285
aUnicodeSpec.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
1286
}
1287
1288
return NS_OK;
1289
}
1290
1291
NS_IMETHODIMP
1292
nsStandardURL::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
1293
nsAutoCString unicodeHostPort;
1294
1295
nsresult rv = GetDisplayHost(unicodeHostPort);
1296
if (NS_FAILED(rv)) {
1297
return rv;
1298
}
1299
1300
if (StringBeginsWith(Hostport(), NS_LITERAL_CSTRING("["))) {
1301
aUnicodeHostPort.AssignLiteral("[");
1302
aUnicodeHostPort.Append(unicodeHostPort);
1303
aUnicodeHostPort.AppendLiteral("]");
1304
} else {
1305
aUnicodeHostPort.Assign(unicodeHostPort);
1306
}
1307
1308
uint32_t pos = mHost.mPos + mHost.mLen;
1309
if (pos < mPath.mPos)
1310
aUnicodeHostPort += Substring(mSpec, pos, mPath.mPos - pos);
1311
1312
return NS_OK;
1313
}
1314
1315
NS_IMETHODIMP
1316
nsStandardURL::GetDisplayHost(nsACString& aUnicodeHost) {
1317
MOZ_ASSERT(mCheckedIfHostA);
1318
if (mDisplayHost.IsEmpty()) {
1319
return GetAsciiHost(aUnicodeHost);
1320
}
1321
1322
aUnicodeHost = mDisplayHost;
1323
return NS_OK;
1324
}
1325
1326
// result may contain unescaped UTF-8 characters
1327
NS_IMETHODIMP
1328
nsStandardURL::GetPrePath(nsACString& result) {
1329
result = Prepath();
1330
MOZ_ASSERT(mCheckedIfHostA);
1331
if (!gPunycodeHost && !mDisplayHost.IsEmpty()) {
1332
result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
1333
}
1334
return NS_OK;
1335
}
1336
1337
// result may contain unescaped UTF-8 characters
1338
NS_IMETHODIMP
1339
nsStandardURL::GetDisplayPrePath(nsACString& result) {
1340
result = Prepath();
1341
MOZ_ASSERT(mCheckedIfHostA);
1342
if (!mDisplayHost.IsEmpty()) {
1343
result.Replace(mHost.mPos, mHost.mLen, mDisplayHost);
1344
}
1345
return NS_OK;
1346
}
1347
1348
// result is strictly US-ASCII
1349
NS_IMETHODIMP
1350
nsStandardURL::GetScheme(nsACString& result) {
1351
result = Scheme();
1352
return NS_OK;
1353
}
1354
1355
// result may contain unescaped UTF-8 characters
1356
NS_IMETHODIMP
1357
nsStandardURL::GetUserPass(nsACString& result) {
1358
result = Userpass();
1359
return NS_OK;
1360
}
1361
1362
// result may contain unescaped UTF-8 characters
1363
NS_IMETHODIMP
1364
nsStandardURL::GetUsername(nsACString& result) {
1365
result = Username();
1366
return NS_OK;
1367
}
1368
1369
// result may contain unescaped UTF-8 characters
1370
NS_IMETHODIMP
1371
nsStandardURL::GetPassword(nsACString& result) {
1372
result = Password();
1373
return NS_OK;
1374
}
1375
1376
NS_IMETHODIMP
1377
nsStandardURL::GetHostPort(nsACString& result) {
1378
nsresult rv;
1379
if (gPunycodeHost) {
1380
rv = GetAsciiHostPort(result);
1381
} else {
1382
rv = GetDisplayHostPort(result);
1383
}
1384
return rv;
1385
}
1386
1387
NS_IMETHODIMP
1388
nsStandardURL::GetHost(nsACString& result) {
1389
nsresult rv;
1390
if (gPunycodeHost) {
1391
rv = GetAsciiHost(result);
1392
} else {
1393
rv = GetDisplayHost(result);
1394
}
1395
return rv;
1396
}
1397
1398
NS_IMETHODIMP
1399
nsStandardURL::GetPort(int32_t* result) {
1400
// should never be more than 16 bit
1401
MOZ_ASSERT(mPort <= std::numeric_limits<uint16_t>::max());
1402
*result = mPort;
1403
return NS_OK;
1404
}
1405
1406
// result may contain unescaped UTF-8 characters
1407
NS_IMETHODIMP
1408
nsStandardURL::GetPathQueryRef(nsACString& result) {
1409
result = Path();
1410
return NS_OK;
1411
}
1412
1413
// result is ASCII
1414
NS_IMETHODIMP
1415
nsStandardURL::GetAsciiSpec(nsACString& result) {
1416
result = mSpec;
1417
return NS_OK;
1418
}
1419
1420
// result is ASCII
1421
NS_IMETHODIMP
1422
nsStandardURL::GetAsciiHostPort(nsACString& result) {
1423
result = Hostport();
1424
return NS_OK;
1425
}
1426
1427
// result is ASCII
1428
NS_IMETHODIMP
1429
nsStandardURL::GetAsciiHost(nsACString& result) {
1430
result = Host();
1431
return NS_OK;
1432
}
1433
1434
static bool IsSpecialProtocol(const nsACString& input) {
1435
nsACString::const_iterator start, end;
1436
input.BeginReading(start);
1437
nsACString::const_iterator iterator(start);
1438
input.EndReading(end);
1439
1440
while (iterator != end && *iterator != ':') {
1441
iterator++;
1442
}
1443
1444
nsAutoCString protocol(nsDependentCSubstring(start.get(), iterator.get()));
1445
1446
return protocol.LowerCaseEqualsLiteral("http") ||
1447
protocol.LowerCaseEqualsLiteral("https") ||
1448
protocol.LowerCaseEqualsLiteral("ftp") ||
1449
protocol.LowerCaseEqualsLiteral("ws") ||
1450
protocol.LowerCaseEqualsLiteral("wss") ||
1451
protocol.LowerCaseEqualsLiteral("file") ||
1452
protocol.LowerCaseEqualsLiteral("gopher");
1453
}
1454
1455
nsresult nsStandardURL::SetSpecInternal(const nsACString& input) {
1456
return SetSpecWithEncoding(input, nullptr);
1457
}
1458
1459
nsresult nsStandardURL::SetSpecWithEncoding(const nsACString& input,
1460
const Encoding* encoding) {
1461
const nsPromiseFlatCString& flat = PromiseFlatCString(input);
1462
LOG(("nsStandardURL::SetSpec [spec=%s]\n", flat.get()));
1463
1464
if (input.Length() > (uint32_t)net_GetURLMaxLength()) {
1465
return NS_ERROR_MALFORMED_URI;
1466
}
1467
1468
// filter out unexpected chars "\r\n\t" if necessary
1469
nsAutoCString filteredURI;
1470
net_FilterURIString(flat, filteredURI);
1471
1472
if (filteredURI.Length() == 0) {
1473
return NS_ERROR_MALFORMED_URI;
1474
}
1475
1476
// Make a backup of the curent URL
1477
nsStandardURL prevURL(false, false);
1478
prevURL.CopyMembers(this, eHonorRef, EmptyCString());
1479
Clear();
1480
1481
if (IsSpecialProtocol(filteredURI)) {
1482
// Bug 652186: Replace all backslashes with slashes when parsing paths
1483
// Stop when we reach the query or the hash.
1484
auto start = filteredURI.BeginWriting();
1485
auto end = filteredURI.EndWriting();
1486
while (start != end) {
1487
if (*start == '?' || *start == '#') {
1488
break;
1489
}
1490
if (*start == '\\') {
1491
*start = '/';
1492
}
1493
start++;
1494
}
1495
}
1496
1497
const char* spec = filteredURI.get();
1498
int32_t specLength = filteredURI.Length();
1499
1500
// parse the given URL...
1501
nsresult rv = ParseURL(spec, specLength);
1502
if (NS_SUCCEEDED(rv)) {
1503
// finally, use the URLSegment member variables to build a normalized
1504
// copy of |spec|
1505
rv = BuildNormalizedSpec(spec, encoding);
1506
}
1507
1508
// Make sure that a URLTYPE_AUTHORITY has a non-empty hostname.
1509
if (mURLType == URLTYPE_AUTHORITY && mHost.mLen == -1) {
1510
rv = NS_ERROR_MALFORMED_URI;
1511
}
1512
1513
if (NS_FAILED(rv)) {
1514
Clear();
1515
// If parsing the spec has failed, restore the old URL
1516
// so we don't end up with an empty URL.
1517
CopyMembers(&prevURL, eHonorRef, EmptyCString());
1518
return rv;
1519
}
1520
1521
if (LOG_ENABLED()) {
1522
LOG((" spec = %s\n", mSpec.get()));
1523
LOG((" port = %d\n", mPort));
1524
LOG((" scheme = (%u,%d)\n", mScheme.mPos, mScheme.mLen));
1525
LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen));
1526
LOG((" username = (%u,%d)\n", mUsername.mPos, mUsername.mLen));
1527
LOG((" password = (%u,%d)\n", mPassword.mPos, mPassword.mLen));
1528
LOG((" hostname = (%u,%d)\n", mHost.mPos, mHost.mLen));
1529
LOG((" path = (%u,%d)\n", mPath.mPos, mPath.mLen));
1530
LOG((" filepath = (%u,%d)\n", mFilepath.mPos, mFilepath.mLen));
1531
LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen));
1532
LOG((" basename = (%u,%d)\n", mBasename.mPos, mBasename.mLen));
1533
LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen));
1534
LOG((" query = (%u,%d)\n", mQuery.mPos, mQuery.mLen));
1535
LOG((" ref = (%u,%d)\n", mRef.mPos, mRef.mLen));
1536
}
1537
1538
return rv;
1539
}
1540
1541
nsresult nsStandardURL::SetScheme(const nsACString& input) {
1542
const nsPromiseFlatCString& scheme = PromiseFlatCString(input);
1543
1544
LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
1545
1546
if (scheme.IsEmpty()) {
1547
NS_WARNING("cannot remove the scheme from an url");
1548
return NS_ERROR_UNEXPECTED;
1549
}
1550
if (mScheme.mLen < 0) {
1551
NS_WARNING("uninitialized");
1552
return NS_ERROR_NOT_INITIALIZED;
1553
}
1554
1555
if (!net_IsValidScheme(scheme)) {
1556
NS_WARNING("the given url scheme contains invalid characters");
1557
return NS_ERROR_UNEXPECTED;
1558
}
1559
1560
if (mSpec.Length() + input.Length() - Scheme().Length() >
1561
(uint32_t)net_GetURLMaxLength()) {
1562
return NS_ERROR_MALFORMED_URI;
1563
}
1564
1565
InvalidateCache();
1566
1567
int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
1568
1569
if (shift) {
1570
mScheme.mLen = scheme.Length();
1571
ShiftFromAuthority(shift);
1572
}
1573
1574
// ensure new scheme is lowercase
1575
//
1576
// XXX the string code unfortunately doesn't provide a ToLowerCase
1577
// that operates on a substring.
1578
net_ToLowerCase((char*)mSpec.get(), mScheme.mLen);
1579
return NS_OK;
1580
}
1581
1582
nsresult nsStandardURL::SetUserPass(const nsACString& input) {
1583
const nsPromiseFlatCString& userpass = PromiseFlatCString(input);
1584
1585
LOG(("nsStandardURL::SetUserPass [userpass=%s]\n", userpass.get()));
1586
1587
if (mURLType == URLTYPE_NO_AUTHORITY) {
1588
if (userpass.IsEmpty()) return NS_OK;
1589
NS_WARNING("cannot set user:pass on no-auth url");
1590
return NS_ERROR_UNEXPECTED;
1591
}
1592
if (mAuthority.mLen < 0) {
1593
NS_WARNING("uninitialized");
1594
return NS_ERROR_NOT_INITIALIZED;
1595
}
1596
1597
if (mSpec.Length() + input.Length() - Userpass(true).Length() >
1598
(uint32_t)net_GetURLMaxLength()) {
1599
return NS_ERROR_MALFORMED_URI;
1600
}
1601
1602
InvalidateCache();
1603
1604
NS_ASSERTION(mHost.mLen >= 0, "uninitialized");
1605
1606
nsresult rv;
1607
uint32_t usernamePos, passwordPos;
1608
int32_t usernameLen, passwordLen;
1609
1610
rv = mParser->ParseUserInfo(userpass.get(), userpass.Length(), &usernamePos,
1611
&usernameLen, &passwordPos, &passwordLen);
1612
if (NS_FAILED(rv)) return rv;
1613
1614
// build new user:pass in |buf|
1615
nsAutoCString buf;
1616
if (usernameLen > 0 || passwordLen > 0) {
1617
nsSegmentEncoder encoder;
1618
bool ignoredOut;
1619
usernameLen = encoder.EncodeSegmentCount(
1620
userpass.get(), URLSegment(usernamePos, usernameLen),
1621
esc_Username | esc_AlwaysCopy, buf, ignoredOut);
1622
if (passwordLen > 0) {
1623
buf.Append(':');
1624
passwordLen = encoder.EncodeSegmentCount(
1625
userpass.get(), URLSegment(passwordPos, passwordLen),
1626
esc_Password | esc_AlwaysCopy, buf, ignoredOut);
1627
} else {
1628
passwordLen = -1;
1629
}
1630
if (mUsername.mLen < 0 && mPassword.mLen < 0) {
1631
buf.Append('@');
1632
}
1633
}
1634
1635
int32_t shift = 0;
1636
1637
if (mUsername.mLen < 0 && mPassword.mLen < 0) {
1638
// no existing user:pass
1639
if (!buf.IsEmpty()) {
1640
mSpec.Insert(buf, mHost.mPos);
1641
mUsername.mPos = mHost.mPos;
1642
shift = buf.Length();
1643
}
1644
} else {
1645
// replace existing user:pass
1646
uint32_t userpassLen = 0;
1647
if (mUsername.mLen > 0) {
1648
userpassLen += mUsername.mLen;
1649
}
1650
if (mPassword.mLen > 0) {
1651
userpassLen += (mPassword.mLen + 1);
1652
}
1653
if (buf.IsEmpty()) {
1654
// remove `@` character too
1655
userpassLen++;
1656
}
1657
mSpec.Replace(mAuthority.mPos, userpassLen, buf);
1658
shift = buf.Length() - userpassLen;
1659
}
1660
if (shift) {
1661
ShiftFromHost(shift);
1662
MOZ_DIAGNOSTIC_ASSERT(mAuthority.mLen >= -shift);
1663
mAuthority.mLen += shift;
1664
}
1665
// update positions and lengths
1666
mUsername.mLen = usernameLen > 0 ? usernameLen : -1;
1667
mUsername.mPos = mAuthority.mPos;
1668
mPassword.mLen = passwordLen > 0 ? passwordLen : -1;
1669
if (passwordLen > 0) {
1670
if (mUsername.mLen > 0) {
1671
mPassword.mPos = mUsername.mPos + mUsername.mLen + 1;
1672
} else {
1673
mPassword.mPos = mAuthority.mPos + 1;
1674
}
1675
}
1676
1677
MOZ_ASSERT(mUsername.mLen != 0 && mPassword.mLen != 0);
1678
return NS_OK;
1679
}
1680
1681
nsresult nsStandardURL::SetUsername(const nsACString& input) {
1682
const nsPromiseFlatCString& username = PromiseFlatCString(input);
1683
1684
LOG(("nsStandardURL::SetUsername [username=%s]\n", username.get()));
1685
1686
if (mURLType == URLTYPE_NO_AUTHORITY) {
1687
if (username.IsEmpty()) return NS_OK;
1688
NS_WARNING("cannot set username on no-auth url");
1689
return NS_ERROR_UNEXPECTED;
1690
}
1691
1692
if (mSpec.Length() + input.Length() - Username().Length() >
1693
(uint32_t)net_GetURLMaxLength()) {
1694