1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt3Support module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "q3url.h"
43 
44 #ifndef QT_NO_URL
45 
46 #include "q3cstring.h"
47 #include "qdir.h"
48 
49 QT_BEGIN_NAMESPACE
50 
51 // used by q3filedialog.cpp
52 bool qt_resolve_symlinks = true;
53 
54 class Q3UrlPrivate
55 {
56 public:
57     QString protocol;
58     QString user;
59     QString pass;
60     QString host;
61     QString path, cleanPath;
62     QString refEncoded;
63     QString queryEncoded;
64     bool isValid;
65     int port;
66     bool cleanPathDirty;
67 };
68 
69 /*!
70     Replaces backslashes with slashes and removes multiple occurrences
71     of slashes or backslashes if \c allowMultiple is false.
72 */
73 
slashify(QString & s,bool allowMultiple=true)74 static void slashify( QString& s, bool allowMultiple = true )
75 {
76     bool justHadSlash = false;
77     for ( int i = 0; i < (int)s.length(); i++ ) {
78 	if ( !allowMultiple && justHadSlash &&
79 	     ( s[ i ] == QLatin1Char('/') || s[ i ] == QLatin1Char('\\') ) ) {
80 	    s.remove( i, 1 );
81 	    --i;
82 	    continue;
83 	}
84 	if ( s[ i ] == QLatin1Char('\\') )
85 	    s[ i ] = QLatin1Char('/');
86 #if defined (Q_WS_MAC9)
87 	if ( s[ i ] == QLatin1Char(':') && (i == (int)s.length()-1 || s[ i + 1 ] != QLatin1Char('/') ) ) //mac colon's go away, unless after a protocol
88 		s[ i ] = QLatin1Char('/');
89 #endif
90 	if ( s[ i ] == QLatin1Char('/') )
91 	    justHadSlash = true;
92 	else
93 	    justHadSlash = false;
94     }
95 }
96 
97 
98 
99 /*!
100     \class Q3Url
101     \brief The Q3Url class provides a URL parser and simplifies working with URLs.
102 
103     \compat
104 
105     The Q3Url class is provided for simple work with URLs. It can
106     parse, decode, encode, etc.
107 
108     Q3Url works with the decoded path and encoded query in turn.
109 
110     Example:
111 
112     <tt>http://example.com:80/cgi-bin/test%20me.pl?cmd=Hello%20you</tt>
113 
114     \table
115     \header \i Function	    \i Returns
116     \row \i \l protocol()   \i "http"
117     \row \i \l host()	    \i "example.com"
118     \row \i \l port()	    \i 80
119     \row \i \l path()	    \i "/cgi-bin/test&nbsp;me.pl"
120     \row \i \l fileName()   \i "test&nbsp;me.pl"
121     \row \i \l query()	    \i "cmd=Hello%20you"
122     \endtable
123 
124     Example:
125 
126     <tt>http://qt.nokia.com/doc/qdockarea.html#lines</tt>
127 
128     \table
129     \header \i Function	    \i Returns
130     \row \i \l protocol()   \i "http"
131     \row \i \l host()	    \i "qt.nokia.com"
132     \row \i \l fileName()   \i "doc/qdockarea.html"
133     \row \i \l ref()	    \i "lines"
134     \endtable
135 
136     The individual parts of a URL can be set with setProtocol(),
137     setHost(), setPort(), setPath(), setFileName(), setRef() and
138     setQuery(). A URL could contain, for example, an ftp address which
139     requires a user name and password; these can be set with setUser()
140     and setPassword().
141 
142     Because path is always encoded internally you must not use "%00"
143     in the path, although this is okay (but not recommended) for the
144     query.
145 
146     Q3Url is normally used like this:
147 
148     \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 0
149 
150     You can then access and manipulate the various parts of the URL.
151 
152     To make it easy to work with Q3Urls and QStrings, Q3Url implements
153     the necessary cast and assignment operators so you can do
154     following:
155 
156     \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 1
157 
158     Use the static functions, encode() and decode() to encode or
159     decode a URL in a string. (They operate on the string in-place.)
160     The isRelativeUrl() static function returns true if the given
161     string is a relative URL.
162 
163     If you want to use a URL to work on a hierarchical structure (e.g.
164     a local or remote filesystem), you might want to use the subclass
165     Q3UrlOperator.
166 
167     \sa Q3UrlOperator
168 */
169 
170 
171 /*!
172     Constructs an empty URL that is invalid.
173 */
174 
Q3Url()175 Q3Url::Q3Url()
176 {
177     d = new Q3UrlPrivate;
178     d->isValid = false;
179     d->port = -1;
180     d->cleanPathDirty = true;
181 }
182 
183 /*!
184     Constructs a URL by parsing the string \a url.
185 
186     If you pass a string like "/home/qt", the "file" protocol is
187     assumed.
188 */
189 
Q3Url(const QString & url)190 Q3Url::Q3Url( const QString& url )
191 {
192     d = new Q3UrlPrivate;
193     d->protocol = QLatin1String("file");
194     d->port = -1;
195     parse( url );
196 }
197 
198 /*!
199     Copy constructor. Copies the data of \a url.
200 */
201 
Q3Url(const Q3Url & url)202 Q3Url::Q3Url( const Q3Url& url )
203 {
204     d = new Q3UrlPrivate;
205     *d = *url.d;
206 }
207 
208 /*!
209     Returns true if \a url is relative; otherwise returns false.
210 */
211 
isRelativeUrl(const QString & url)212 bool Q3Url::isRelativeUrl( const QString &url )
213 {
214     int colon = url.find( QLatin1Char(':') );
215     int slash = url.find( QLatin1Char('/') );
216 
217     return ( slash != 0 && ( colon == -1 || ( slash != -1 && colon > slash ) ) );
218 }
219 
220 /*!
221     Constructs an URL taking \a url as the base (context) and
222     \a relUrl as a relative URL to \a url. If \a relUrl is not relative,
223     \a relUrl is taken as the new URL.
224 
225     For example, the path of
226     \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 2
227     will be "/qt/srource/qt-2.1.0.tar.gz".
228 
229     On the other hand,
230     \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 3
231     will result in a new URL, "ftp://ftp.qt.nokia.com/usr/local",
232     because "/usr/local" isn't relative.
233 
234     Similarly,
235     \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 4
236     will result in a new URL, with "/usr/local" as the path
237     and "file" as the protocol.
238 
239     Normally it is expected that the path of \a url points to a
240     directory, even if the path has no slash at the end. But if you
241     want the constructor to handle the last part of the path as a file
242     name if there is no slash at the end, and to let it be replaced by
243     the file name of \a relUrl (if it contains one), set \a checkSlash
244     to true.
245 */
246 
Q3Url(const Q3Url & url,const QString & relUrl,bool checkSlash)247 Q3Url::Q3Url( const Q3Url& url, const QString& relUrl, bool checkSlash )
248 {
249     d = new Q3UrlPrivate;
250     QString rel = relUrl;
251     slashify( rel );
252 
253     Q3Url urlTmp( url );
254     if ( !urlTmp.isValid() ) {
255 	urlTmp.reset();
256     }
257     if ( isRelativeUrl( rel ) ) {
258 	if ( rel[ 0 ] == QLatin1Char('#') ) {
259 	    *this = urlTmp;
260 	    rel.remove( (uint)0, 1 );
261 	    decode( rel );
262 	    setRef( rel );
263 	} else if ( rel[ 0 ] == QLatin1Char('?') ) {
264 	    *this = urlTmp;
265 	    rel.remove( (uint)0, 1 );
266 	    setQuery( rel );
267 	} else {
268 	    decode( rel );
269 	    *this = urlTmp;
270 	    setRef( QString() );
271 	    if ( checkSlash && d->cleanPath[(int)path().length()-1] != QLatin1Char('/') ) {
272 		if ( isRelativeUrl( path() ) )
273 		    setEncodedPathAndQuery( rel );
274 		else
275 		    setFileName( rel );
276 	    } else {
277 		QString p = urlTmp.path();
278 		if ( p.isEmpty() ) {
279 		    // allow URLs like "file:foo"
280 		    if ( !d->host.isEmpty() && !d->user.isEmpty() && !d->pass.isEmpty() )
281 			p = QLatin1String("/");
282 		}
283 		if ( !p.isEmpty() && !p.endsWith(QLatin1Char('/')) )
284 		    p += QLatin1Char('/');
285 		p += rel;
286 		d->path = p;
287 		d->cleanPathDirty = true;
288 	    }
289 	}
290     } else {
291 	if ( rel[ 0 ] == QChar( QLatin1Char('/') ) ) {
292 	    *this = urlTmp;
293 	    setEncodedPathAndQuery( rel );
294 	} else {
295 	    *this = rel;
296 	}
297     }
298 }
299 
300 /*!
301     Destructor.
302 */
303 
~Q3Url()304 Q3Url::~Q3Url()
305 {
306     delete d;
307     d = 0;
308 }
309 
310 /*!
311     Returns the protocol of the URL. Typically, "file", "http", "ftp",
312     etc.
313 
314     \sa setProtocol()
315 */
316 
protocol() const317 QString Q3Url::protocol() const
318 {
319     return d->protocol;
320 }
321 
322 /*!
323     Sets the protocol of the URL to \a protocol. Typically, "file",
324     "http", "ftp", etc.
325 
326     \sa protocol()
327 */
328 
setProtocol(const QString & protocol)329 void Q3Url::setProtocol( const QString& protocol )
330 {
331     d->protocol = protocol;
332     if ( hasHost() )
333 	d->isValid = true;
334 }
335 
336 /*!
337     Returns the username of the URL.
338 
339     \sa setUser() setPassword()
340 */
341 
user() const342 QString Q3Url::user() const
343 {
344     return  d->user;
345 }
346 
347 /*!
348     Sets the username of the URL to \a user.
349 
350     \sa user() setPassword()
351 */
352 
setUser(const QString & user)353 void Q3Url::setUser( const QString& user )
354 {
355     d->user = user;
356 }
357 
358 /*!
359     Returns true if the URL contains a username; otherwise returns
360     false.
361 
362     \sa setUser() setPassword()
363 */
364 
hasUser() const365 bool Q3Url::hasUser() const
366 {
367     return !d->user.isEmpty();
368 }
369 
370 /*!
371     Returns the password of the URL.
372 
373     \warning Passwords passed in URLs are normally \e insecure; this
374     is due to the mechanism, not because of Qt.
375 
376     \sa setPassword() setUser()
377 */
378 
password() const379 QString Q3Url::password() const
380 {
381     return d->pass;
382 }
383 
384 /*!
385     Sets the password of the URL to \a pass.
386 
387     \warning Passwords passed in URLs are normally \e insecure; this
388     is due to the mechanism, not because of Qt.
389 
390     \sa password() setUser()
391 */
392 
setPassword(const QString & pass)393 void Q3Url::setPassword( const QString& pass )
394 {
395     d->pass = pass;
396 }
397 
398 /*!
399     Returns true if the URL contains a password; otherwise returns
400     false.
401 
402     \warning Passwords passed in URLs are normally \e insecure; this
403     is due to the mechanism, not because of Qt.
404 
405     \sa setPassword() setUser()
406 */
407 
hasPassword() const408 bool Q3Url::hasPassword() const
409 {
410     return !d->pass.isEmpty();
411 }
412 
413 /*!
414     Returns the hostname of the URL.
415 
416     \sa setHost() hasHost()
417 */
418 
host() const419 QString Q3Url::host() const
420 {
421     return d->host;
422 }
423 
424 /*!
425     Sets the hostname of the URL to \a host.
426 
427     \sa host() hasHost()
428 */
429 
setHost(const QString & host)430 void Q3Url::setHost( const QString& host )
431 {
432     d->host = host;
433     if ( !d->protocol.isNull() && d->protocol != QLatin1String("file") )
434 	d->isValid = true;
435 }
436 
437 /*!
438     Returns true if the URL contains a hostname; otherwise returns
439     false.
440 
441     \sa setHost()
442 */
443 
hasHost() const444 bool Q3Url::hasHost() const
445 {
446     return !d->host.isEmpty();
447 }
448 
449 /*!
450     Returns the port of the URL or -1 if no port has been set.
451 
452     \sa setPort()
453 */
454 
port() const455 int Q3Url::port() const
456 {
457     return d->port;
458 }
459 
460 /*!
461     Sets the port of the URL to \a port.
462 
463     \sa port()
464 */
465 
setPort(int port)466 void Q3Url::setPort( int port )
467 {
468     d->port = port;
469 }
470 
471 /*!
472     Returns true if the URL contains a port; otherwise returns false.
473 
474     \sa setPort()
475 */
476 
hasPort() const477 bool Q3Url::hasPort() const
478 {
479     return d->port >= 0;
480 }
481 
482 /*!
483     Sets the path of the URL to \a path.
484 
485     \sa path() hasPath()
486 */
487 
setPath(const QString & path)488 void Q3Url::setPath( const QString& path )
489 {
490     d->path = path;
491     slashify( d->path );
492     d->cleanPathDirty = true;
493     d->isValid = true;
494 }
495 
496 /*!
497     Returns true if the URL contains a path; otherwise returns false.
498 
499     \sa path() setPath()
500 */
501 
hasPath() const502 bool Q3Url::hasPath() const
503 {
504     return !d->path.isEmpty();
505 }
506 
507 /*!
508     Sets the query of the URL to \a txt. \a txt must be encoded.
509 
510     \sa query() encode()
511 */
512 
setQuery(const QString & txt)513 void Q3Url::setQuery( const QString& txt )
514 {
515     d->queryEncoded = txt;
516 }
517 
518 /*!
519     Returns the (encoded) query of the URL.
520 
521     \sa setQuery() decode()
522 */
523 
query() const524 QString Q3Url::query() const
525 {
526     return d->queryEncoded;
527 }
528 
529 /*!
530     Returns the (encoded) reference of the URL.
531 
532     \sa setRef() hasRef() decode()
533 */
534 
ref() const535 QString Q3Url::ref() const
536 {
537     return d->refEncoded;
538 }
539 
540 /*!
541     Sets the reference of the URL to \a txt. \a txt must be encoded.
542 
543     \sa ref() hasRef() encode()
544 */
545 
setRef(const QString & txt)546 void Q3Url::setRef( const QString& txt )
547 {
548     d->refEncoded = txt;
549 }
550 
551 /*!
552     Returns true if the URL has a reference; otherwise returns false.
553 
554     \sa setRef()
555 */
556 
hasRef() const557 bool Q3Url::hasRef() const
558 {
559     return !d->refEncoded.isEmpty();
560 }
561 
562 /*!
563     Returns true if the URL is valid; otherwise returns false. A URL
564     is invalid if it cannot be parsed, for example.
565 */
566 
isValid() const567 bool Q3Url::isValid() const
568 {
569     return d->isValid;
570 }
571 
572 /*!
573     Resets all parts of the URL to their default values and
574     invalidates it.
575 */
576 
reset()577 void Q3Url::reset()
578 {
579     d->protocol = QLatin1String("file");
580     d->user = QLatin1String("");
581     d->pass = QLatin1String("");
582     d->host = QLatin1String("");
583     d->path = QLatin1String("");
584     d->queryEncoded = QLatin1String("");
585     d->refEncoded = QLatin1String("");
586     d->isValid = true;
587     d->port = -1;
588     d->cleanPathDirty = true;
589 }
590 
591 /*!
592     Parses the \a url. Returns true on success; otherwise returns false.
593 */
594 
parse(const QString & url)595 bool Q3Url::parse( const QString& url )
596 {
597     QString url_( url );
598     slashify( url_ );
599 
600     if ( url_.isEmpty() ) {
601 	d->isValid = false;
602 	return false;
603     }
604 
605     d->cleanPathDirty = true;
606     d->isValid = true;
607     QString oldProtocol = d->protocol;
608     d->protocol.clear();
609 
610     const int Init	= 0;
611     const int Protocol	= 1;
612     const int Separator1= 2; // :
613     const int Separator2= 3; // :/
614     const int Separator3= 4; // :// or more slashes
615     const int User	= 5;
616     const int Pass	= 6;
617     const int Host	= 7;
618     const int Path	= 8;
619     const int Ref	= 9;
620     const int Query	= 10;
621     const int Port	= 11;
622     const int Done	= 12;
623 
624     const int InputAlpha= 1;
625     const int InputDigit= 2;
626     const int InputSlash= 3;
627     const int InputColon= 4;
628     const int InputAt	= 5;
629     const int InputHash = 6;
630     const int InputQuery= 7;
631 
632     static uchar table[ 12 ][ 8 ] = {
633      /* None       InputAlpha  InputDigit  InputSlash  InputColon  InputAt     InputHash   InputQuery */
634 	{ 0,       Protocol,   0,          Path,       0,          0,          0,          0,         }, // Init
635 	{ 0,       Protocol,   Protocol,   0,          Separator1, 0,          0,          0,         }, // Protocol
636 	{ 0,       Path,       Path,       Separator2, 0,          0,          0,          0,         }, // Separator1
637 	{ 0,       Path,       Path,       Separator3, 0,          0,          0,          0,         }, // Separator2
638 	{ 0,       User,       User,       Separator3, Pass,       Host,       0,          0,         }, // Separator3
639 	{ 0,       User,       User,       User,       Pass,       Host,       User,       User,      }, // User
640 	{ 0,       Pass,       Pass,       Pass,       Pass,       Host,       Pass,       Pass,      }, // Pass
641 	{ 0,       Host,       Host,       Path,       Port,       Host,       Ref,        Query,     }, // Host
642 	{ 0,       Path,       Path,       Path,       Path,       Path,       Ref,        Query,     }, // Path
643 	{ 0,       Ref,        Ref,        Ref,        Ref,        Ref,        Ref,        Query,     }, // Ref
644 	{ 0,       Query,      Query,      Query,      Query,      Query,      Query,      Query,     }, // Query
645 	{ 0,       0,          Port,       Path,       0,          0,          0,          0,         }  // Port
646     };
647 
648     bool relPath = false;
649 
650     relPath = false;
651     bool forceRel = false;
652 
653     // If ':' is at pos 1, we have only one letter
654     // before that separator => that's a drive letter!
655     if ( url_.length() >= 2 && url_[1] == QLatin1Char(':') )
656 	relPath = forceRel = true;
657 
658     int hasNoHost = -1;
659     int cs = url_.find( QLatin1String(":/") );
660     if ( cs != -1 ) // if a protocol is there, find out if there is a host or directly the path after it
661 	hasNoHost = url_.find( QLatin1String("///"), cs );
662     table[ 4 ][ 1 ] = User;
663     table[ 4 ][ 2 ] = User;
664     if ( cs == -1 || forceRel ) { // we have a relative file
665 	if ( url.find( QLatin1Char(':') ) == -1 || forceRel ) {
666 	    table[ 0 ][ 1 ] = Path;
667 	    // Filenames may also begin with a digit
668 	    table[ 0 ][ 2 ] = Path;
669 	} else {
670 	    table[ 0 ][ 1 ] = Protocol;
671 	}
672 	relPath = true;
673     } else { // some checking
674 	table[ 0 ][ 1 ] = Protocol;
675 
676 	// find the part between the protocol and the path as the meaning
677 	// of that part is dependent on some chars
678 	++cs;
679 	while ( url_[ cs ] == QLatin1Char('/') )
680 	    ++cs;
681 	int slash = url_.find( QLatin1Char('/'), cs );
682 	if ( slash == -1 )
683 	    slash = url_.length() - 1;
684 	QString tmp = url_.mid( cs, slash - cs + 1 );
685 
686 	if ( !tmp.isEmpty() ) { // if this part exists
687 
688 	    // look for the @ in this part
689 	    int at = tmp.find( QLatin1Char('@') );
690 	    if ( at != -1 )
691 		at += cs;
692 	    // we have no @, which means host[:port], so directly
693 	    // after the protocol the host starts, or if the protocol
694 	    // is file or there were more than 2 slashes, it is the
695 	    // path
696 	    if ( at == -1 ) {
697 		if ( url_.left( 4 ) == QLatin1String("file") || hasNoHost != -1 )
698 		    table[ 4 ][ 1 ] = Path;
699 		else
700 		    table[ 4 ][ 1 ] = Host;
701 		table[ 4 ][ 2 ] = table[ 4 ][ 1 ];
702 	    }
703 	}
704     }
705 
706     int state = Init; // parse state
707     int input; // input token
708 
709     QChar c = url_[ 0 ];
710     int i = 0;
711     QString port;
712 
713     for ( ;; ) {
714 	switch ( c.latin1() ) {
715 	case '?':
716 	    input = InputQuery;
717 	    break;
718 	case '#':
719 	    input = InputHash;
720 	    break;
721 	case '@':
722 	    input = InputAt;
723 	    break;
724 	case ':':
725 	    input = InputColon;
726 	    break;
727 	case '/':
728 	    input = InputSlash;
729 	    break;
730 	case '1': case '2': case '3': case '4': case '5':
731 	case '6': case '7': case '8': case '9': case '0':
732 	    input = InputDigit;
733 	    break;
734 	default:
735 	    input = InputAlpha;
736 	}
737 
738 	state = table[ state ][ input ];
739 
740 	switch ( state ) {
741 	case Protocol:
742 	    d->protocol += c;
743 	    break;
744 	case User:
745 	    d->user += c;
746 	    break;
747 	case Pass:
748 	    d->pass += c;
749 	    break;
750 	case Host:
751 	    d->host += c;
752 	    break;
753 	case Path:
754 	    d->path += c;
755 	    break;
756 	case Ref:
757 	    d->refEncoded += c;
758 	    break;
759 	case Query:
760 	    d->queryEncoded += c;
761 	    break;
762 	case Port:
763 	    port += c;
764 	    break;
765 	default:
766 	    break;
767 	}
768 
769 	++i;
770 	if ( i > (int)url_.length() - 1 || state == Done || state == 0 )
771 	    break;
772 	c = url_[ i ];
773 
774     }
775 
776     if ( !port.isEmpty() ) {
777 	port.remove( (uint)0, 1 );
778 	d->port = port.toInt();
779     }
780 
781     // error
782     if ( i < (int)url_.length() - 1 ) {
783 	d->isValid = false;
784 	return false;
785     }
786 
787 
788     if ( d->protocol.isEmpty() )
789 	d->protocol = oldProtocol;
790 
791     if ( d->path.isEmpty() )
792 	d->path = QLatin1String("/");
793 
794     // hack for windows
795     if ( d->path.length() == 2 && d->path[ 1 ] == QLatin1Char(':') )
796 	d->path += QLatin1Char('/');
797 
798     // #### do some corrections, should be done nicer too
799     if ( !d->pass.isEmpty() ) {
800 	if ( d->pass[ 0 ] == QLatin1Char(':') )
801 	    d->pass.remove( (uint)0, 1 );
802 	decode( d->pass );
803     }
804     if ( !d->user.isEmpty() ) {
805 	decode( d->user );
806     }
807     if ( !d->path.isEmpty() ) {
808 	if ( d->path[ 0 ] == QLatin1Char('@') || d->path[ 0 ] == QLatin1Char(':') )
809 	    d->path.remove( (uint)0, 1 );
810 	if ( d->path[ 0 ] != QLatin1Char('/') && !relPath && d->path[ 1 ] != QLatin1Char(':') )
811 	    d->path.prepend( QLatin1Char('/') );
812     }
813     if ( !d->refEncoded.isEmpty() && d->refEncoded[ 0 ] == QLatin1Char('#') )
814 	d->refEncoded.remove( (uint)0, 1 );
815     if ( !d->queryEncoded.isEmpty() && d->queryEncoded[ 0 ] == QLatin1Char('?') )
816 	d->queryEncoded.remove( (uint)0, 1 );
817     if ( !d->host.isEmpty() && d->host[ 0 ] == QLatin1Char('@') )
818 	d->host.remove( (uint)0, 1 );
819 
820 #if defined(Q_OS_WIN32)
821     // hack for windows file://machine/path syntax
822     if ( d->protocol == QLatin1String("file") ) {
823 	if ( url.startsWith(QLatin1String("file://")) &&
824 	     d->path.length() > 1 && d->path[ 1 ] != QLatin1Char(':') )
825 		 d->path.prepend( QLatin1Char('/') );
826     }
827 #endif
828 
829     decode( d->path );
830     d->cleanPathDirty = true;
831 
832 #if 0
833     qDebug( "URL: %s", url.latin1() );
834     qDebug( "protocol: %s", d->protocol.latin1() );
835     qDebug( "user: %s", d->user.latin1() );
836     qDebug( "pass: %s", d->pass.latin1() );
837     qDebug( "host: %s", d->host.latin1() );
838     qDebug( "path: %s", path().latin1() );
839     qDebug( "ref: %s", d->refEncoded.latin1() );
840     qDebug( "query: %s", d->queryEncoded.latin1() );
841     qDebug( "port: %d\n\n----------------------------\n\n", d->port );
842 #endif
843 
844     return true;
845 }
846 
847 /*!
848     \overload
849 
850     Parses \a url and assigns the resulting data to this class.
851 
852     If you pass a string like "/home/qt" the "file" protocol will be
853     assumed.
854 */
855 
operator =(const QString & url)856 Q3Url& Q3Url::operator=( const QString& url )
857 {
858     reset();
859     parse( url );
860 
861     return *this;
862 }
863 
864 /*!
865     Assigns the data of \a url to this class.
866 */
867 
operator =(const Q3Url & url)868 Q3Url& Q3Url::operator=( const Q3Url& url )
869 {
870     *d = *url.d;
871     return *this;
872 }
873 
874 /*!
875     Compares this URL with \a url and returns true if they are equal;
876     otherwise returns false.
877 */
878 
operator ==(const Q3Url & url) const879 bool Q3Url::operator==( const Q3Url& url ) const
880 {
881     if ( !isValid() || !url.isValid() )
882 	return false;
883 
884     if ( d->protocol == url.d->protocol &&
885 	 d->user == url.d->user &&
886 	 d->pass == url.d->pass &&
887 	 d->host == url.d->host &&
888 	 d->path == url.d->path &&
889 	 d->queryEncoded == url.d->queryEncoded &&
890 	 d->refEncoded == url.d->refEncoded &&
891 	 d->isValid == url.d->isValid &&
892 	 d->port == url.d->port )
893 	return true;
894 
895     return false;
896 }
897 
898 /*!
899     \overload
900 
901     Compares this URL with \a url. \a url is parsed first. Returns
902     true if \a url is equal to this url; otherwise returns false.
903 */
904 
operator ==(const QString & url) const905 bool Q3Url::operator==( const QString& url ) const
906 {
907     Q3Url u( url );
908     return ( *this == u );
909 }
910 
911 /*!
912     Sets the file name of the URL to \a name. If this URL contains a
913     fileName(), the original file name is replaced by \a name.
914 
915     See the documentation of fileName() for a more detailed discussion
916     of what is handled as file name and what is handled as a directory
917     path.
918 
919     \sa fileName()
920 */
921 
setFileName(const QString & name)922 void Q3Url::setFileName( const QString& name )
923 {
924     QString fn( name );
925     slashify( fn );
926 
927     while ( fn[ 0 ] == QLatin1Char( '/' ) )
928 	fn.remove( (uint)0, 1 );
929 
930     QString p;
931     if ( path().isEmpty() ) {
932 	p = QLatin1String("/");
933     } else {
934 	p = path();
935 	int slash = p.findRev( QLatin1Char( '/' ) );
936 	if ( slash == -1 ) {
937 	    p = QLatin1String("/");
938 	} else if ( p[ (int)p.length() - 1 ] != QLatin1Char( '/' ) ) {
939 	    p.truncate( slash + 1 );
940 	}
941     }
942 
943     p += fn;
944     if ( !d->queryEncoded.isEmpty() )
945 	p += QLatin1Char('?') + d->queryEncoded;
946     setEncodedPathAndQuery( p );
947 }
948 
949 /*!
950     Returns the encoded path and query.
951 
952     \sa decode()
953 */
954 
encodedPathAndQuery()955 QString Q3Url::encodedPathAndQuery()
956 {
957     QString p = path();
958     if ( p.isEmpty() )
959 	p = QLatin1String("/");
960 
961     encode( p );
962 
963     if ( !d->queryEncoded.isEmpty() ) {
964 	p += QLatin1Char('?');
965 	p += d->queryEncoded;
966     }
967 
968     return p;
969 }
970 
971 /*!
972     Parses \a pathAndQuery for a path and query and sets those values.
973     The whole string must be encoded.
974 
975     \sa encode()
976 */
977 
setEncodedPathAndQuery(const QString & pathAndQuery)978 void Q3Url::setEncodedPathAndQuery( const QString& pathAndQuery )
979 {
980     d->cleanPathDirty = true;
981     int pos = pathAndQuery.find( QLatin1Char('?') );
982     if ( pos == -1 ) {
983 	d->path = pathAndQuery;
984 	d->queryEncoded = QLatin1String("");
985     } else {
986 	d->path = pathAndQuery.left( pos );
987 	d->queryEncoded = pathAndQuery.mid( pos + 1 );
988     }
989 
990     decode( d->path );
991     d->cleanPathDirty = true;
992 }
993 
994 /*!
995     Returns the path of the URL. If \a correct is true, the path is
996     cleaned (deals with too many or too few slashes, cleans things
997     like "/../..", etc). Otherwise path() returns exactly the path
998     that was parsed or set.
999 
1000     \sa setPath() hasPath()
1001 */
path(bool correct) const1002 QString Q3Url::path( bool correct ) const
1003 {
1004     if ( !correct )
1005 	return d->path;
1006 
1007     if ( d->cleanPathDirty ) {
1008 	bool check = true;
1009 	if ( QDir::isRelativePath( d->path ) ) {
1010 	    d->cleanPath = d->path;
1011 	} else if ( isLocalFile() ) {
1012 #if defined(Q_OS_WIN32)
1013 	    // hack for stuff like \\machine\path and //machine/path on windows
1014 	    if ( ( d->path.startsWith(QLatin1Char('/')) || d->path.startsWith(QLatin1Char('\\')) ) &&
1015 		 d->path.length() > 1 ) {
1016 		d->cleanPath = d->path;
1017 		bool share = (d->cleanPath[0] == QLatin1Char('\\') && d->cleanPath[1] == QLatin1Char('\\')) ||
1018 		             (d->cleanPath[0] == QLatin1Char('/') && d->cleanPath[1] == QLatin1Char('/'));
1019 		slashify( d->cleanPath, false );
1020 		d->cleanPath = QDir::cleanDirPath( d->cleanPath );
1021 		if ( share ) {
1022 		    check = false;
1023 		    while (d->cleanPath.at(0) != QLatin1Char('/') || d->cleanPath.at(1) != QLatin1Char('/'))
1024 			d->cleanPath.prepend(QLatin1Char('/'));
1025 		}
1026 	    }
1027 #endif
1028 	    if ( check ) {
1029 		QFileInfo fi( d->path );
1030 		if ( !fi.exists() )
1031 		    d->cleanPath = d->path;
1032 		else if ( fi.isDir() ) {
1033                     QString canPath = QDir( d->path ).canonicalPath();
1034                     QString dir;
1035                     if ( qt_resolve_symlinks && !canPath.isNull() )
1036                        dir = QDir::cleanDirPath( canPath );
1037                     else
1038                        dir = QDir::cleanDirPath( QDir( d->path ).absPath() );
1039                     dir += QLatin1Char('/');
1040 		    if ( dir == QLatin1String("//") )
1041 			d->cleanPath = QLatin1String("/");
1042 		    else
1043 			d->cleanPath = dir;
1044 		} else {
1045 		    QString p =
1046 			QDir::cleanDirPath( (qt_resolve_symlinks ?
1047 					    fi.dir().canonicalPath() :
1048 					    fi.dir().absPath()) );
1049 		    d->cleanPath = p + QLatin1Char('/') + fi.fileName();
1050 		}
1051 	    }
1052 	} else {
1053 		d->cleanPath = QDir::cleanDirPath( d->path );
1054 	    if ( d->path.length() > 1 && d->path.endsWith(QLatin1Char('/')) )
1055     		d->cleanPath += QLatin1Char('/');
1056 	}
1057 
1058 	if ( check )
1059 	    slashify( d->cleanPath, false );
1060 	d->cleanPathDirty = false;
1061     }
1062 
1063     return d->cleanPath;
1064 }
1065 
1066 /*!
1067     Returns true if the URL is a local file; otherwise returns false.
1068 */
1069 
isLocalFile() const1070 bool Q3Url::isLocalFile() const
1071 {
1072     return d->protocol == QLatin1String("file");
1073 }
1074 
1075 /*!
1076     Returns the file name of the URL. If the path of the URL doesn't
1077     have a slash at the end, the part between the last slash and the
1078     end of the path string is considered to be the file name. If the
1079     path has a slash at the end, an empty string is returned here.
1080 
1081     \sa setFileName()
1082 */
1083 
fileName() const1084 QString Q3Url::fileName() const
1085 {
1086     if ( d->path.isEmpty() || d->path.endsWith( QLatin1Char('/') )
1087 #ifdef Q_WS_WIN
1088 	|| d->path.endsWith( QLatin1Char('\\') )
1089 #endif
1090 	)
1091 	return QString();
1092 
1093     return QFileInfo( d->path ).fileName();
1094 }
1095 
1096 /*!
1097     Adds the path \a pa to the path of the URL.
1098 
1099     \sa setPath() hasPath()
1100 */
1101 
addPath(const QString & pa)1102 void Q3Url::addPath( const QString& pa )
1103 {
1104     if ( pa.isEmpty() )
1105 	return;
1106 
1107     QString p( pa );
1108     slashify( p );
1109 
1110     if ( path().isEmpty() ) {
1111 	if ( p[ 0 ] != QLatin1Char( '/' ) )
1112 	    d->path = QLatin1Char('/') + p;
1113 	else
1114 	    d->path = p;
1115     } else {
1116 	if ( p[ 0 ] != QLatin1Char( '/' ) && d->path[ (int)d->path.length() - 1 ] != QLatin1Char('/') )
1117 	    d->path += QLatin1Char('/') + p;
1118 	else
1119 	    d->path += p;
1120     }
1121     d->cleanPathDirty = true;
1122 }
1123 
1124 /*!
1125     Returns the directory path of the URL. This is the part of the
1126     path of the URL without the fileName(). See the documentation of
1127     fileName() for a discussion of what is handled as file name and
1128     what is handled as directory path.
1129 
1130     \sa setPath() hasPath()
1131 */
1132 
dirPath() const1133 QString Q3Url::dirPath() const
1134 {
1135     if ( path().isEmpty() )
1136 	return QString();
1137 
1138     QString s = path();
1139     int pos = s.findRev( QLatin1Char('/') );
1140     if ( pos == -1 ) {
1141 	return QString::fromLatin1( "." );
1142     } else {
1143 	if ( pos == 0 )
1144 	    return QString::fromLatin1( "/" );
1145 	return s.left( pos );
1146     }
1147 }
1148 
1149 /*!
1150     Encodes the \a url in-place into UTF-8.  For example
1151 
1152     \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 5
1153 
1154   \sa decode()
1155 */
1156 
encode(QString & url)1157 void Q3Url::encode( QString& url )
1158 {
1159     if ( url.isEmpty() )
1160 	return;
1161 
1162     Q3CString curl = url.utf8();
1163     int oldlen = curl.length();
1164 
1165     const Q3CString special( "+<>#@\"&%$:,;?={}|^~[]\'`\\ \n\t\r" );
1166     QString newUrl;
1167     int newlen = 0;
1168 
1169     for ( int i = 0; i < oldlen ;++i ) {
1170 	uchar inCh = (uchar)curl[ i ];
1171 
1172 	if ( inCh >= 128 || special.contains(inCh) ) {
1173 	    newUrl[ newlen++ ] = QLatin1Char( '%' );
1174 
1175 	    ushort c = inCh / 16;
1176 	    c += c > 9 ? 'A' - 10 : '0';
1177 	    newUrl[ newlen++ ] = c;
1178 
1179 	    c = inCh % 16;
1180 	    c += c > 9 ? 'A' - 10 : '0';
1181 	    newUrl[ newlen++ ] = c;
1182 	} else {
1183 	    newUrl[ newlen++ ] = inCh;
1184 	}
1185     }
1186 
1187     url = newUrl;
1188 }
1189 
hex_to_int(uchar c)1190 static uchar hex_to_int( uchar c )
1191 {
1192     if ( c >= 'A' && c <= 'F' )
1193 	return c - 'A' + 10;
1194     if ( c >= 'a' && c <= 'f')
1195 	return c - 'a' + 10;
1196     if ( c >= '0' && c <= '9')
1197 	return c - '0';
1198     return 0;
1199 }
1200 
1201 /*!
1202     Decodes the \a url in-place into UTF-8.  For example
1203 
1204     \snippet doc/src/snippets/code/src_qt3support_network_q3url.cpp 6
1205 
1206     \sa encode()
1207 */
1208 
decode(QString & url)1209 void Q3Url::decode( QString& url )
1210 {
1211     if ( url.isEmpty() )
1212 	return;
1213 
1214     int newlen = 0;
1215     Q3CString curl = url.utf8();
1216     int oldlen = curl.length();
1217 
1218     Q3CString newUrl(oldlen);
1219 
1220     int i = 0;
1221     while ( i < oldlen ) {
1222 	uchar c = (uchar)curl[ i++ ];
1223 	if ( c == '%' && i <= oldlen - 2 ) {
1224 	    c = hex_to_int( (uchar)curl[ i ] ) * 16 + hex_to_int( (uchar)curl[ i + 1 ] );
1225 	    i += 2;
1226 	}
1227 	newUrl [ newlen++ ] = c;
1228     }
1229     newUrl.truncate( newlen );
1230 
1231     url = QString::fromUtf8(newUrl.data());
1232 }
1233 
1234 
1235 /*!
1236     Composes a string version of the URL and returns it. If \a
1237     encodedPath is true the path in the returned string is encoded. If
1238     \a forcePrependProtocol is true and \a encodedPath looks like a
1239     local filename, the "file:/" protocol is also prepended.
1240 
1241     \sa encode() decode()
1242 */
1243 
toString(bool encodedPath,bool forcePrependProtocol) const1244 QString Q3Url::toString( bool encodedPath, bool forcePrependProtocol ) const
1245 {
1246     QString res, p = path();
1247     if ( encodedPath )
1248 	encode( p );
1249 
1250     if ( isLocalFile() ) {
1251 	if ( forcePrependProtocol )
1252 	    res = d->protocol + QLatin1Char(':') + p;
1253 	else
1254 	    res = p;
1255     } else if ( d->protocol == QLatin1String("mailto") ) {
1256 	res = d->protocol + QLatin1Char(':') + p;
1257     } else {
1258 	res = d->protocol + QLatin1String("://");
1259 	if ( !d->user.isEmpty() || !d->pass.isEmpty() ) {
1260 	    QString tmp;
1261 	    if ( !d->user.isEmpty() ) {
1262 		tmp = d->user;
1263 		encode( tmp );
1264 		res += tmp;
1265 	    }
1266 	    if ( !d->pass.isEmpty() ) {
1267 		tmp = d->pass;
1268 		encode( tmp );
1269 		res += QLatin1Char(':') + tmp;
1270 	    }
1271 	    res += QLatin1Char('@');
1272 	}
1273 	res += d->host;
1274 	if ( d->port != -1 )
1275         res += QLatin1Char(':') + QString::number( d->port );
1276 	if ( !p.isEmpty() ) {
1277 	    if ( !d->host.isEmpty() && p[0]!= QLatin1Char( '/' ) )
1278 		res += QLatin1Char('/');
1279 	    res += p;
1280 	}
1281     }
1282 
1283     if ( !d->refEncoded.isEmpty() )
1284 	res += QLatin1Char('#') + d->refEncoded;
1285     if ( !d->queryEncoded.isEmpty() )
1286 	res += QLatin1Char('?') + d->queryEncoded;
1287 
1288     return res;
1289 }
1290 
1291 /*!
1292     Composes a string version of the URL and returns it.
1293 
1294     \sa Q3Url::toString()
1295 */
1296 
operator QString() const1297 Q3Url::operator QString() const
1298 {
1299     return toString();
1300 }
1301 
1302 /*!
1303     Changes the directory to one directory up. This function always returns
1304     true.
1305 
1306     \sa setPath()
1307 */
1308 
cdUp()1309 bool Q3Url::cdUp()
1310 {
1311     d->path += QLatin1String("/..");
1312     d->cleanPathDirty = true;
1313     return true;
1314 }
1315 
1316 QT_END_NAMESPACE
1317 
1318 #endif // QT_NO_URL
1319