1 /*  $Id: test_ncbi_url.cpp 613672 2020-08-11 16:34:37Z grichenk $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Author:  Aleksey Grichenko
27  *
28  * File Description:
29  *   TEST for:  CUrl class
30  *
31  */
32 
33 #include <ncbi_pch.hpp>
34 #include <corelib/ncbistr.hpp>
35 #include <corelib/ncbi_url.hpp>
36 
37 #define BOOST_AUTO_TEST_MAIN
38 #include <corelib/test_boost.hpp>
39 
40 #include <common/test_assert.h>  /* This header must go last */
41 
42 
43 USING_NCBI_SCOPE;
44 
45 
46 const char* kScheme = "scheme";
47 const char* kSchemeLB = NCBI_SCHEME_SERVICE;
48 const char* kUser = "user";
49 const char* kPassword = "password";
50 const char* kHostShort = "hostname";
51 const char* kHostFull = "host.net";
52 const char* kHostIPv6 = "[1:2:3:4]";
53 const char* kServiceSimple = "servicename";
54 const char* kServiceSpecial = "/service/name";
55 const char* kPort = "1234";
56 const char* kPathAbs = "/path/file";
57 const char* kPathRel = "rel/path/file";
58 const char* kArgs = "arg1=val1&arg2=val2";
59 const char* kFragment = "fragment";
60 
61 
62 enum EUrlParts {
63     fHostNone       = 0,
64     fHostShort      = 0x0001,
65     fHostFull       = 0x0002,
66     fHostIPv6       = 0x0003,
67     fHostMask       = 0x0003,
68     fService        = 0x0004,
69     fServiceSimple  = 0,
70     fServiceSpecial = 0x0001,
71     fAuthorityMask  = 0x0007, // host or service
72 
73     fScheme         = 0x0008,
74     fUser           = 0x0010,
75     fPassword       = 0x0020,
76     fPort           = 0x0040,
77 
78     fPathNone       = 0,
79     fPathRoot       = 0x0080,
80     fPathAbs        = 0x0100,
81     fPathRel        = 0x0180,
82     fPathMask       = 0x0180,
83 
84     fArgs           = 0x0200,
85     fFragment       = 0x0400,
86 
87     fMax            = 0x0800
88 };
89 
90 
s_TestUrl(string url_str,int flags)91 void s_TestUrl(string url_str, int flags)
92 {
93     CUrl url(url_str);
94     if (url.GetIsGeneric()) {
95         BOOST_CHECK(url_str.find("//") != NPOS);
96     }
97     else {
98         BOOST_CHECK(url_str.find("//") == NPOS);
99     }
100 
101     if (flags & fScheme) {
102         BOOST_CHECK_EQUAL(url.GetScheme(), kScheme);
103     }
104     else {
105         BOOST_CHECK(url.GetScheme().empty());
106     }
107 
108     if (flags & fService) {
109         BOOST_CHECK(url.GetHost().empty());
110         BOOST_CHECK(url.IsService());
111         switch (flags & fHostMask) {
112         case fServiceSimple:
113             BOOST_CHECK_EQUAL(url.GetService(), kServiceSimple);
114             break;
115         case fServiceSpecial:
116             BOOST_CHECK_EQUAL(url.GetService(), kServiceSpecial);
117             break;
118         default:
119             BOOST_ERROR("Invalid service type flag");
120         }
121     }
122     else {
123         BOOST_CHECK(url.GetService().empty());
124         switch (flags & fHostMask) {
125         case fHostNone:
126             BOOST_CHECK(url.GetHost().empty());
127             break;
128         case fHostShort:
129             BOOST_CHECK_EQUAL(url.GetHost(), kHostShort);
130             break;
131         case fHostFull:
132             BOOST_CHECK_EQUAL(url.GetHost(), kHostFull);
133             break;
134         case fHostIPv6:
135             BOOST_CHECK_EQUAL(url.GetHost(), kHostIPv6);
136             break;
137         default:
138             BOOST_ERROR("Invalid host type flag");
139             break;
140         }
141     }
142 
143     if (flags & fUser) {
144         BOOST_CHECK_EQUAL(url.GetUser(), kUser);
145     }
146     else {
147         BOOST_CHECK(url.GetUser().empty());
148     }
149 
150     if (flags & fPassword) {
151         BOOST_CHECK_EQUAL(url.GetPassword(), kPassword);
152     }
153     else {
154         BOOST_CHECK(url.GetPassword().empty());
155     }
156 
157     if (flags & fPort) {
158         BOOST_CHECK_EQUAL(url.GetPort(), kPort);
159     }
160     else {
161         BOOST_CHECK(url.GetPort().empty());
162     }
163 
164     switch (flags & fPathMask) {
165     case fPathNone:
166         BOOST_CHECK(url.GetPath().empty());
167         break;
168     case fPathRoot:
169         BOOST_CHECK_EQUAL(url.GetPath(), "/");
170         break;
171     case fPathAbs:
172         BOOST_CHECK_EQUAL(url.GetPath(), kPathAbs);
173         break;
174     case fPathRel:
175         BOOST_CHECK_EQUAL(url.GetPath(), kPathRel);
176         break;
177     default:
178         BOOST_ERROR("Invalid path type flag");
179     }
180 
181     if (flags & fArgs) {
182         BOOST_CHECK_EQUAL(url.GetArgs().GetQueryString(CUrlArgs::eAmp_Char), kArgs);
183     }
184     else {
185         BOOST_CHECK(url.GetArgs().GetArgs().empty());
186     }
187 
188 
189     if (flags & fFragment) {
190         BOOST_CHECK_EQUAL(url.GetFragment(), kFragment);
191     }
192     else {
193         BOOST_CHECK(url.GetFragment().empty());
194     }
195 
196     BOOST_CHECK_EQUAL(url_str, url.ComposeUrl(CUrlArgs::eAmp_Char));
197 }
198 
199 
BOOST_AUTO_TEST_CASE(s_UrlTestParsed)200 BOOST_AUTO_TEST_CASE(s_UrlTestParsed)
201 {
202     cout << "*** Testing parsed URLs" << endl;
203     string surl;
204     for (int flags = 0; flags < fMax; ++flags) {
205         surl.clear();
206         if (flags & fScheme) {
207             // Skip scheme-only URLs.
208             if (flags == fScheme) continue;
209             surl = ((flags & fService) ? string(kScheme) + "+" + kSchemeLB : kScheme);
210             // When testing relative path, do not include "//".
211             surl += !(flags & (fHostMask | fService)) ? ":" : "://";
212         }
213         else if (flags & fService) {
214             // Simple service URLs do not require ncbilb scheme.
215             if (flags != fService) {
216                 surl = string(kSchemeLB) + "://";
217             }
218         }
219         else if ((flags & fHostMask) != fHostNone) {
220             // If host is present, the authority part must start with "//".
221             surl = "//";
222         }
223 
224         string authority;
225         if (flags & fService) {
226             // Port is not allowed for services.
227             if (flags & fPort) continue;
228             switch (flags & fHostMask) {
229             case fServiceSimple:
230                 authority = kServiceSimple;
231                 break;
232             case fServiceSpecial:
233                 authority = NStr::URLEncode(kServiceSpecial);
234                 break;
235             default:
236                 continue; // invalid service type
237             }
238         }
239         else {
240             switch (flags & fHostMask) {
241             case fHostNone:
242                 break;
243             case fHostShort:
244                 authority = kHostShort;
245                 break;
246             case fHostFull:
247                 authority = kHostFull;
248                 break;
249             case fHostIPv6:
250                 authority = kHostIPv6;
251                 break;
252             default:
253                 continue; // unused host type
254             }
255             if (flags & fPort) {
256                 authority += string(":") + kPort;
257             }
258         }
259 
260         // Do not test user info and port if host/service is missing
261         if (!(flags & fAuthorityMask)) {
262             if (flags & (fUser | fPassword | fPort)) continue;
263         }
264 
265         string user_info;
266         if (flags & fUser) {
267             user_info = kUser;
268         }
269         if (flags & fPassword) {
270             // TODO: Is password allowed without user?
271             user_info += string(":") + kPassword;
272         }
273         if (!user_info.empty()) {
274             authority = user_info + "@" + authority;
275         }
276         surl += authority;
277 
278         switch (flags & fPathMask) {
279         case fPathNone:
280             break;
281         case fPathRoot:
282             surl += "/";
283             break;
284         case fPathAbs:
285             surl += kPathAbs;
286             break;
287         case fPathRel:
288             // Relative path can be combined only with scheme
289             if (flags & ~(fScheme | fPathRel)) continue;
290             surl += kPathRel;
291             break;
292         default:
293             continue;
294         }
295 
296         if (flags & fArgs) {
297             surl += string("?") + kArgs;
298         }
299 
300         string fragment;
301         if (flags & fFragment) {
302             surl += string("#") + kFragment;
303         }
304 
305         cout << "Testing: " << surl << endl;
306         s_TestUrl(surl, flags);
307     }
308 }
309 
310 
311 struct SCUrlTest {
312     string  m_Expected;
313     string  m_UrlString;
314     string  m_Scheme;
315     string  m_Service;
316     string  m_Host;
317     string  m_Path;
318     bool    m_Generic;
319 
CompareSCUrlTest320     void Compare(const CUrl& url)
321     {
322         string result(url.ComposeUrl(CUrlArgs::eAmp_Char));
323         BOOST_CHECK_EQUAL(m_Expected, result);
324         BOOST_CHECK_EQUAL(m_Scheme, url.GetScheme());
325         BOOST_CHECK_EQUAL(!m_Service.empty(), url.IsService());
326         BOOST_CHECK_EQUAL(m_Service, url.GetService());
327         BOOST_CHECK_EQUAL(m_Host, url.GetHost());
328         BOOST_CHECK_EQUAL(m_Path, url.GetPath());
329         BOOST_CHECK_EQUAL(m_Generic, url.GetIsGeneric());
330     }
331 };
332 
333 
334 static SCUrlTest s_CUrlTests[] = {
335     { "service",                            "service",                              "",         "service",  "",     "",             false },
336     { "Some/path",                          "Some/path",                            "",         "",         "",     "Some/path",    false },
337     { "//host",                             "//host",                               "",         "",         "host", "",             true },
338     { "//host/Some/path",                   "//host/Some/path",                     "",         "",         "host", "/Some/path",   true },
339     { "http://host",                        "http://host",                          "http",     "",         "host", "",             true },
340     { "http://host/Some/path",              "http://host/Some/path",                "http",     "",         "host", "/Some/path",   true },
341 
342     { "ncbilb://service",                   "ncbilb://service",                     "",         "service",  "",     "",             true },
343     { "ncbilb://service/Some/path",         "ncbilb://service/Some/path",           "",         "service",  "",     "/Some/path",   true },
344     { "http+ncbilb://service",              "http+ncbilb://service",                "http",     "service",  "",     "",             true },
345     { "http+ncbilb://service/Some/path",    "http+ncbilb://service/Some/path",      "http",     "service",  "",     "/Some/path",   true },
346 
347     { "scheme:Some/path",                   "scheme:Some/path",                     "scheme",   "",         "",     "Some/path",    false },
348     { "scheme://host",                      "scheme://host",                        "scheme",   "",         "host", "",             true },
349     { "scheme://host/Some/path",            "scheme://host/Some/path",              "scheme",   "",         "host", "/Some/path",   true },
350     { "scheme+ncbilb://service",            "scheme+ncbilb://service",              "scheme",   "service",  "",     "",             true },
351     { "scheme+ncbilb://service/Some/path",  "scheme+ncbilb://service/Some/path",    "scheme",   "service",  "",     "/Some/path",   true }
352 };
353 
354 
BOOST_AUTO_TEST_CASE(s_UrlTestComposed)355 BOOST_AUTO_TEST_CASE(s_UrlTestComposed)
356 {
357     cout << "*** Testing composed URLs" << endl;
358     for (auto test : s_CUrlTests) {
359         cout << "Testing: " << test.m_UrlString << endl;
360         CUrl purl(test.m_UrlString);
361         test.Compare(purl);
362 
363         CUrl curl;
364         if (!test.m_Scheme.empty())   curl.SetScheme(test.m_Scheme);
365         if (!test.m_Service.empty())  curl.SetService(test.m_Service);
366         if (!test.m_Host.empty())     curl.SetHost(test.m_Host);
367         if (!test.m_Path.empty())     curl.SetPath(test.m_Path);
368         curl.SetIsGeneric(test.m_Generic);
369         test.Compare(curl);
370     }
371 }
372 
373 
BOOST_AUTO_TEST_CASE(s_UrlTestSpecial)374 BOOST_AUTO_TEST_CASE(s_UrlTestSpecial)
375 {
376     cout << "*** Testing special cases" << endl;
377     // Service-only URL may include ncbilb scheme.
378     string surl = string(kSchemeLB) + "://" + kServiceSimple;
379     cout << "Testing: " << surl << endl;
380     s_TestUrl(surl, fService);
381 
382     {
383         cout << "Testing: ignore ncbilb scheme"<< endl;
384         CUrl url;
385         url.SetService(kServiceSimple);
386         url.SetScheme(kSchemeLB);
387         BOOST_CHECK(url.IsService());
388         BOOST_CHECK_EQUAL(url.GetService(), kServiceSimple);
389         BOOST_CHECK(url.GetScheme().empty());
390     }
391     {
392         cout << "Testing: ignore ncbilb scheme / switch to service" << endl;
393         CUrl url;
394         url.SetHost(kServiceSimple);
395         url.SetScheme(kSchemeLB);
396         BOOST_CHECK(url.IsService());
397         BOOST_CHECK_EQUAL(url.GetService(), kServiceSimple);
398         BOOST_CHECK(url.GetScheme().empty());
399     }
400     {
401         cout << "Testing: strip +ncbilb scheme" << endl;
402         CUrl url;
403         url.SetService(kServiceSimple);
404         url.SetScheme(string(kScheme) + "+" + kSchemeLB);
405         BOOST_CHECK(url.IsService());
406         BOOST_CHECK_EQUAL(url.GetService(), kServiceSimple);
407         BOOST_CHECK_EQUAL(url.GetScheme(), kScheme);
408     }
409     {
410         cout << "Testing: strip ncbilb scheme / switch to service" << endl;
411         CUrl url;
412         url.SetHost(kServiceSimple);
413         url.SetScheme(string(kScheme) + "+" + kSchemeLB);
414         BOOST_CHECK(url.IsService());
415         BOOST_CHECK_EQUAL(url.GetService(), kServiceSimple);
416         BOOST_CHECK_EQUAL(url.GetScheme(), kScheme);
417     }
418 
419     cout << "Testing: parse host:port/path" << endl;
420     {
421         CUrl url(string(kHostShort) + ":" + kPort);
422         BOOST_CHECK(url.GetScheme().empty());
423         BOOST_CHECK(url.GetUser().empty());
424         BOOST_CHECK(url.GetPassword().empty());
425         BOOST_CHECK_EQUAL(url.GetHost(), kHostShort);
426         BOOST_CHECK_EQUAL(url.GetPort(), kPort);
427         BOOST_CHECK(url.GetPath().empty());
428     }
429     {
430         CUrl url(string(kHostFull) + ":" + kPort);
431         BOOST_CHECK(url.GetScheme().empty());
432         BOOST_CHECK(url.GetUser().empty());
433         BOOST_CHECK(url.GetPassword().empty());
434         BOOST_CHECK_EQUAL(url.GetHost(), kHostFull);
435         BOOST_CHECK_EQUAL(url.GetPort(), kPort);
436         BOOST_CHECK(url.GetPath().empty());
437     }
438     {
439         CUrl url(string(kHostShort) + ":" + kPort + kPathAbs);
440         BOOST_CHECK(url.GetScheme().empty());
441         BOOST_CHECK(url.GetUser().empty());
442         BOOST_CHECK(url.GetPassword().empty());
443         BOOST_CHECK_EQUAL(url.GetHost(), kHostShort);
444         BOOST_CHECK_EQUAL(url.GetPort(), kPort);
445         BOOST_CHECK_EQUAL(url.GetPath(), kPathAbs);
446     }
447     {
448         CUrl url(string(kHostFull) + ":" + kPort + kPathAbs);
449         BOOST_CHECK(url.GetScheme().empty());
450         BOOST_CHECK(url.GetUser().empty());
451         BOOST_CHECK(url.GetPassword().empty());
452         BOOST_CHECK_EQUAL(url.GetHost(), kHostFull);
453         BOOST_CHECK_EQUAL(url.GetPort(), kPort);
454         BOOST_CHECK_EQUAL(url.GetPath(), kPathAbs);
455     }
456     {
457         CUrl url("http:123");
458         BOOST_CHECK_EQUAL(url.GetScheme(), "http");
459         BOOST_CHECK(url.GetUser().empty());
460         BOOST_CHECK(url.GetPassword().empty());
461         BOOST_CHECK(url.GetHost().empty());
462         BOOST_CHECK(url.GetPort().empty());
463         BOOST_CHECK_EQUAL(url.GetPath(), "123");
464     }
465     {
466         CUrl url("https:123");
467         BOOST_CHECK_EQUAL(url.GetScheme(), "https");
468         BOOST_CHECK(url.GetUser().empty());
469         BOOST_CHECK(url.GetPassword().empty());
470         BOOST_CHECK(url.GetHost().empty());
471         BOOST_CHECK(url.GetPort().empty());
472         BOOST_CHECK_EQUAL(url.GetPath(), "123");
473     }
474     {
475         CUrl url("file:123");
476         BOOST_CHECK_EQUAL(url.GetScheme(), "file");
477         BOOST_CHECK(url.GetUser().empty());
478         BOOST_CHECK(url.GetPassword().empty());
479         BOOST_CHECK(url.GetHost().empty());
480         BOOST_CHECK(url.GetPort().empty());
481         BOOST_CHECK_EQUAL(url.GetPath(), "123");
482     }
483     {
484         CUrl url("ftp:123");
485         BOOST_CHECK_EQUAL(url.GetScheme(), "ftp");
486         BOOST_CHECK(url.GetUser().empty());
487         BOOST_CHECK(url.GetPassword().empty());
488         BOOST_CHECK(url.GetHost().empty());
489         BOOST_CHECK(url.GetPort().empty());
490         BOOST_CHECK_EQUAL(url.GetPath(), "123");
491     }
492     {
493         CUrl url(string(kScheme) + ":0123");
494         BOOST_CHECK_EQUAL(url.GetScheme(), kScheme);
495         BOOST_CHECK(url.GetUser().empty());
496         BOOST_CHECK(url.GetPassword().empty());
497         BOOST_CHECK(url.GetHost().empty());
498         BOOST_CHECK(url.GetPort().empty());
499         BOOST_CHECK_EQUAL(url.GetPath(), "0123");
500     }
501     {
502         CUrl url(string(kScheme) + ":76543");
503         BOOST_CHECK_EQUAL(url.GetScheme(), kScheme);
504         BOOST_CHECK(url.GetUser().empty());
505         BOOST_CHECK(url.GetPassword().empty());
506         BOOST_CHECK(url.GetHost().empty());
507         BOOST_CHECK(url.GetPort().empty());
508         BOOST_CHECK_EQUAL(url.GetPath(), "76543");
509     }
510 }
511