LeftHookRoll
An HTTP/1.0 compliant web server, as specified by RFC1945
Loading...
Searching...
No Matches
ConfigParser.cpp
Go to the documentation of this file.
1#include <fstream>
2#include <sstream>
3#include <cstring>
4#include <cstdlib>
5#include <cctype>
6#include <arpa/inet.h>
7#include <netdb.h>
8#include "../includes/ConfigParser.hpp"
9
10// ConfigException
11
13 : FatalException("webserv: config error: " + msg) {}
14
16
17// Canonical form
18
19ConfigParser::ConfigParser(const std::string& filePath)
20 : _filePath(filePath), _pos(0) {}
21
23
24// Public API
25
26std::vector<ServerConf> ConfigParser::parse()
27{
28 std::ifstream file(_filePath.c_str());
29 if (!file.is_open())
30 throw ConfigException("cannot open file: " + _filePath);
31
32 std::ostringstream ss;
33 ss << file.rdbuf();
34 if (file.bad())
35 throw ConfigException("read error on file: " + _filePath);
36
37 _tokenize(ss.str());
38
39 std::vector<ServerConf> servers;
40 while (!_atEnd())
41 {
42 if (_peek() != "server")
43 throw ConfigException("expected 'server' block, got: '" + _peek() + "'");
44 _consume();
45 _expect("{");
46 servers.push_back(_parseServerBlock());
47 }
48
49 if (servers.empty())
50 throw ConfigException("config contains no server blocks");
51
52 return servers;
53}
54
55
56void ConfigParser::_tokenize(const std::string& content)
57{
58 //
59 size_t i = 0;
60 while (i < content.size())
61 {
62 const char c = content[i];
63
64 if (std::isspace(c))
65 {
66 i++; continue;
67 }
68
69 if (c == '#')
70 {
71 while (i < content.size() && content[i] != '\n')
72 i++;
73 continue;
74 }
75
76 if (c == '{' || c == '}' || c == ';')
77 {
78 _tokens.push_back(std::string(1, c));
79 i++;
80 continue;
81 }
82
83 size_t start = i;
84 while (i < content.size()
85 && !std::isspace(content[i])
86 && content[i] != '{'
87 && content[i] != '}'
88 && content[i] != ';'
89 && content[i] != '#')
90 {
91 i++;
92 }
93 _tokens.push_back(content.substr(start, i - start));
94 }
95}
96
98{
99 return _pos >= _tokens.size();
100}
101
102const std::string& ConfigParser::_peek() const
103{
104 if (_atEnd())
105 throw ConfigException("unexpected end of config file");
106 return _tokens[_pos];
107}
108
109const std::string& ConfigParser::_consume()
110{
111 const std::string& t = _peek();
112 _pos++;
113 return t;
114}
115
116void ConfigParser::_expect(const std::string& token)
117{
118 const std::string got = _consume();
119 if (got != token)
120 throw ConfigException("expected '" + token + "', got '" + got + "'");
121}
122
123
125{
126 ServerConf conf;
127
128 while (!_atEnd() && _peek() != "}")
129 {
130 const std::string directive = _consume();
131
132 if (directive == "listen")
133 _parseListen(conf);
134 else if (directive == "server_name")
135 _parseServerName(conf);
136 else if (directive == "client_max_body_size")
137 _parseMaxBodySize(conf);
138 else if (directive == "error_page")
139 _parseErrorPage(conf);
140 else if (directive == "location")
141 {
142 const std::string path = _consume();
143 _expect("{");
145 }
146 else
147 throw ConfigException("unknown server directive: '" + directive + "'");
148 }
149 _expect("}");
150 return conf;
151}
152
154{
155 LocationConf loc;
156 loc.setPath(path);
157
158 while (!_atEnd() && _peek() != "}")
159 {
160 const std::string directive = _consume();
161
162 if (directive == "root")
163 _parseRoot(loc);
164 else if (directive == "methods")
165 _parseMethods(loc);
166 else if (directive == "autoindex")
167 _parseAutoIndex(loc);
168 else if (directive == "index")
169 _parseIndex(loc);
170 else if (directive == "upload_store")
172 else if (directive == "return")
173 _parseReturn(loc);
174 else if (directive == "cgi_interpreter")
176 else
177 throw ConfigException("unknown location directive: '" + directive + "'");
178 }
179 _expect("}");
180 return loc;
181}
182
183
185{
186 const std::string value = _consume();
187 _expect(";");
189}
190
192{
193 const std::string name = _consume();
194 _expect(";");
195 conf.setServerName(name);
196}
197
199{
200 const std::string value = _consume();
201 _expect(";");
202 conf.setMaxBodySize(_parseBodySize(value));
203}
204
206{
207 const std::string code = _consume();
208 const std::string path = _consume();
209 _expect(";");
210
211 if (code.size() != 3 || code.find_first_not_of("0123456789") != std::string::npos)
212 throw ConfigException("invalid error_page code: '" + code + "'");
213
214 conf.addErrorPage(code, path);
215}
216
218{
219 const std::string root = _consume();
220 _expect(";");
221 loc.setRoot(root);
222}
223
225{
226 if (_peek() == ";")
227 throw ConfigException("'methods' directive requires at least one method");
228
229 while (!_atEnd() && _peek() != ";")
231
232 _expect(";");
233}
234
236{
237 const std::string value = _consume();
238 _expect(";");
239
240 if (value == "on")
241 loc.setAutoIndex(true);
242 else if (value == "off")
243 loc.setAutoIndex(false);
244 else
245 throw ConfigException("autoindex must be 'on' or 'off', got: '" + value + "'");
246}
247
249{
250 const std::string page = _consume();
251 _expect(";");
252 loc.setDefaultPage(page);
253}
254
256{
257 const std::string dir = _consume();
258 _expect(";");
259 loc.setStorageLocation(dir);
260}
261
263{
264 const std::string code = _consume();
265 const std::string url = _consume();
266 _expect(";");
267
268 if (code.find_first_not_of("0123456789") != std::string::npos)
269 throw ConfigException("invalid return code: '" + code + "'");
270
271 loc.setReturnCode(code);
272 loc.setReturnURL(url);
273}
274
276{
277 const std::string path = _consume();
278 const std::string ext = _consume();
279 _expect(";");
280 loc.addCgiInterpreter(ext, path);
281}
282
283struct sockaddr_in ConfigParser::_parseSockAddr(const std::string& listenValue)
284{
285 struct sockaddr_in addr;
286 std::memset(&addr, 0, sizeof(addr));
287 addr.sin_family = AF_INET;
288
289 size_t colon = listenValue.find(':');
290 if (colon != std::string::npos)
291 {
292 const std::string ipStr = listenValue.substr(0, colon);
293 const std::string portStr = listenValue.substr(colon + 1);
294
295 if (portStr.empty() || portStr.find_first_not_of("0123456789") != std::string::npos)
296 throw ConfigException("invalid port in listen: '" + portStr + "'");
297
298 const int port = std::atoi(portStr.c_str());
299 if (port <= 0 || port > 65535)
300 throw ConfigException("port out of range in listen: '" + portStr + "'");
301
302 if (ipStr.empty())
303 throw ConfigException("invalid IP address in listen: '" + ipStr + "'");
304
305 struct addrinfo hints;
306 std::memset(&hints, 0, sizeof(hints));
307 hints.ai_family = AF_INET;
308 hints.ai_socktype = SOCK_STREAM;
309
310 struct addrinfo* result = NULL;
311 int rc = getaddrinfo(ipStr.c_str(), NULL, &hints, &result);
312 if (rc != 0 || result == NULL)
313 throw ConfigException("invalid IP/host in listen: '" + ipStr + "'");
314
315 const struct sockaddr_in* resolved =
316 reinterpret_cast<const struct sockaddr_in*>(result->ai_addr);
317 addr.sin_addr = resolved->sin_addr;
318 freeaddrinfo(result);
319
320 addr.sin_port = htons(static_cast<uint16_t>(port));
321 }
322 else
323 {
324 if (listenValue.empty() || listenValue.find_first_not_of("0123456789") != std::string::npos)
325 throw ConfigException("invalid listen value: '" + listenValue + "'");
326
327 const int port = std::atoi(listenValue.c_str());
328 if (port <= 0 || port > 65535)
329 throw ConfigException("port out of range in listen: '" + listenValue + "'");
330
331 addr.sin_addr.s_addr = htonl(INADDR_ANY);
332 addr.sin_port = htons(static_cast<uint16_t>(port));
333 }
334 return addr;
335}
336
337size_t ConfigParser::_parseBodySize(const std::string& value)
338{
339 if (value.empty())
340 throw ConfigException("empty client_max_body_size value");
341 const char suffix = value[value.size() - 1];
342 size_t multiplier = 1;
343 std::string numStr = value;
344
345 if (suffix == 'k' || suffix == 'K')
346 {
347 multiplier = 1024;
348 numStr = value.substr(0, value.size() - 1);
349 }
350 else if (suffix == 'm' || suffix == 'M')
351 {
352 multiplier = 1024 * 1024;
353 numStr = value.substr(0, value.size() - 1);
354 }
355 else if (suffix == 'g' || suffix == 'G')
356 {
357 multiplier = 1024UL * 1024 * 1024;
358 numStr = value.substr(0, value.size() - 1);
359 }
360
361 if (numStr.empty() || numStr.find_first_not_of("0123456789") != std::string::npos)
362 throw ConfigException("invalid client_max_body_size value: '" + value + "'");
363
364 return static_cast<size_t>(std::atol(numStr.c_str())) * multiplier;
365}
366
368{
369 if (token == "GET")
370 return GET;
371 if (token == "POST")
372 return POST;
373 if (token == "DELETE")
374 return DELETE;
375 throw ConfigException("unknown HTTP method: '" + token + "'");
376}
HTTPMethod
Represents the HTTP methods supported by the server as a bitmask.
@ GET
@ POST
@ DELETE
ConfigException(const std::string &msg)
const std::string & _consume()
struct sockaddr_in _parseSockAddr(const std::string &listenValue)
std::vector< ServerConf > parse()
Parses the config file and returns one ServerConf per server block.
void _parseUploadStore(LocationConf &loc)
void _parseRoot(LocationConf &loc)
ServerConf _parseServerBlock()
LocationConf _parseLocationBlock(const std::string &path)
void _parseMethods(LocationConf &loc)
void _parseMaxBodySize(ServerConf &conf)
void _parseServerName(ServerConf &conf)
ConfigParser(const std::string &filePath)
void _parseIndex(LocationConf &loc)
bool _atEnd() const
const std::string & _peek() const
void _tokenize(const std::string &content)
void _expect(const std::string &token)
void _parseErrorPage(ServerConf &conf)
void _parseCgiInterpreter(LocationConf &loc)
void _parseListen(ServerConf &conf)
std::vector< std::string > _tokens
HTTPMethod _parseMethodToken(const std::string &token)
void _parseAutoIndex(LocationConf &loc)
std::string _filePath
size_t _parseBodySize(const std::string &value)
void _parseReturn(LocationConf &loc)
void setDefaultPage(const std::string &defaultPage)
void addAllowedMethod(HTTPMethod method)
void setAutoIndex(bool autoIndex)
void addCgiInterpreter(const std::string &ext, const std::string &interpreterPath)
void setReturnURL(const std::string &url)
void setReturnCode(const std::string &code)
void setRoot(const std::string &root)
void setPath(const std::string &path)
void setStorageLocation(const std::string &storageLocation)
void setInterfacePortPair(const struct sockaddr_in &address)
void addErrorPage(const std::string &errorCode, const std::string &errorPagePath)
Adds a custom error page mapping (e.g., "404" -> "/errors/404.html").
void addLocation(const LocationConf &location)
Adds a parsed LocationConf block to this server.
void setServerName(const std::string &name)
void setMaxBodySize(size_t size)