/* ************************************************************************** */ /* */ /* ::: :::::::: */ /* ConfigParser.cpp :+: :+: :+: */ /* +:+ +:+ +:+ */ /* By: me +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2022/07/13 22:11:17 by me #+# #+# */ /* Updated: 2022/07/27 19:27:55 by me ### ########.fr */ /* */ /* ************************************************************************** */ #include "ConfigParser.hpp" /***** Stuf to rework need to figure out why return std::vector * rather than just simple not a pointer... is there a good reason? */ // Default ConfigParser::ConfigParser() { std::cout << "Default Constructor\n"; // don't use yet, you have no idea what the defaults are } //ConfigParser::ConfigParser(std::string &path) //ConfigParser::ConfigParser(char & path) ConfigParser::ConfigParser(const char* path) { std::cout << "Param Constructor\n"; std::ifstream file; std::string buf; size_t comment; _content.clear(); file.open(path); if (file.is_open()) { // are there more throws i need to add in case of errors, what would // those errors be? while (!file.eof()) { getline(file, buf); // remove # comments here. if ((comment = buf.find_first_of("#")) == std::string::npos) { // remove empty lines, i think... if ((buf.find_first_not_of(" \t")) != std::string::npos) _content.append(buf + '\n'); } else if (comment > 0 && (buf.find_first_not_of(" \t")) < comment) { // else if (comment > 0 && (buf.find_first_not_of(" \t")) != std::string::npos) // is there a comment at the end of the line std::string tmp = buf.substr(0, comment - 1); _content.append(tmp + '\n'); } } file.close(); } else throw std::invalid_argument("open config"); } ConfigParser::~ConfigParser() { // do i need to destroy anything, won't it handle itself? } /* ConfigParser & ConfigParser::operator=(const ConfigParser& rhs) { if (this == rhs) // * & ? return (*this); // * ? // make some stuff equal return (*this); } */ std::vector * ConfigParser::parse() { std::vector * ret = new std::vector(); // std::vector ret; size_t start = 0; size_t curr = _content.find_first_not_of(" \t\n", 0); if (curr == std::string::npos) throw std::invalid_argument("empty config file"); while (curr != std::string::npos) { // why no checks here // if not here do i need them elsewhere? start = _content.find_first_not_of(" \t\n", curr); curr = _content.find_first_of(" \t\n", start); std::string key = _content.substr(start, curr - start); if (key != "server") throw std::invalid_argument("bad config file arguments 1"); // Server server = parse_server(&curr); // ret->push_back(server); // why not this? ret->push_back(_parse_server(&curr)); } return (ret); } // might need new names for Prev and Curr, not super descriptive... ServerConfig ConfigParser::_parse_server(size_t *start) { ServerConfig ret; size_t curr = _content.find_first_not_of(" \t\n", *start); if (curr == std::string::npos || _content[curr] != '{') throw std::invalid_argument("bad config file syntax 1"); curr = _content.find_first_of(" \t\n", curr + 1); // if (curr == std::string::npos) // are there other things to check for? // throw std::invalid_argument("bad config file syntax"); while (curr != std::string::npos) // here curr == { + 1 { // so this moves curr to past the word... std::string key = _get_first_word(&curr); // now curr is on space after 1st word. if (key == "}") { // why +1 curr is already after it no? *start = _content.find_first_not_of(" \t\n", curr + 1); break ; } else if (key == "location") { // this does assume we have locations in Server... // could change the name but it's so clear... ret.locations.push_back(_parse_location(&curr)); } else { std::string values = _get_rest_of_line(&curr); // curr now should be \n // checking for ; in _set_value, check key and value _set_server_values(&ret, key, values); // handles the throws } } return (ret); } LocationConfig ConfigParser::_parse_location(size_t *start) { LocationConfig ret; size_t curr = *start; // start is after the 1st word aka "location" ret.path = _get_first_word(&curr); // in theory now curr should be right after the "path" curr = _content.find_first_not_of(" \t\n", curr); if (curr == std::string::npos || _content[curr] != '{') throw std::invalid_argument("bad config file syntax 2"); curr = _content.find_first_of(" \t\n", curr + 1); // if (curr == std::string::npos) // are there other things to check for? // throw std::invalid_argument("bad config file syntax"); while (curr != std::string::npos) { // so this moves curr to past the word... std::string key = _get_first_word(&curr); // now curr is on space after 1st word. if (key == "}") { *start = curr; break ; } else { std::string values = _get_rest_of_line(&curr); // curr now should be \n // checking for ; in _set_value, check key and value _set_location_values(&ret, key, values); //handles the throws } } return (ret); } // ok you need to think through these throws, when will each occur? void ConfigParser::_set_server_values(ServerConfig *server, \ const std::string key, std::string value) { // check key for ; // check values for ; at end and right number of words depending on key if (key.find_first_of(";") != std::string::npos) throw std::invalid_argument("bad config file arguments 2"); // there shouldn't be any tabs, right? not between values... if (value.find_first_of("\t") != std::string::npos) { std::cout << value << "\n"; throw std::invalid_argument("bad config file arguments 3"); } size_t i = value.find_first_of(";"); // so you can't have no ; // you can't have just ; // and you can't have a ; not at the end or several ; // in theory value_find_last_of should find the only ; if (i == std::string::npos || (value.find_last_not_of(" \n")) != i \ || value.compare(";") == 0) throw std::invalid_argument("bad config file arguments 4"); // we Trim value. // is this valid? // would it be better to shove the result directly in tmp_val? // like call substr in split? //value = value.substr(0, i - 1); value = value.substr(0, i); std::vector tmp_val = split(value, ' '); size_t size = tmp_val.size(); // would if if be more optimized? if (size < 1) throw std::invalid_argument("missing value"); else if (key == "server_name" && size == 1) { server->server_name = tmp_val[0]; } else if (key == "listen" && size == 1) { if (tmp_val[0].find_first_of(":") == std::string::npos) { // why not store as vector [4] ? server->host = "0.0.0.0"; server->port = tmp_val[0]; } else { // maybe do this differently? std::vector tmp2 = split(tmp_val[0], ':'); // i might take issue with this, will see if (server->host != "" && server->host != tmp2[0]) throw std::invalid_argument("bad listen"); server->host = tmp2[0]; server->port = tmp2[1]; } } else if (key == "root" && size == 1) { server->root = tmp_val[0]; } else if (key == "autoindex" && size == 1) { server->autoindex = (tmp_val[0] == "on" ? true : false); } else if (key == "client_body_limit" && size == 1) { server->client_body_limit = atoi(tmp_val[0].c_str()); } else if (key == "recv_timeout" && size == 1) { // what is tv_sec and do i need it? // ok so i don't fully understand this part but ok, keep for now... server->recv_timeout.tv_sec = atoi(tmp_val[0].c_str()); } else if (key == "send_timeout" && size == 1) { server->send_timeout.tv_sec = atoi(tmp_val[0].c_str()); } /* else { throw std::invalid_argument("should only have 1 value"); // yea ok but it could also be something else like too many // args } */ else if (key == "index") { // could run more tests on value content but meh... for (unsigned long i = 0; i != tmp_val.size(); i++) server->index.push_back(tmp_val[i]); } else if (key == "allow_methods") { // might do something different here // like change how methods are stored? for (unsigned long i = 0; i != tmp_val.size(); i++) server->allow_methods.push_back(_str_to_method_type(tmp_val[i])); } else if (key == "return") { // could run more checks here too // like tmp_val.size() must be 2 // and tmp_val[0] should be a number and tmp_val[1] a string? server->redirect_status = atoi(tmp_val[0].c_str()); server->redirect_uri = tmp_val[1]; } else if (key == "error_page") { // something more complicated? // like make sure ints then 1 string? std::string path = tmp_val[tmp_val.size() - 1]; for (unsigned long i = 0; i != tmp_val.size() - 1; i++) { int status_code = atoi(tmp_val[i].c_str()); // yea IDK i might not want to store this like that... if (server->error_pages.find(status_code) != server->error_pages.end()) continue ; server->error_pages[status_code] = path; } } else { throw std::invalid_argument("wrong number of values"); } } // again not sure i want an int ret void ConfigParser::_set_location_values(LocationConfig *location, \ const std::string key, std::string value) { // check key for ; // check values for ; at end and right number of words depending on key if (key.find_first_of(";") != std::string::npos) throw std::invalid_argument("bad config file arguments 5"); // there shouldn't be any tabs, right? not between values... if (value.find_first_of("\t") != std::string::npos) throw std::invalid_argument("bad config file arguments 6"); size_t i = value.find_first_of(";"); // so you can't have no ; // you can't have just ; // and you can't have a ; not at the end or several ; // in theory value_find_last_of should find the only ; if (i == std::string::npos || (value.find_last_not_of(" \n")) != i \ || value.compare(";") == 0) throw std::invalid_argument("bad config file arguments 7"); // we Trim value. // is this valid? // could do like above? value = value.substr(0, i); std::vector tmp_val = ::split(value, ' '); size_t size = tmp_val.size(); if (size < 1) throw std::invalid_argument("missing value"); else if (key == "root" && size == 1) { location->root = tmp_val[0]; } else if (key == "client_body_limit" && size == 1) { location->client_body_limit = atoi(tmp_val[0].c_str()); } /* else { throw std::invalid_argument("should only have 1 argument"); } */ else if (key == "index") { for (unsigned long i = 0; i != tmp_val.size(); i++) location->index.push_back(tmp_val[i]); } else if (key == "allow_methods") { for (unsigned long i = 0; i != tmp_val.size(); i++) location->allow_methods.push_back(_str_to_method_type(tmp_val[i])); } else if (key == "cgi_info") { // ok wtf is all this even doing, figure that out unsigned long i = value.find_first_of(" "); if (i == std::string::npos) throw std::invalid_argument("bad config file arguments 8"); // ok why an int now, we gotta be more consistent! int j = value.find_first_not_of(" ", i); location->cgi_info[value.substr(0, i)] = value.substr(j, value.length()); } else { throw std::invalid_argument("bad config file arguments 9"); } } // assumes curr is on a space or \t or \n // get first word? next word? word? std::string ConfigParser::_get_first_word(size_t *curr) { size_t start; // are these checks excessive? if ((start = _content.find_first_not_of(" \t\n", *curr)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); if ((*curr = _content.find_first_of(" \t\n", start)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); std::string key = _content.substr(start, *curr - start); return (key); } // also assumes curr is on a space \t or \n std::string ConfigParser::_get_rest_of_line(size_t *curr) { size_t start; if ((start = _content.find_first_not_of(" \t\n", *curr)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); // std::cout << "start + 4 = " << _content.substr(start, 4) << "\n"; // std::cout << "curr + 4 = " << _content.substr(*curr, 4) << "\n"; if ((*curr = _content.find_first_of("\n", start)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); std::string values = _content.substr(start, *curr - start); // std::cout << "curr + 4 = " << _content.substr(*curr, 4) << "\n"; std::cout << "rest of Line values: " << values << "\n"; return (values); } MethodType ConfigParser::_str_to_method_type(std::string str) { if (str == "GET") return GET; else if (str == "POST") return POST; else if (str == "DELETE") return DELETE; return INVALID; } void ConfigParser::_print_content() const { std::cout << _content; } // I might need to make my own Exceptions to throw...