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