LCOV - code coverage report
Current view: top level - core/search - SPSearchUrl.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 235 261 90.0 %
Date: 2024-05-12 00:16:13 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /**
       2             : Copyright (c) 2020-2022 Roman Katuntsev <sbkarr@stappler.org>
       3             : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       4             : 
       5             : Permission is hereby granted, free of charge, to any person obtaining a copy
       6             : of this software and associated documentation files (the "Software"), to deal
       7             : in the Software without restriction, including without limitation the rights
       8             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       9             : copies of the Software, and to permit persons to whom the Software is
      10             : furnished to do so, subject to the following conditions:
      11             : 
      12             : The above copyright notice and this permission notice shall be included in
      13             : all copies or substantial portions of the Software.
      14             : 
      15             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      16             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      17             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      18             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      19             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      20             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      21             : THE SOFTWARE.
      22             : **/
      23             : 
      24             : #include "SPUrl.h"
      25             : #include "SPSearchConfiguration.h"
      26             : 
      27             : namespace STAPPLER_VERSIONIZED stappler::search {
      28             : 
      29             : using Scheme =  chars::Compose<char, chars::CharGroup<char, CharGroupId::Alphanumeric>, chars::Chars<char, '+', '-', '.'>>;
      30             : using Ipv6 =  chars::Compose<char, chars::CharGroup<char, CharGroupId::Hexadecimial>, chars::Chars<char, ':'>>;
      31             : 
      32             : using Unreserved = chars::Compose<char, chars::CharGroup<char, CharGroupId::Alphanumeric>, chars::Chars<char, '-', '.', '_', '~', '%'>>;
      33             : using SubDelim = chars::Chars<char, '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='>;
      34             : using GenDelim = chars::Chars<char, ':', '/', '?', '#', '[', ']', '@'>;
      35             : 
      36             : using UnreservedUni = chars::Compose<char, Unreserved, chars::UniChar>;
      37             : 
      38         450 : static bool validateScheme(const StringView &r) {
      39         450 :         auto cpy = r;
      40         450 :         if (cpy.is<StringView::Compose<StringView::CharGroup<CharGroupId::Alphanumeric>, StringView::Chars<'.'>>>()) {
      41         450 :                 cpy ++;
      42         450 :                 cpy.skipChars<Scheme>();
      43         450 :                 if (cpy.empty()) {
      44         450 :                         return true;
      45             :                 }
      46             :         }
      47           0 :         return false;
      48             : }
      49             : 
      50         825 : static bool validateHost(StringView &r) {
      51         825 :         if (r.empty()) {
      52           0 :                 return false;
      53             :         }
      54         825 :         auto cpy = r;
      55         825 :         if (cpy.is('[')) {
      56             :                 // check ipv6
      57         100 :                 ++ cpy;
      58         100 :                 cpy.skipChars<Ipv6>();
      59         100 :                 if (cpy.is(']')) {
      60         100 :                         cpy ++;
      61         100 :                         if (cpy.empty()) {
      62         100 :                                 return true;
      63             :                         }
      64             :                 }
      65             :         } else {
      66         725 :                 cpy.skipChars<Unreserved, SubDelim, chars::UniChar>();
      67         725 :                 if (cpy.empty()) {
      68         725 :                         auto c = r.sub(r.size() - 1, 1);
      69         750 :                         while (c.is(',') || c.is('.') || c.is(';')) {
      70          25 :                                 r = r.sub(0, r.size() - 1);
      71          25 :                                 c = r.sub(r.size() - 1, 1);
      72             :                         }
      73         725 :                         if (c.is<StringView::Compose<StringView::CharGroup<CharGroupId::Alphanumeric>, chars::UniChar>>()) {
      74         725 :                                 StringView h(r);
      75         725 :                                 h.readUntil<chars::UniChar>();
      76         725 :                                 if (!h.empty()) {
      77          50 :                                         auto h = r;
      78          50 :                                         auto domain = h.backwardReadUntil<StringView::Chars<'.'>>();
      79          50 :                                         if (h.back() == '.' && !domain.empty()) {
      80          50 :                                                 auto tmp = domain;
      81          50 :                                                 tmp.skipChars<StringView::CharGroup<CharGroupId::Alphanumeric>>();
      82          50 :                                                 if (!tmp.empty()) {
      83          50 :                                                         if (UrlView::isValidIdnTld(domain)) {
      84          50 :                                                                 return true;
      85             :                                                         }
      86             :                                                 }
      87             :                                         }
      88             :                                 } else {
      89         675 :                                         return true;
      90             :                                 }
      91             :                         }
      92             :                 }
      93             :         }
      94           0 :         return false;
      95             : }
      96             : 
      97         475 : static bool validateUserOrPassword(const StringView &r) {
      98         475 :         auto cpy = r;
      99         475 :         cpy.skipChars<Unreserved, SubDelim, chars::UniChar>();
     100         475 :         if (cpy.empty()) {
     101         475 :                 return true;
     102             :         }
     103           0 :         return false;
     104             : }
     105             : 
     106         950 : bool parseUrl(StringView &s, const Callback<void(StringViewUtf8, UrlToken)> &cb) {
     107         950 :         UrlToken state = UrlToken::Scheme;
     108             : 
     109         950 :         StringView tmp;
     110         950 :         if (s.is('[')) {
     111          25 :                 state = UrlToken::Host;
     112         925 :         } else if (s.is("mailto:")) {
     113          50 :                 cb(StringView(s, 6), UrlToken::Scheme);
     114          50 :                 s += 6;
     115          50 :                 cb(StringView(s, 1), UrlToken::Blank);
     116          50 :                 ++ s;
     117          50 :                 state = UrlToken::User;
     118             :         } else {
     119         875 :                 tmp = s.readChars<UnreservedUni>();
     120             :         }
     121             : 
     122         950 :         if (state == UrlToken::Scheme) {
     123         875 :                 if (s.is(':')) {
     124             :                         // scheme or host+port
     125         625 :                         if (tmp.empty()) {
     126           0 :                                 return false; // error
     127             :                         }
     128             : 
     129         625 :                         if (s.is("://")) {
     130         350 :                                 if (!validateScheme(tmp)) {
     131           0 :                                         return false;
     132             :                                 }
     133             : 
     134         350 :                                 cb(tmp, UrlToken::Scheme);
     135         350 :                                 cb(StringView(s, 3), UrlToken::Blank);
     136         350 :                                 s += 3;
     137             : 
     138         350 :                                 if (s.is('[')) {
     139          25 :                                         state = UrlToken::Host;
     140             :                                 } else {
     141         325 :                                         state = UrlToken::User;
     142             :                                 }
     143             :                         } else {
     144             :                                 // if it's port, next chars will be numbers only
     145         275 :                                 auto tmpS = s;
     146         275 :                                 tmpS ++;
     147         275 :                                 auto port = tmpS.readChars<StringView::CharGroup<CharGroupId::Numbers>>();
     148         275 :                                 if (!port.empty() && !tmpS.is<UnreservedUni>() && !tmpS.is('@')) {
     149             :                                         // host + port
     150         100 :                                         if (!validateHost(tmp)) {
     151          25 :                                                 return true;
     152             :                                         }
     153             : 
     154         100 :                                         cb(tmp, UrlToken::Host);
     155         100 :                                         cb(StringView(port.data() - 1, 1), UrlToken::Blank);
     156         100 :                                         cb(port, UrlToken::Port);
     157         100 :                                         s = tmpS;
     158             : 
     159         100 :                                         if (s.is('/')) {
     160          25 :                                                 state = UrlToken::Path;
     161          75 :                                         } else if (s.is<StringView::Chars<'?'>>()) {
     162          25 :                                                 state = UrlToken::Query;
     163          50 :                                         } else if (s.is('#')) {
     164          25 :                                                 state = UrlToken::Fragment;
     165             :                                         } else {
     166          25 :                                                 return true;
     167             :                                         }
     168             :                                 } else {
     169         175 :                                         tmpS = s;
     170         175 :                                         ++ tmpS;
     171         175 :                                         auto arg = tmpS.readChars<UnreservedUni, SubDelim>();
     172         175 :                                         if (tmpS.is('@')) {
     173             :                                                 // username + password
     174          75 :                                                 if (!validateUserOrPassword(tmp) || !validateUserOrPassword(arg)) {
     175           0 :                                                         return false;
     176             :                                                 }
     177             : 
     178          75 :                                                 cb(tmp, UrlToken::User);
     179          75 :                                                 cb(StringView(arg.data() - 1, 1), UrlToken::Blank);
     180          75 :                                                 cb(arg, UrlToken::Password);
     181          75 :                                                 cb(StringView(tmpS, 1), UrlToken::Blank);
     182          75 :                                                 state = UrlToken::Host;
     183          75 :                                                 ++ tmpS;
     184          75 :                                                 s = tmpS;
     185             :                                         } else {
     186             :                                                 // scheme without authority segment
     187         100 :                                                 if (!validateScheme(tmp)) {
     188           0 :                                                         return false;
     189             :                                                 }
     190         100 :                                                 cb(tmp, UrlToken::Scheme);
     191         100 :                                                 state = UrlToken::Path;
     192         100 :                                                 cb(s.sub(0, 1), UrlToken::Blank);
     193         100 :                                                 ++ s;
     194             :                                         }
     195             :                                 }
     196             :                         }
     197         250 :                 } else if (s.is('@')) {
     198         100 :                         if (tmp.empty() || !validateUserOrPassword(tmp)) {
     199           0 :                                 return false;
     200             :                         }
     201         100 :                         cb(tmp, UrlToken::User);
     202         100 :                         state = UrlToken::Host;
     203         100 :                         cb(StringView(s, 1), UrlToken::Blank);
     204         100 :                         ++ s;
     205         150 :                 } else if (s.is('/')) {
     206             :                         // host + path
     207          50 :                         if (!tmp.empty()) {
     208          25 :                                 if (!validateHost(tmp)) {
     209           0 :                                         return false;
     210             :                                 }
     211          25 :                                 cb(tmp, UrlToken::Host);
     212             :                         }
     213          50 :                         state = UrlToken::Path;
     214         100 :                 } else if (s.is('?')) {
     215             :                         // host + query
     216          25 :                         if (tmp.empty()) {
     217           0 :                                 return false;
     218             :                         }
     219          25 :                         if (!validateHost(tmp)) {
     220           0 :                                 return false;
     221             :                         }
     222          25 :                         cb(tmp, UrlToken::Host);
     223          25 :                         state = UrlToken::Query;
     224          75 :                 } else if (s.is('#')) {
     225             :                         // host + fragment
     226          25 :                         if (tmp.empty()) {
     227           0 :                                 return false;
     228             :                         }
     229          25 :                         if (!validateHost(tmp)) {
     230           0 :                                 return false;
     231             :                         }
     232          25 :                         cb(tmp, UrlToken::Host);
     233          25 :                         state = UrlToken::Fragment;
     234             :                 } else {
     235             :                         // assume host-only
     236          50 :                         if (!tmp.empty()) {
     237          50 :                                 if (!validateHost(tmp)) {
     238           0 :                                         return false;
     239             :                                 }
     240          50 :                                 cb(tmp, UrlToken::Host);
     241          50 :                                 return true;
     242             :                         }
     243           0 :                         return false;
     244             :                 }
     245             :         }
     246             : 
     247         875 :         if (state == UrlToken::User) {
     248         375 :                 auto tmp_s = s;
     249         375 :                 tmp = tmp_s.readChars<UnreservedUni, SubDelim>();
     250             : 
     251         375 :                 if (tmp_s.is('@')) {
     252             :                         // user only part
     253         150 :                         if (!validateUserOrPassword(tmp)) {
     254          25 :                                 return false;
     255             :                         }
     256         150 :                         cb(tmp, UrlToken::User);
     257         150 :                         state = UrlToken::Host;
     258         150 :                         cb(StringView(tmp_s, 1), UrlToken::Blank);
     259         150 :                         ++ tmp_s;
     260         150 :                         s = tmp_s;
     261         225 :                 } else if (tmp_s.is(':')) {
     262             :                         // user + password or host + port
     263         125 :                         tmp_s ++;
     264         125 :                         auto tmpS = tmp_s;
     265             : 
     266             :                         // if it's port, next chars will be numbers only
     267         125 :                         auto port = tmpS.readChars<StringView::CharGroup<CharGroupId::Numbers>>();
     268         125 :                         if (!port.empty() && !tmpS.is('@')) {
     269             :                                 // host + port
     270          75 :                                 if (!validateHost(tmp)) {
     271           0 :                                         return true;
     272             :                                 }
     273             : 
     274          75 :                                 cb(tmp, UrlToken::Host);
     275          75 :                                 cb(StringView(port.data() - 1, 1), UrlToken::Blank);
     276          75 :                                 cb(port, UrlToken::Port);
     277          75 :                                 s = tmpS;
     278             : 
     279          75 :                                 if (s.is('/')) {
     280          25 :                                         state = UrlToken::Path;
     281          50 :                                 } else if (s.is('?')) {
     282          25 :                                         state = UrlToken::Query;
     283          25 :                                 } else if (s.is('#')) {
     284          25 :                                         state = UrlToken::Fragment;
     285             :                                 } else {
     286           0 :                                         return true;
     287             :                                 }
     288             :                         } else {
     289             :                                 // user + password
     290          50 :                                 if (!validateUserOrPassword(tmp)) {
     291           0 :                                         return false;
     292             :                                 }
     293          50 :                                 cb(tmp, UrlToken::User);
     294             : 
     295          50 :                                 if (tmpS.is('@')) {
     296          25 :                                         cb(StringView(port.data() - 1, 1), UrlToken::Blank);
     297          25 :                                         cb(port, UrlToken::Password);
     298          25 :                                         ++ tmpS;
     299          25 :                                         s = tmpS;
     300          25 :                                         state = UrlToken::Host;
     301             :                                 } else {
     302          25 :                                         tmp = tmp_s.readChars<UnreservedUni, SubDelim>();
     303          25 :                                         if (!tmp_s.is('@')) {
     304           0 :                                                 return false;
     305             :                                         }
     306          25 :                                         ++ tmp_s;
     307          25 :                                         if (!validateUserOrPassword(tmp)) {
     308           0 :                                                 return false;
     309             :                                         }
     310          25 :                                         cb(StringView(tmp.data() - 1, 1), UrlToken::Blank);
     311          25 :                                         cb(tmp, UrlToken::Password);
     312          25 :                                         s = tmp_s;
     313          25 :                                         cb(StringView(s.data() - 1, 1), UrlToken::Blank);
     314          25 :                                         state = UrlToken::Host;
     315             :                                 }
     316             :                         }
     317             :                 } else {
     318             :                         // host
     319         100 :                         if (!validateHost(tmp)) {
     320           0 :                                 return false;
     321             :                         }
     322             : 
     323         100 :                         cb(tmp, UrlToken::Host);
     324         100 :                         s = tmp_s;
     325             : 
     326         100 :                         if (tmp_s.is('/')) {
     327          50 :                                 state = UrlToken::Path;
     328          50 :                         } else if (tmp_s.is('?')) {
     329          25 :                                 state = UrlToken::Query;
     330          25 :                         } else if (tmp_s.is('#')) {
     331           0 :                                 state = UrlToken::Fragment;
     332             :                         } else {
     333          25 :                                 return true;
     334             :                         }
     335             :                 }
     336             :         }
     337             : 
     338         850 :         if (state == UrlToken::Host) {
     339         425 :                 bool stop = false;
     340         425 :                 if (s.is('[')) {
     341         100 :                         auto t = s;
     342         100 :                         ++ t;
     343         100 :                         tmp = t.readChars<UnreservedUni, SubDelim, StringView::Chars<':'>>();
     344         100 :                         if (t.is(']')) {
     345         100 :                                 ++ t;
     346         100 :                                 tmp = StringView(s.data(), t.data() - s.data());
     347         100 :                                 s = t;
     348             :                         } else {
     349           0 :                                 return false;
     350             :                         }
     351             :                 } else {
     352         325 :                         tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'[', ']'>>();
     353             :                 }
     354         425 :                 if (!validateHost(tmp)) {
     355           0 :                         return false;
     356             :                 }
     357         425 :                 cb(tmp, UrlToken::Host);
     358         425 :                 if (s.is(':')) {
     359         300 :                         auto tmp2 = s;
     360         300 :                         ++ tmp2;
     361         300 :                         auto port = tmp2.readChars<StringView::CharGroup<CharGroupId::Numbers>>();
     362         300 :                         if (port.empty() || s.is<UnreservedUni>()) {
     363          50 :                                 state = UrlToken::Path;
     364          50 :                                 stop = true;
     365             :                         } else {
     366         250 :                                 cb(StringView(port.data() - 1, 1), UrlToken::Blank);
     367         250 :                                 cb(port, UrlToken::Port);
     368         250 :                                 s = tmp2;
     369             :                         }
     370             :                 }
     371         425 :                 if (stop) {
     372             :                         // do nothing
     373         375 :                 } else if (s.is('/')) {
     374         100 :                         state = UrlToken::Path;
     375         275 :                 } else if (s.is('?')) {
     376          25 :                         state = UrlToken::Query;
     377         250 :                 } else if (s.is('#')) {
     378         125 :                         state = UrlToken::Fragment;
     379             :                 } else {
     380         125 :                         return true;
     381             :                 }
     382             :         }
     383             : 
     384         725 :         if (state == UrlToken::Path) {
     385         400 :                 tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'/', ':', '@'>>();
     386         400 :                 if (!tmp.empty()) {
     387         400 :                         cb(tmp, UrlToken::Path);
     388             :                 }
     389             : 
     390         400 :                 if (s.is('?')) {
     391         150 :                         state = UrlToken::Query;
     392         250 :                 } else if (s.is('#')) {
     393           0 :                         state = UrlToken::Fragment;
     394             :                 } else {
     395         250 :                         return true;
     396             :                 }
     397             :         }
     398             : 
     399         475 :         if (state == UrlToken::Query) {
     400         275 :                 tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'/', ':', '@', '?', '[', ']'>>();
     401         275 :                 if (!tmp.empty()) {
     402         275 :                         if (tmp.is('?')) {
     403         275 :                                 cb(StringView(tmp, 1), UrlToken::Blank);
     404         275 :                                 ++ tmp;
     405             :                         }
     406         275 :                         if (!tmp.empty()) {
     407         275 :                                 cb(tmp, UrlToken::Query);
     408             :                         }
     409             :                 }
     410             : 
     411         275 :                 if (s.is('#')) {
     412         225 :                         state = UrlToken::Fragment;
     413             :                 } else {
     414          50 :                         return true;
     415             :                 }
     416             :         }
     417             : 
     418         425 :         if (state == UrlToken::Fragment) {
     419         425 :                 tmp = s.readChars<UnreservedUni, SubDelim, StringView::Chars<'/', ':', '@', '?', '#', '[', ']'>>();
     420         425 :                 if (!tmp.empty()) {
     421         425 :                         if (tmp.is('#')) {
     422         425 :                                 cb(StringView(tmp, 1), UrlToken::Blank);
     423         425 :                                 ++ tmp;
     424             :                         }
     425         425 :                         if (!tmp.empty()) {
     426         425 :                                 cb(tmp, UrlToken::Fragment);
     427             :                         }
     428             :                 }
     429             :         }
     430             : 
     431         425 :         return true;
     432             : }
     433             : 
     434             : }

Generated by: LCOV version 1.14