Skip to content

Commit 65eab00

Browse files
committed
added URI class
1 parent 4db2bef commit 65eab00

8 files changed

Lines changed: 286 additions & 0 deletions

File tree

include/webframe.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <webframe/exception.hpp>
3434
#include <webframe/handler.hpp>
3535
#include <webframe/router.hpp>
36+
#include <webframe/uri.hpp>
3637

3738
#if defined(_WIN32) && defined(WEBFRAME_DESKTOP_RUNTIME)
3839
#define WEBFRAME_WIN32_APP 1
@@ -83,6 +84,8 @@ namespace webframe
8384
* @return the path of the request
8485
*/
8586
virtual std::string get_path() const = 0;
87+
88+
virtual std::string get_uri() const = 0;
8689

8790
/**
8891
* @brief get the value of a specific header

include/webframe/uri.hpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* WebFrame
2+
*
3+
* Copyright (C) 2026 Maxtek Consulting
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
#ifndef WEBFRAME_URI_HPP
20+
#define WEBFRAME_URI_HPP
21+
22+
#include <string>
23+
#include <memory>
24+
#include <unordered_map>
25+
26+
namespace webframe
27+
{
28+
class uri
29+
{
30+
public:
31+
uri(const std::string& str);
32+
~uri() = default;
33+
34+
std::string get_scheme() const;
35+
std::string get_host() const;
36+
int get_port() const;
37+
std::string get_path() const;
38+
bool get_query(const std::string& key, std::string& value) const;
39+
std::string get_fragment() const;
40+
private:
41+
static bool find_keyword(const std::string& input_url, size_t& st, size_t& before, const std::string& delim, std::string& result);
42+
static bool split_query(const std::string& str, const std::string& delim, std::string& key, std::string& value);
43+
void parse(const std::string& str);
44+
std::string _scheme;
45+
std::string _userinfo;
46+
std::string _host;
47+
int _port;
48+
std::string _path;
49+
std::unordered_map<std::string, std::string> _query;
50+
std::string _fragment;
51+
};
52+
}
53+
54+
#endif

src/runtimes/desktop/include/desktop/request.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ namespace webframe::desktop
1010
public:
1111
request(const wxWebViewHandlerRequest *request);
1212
~request() = default;
13+
1314
webframe::method get_method() const override;
15+
16+
std::string get_uri() const override;
17+
1418
std::string get_path() const override;
1519
bool get_header(const std::string &key, std::string &value) const override;
1620
std::pair<const uint8_t *, size_t> get_body() const override;

src/runtimes/desktop/request.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ namespace webframe
4747
return std::string(uri_path.ToStdString());
4848
}
4949

50+
std::string request::get_uri() const
51+
{
52+
return _request->GetURI().ToStdString();
53+
}
54+
5055
bool request::get_header(const std::string &key, std::string &value) const
5156
{
5257
wxString header_value = _request->GetHeader(key);

src/runtimes/server/request.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ namespace webframe::server
3636

3737
}
3838

39+
std::string request::get_uri() const
40+
{
41+
const char *uri = evhttp_request_get_uri(_req);
42+
return std::string(uri);
43+
}
44+
3945
std::string request::get_path() const
4046
{
4147
const evhttp_uri *uri = evhttp_request_get_evhttp_uri(_req);

src/runtimes/server/server.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ namespace webframe::server
1616
~request() = default;
1717
webframe::method get_method() const override;
1818
std::string get_path() const override;
19+
20+
std::string get_uri() const override;
21+
1922
bool get_header(const std::string &key, std::string &value) const override;
2023
std::pair<const uint8_t *, size_t> get_body() const override;
2124
void read_body(const std::function<void(const uint8_t *, size_t)> &callback) const override;

src/uri.cpp

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
#include <webframe.hpp>
2+
3+
namespace webframe
4+
{
5+
uri::uri(const std::string &str)
6+
{
7+
parse(str);
8+
}
9+
10+
std::string uri::get_scheme() const
11+
{
12+
return _scheme;
13+
}
14+
15+
std::string uri::get_host() const
16+
{
17+
return _host;
18+
}
19+
20+
int uri::get_port() const
21+
{
22+
return _port;
23+
}
24+
25+
std::string uri::get_path() const
26+
{
27+
return _path;
28+
}
29+
30+
bool uri::get_query(const std::string &key, std::string &value) const
31+
{
32+
auto it = _query.find(key);
33+
bool found(false);
34+
if (it != _query.end())
35+
{
36+
value = it->second;
37+
found = true;
38+
}
39+
return found;
40+
}
41+
42+
std::string uri::get_fragment() const
43+
{
44+
return _fragment;
45+
}
46+
47+
bool uri::find_keyword(const std::string &input_url, size_t &st, size_t &before, const std::string &delim, std::string &result)
48+
{
49+
size_t temp_st = st;
50+
51+
st = input_url.find(delim, before);
52+
if (st == std::string::npos)
53+
{
54+
st = temp_st;
55+
return false;
56+
}
57+
58+
result = input_url.substr(before, st - before);
59+
before = st + delim.length();
60+
61+
if (result.empty())
62+
return false;
63+
64+
return true;
65+
}
66+
67+
bool uri::split_query(const std::string &str, const std::string &delim, std::string &key, std::string &value)
68+
{
69+
size_t st = str.find(delim);
70+
71+
if (st == std::string::npos)
72+
{
73+
key = str;
74+
value = "";
75+
return false;
76+
}
77+
78+
key = str.substr(0, st);
79+
value = str.substr(st + delim.length());
80+
81+
return true;
82+
}
83+
84+
void uri::parse(const std::string &str)
85+
{
86+
size_t st = 0;
87+
size_t before = 0;
88+
89+
// scheme 파싱 (예: "http", "https")
90+
// has_authority: authority(host/userinfo/port) 파싱 여부를 결정하는 플래그
91+
// - "://" 있음 → scheme 있는 절대 URL
92+
// - "//"로 시작 → scheme-relative URL
93+
// - 그 외(상대 경로 등) → authority 없음, path 파싱만 수행
94+
bool has_authority = uri::find_keyword(str, st, before, "://", _scheme);
95+
if (!has_authority && str.size() >= 2 && str[0] == '/' && str[1] == '/')
96+
{
97+
has_authority = true;
98+
before = 2;
99+
}
100+
101+
if (has_authority)
102+
{
103+
// userinfo 파싱 — "@" 앞의 "user:pass" 부분 분리
104+
// authority 영역(다음 "/" 또는 "?" 또는 "#"까지)에서만 "@"를 탐색
105+
size_t authority_end = str.find_first_of("/?#", before);
106+
size_t at_pos = str.find('@', before);
107+
if (at_pos != std::string::npos &&
108+
(authority_end == std::string::npos || at_pos < authority_end))
109+
{
110+
_userinfo = str.substr(before, at_pos - before);
111+
before = at_pos + 1;
112+
}
113+
114+
// host 파싱 — "/" 또는 "?" 또는 "#"가 나오기 전까지가 host
115+
// path가 없는 URL(예: http://example.com)도 처리
116+
size_t host_end = str.find_first_of("/?#", before);
117+
if (host_end == std::string::npos)
118+
{
119+
_host = str.substr(before);
120+
before = str.length();
121+
}
122+
else
123+
{
124+
_host = str.substr(before, host_end - before);
125+
before = host_end;
126+
}
127+
128+
_port = 8080; // default port
129+
130+
// host에서 port 분리 (IPv6 bracketed notation 지원)
131+
if (!_host.empty() && _host.front() == '[')
132+
{
133+
// IPv6: [2001:db8::1] 또는 [2001:db8::1]:8080
134+
size_t bracket_close = _host.find(']');
135+
if (bracket_close != std::string::npos)
136+
{
137+
// bracket 뒤에 ":port"가 있는지 확인
138+
if (bracket_close + 1 < _host.length() && _host[bracket_close + 1] == ':')
139+
{
140+
_port = std::atoi(_host.substr(bracket_close + 2).c_str());
141+
}
142+
// bracket 안의 주소만 추출 ([ ] 제거)
143+
_host = _host.substr(1, bracket_close - 1);
144+
}
145+
}
146+
else
147+
{
148+
size_t colon_pos = _host.find(':');
149+
if (colon_pos != std::string::npos)
150+
{
151+
_port = std::atoi(_host.substr(colon_pos + 1).c_str());
152+
_host = _host.substr(0, colon_pos);
153+
}
154+
}
155+
}
156+
157+
// fragment(#) 분리 — 이후 path/query 파싱 범위를 제한
158+
size_t frag_pos = str.find('#', before);
159+
size_t effective_end = (frag_pos != std::string::npos) ? frag_pos : str.length();
160+
161+
if (frag_pos != std::string::npos && frag_pos + 1 < str.length())
162+
{
163+
_fragment = str.substr(frag_pos + 1);
164+
}
165+
166+
// path 파싱 — "?" 또는 "#" 이전까지의 "/" 구분 세그먼트
167+
size_t query_pos = str.find('?', before);
168+
if (query_pos != std::string::npos && query_pos >= effective_end)
169+
query_pos = std::string::npos; // "#" 뒤의 "?"는 무시
170+
171+
size_t path_end = effective_end;
172+
if (query_pos != std::string::npos)
173+
path_end = query_pos;
174+
175+
_path = str.substr(before, path_end - before);
176+
177+
// query string 파싱 — "#" 이전까지만
178+
if (query_pos != std::string::npos && query_pos + 1 < effective_end)
179+
{
180+
const std::string query_string = str.substr(query_pos + 1, effective_end - query_pos - 1);
181+
182+
size_t q_before = 0;
183+
while (q_before < query_string.length())
184+
{
185+
size_t amp_pos = query_string.find('&', q_before);
186+
std::string pair;
187+
188+
if (amp_pos == std::string::npos)
189+
{
190+
pair = query_string.substr(q_before);
191+
q_before = query_string.length();
192+
}
193+
else
194+
{
195+
pair = query_string.substr(q_before, amp_pos - q_before);
196+
q_before = amp_pos + 1;
197+
}
198+
199+
if (!pair.empty())
200+
{
201+
std::string key, value;
202+
uri::split_query(pair, "=", key, value);
203+
if (!key.empty())
204+
_query.insert(std::unordered_map<std::string, std::string>::value_type(key, value));
205+
}
206+
}
207+
}
208+
}
209+
210+
}

tests/include/test/message.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace webframe::test
4848
public:
4949
webframe::method get_method() const override;
5050
std::string get_path() const override;
51+
std::string get_uri() const override { return ""; };
5152
bool get_header(const std::string &key, std::string &value) const override;
5253
std::pair<const uint8_t *, size_t> get_body() const override;
5354
void read_body(const std::function<void(const uint8_t *, size_t)> &callback) const override;

0 commit comments

Comments
 (0)