/[dotgnu-pnet]/pnetlib/System/Uri.cs
ViewVC logotype

Contents of /pnetlib/System/Uri.cs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.32 - (show annotations) (download)
Fri Nov 22 19:06:29 2002 UTC (21 years, 5 months ago) by t3rmin4t0r
Branch: MAIN
CVS Tags: r_0_4_8, r_0_5_0, r_0_5_2, r_0_5_4
Changes since 1.31: +48 -20 lines
fixes to tests and libs so that the testsuites run

1 /*
2 * Uri.cs - Implementation of "System.Uri".
3 *
4 * Copyright (C) 2002 Free Software Foundation, Inc.
5 * Copyright (C) 2002 Gerard Toonstra.
6 * Copyright (C) 2002 Rich Baumann.
7 *
8 * Contributed by Stephen Compall <rushing@sigecom.net>
9 * Contributions by Gerard Toonstra <toonstra@ntlworld.com>
10 * Contributions by Rich Baumann <biochem333@nyc.rr.com>
11 * Contributions by Gopal V <gopalv82@symonds.net>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 */
27
28 namespace System
29 {
30
31 using System.IO;
32 using System.Net;
33 using System.Text;
34 using System.Net.Sockets;
35
36 /*
37 global TODO:
38 * adapt to use the authority struct
39 */
40
41 public class Uri : MarshalByRefObject
42 {
43
44 // the capital letters are between 0x41 and 0x5A
45 // the lowercase letters are between 0x61 and 0x7A
46 // the numbers are between 0x30 and 0x39
47 // .=2E +=2B -=2D
48 // beware, for they are valid scheme chars
49 // now I don't need the string anymore FOR VALIDSCHEMECHARS
50
51 // magic strings...
52 public static readonly String SchemeDelimiter = "://";
53 public static readonly String UriSchemeFile = "file";
54 public static readonly String UriSchemeFtp = "ftp";
55 public static readonly String UriSchemeGopher = "gopher";
56 public static readonly String UriSchemeHttp = "http";
57 public static readonly String UriSchemeHttps = "https";
58 public static readonly String UriSchemeMailto = "mailto";
59 public static readonly String UriSchemeNews = "news";
60 public static readonly String UriSchemeNntp = "nntp";
61
62 // end magic strings
63
64 // state. mostly the same as UriBuilder
65
66
67 // here are some of chiraz's extras
68 // note that I did not make them capitalised; I do not want
69 // namespace conflict
70 // use this.path instead of absolutePath
71 private String absoluteUri; // the absolute uri to the
72 // resource as originally
73 // passed to the constructor
74 // (don't use)
75 // is true if the user had already escaped the URL before it was
76 // passed into the constructor. (escaped was true).
77 // however, if the user did escape it, but didn't tell the
78 // constructor, it is up to the system to detect whether or not
79 // it was escaped.
80 private bool userEscaped;
81 private UriHostNameType hostNameType;
82 // authority, isdefaultport, isloopback
83
84 // Holds the scheme information. (search 0->:)
85 // doesn't contain ://
86 private String scheme;
87
88 // authority = userinfo+host+port
89 // userinfo = username:password
90 private String userinfo;
91 // host does not contain the port
92 private String host;
93 // if IsDefaultPort, don't need to print! -1 for none
94 private int port;
95
96 // technically optional, but they want a path :)
97 // contains the slash
98 private String path;
99
100 // doesn't contain the ? mark
101 private String query;
102
103 // remember: this is not part of the uri
104 // doesn't contain the #
105 private String fragment;
106 // also known as hash
107
108 // end of state
109
110 // Constructors.
111 public Uri(String uriString) : this(uriString, false)
112 {
113 }
114
115 public Uri(String uriString, bool dontEscape)
116 {
117 if (uriString == null)
118 {
119 throw new ArgumentNullException("uriString");
120 }
121
122 userEscaped = dontEscape;
123 this.absoluteUri = uriString.Trim();
124
125 this.Parse();
126 this.Canonicalize();
127 }
128
129 public Uri(Uri baseUri, String relativeUri) : this(baseUri, relativeUri, false)
130 {
131 }
132
133 public Uri(Uri baseUri, String relativeUri, bool dontEscape)
134 {
135 if (baseUri== null)
136 throw new ArgumentNullException("baseUri");
137
138 if (relativeUri== null)
139 throw new ArgumentNullException("relativeUri");
140
141 // Making local copies that we use for modification
142 String myBaseUri = baseUri.AbsoluteUri.Trim();
143 String myRelativeUri = relativeUri.Trim();
144 userEscaped = dontEscape;
145
146 int newlastchar;
147 for (newlastchar = myBaseUri.Length;
148 myBaseUri[--newlastchar] == '/';)
149 ; // empty body
150 myBaseUri = myBaseUri.Substring(0, newlastchar + 1);
151
152 for (newlastchar = -1; myRelativeUri[++newlastchar] == '/';)
153 ; // empty body
154 myRelativeUri = myRelativeUri.Substring(newlastchar);
155
156 this.absoluteUri = String.Concat(myBaseUri, "/", myRelativeUri);
157
158 this.Parse();
159 this.Canonicalize();
160 }
161
162 // methods
163 protected virtual void Canonicalize()
164 {
165 // TODO: `replace' this with something more efficient, i.e.
166 // scan like Flex and output to a StringBuilder(path.Length)
167 this.path = this.path.Replace('\\', '/');
168 while (this.path.IndexOf("//") >= 0) // double-slashes to strip
169 {
170 this.path = this.path.Replace("//", "/");
171 }
172
173 // find out if .. dirs are present
174 if (path.IndexOf("/../") > -1 || path.EndsWith("/..")
175 || path.IndexOf("/./") > -1 || path.EndsWith("/."))
176 {
177 path = StripMetaDirectories(path);
178 }
179
180 // remove the slash at the end, unless it's alone
181 int psize = path.Length; // efficiency
182 if (psize > 1)
183 {
184 if (path[psize-1] == '/')
185 path = path.Substring(0, psize-1);
186 }
187 else
188 path = "/";
189 }
190
191 // The following takes . or .. directories out of an absolute
192 // path. Throws UriFormatException if the ..s try to extend
193 // beyond the root dir.
194 private static String StripMetaDirectories(String oldpath)
195 {
196 // use toBeRemoved when you detect a .., and need to
197 // remove previous directories because of it. i.e.,
198 // /gd/gnuorg/../.. will (going backwards) make
199 // toBeRemoved=2 on the .. couple, then decrement it
200 // by deleting gnuorg and gd
201 int toBeRemoved = 0;
202
203 // in abspath, dirs[0] is ""
204 String[] dirs = oldpath.Split('/');
205
206 // the scanner will set not-shown to "" to make the
207 // tests at the end faster
208 // scan all but 0
209 for (int curDir = dirs.Length; --curDir >= 1;)
210 {
211 if (dirs[curDir] == "..")
212 {
213 ++toBeRemoved;
214 // removed w/o affecting toBeRemoved
215 dirs[curDir] = null;
216 }
217 else if (dirs[curDir] == ".")
218 dirs[curDir] = null; // doesn't affect anything
219 else if (toBeRemoved > 0) // remove this one
220 {
221 --toBeRemoved;
222 dirs[curDir] = null;
223 }
224 // if normal state (no .., normal dir) do nothing
225 }
226
227 if(dirs[0].Length==0) // leading slash
228 {
229 dirs[0]=null;
230 }
231
232 if (toBeRemoved > 0) // wants to delete root
233 throw new UriFormatException
234 (S._("Arg_UriPathAbs"));
235
236 StringBuilder newpath = new StringBuilder(oldpath.Length);
237 foreach (String dir in dirs)
238 if (dir!=null) // visible?
239 newpath.Append('/').Append(dir);
240
241 // we always must have at least a slash
242 // special case: if the last one is "invisible", add
243 // a slash, because it is the directory mark of the
244 // previous item
245 if (newpath.Length == 0
246 || dirs[dirs.Length-1].Length == 0)
247 newpath.Append('/');
248 return newpath.ToString();
249 }
250
251 public static UriHostNameType CheckHostName(String name)
252 {
253 if (name == null || name.Length == 0)
254 return UriHostNameType.Unknown;
255
256 bool isDns = true;
257 foreach (String tok in name.Split('.'))
258 {
259 if(tok.Length==0) // ".." case
260 {
261 isDns = false;
262 break;
263 }
264 if (!Char.IsLetter(tok, 0)
265 || !Char.IsLetterOrDigit(tok, tok.Length - 1)
266 || !CharsAreAlnumDash(tok, 1, tok.Length - 2))
267 {
268 isDns = false;
269 break;
270 }
271 }
272 if (isDns)
273 return UriHostNameType.Dns;
274
275 // TODO: make this more efficient (hint: IPAddress is in
276 // same assembly)
277 try
278 {
279 System.Net.IPAddress.Parse(name);
280 return UriHostNameType.IPv4;
281 }
282 catch (FormatException)
283 {
284 // not IPv4
285 }
286
287 // IPv6, see http://search.ietf.org/internet-drafts/draft-ietf-ipngwg-addr-arch-v3-09.txt
288 // section 2.2, page 4, for my source in implementation
289 // TODO: change to some IPng class/struct
290 try
291 {
292 String[] parts = name.Split(':');
293 int dex = parts.Length;
294
295 // 128-bit. The spec allows the last two words
296 // to be specified by the old IPv4 method
297 // can't be more than 8 parts
298 // can't be less than 3 parts
299 if (parts.Length > 8 || parts.Length < 3)
300 throw new FormatException();
301
302 // presence of . in the last element means it
303 // *must* be the special IPv4
304 bool usingIPv4maybe = false;
305 if (parts[parts.Length - 1].IndexOf('.') >= 0)
306 {
307 // only 6 to check now
308 dex -= 2;
309 usingIPv4maybe = true;
310 }
311
312 // *once* in an IPv6 addr, you can specify 0
313 // across a range by leaving the part blank,
314 // i.e. ::0, ::, F1F0::207.241.30.1
315 bool usedZeroCompress = false;
316 while (--dex >= 0) // check all hexes
317 {
318 int pos = 0;
319 // handle special case to avoid parsing
320 // if we had a zerocompress in pos 1
321 // or we have zerocompress in next-to-last
322 // then the last can be empty
323 if ((dex == 0
324 && parts[0].Length == 0
325 && parts[1].Length == 0)
326 || (dex == parts.Length - 1
327 && parts[dex].Length == 0
328 && parts[dex-1].Length == 0))
329 continue;
330
331 // check for 0 compress
332 if (parts[dex].Length == 0 && dex != 0)
333 {
334 // may only use once
335 if (usedZeroCompress)
336 throw new FormatException();
337 else
338 usedZeroCompress = true;
339 }
340
341 parseHexWord(parts[dex], ref pos);
342
343 // if parseHexWord doesn't move pos to
344 // the end, there are bad chars
345 if (pos != parts[dex].Length)
346 throw new FormatException();
347 }
348
349 // we should have had enough items!
350 if (!usedZeroCompress
351 && ((usingIPv4maybe && parts.Length < 7)
352 || (!usingIPv4maybe && parts.Length < 8)))
353 throw new FormatException();
354
355 // check the last element if believed IPv4
356 if (usingIPv4maybe)
357 // will throw FormatException if bad
358 System.Net.IPAddress.Parse(parts[parts.Length - 1]);
359
360 // if we got this far, it's fine
361 return UriHostNameType.IPv6;
362 }
363 catch (FormatException)
364 {
365 // not IPv6
366 }
367
368 return UriHostNameType.Unknown;
369 }
370
371 // check if characters in a String in a given range are
372 // alphanumeric or -.
373 private static bool CharsAreAlnumDash(String checkthis,
374 int first,
375 int last)
376 {
377 char check;
378 for (; first <= last; ++first)
379 {
380 check = checkthis[first];
381 if (!Char.IsLetterOrDigit(check) && check != '-')
382 return false;
383 }
384 return true;
385 }
386
387 // Takes a location in a string, and returns a ushort if it can be
388 // hex-parsed into a, well, ushort. Throws FormatException if index
389 // doesn't point to a hex char (will fix index).
390 private static ushort parseHexWord(String src, ref int index)
391 {
392 int buildretval = 0;
393 int stop= (index+4 <=src.Length) ? (index+4) : src.Length;
394 while(index < stop)
395 {
396 if (!IsHexDigit(src[index]))
397 break;
398 buildretval <<= 4;
399 buildretval |= FromHex(src[index]);
400 index++;
401 }
402 if (stop != index)
403 throw new FormatException(S._("Arg_HexDigit"));
404 return (ushort)buildretval;
405 }
406
407 public static bool CheckSchemeName(String schemeName)
408 {
409 if (schemeName == null || schemeName.Length == 0)
410 return false;
411
412 if (!Char.IsLetter(schemeName[0]))
413 return false;
414 for (int i = 0; ++i < schemeName.Length;) // starts with 1
415 {
416 if (!Uri.isValidSchemeChar(schemeName[i]))
417 return false;
418 }
419 return true;
420 }
421
422 // support for above method
423 private static bool isValidSchemeChar(char character)
424 {
425 return (
426 // check letters
427 (character >= 'a' && character <= 'z') ||
428 (character >= 'A' && character <= 'Z') ||
429 // check numbers
430 (character >= '0' && character <= '9') ||
431 // check the other three
432 character == '.' || character == '+' || character == '-'
433 );
434 }
435
436 protected virtual void CheckSecurity()
437 {
438 // do nothing in base class
439 }
440
441 public override bool Equals(Object comparand)
442 {
443 Uri rurib;
444 if (comparand == null
445 || (!(comparand is String)
446 && !(comparand is Uri)))
447 return false;
448 else if (comparand is String)
449 rurib = new Uri((String)comparand);
450 else if (comparand is Uri)
451 rurib = (Uri)comparand;
452 else
453 return false;
454
455 // do not check query and fragment
456 // this makes the boolean
457 return (String.Equals(this.Host, rurib.Host)
458 && String.Equals(this.AbsolutePath,
459 rurib.AbsolutePath)
460 && String.Equals(this.Scheme, rurib.Scheme));
461 }
462
463 protected virtual void Escape()
464 {
465 this.path = EscapeString(this.path);
466 }
467
468 protected static String EscapeString(String str)
469 {
470 if (str == null || str.Length == 0)
471 return "";
472
473 // assume that all characters are OK for escaping
474 // must change code for editable URI
475 // also, does not see if string already escaped
476 char chk;
477 StringBuilder ret = new StringBuilder(str.Length);
478 for (int i = 0; i < str.Length; i++)
479 {
480 chk = str[i];
481 if (IsExcludedCharacter(chk) || IsReserved(chk))
482 ret.Append(HexEscape(chk));
483 else
484 ret.Append(chk);
485 }
486 return ret.ToString();
487 }
488
489 public static int FromHex(char digit)
490 {
491 if (digit >= '0' && digit <= '9')
492 return digit - '0';
493 else if (digit >= 'A' && digit <= 'F')
494 return digit - 55;
495 else if (digit >= 'a' && digit <= 'f')
496 return digit - 87;
497 else
498 throw new ArgumentException(S._("Arg_HexDigit"), "digit");
499 }
500
501 public override int GetHashCode()
502 {
503 String full = this.ToString();
504 int hash = full.IndexOf('#');
505 if (hash == -1)
506 return full.GetHashCode();
507 else
508 return full.Substring(0, hash).GetHashCode();
509 }
510
511 public String GetLeftPart(UriPartial part)
512 {
513 switch (part)
514 {
515 case UriPartial.Path:
516 return this.ToStringNoFragQuery();
517 case UriPartial.Authority:
518 return String.Concat(this.scheme,
519 this.schemeDelim(),
520 this.Authority);
521 case UriPartial.Scheme:
522 return String.Concat(this.scheme,
523 this.schemeDelim());
524 default:
525 throw new ArgumentException(S._("Arg_UriPartial"));
526 }
527 }
528
529 // gets proper delimiter for current scheme
530 private String schemeDelim()
531 {
532 if (String.Equals(this.scheme, "mailto"))
533 return ":";
534 else
535 return "://";
536 }
537
538 public static String HexEscape(char character)
539 {
540 if (character > 255)
541 throw new ArgumentOutOfRangeException("character");
542 char[] maker = new char[3];
543 maker[0] = '%';
544 maker[1] = HexForIndex(character >> 4);
545 // 0b00001111 == 0x0F == 15
546 maker[2] = HexForIndex(character & 15);
547 return new String(maker);
548 }
549
550 // support for above method, no error checking
551 private static char HexForIndex(int index)
552 {
553 if (index <= 9)
554 return (char)(index + '0');
555 else
556 return (char)(index + 55);
557 }
558
559 public static char HexUnescape(String pattern, ref int index)
560 {
561 char mychar;
562
563 if ((pattern.Length < (index + 3)) || (index < 0))
564 {
565 throw new ArgumentOutOfRangeException();
566 }
567 if (IsHexEncoding(pattern, index))
568 {
569 if (pattern[index+1] >= 0x41)
570 {
571 mychar = (char)(pattern[index+1] - 0x41 + 10);
572 }
573 else if (pattern[index+1] >= 0x61)
574 {
575 mychar = (char)(pattern[index+1] - 0x61 + 10);
576 }
577 else
578 {
579 mychar = (char)(pattern[index+1] - 0x30);
580 }
581
582 mychar = (char)(mychar << 4);
583
584 if (pattern[index+2] >= 0x41)
585 {
586 mychar = (char)(mychar +pattern[index+2] - 0x41 + 10);
587 }
588 else if (pattern[index+1] >= 0x61)
589 {
590 mychar = (char)(mychar + pattern[index+2] - 0x61 + 10);
591 }
592 else
593 {
594 mychar = (char)(mychar + pattern[index+2] - 0x30);
595 }
596
597 return mychar;
598 }
599 else
600 {
601 return pattern[index];
602 }
603 }
604
605 protected virtual bool IsBadFileSystemCharacter(char character)
606 {
607 return (Array.IndexOf(Path.InvalidPathChars, character)
608 >= 0);
609 }
610
611 protected static bool IsExcludedCharacter(char character)
612 {
613 return (character < 0x20 || character > 0x7F
614 || "<>#%\"{}|\\^[]`".IndexOf(character) >= 0);
615 }
616
617 public static bool IsHexDigit(char character)
618 {
619 return
620 (
621 (character >= '0' && character <= '9')
622 || (character >= 'A' && character <= 'F')
623 || (character >= 'a' && character <= 'f')
624 );
625 }
626
627 public static bool IsHexEncoding(String pattern, int index)
628 {
629 if (index >= 0 && pattern.Length - index >= 3)
630 return ((pattern[index] == '%') &&
631 IsHexDigit(pattern[index+1]) &&
632 IsHexDigit(pattern[index+2]));
633 else
634 return false;
635 }
636
637 // ECMA specifies that "IsReservedCharacter" is virtual,
638 // even though it doesn't make much sense. We need the
639 // method in some static contexts, so we define this
640 // private version also.
641 private static bool IsReserved(char character)
642 {
643 return (";/:@&=+$,".IndexOf(character) >= 0);
644 }
645
646 protected virtual bool IsReservedCharacter(char character)
647 {
648 return IsReserved(character);
649 }
650
651 // TODO: test
652 // this should return a string of the argument uri's address, relative to this uri's address
653 // (Rich Baumann - biochem333@nyc.rr.com)
654 [TODO]
655 public String MakeRelative(Uri toUri)
656 {
657 if (this.host.Equals(toUri.host))
658 {
659 if (this.path.Equals(toUri.path)) { return ""; } // return empty string... URIs are identical
660
661 String[] thisUri = this.path.Split('/'); // split the path up at the / chars
662 String[] otherUri = toUri.path.Split('/'); // now make tokens for the other uri
663
664 int currentItem = 0; // loop var
665 StringBuilder myStringBuilder = new StringBuilder(toUri.path.Length); // temp var (return)
666
667 while ((currentItem < thisUri.Length) && (currentItem < otherUri.Length))
668 {
669 if (!(thisUri[currentItem].Equals(otherUri[currentItem]))) // check for uri deviations
670 {
671 break; // if not equal, we've found deviation
672 }
673 ++currentItem;
674 }
675
676 // this part assumes that blah/blah/ is never given as blah/blah and vice-versa
677 // if this assumption is in error, I don't see how to figure out how many ../ are needed
678 bool thisFile = !(this.path.EndsWith("/")); // ends with a file...
679 bool otherFile = !(toUri.path.EndsWith("/")); // ...or a path segment
680
681 int tmp = thisUri.Length - currentItem - (thisFile ? 2 : 1); // calculate # of ../ needed
682 int tmp2 = otherUri.Length - currentItem - 1; // calculate # of tokens left in otherUri
683
684 for (int i = 0; i < tmp; i++) { myStringBuilder.Append("../"); } // add needed ../
685
686 for (int i = 0; i < tmp2; i++, currentItem++) // go through all remaining otherUri tokens
687 {
688 myStringBuilder.Append(otherUri[currentItem]); // add next part of path
689 myStringBuilder.Append('/'); // add path separator
690 }
691
692 if (otherFile) { myStringBuilder.Remove(myStringBuilder.Length - 1, 1); } // ends with a file... strip last /
693
694 return myStringBuilder.ToString(); // return relative
695 }
696 else
697 {
698 return toUri.AbsoluteUri; // return absolute... URIs are on on different hosts
699 }
700 }
701
702 protected virtual void Parse()
703 {
704 int curpos = absoluteUri.IndexOf(':');
705 int nextpos = 0;
706
707 // Set all to nothing just in case info was left behind
708 // somewhere somehow... TODO remove
709 path = "";
710 fragment = "";
711 host = "";
712 port = -1;
713 query = "";
714 scheme = "";
715 userinfo = "";
716
717
718 if (curpos > 0) // scheme specified (or a port delim)
719 {
720 String maybescheme = absoluteUri.Substring(0, curpos).ToLower();
721
722 // not giving a scheme is equivalent to "http"
723 if (!CheckSchemeName(maybescheme))
724 {
725 try
726 {
727 new UriAuthority(this.scheme);
728 }
729 catch (UriFormatException) // not authority
730 {
731 throw new UriFormatException
732 (S._("Arg_UriScheme"));
733 }
734 // else is authority
735 this.scheme = "http";
736 this.port = 80;
737 }
738 else // ok, it's a real scheme
739 {
740 this.scheme=AbsoluteUri.Substring(0,curpos);
741 // some Uris don't use the // after scheme:
742 if (String.Compare(AbsoluteUri, curpos,
743 SchemeDelimiter, 0, 3) == 0)
744 curpos += 3;
745 else
746 ++curpos;
747 }
748 }
749 else // scheme not specified
750 {
751 this.scheme = "http";
752 this.port = 80;
753 }
754 // end of scheme parsing
755 // curpos is now at the authority
756
757 // put nextpos post-authority
758 nextpos = absoluteUri.IndexOfAny(new char[]{'/', '?', '#'},
759 curpos);
760 if (nextpos < 0)
761 nextpos = absoluteUri.Length;
762
763 // even if the "scheme" was an authority, we have to
764 // redo because we cut off the potential port (:)
765 UriAuthority tryauth = new UriAuthority
766 (absoluteUri.Substring(curpos, nextpos - curpos));
767
768 this.userinfo = tryauth.userinfo;
769 this.host = tryauth.hostname;
770 this.port = tryauth.port;
771 this.hostNameType = tryauth.hosttype;
772 curpos = nextpos;
773
774 if (nextpos < absoluteUri.Length) // implies curpos also
775 {
776 nextpos = absoluteUri.IndexOf('?', curpos);
777 if (nextpos >= 0)
778 {
779 // there is query mark
780 query = absoluteUri.Substring(nextpos + 1);
781 }
782 else
783 {
784 nextpos = absoluteUri.IndexOf('#', curpos);
785 if (nextpos >= 0) // there is fragment
786 fragment = absoluteUri.Substring(nextpos + 1);
787 }
788 if (nextpos == -1) // no path nor query
789 path = absoluteUri.Substring(curpos);
790 else
791 path = absoluteUri.Substring(curpos, nextpos - curpos);
792 }
793
794 if (!userEscaped)
795 {
796 if (needsEscaping(path,false))
797 // Escape() only affects path
798 this.Escape();
799 if (needsEscaping(query,false)) //query = *( uchar | reserved )
800 query = EscapeString(query);
801 if (needsEscaping(fragment,false)) //fragment = *(uchar|reserved)
802 fragment = EscapeString(fragment);
803 }
804 else // user should have escaped
805 {
806 if (needsEscaping(path,false)
807 || needsEscaping(query,true)
808 || needsEscaping(fragment,true))
809 throw new UriFormatException(S._("Arg_UriNotEscaped"));
810 }
811
812 }
813
814 public override String ToString()
815 {
816 StringBuilder myStringBuilder = new StringBuilder(absoluteUri.Length);
817
818 myStringBuilder.Append(this.scheme);
819
820 myStringBuilder.Append(this.schemeDelim());
821
822 if (this.userinfo.Length > 0)
823 {
824 myStringBuilder.Append(this.userinfo);
825 myStringBuilder.Append('@');
826 }
827
828 myStringBuilder.Append(host);
829
830 if (this.port >= 0)
831 {
832 myStringBuilder.Append(':');
833 myStringBuilder.Append(this.port);
834 }
835
836 myStringBuilder.Append(PathAndQuery);
837
838 if (this.fragment.Length > 0)
839 {
840 myStringBuilder.Append('#');
841 myStringBuilder.Append(this.fragment);
842 }
843
844 return Unescape(myStringBuilder.ToString());
845 }
846
847 protected virtual String Unescape(String path)
848 {
849 StringBuilder retStr = new StringBuilder(path.Length);
850 int afterPrevPcntSignIndex = 0;
851
852 for (int lastPcntSignIndex = path.IndexOf('%');
853 lastPcntSignIndex >= 0;
854 lastPcntSignIndex = path.IndexOf('%', lastPcntSignIndex))
855 {
856 // append string up to % sign
857 retStr.Append(path, afterPrevPcntSignIndex,
858 lastPcntSignIndex-afterPrevPcntSignIndex);
859 // get the hex character, or just %, and append
860 retStr.Append(HexUnescapeWithUTF8(path, ref lastPcntSignIndex));
861 afterPrevPcntSignIndex = lastPcntSignIndex;
862 }
863 // then push on the rest of the string
864 return retStr.Append(path, afterPrevPcntSignIndex,
865 path.Length - afterPrevPcntSignIndex).ToString();
866 // and return it
867 }
868
869 private static char HexUnescapeWithUTF8(String path, ref int pcntSignIndex)
870 {
871 if (IsHexEncoding(path, pcntSignIndex))
872 {
873 char c1 = HexUnescape(path, ref pcntSignIndex); // changes pcntSignIndex
874
875 switch (UTF8SizeFor1stByte(c1))
876 {
877 case 2:
878 if (IsHexEncoding(path, pcntSignIndex)) // 2nd byte is Hex encoding
879 {
880 int psiCopy = pcntSignIndex; // save in case not 2-byte UTF8
881 char c2 = HexUnescape(path, ref psiCopy);
882 if ((c2 & 0xC0) == 0x80) // is UTF8 2-byte?
883 {
884 pcntSignIndex = psiCopy;
885 return (char)(((c1 & 0x1F) << 6) | (c2 & 0x3F)); // build
886 }
887 }
888 // else all
889 goto default;
890 case 3:
891 if (path.Length - pcntSignIndex >= 6 && IsHexEncoding(path, pcntSignIndex)
892 && IsHexEncoding(path, pcntSignIndex+3)) // 2nd and 3rd bytes are hex encoded
893 {
894 int psiCopy = pcntSignIndex; // save again
895 // psiCopy will change to compensate
896 char c2 = HexUnescape(path, ref psiCopy), c3 = HexUnescape(path, ref psiCopy);
897 if ((c2 & 0xC0) == 0x80 && (c3 & 0xC0) == 0x80) // is UTF8 3-byte?
898 {
899 pcntSignIndex = psiCopy;
900 return (char)(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6)
901 | (c3 & 0x3F)); // build
902 }
903 }
904 // else all
905 goto default;
906 default:
907 return c1;
908 } // switch
909 }
910 else // following % sign is not hex
911 {
912 return path[pcntSignIndex++]; // don't reread char, return % (or whatever)
913 }
914 }
915
916 private static int UTF8SizeFor1stByte(char c)
917 {
918 if ((c & 0x80) == 0)
919 return 1;
920 else if ((c & 0xE0) == 0xC0)
921 return 2;
922 else if ((c & 0xF0) == 0xE0)
923 return 3;
924 else
925 return 0;
926 }
927
928 // properties
929 public String AbsolutePath
930 {
931 get
932 {
933 return this.path;
934 }
935 }
936
937 public String AbsoluteUri
938 {
939 get
940 {
941 return this.absoluteUri;
942 }
943 }
944
945 public String Authority
946 {
947 get
948 {
949 StringBuilder authret = new StringBuilder();
950 if (this.userinfo.Length > 0)
951 authret.Append(this.userinfo).Append('@');
952 authret.Append(host);
953 if (!this.IsDefaultPort)
954 authret.Append(':').Append(this.port);
955 return authret.ToString();
956 }
957 }
958
959 public String Fragment
960 {
961 get
962 {
963 if (this.fragment.Length == 0)
964 return this.fragment;
965 else
966 return String.Concat("#", this.fragment);
967 }
968 }
969
970 public String Host
971 {
972 get
973 {
974 return this.host;
975 }
976 }
977
978 public UriHostNameType HostNameType
979 {
980 get
981 {
982 return this.hostNameType;
983 }
984 }
985
986 public bool IsDefaultPort
987 {
988 get
989 {
990 try
991 {
992 return (this.port == -1 || this.port == Uri.DefaultPortForScheme(this.scheme));
993 }
994 catch (ArgumentException ae)
995 {
996 return false;
997 }
998 }
999 }
1000
1001 public bool IsFile
1002 {
1003 get
1004 {
1005 return String.Equals(this.scheme, Uri.UriSchemeFile);
1006 }
1007 }
1008
1009 public bool IsLoopback
1010 {
1011 get
1012 {
1013 try
1014 {
1015 IPAddress ip=IPAddress.Parse(this.host);
1016 return IPAddress.IsLoopback(ip);
1017 }
1018 catch(FormatException) //must be a name
1019 {
1020 try
1021 {
1022 IPHostEntry iph = Dns.GetHostByName(this.host);
1023 foreach(IPAddress ip in iph.AddressList)
1024 {
1025 if(IPAddress.IsLoopback(ip))return true;
1026 }
1027 }
1028 catch(SocketException) // cannot resolve name either
1029 {
1030 return false;
1031 }
1032 }
1033 return false; // no way out now
1034 }
1035 }
1036
1037 public String LocalPath
1038 {
1039 get
1040 {
1041 if (String.Equals(this.scheme, Uri.UriSchemeFile) &&
1042 Path.DirectorySeparatorChar != '/')
1043 return this.path.Replace('/', Path.DirectorySeparatorChar);
1044 else
1045 return this.path;
1046 }
1047 }
1048
1049 public String PathAndQuery
1050 {
1051 get
1052 {
1053 String abspath = this.AbsolutePath;
1054 if (String.Equals(abspath, ""))
1055 return this.Query;
1056 else if (String.Equals(this.query, ""))
1057 return abspath;
1058 else
1059 return String.Concat(this.path, "?", this.query);
1060 }
1061 }
1062
1063 public int Port
1064 {
1065 get
1066 {
1067 if (this.port > -1)
1068 return this.port;
1069 else
1070 {
1071 try
1072 {
1073 return Uri.DefaultPortForScheme(this.scheme);
1074 }
1075 catch (ArgumentException ae)
1076 {
1077 // also means don't know
1078 return -1;
1079 }
1080 }
1081 }
1082 }
1083
1084 public String Query
1085 {
1086 get
1087 {
1088 // gets with the ?
1089 if (this.query == "")
1090 return this.query;
1091 else
1092 return String.Concat("?", this.query);
1093 }
1094 }
1095
1096 public String Scheme
1097 {
1098 get
1099 {
1100 return this.scheme;
1101 }
1102 }
1103
1104 public bool UserEscaped
1105 {
1106 get
1107 {
1108 return this.userEscaped;
1109 }
1110 }
1111
1112 public string UserInfo
1113 {
1114 get
1115 {
1116 return this.userinfo;
1117 }
1118 }
1119
1120
1121 // my junk
1122
1123 // to group the authority stuff together
1124 private struct UriAuthority
1125 {
1126 public String userinfo, hostname;
1127 public int port;
1128 public UriHostNameType hosttype;
1129
1130 public UriAuthority(String authority)
1131 {
1132 int interimpos1=0, interimpos2=0;
1133
1134 // check for userinfo delimiter
1135 interimpos1 = authority.IndexOf('@');
1136 if (interimpos1 > 0)
1137 {
1138 this.userinfo = authority.Substring
1139 (0, interimpos1);
1140
1141 interimpos2 = interimpos1 + 1;
1142 }
1143 else
1144 {
1145 this.userinfo = "";
1146 }
1147
1148 // check remainder for an explicit port
1149 interimpos1 = authority.IndexOf(':', interimpos2);
1150 if (interimpos1 > 0)
1151 {
1152 this.hostname = authority.Substring
1153 (interimpos2, interimpos1
1154 - interimpos2);
1155 try
1156 {
1157 // technically, ports are 16 bit,
1158 // but...
1159 this.port = Int32.Parse
1160 (authority.Substring
1161 (interimpos1 + 1));
1162 }
1163 // this is not a real port, just use
1164 // default
1165 catch (FormatException fe)
1166 {
1167 this.port = -1;
1168 }
1169 // the number is too big
1170 catch (OverflowException oe)
1171 {
1172 throw new UriFormatException
1173 (S._("Arg_UriPort"));
1174 }
1175 }
1176 else // no port indicated
1177 {
1178 // use rest of string
1179 this.hostname = authority.Substring
1180 (interimpos2);
1181
1182 this.port = -1;
1183 }
1184
1185 // now test host, standard says must be IPv4 or DNS
1186 this.hosttype = CheckHostName(this.hostname);
1187 if (this.hosttype != UriHostNameType.Dns
1188 && this.hosttype != UriHostNameType.IPv4)
1189 {
1190 throw new UriFormatException
1191 (S._("Arg_UriHostName"));
1192 }
1193 }
1194 }
1195
1196 // use this to get the default port for the scheme
1197 // makes it really easy to add support for new schemes
1198 // just use a switch/case or something in implementation
1199 internal static int DefaultPortForScheme(String scheme)
1200 {
1201 // We have to do this with if statements because switch
1202 // cannot use "readonly" fields as case labels.
1203 if(scheme == Uri.UriSchemeFile)
1204 return -1;
1205 else if(scheme == Uri.UriSchemeFtp)
1206 return 21;
1207 else if(scheme == Uri.UriSchemeGopher)
1208 return 70;
1209 else if(scheme == Uri.UriSchemeHttp)
1210 return 80;
1211 else if(scheme == Uri.UriSchemeHttps)
1212 return 443;
1213 else if(scheme == Uri.UriSchemeMailto)
1214 return 25;
1215 else if(scheme == Uri.UriSchemeNews)
1216 return 119;
1217 else if(scheme == Uri.UriSchemeNntp)
1218 return 119;
1219 else
1220 throw new ArgumentException();
1221 }
1222
1223 // for use by UriBuilder
1224 internal static String impl_EscapeString(String str)
1225 {
1226 return EscapeString(str);
1227 }
1228
1229 /* do not escape reserved chars for paths */
1230 internal static bool needsEscaping(String instr,bool reservedCheck)
1231 {
1232 char c;
1233 for (int i = 0; i < instr.Length; i++)
1234 {
1235 c = instr[i];
1236 if (IsExcludedCharacter(c) || (IsReserved(c) && reservedCheck))
1237 return true;
1238 }
1239 return false;
1240 }
1241
1242 // for use by comparators, which want none of this
1243 internal String ToStringNoFragQuery()
1244 {
1245 return Uri.ToStringNoFragQuery(this.ToString());
1246 }
1247
1248 internal static String ToStringNoFragQuery(String uri)
1249 {
1250 int queryloc = uri.IndexOf('?');
1251 int hashloc = uri.IndexOf('#');
1252 if (queryloc <= hashloc && queryloc != -1)
1253 return uri.Substring(0, queryloc);
1254 else if (hashloc > -1)
1255 return uri.Substring(0, hashloc);
1256 else
1257 return uri;
1258 }
1259 }; // class Uri
1260
1261 }; // namespace System

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26