Cuberite
A lightweight, fast and extensible game server for Minecraft
UrlParser.cpp
Go to the documentation of this file.
1 
2 // UrlParser.cpp
3 
4 // Implements the cUrlParser class that parses string URL into individual parts
5 
6 #include "Globals.h"
7 
8 #include "UrlParser.h"
9 
10 
11 
12 
13 
15 {
16  if (a_Scheme == "http")
17  {
18  return 80;
19  }
20  else if (a_Scheme == "https")
21  {
22  return 443;
23  }
24  else if (a_Scheme == "ftp")
25  {
26  return 21;
27  }
28  else if (a_Scheme == "mailto")
29  {
30  return 25;
31  }
32  return 0;
33 }
34 
35 
36 
37 
38 
39 std::pair<bool, AString> cUrlParser::ParseAuthorityPart(
40  const AString & a_AuthorityPart,
41  AString & a_Username,
42  AString & a_Password,
43  AString & a_Host,
44  UInt16 & a_Port
45 )
46 {
47  /*
48  a_AuthorityPart format:
49  [user:password@]host[:port]
50  host can be an IPv4, hostname, or an IPv6 enclosed in brackets
51  Assume only the password can contain an additional at-sign
52  */
53 
54  // Split the authority on the last at-sign, if present:
55  auto idxLastAtSign = a_AuthorityPart.find_last_of('@');
56  auto credPart = (idxLastAtSign == AString::npos) ? AString() : a_AuthorityPart.substr(0, idxLastAtSign);
57  auto srvrPart = (idxLastAtSign == AString::npos) ? a_AuthorityPart : a_AuthorityPart.substr(idxLastAtSign + 1);
58 
59  // User credentials are completely optional:
60  auto idxCredColon = credPart.find(':');
61  a_Username = credPart.substr(0, idxCredColon);
62  a_Password = (idxCredColon == AString::npos) ? AString() : credPart.substr(idxCredColon + 1);
63 
64  // Host can be a hostname, IPv4 or [IPv6]. If in brackets, search for the closing bracket first
65  if (srvrPart.empty())
66  {
67  // No host information at all. Bail out with success
68  a_Host.clear();
69  return std::make_pair(true, AString());
70  }
71  if (srvrPart[0] == '[')
72  {
73  // [IPv6] host, search for the closing bracket
74  auto idxClosingBracket = srvrPart.find(']');
75  if (idxClosingBracket == AString::npos)
76  {
77  return std::make_pair(false, "Invalid IPv6-like address, missing closing bracket");
78  }
79  a_Host = srvrPart.substr(0, idxClosingBracket);
80  auto portPart = srvrPart.substr(idxClosingBracket + 1);
81  if (portPart.empty())
82  {
83  // No port was specified, return success
84  return std::make_pair(true, AString());
85  }
86  if (portPart[0] != ':')
87  {
88  return std::make_pair(false, "Invalid port format after IPv6 address, mising colon");
89  }
90  if (!StringToInteger(portPart.substr(2), a_Port))
91  {
92  return std::make_pair(false, "Failed to parse port number after IPv6 address");
93  }
94  return std::make_pair(true, AString());
95  }
96 
97  // Not an [IPv6] address, split on the last colon:
98  auto idxLastColon = srvrPart.find_last_of(':');
99  a_Host = srvrPart.substr(0, idxLastColon);
100  if (idxLastColon == AString::npos)
101  {
102  // No port was specified, return success
103  return std::make_pair(true, AString());
104  }
105  auto portPart = srvrPart.substr(idxLastColon + 1);
106  if (!StringToInteger(portPart, a_Port))
107  {
108  return std::make_pair(false, "Failed to parse port number after hostname");
109  }
110  return std::make_pair(true, AString());
111 }
112 
113 
114 
115 
116 
117 std::pair<bool, AString> cUrlParser::Parse(
118  const AString & a_Url,
119  AString & a_Scheme,
120  AString & a_Username,
121  AString & a_Password,
122  AString & a_Host,
123  UInt16 & a_Port,
124  AString & a_Path,
125  AString & a_Query,
126  AString & a_Fragment
127 )
128 {
129  // Find the scheme - the text before the first colon:
130  auto idxColon = a_Url.find(':');
131  if (idxColon == AString::npos)
132  {
133  return std::make_pair(false, "Cannot parse the Scheme part of the URL");
134  }
135  a_Scheme = StrToLower(a_Url.substr(0, idxColon));
136  a_Port = GetDefaultPort(a_Scheme);
137  if (a_Port == 0)
138  {
139  return std::make_pair(false, fmt::format(FMT_STRING("Unknown URL scheme: \"{}\""), a_Scheme));
140  }
141 
142  // If the next two chars are a double-slash, skip them:
143  auto authStart = idxColon + 1;
144  if (a_Url.substr(authStart, 2) == "//")
145  {
146  authStart += 2;
147  }
148 
149  // The Authority part follows the Scheme, until the first slash:
150  auto idxFirstSlash = a_Url.find('/', authStart + 1);
151  if (idxFirstSlash == AString::npos)
152  {
153  // No slash, the whole end of the Url is the authority part
154  idxFirstSlash = a_Url.size();
155  }
156 
157  // Parse the Authority part into individual components:
158  auto res = ParseAuthorityPart(
159  a_Url.substr(authStart, idxFirstSlash - authStart),
160  a_Username, a_Password,
161  a_Host, a_Port
162  );
163  if (!res.first)
164  {
165  return res;
166  }
167 
168  // Parse the rest into a path, query and fragment:
169  a_Path.clear();
170  a_Query.clear();
171  a_Fragment.clear();
172  if (idxFirstSlash == a_Url.size())
173  {
174  // No additional data, bail out with success
175  return std::make_pair(true, AString());
176  }
177  auto idxPathEnd = a_Url.find_first_of("?#", idxFirstSlash + 1);
178  if (idxPathEnd == AString::npos)
179  {
180  a_Path = a_Url.substr(idxFirstSlash);
181  return std::make_pair(true, AString());
182  }
183  a_Path = a_Url.substr(idxFirstSlash, idxPathEnd - idxFirstSlash);
184  auto idxHash = a_Url.find('#', idxPathEnd);
185  if (idxHash == AString::npos)
186  {
187  a_Query = a_Url.substr(idxPathEnd + 1);
188  return std::make_pair(true, AString());
189  }
190  if (idxHash > idxPathEnd)
191  {
192  a_Query = a_Url.substr(idxPathEnd + 1, idxHash - idxPathEnd - 1);
193  }
194  a_Fragment = a_Url.substr(idxHash + 1);
195  return std::make_pair(true, AString());
196 }
197 
198 
199 
200 
201 
202 std::pair<bool, AString> cUrlParser::Validate(const AString & a_Url)
203 {
204  AString UrlScheme, UrlUsername, UrlPassword, UrlHost, UrlPath, UrlQuery, UrlFragment;
205  UInt16 Port;
206  return Parse(a_Url, UrlScheme, UrlUsername, UrlPassword, UrlHost, Port, UrlPath, UrlQuery, UrlFragment);
207 }
208 
209 
210 
211 
unsigned short UInt16
Definition: Globals.h:158
AString StrToLower(const AString &s)
Returns a lower-cased copy of the string.
std::string AString
Definition: StringUtils.h:11
bool StringToInteger(const AString &a_str, T &a_Num)
Parses any integer type.
Definition: StringUtils.h:143
static std::pair< bool, AString > Parse(const AString &a_Url, AString &a_Scheme, AString &a_Username, AString &a_Password, AString &a_Host, UInt16 &a_Port, AString &a_Path, AString &a_Query, AString &a_Fragment)
Parses the given URL into individual components.
Definition: UrlParser.cpp:117
static std::pair< bool, AString > ParseAuthorityPart(const AString &a_AuthorityPart, AString &a_Username, AString &a_Password, AString &a_Host, UInt16 &a_Port)
Parses the given Authority part of an URL into individual components.
Definition: UrlParser.cpp:39
static std::pair< bool, AString > Validate(const AString &a_Url)
Checks if the supplied URL is valid.
Definition: UrlParser.cpp:202
static UInt16 GetDefaultPort(const AString &a_Scheme)
Returns the default port used by the specified scheme / protocol.
Definition: UrlParser.cpp:14