/* ************************************************************************** */ /* */ /* ::: :::::::: */ /* ConfigParser.cpp :+: :+: :+: */ /* +:+ +:+ +:+ */ /* By: me +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2022/07/13 22:11:17 by me #+# #+# */ /* Updated: 2022/07/25 02:56:26 by me ### ########.fr */ /* */ /* ************************************************************************** */ #include "ConfigParser.hpp" /***** Stuf to rework I don't love the use of EMPTY and FAILURE and those I would rather throw exceptions everywhere... */ // 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; // std::string tmp; // maybe there's a reason we redeclare vars... size_t comment; // just a number so fine to reset _content.clear(); file.open(path); if (file.is_open()) { while (!file.eof()) { getline(file, buf); // remove # comments here. if ((comment = buf.find_first_of("#")) == std::string::npos) { _content.append(buf + '\n'); } // else if (comment > 0 && (buf.find_first_not_of(" \t")) != std::string::npos) // i think this works cuz it will find # no matter what, so it will find // something else first if there is non comment stuff. else if (comment > 0 && (buf.find_first_not_of(" \t")) < comment) { // 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() { // is this the best way to do this? new? std::vector *ret = new std::vector(); size_t prev = 0; size_t curr = _content.find_first_not_of(" \t\n", prev); if (curr == std::string::npos) throw std::invalid_argument("empty config file"); while (cur != std::string::npos) { // ok i don't love her way of scoping out the "server" strings... prev = _content.find_first_not_of(" \t\n", curr); curr = _content.find_first_of(" \t\n", prev); std::string key = _conent.substr(prev, curr - prev); if (key != "server") throw std::invalid_argument("bad config file arguments"); // Server server = parse_server(&curr); // ret->push_back(server); // why not this? ret->push_back(parse_server(&curr); } return (ret); } // could we return a ref instead? // i think no Server ConfigParser::parse_server(size_t *start) { Server ret; size_t key_start; size_t value_end; size_t prev = _content.find_first_not_of(" \t\n", *start); if (prev == std::string::npos || _content[prev] != '{') throw std::invalid_argument("bad config file syntax"); size_t curr = _content.find_first_of(" \t\n", ++prev); while (curr != std::string::npos) { _check_proper_line_end(&prev, &curr); // nope, not an option i think... /* if ((prev = _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", prev)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); */ key_start = prev; std::string key = _content.substr(prev, curr - prev); switch (key) { case "}": *start = _content.find_first_not_of(" \t\n" curr + 1); break ; case "location": // this does assume we have locations in Server... we could change // the name but it's so clear... ret.location.push_back(parse_location(&curr)); default: _check_proper_line_end(&prev, &curr); /* if ((prev = _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", prev)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); */ // why bother with the if.. why not throw exception in check_line... if ((value_end = check_line_syntax(_content.substr(key_start, curr - key_start))) == FAILED) throw std::invalid_argument("bad config file arguments"); if ((int)value_end == EMPTY) continue ; std::string value = _content.substr(prev, value_end - prev + key_start + 1) // I might need to catch here, or i could do nothing and trow all the way to the top! if (_set_server_value(&ret, key, value) == FAILED) throw std::invalid_argument("bad config file arguments"); } } return (ret); } Location ConfigParser::parse_location(size_t *start) { Location ret; size_t key_start; size_t value_end; size_t prev = _content.find_first_not_of(" \t\n", *start); if (prev == std::string::npos || _content[prev] != '{') throw std::invalid_argument("bad config file syntax"); size_t curr = _content.find_first_of(" \t\n", ++prev); while (curr != std::string::npos) { _check_proper_line_end(&prev, &curr); key_start = prev; std::string key = _content.substr(prev, curr - prev); switch (key) { case "}": *start = curr; break ; default: _check_proper_line_end(&prev, &curr); // why bother with the if.. why not throw exception in check_line... if ((value_end = check_line_syntax(_content.substr(key_start, curr - key_start))) == FAILED) throw std::invalid_argument("bad config file arguments"); if ((int)value_end == EMPTY) continue ; std::string value = _content.substr(prev, value_end - prev + key_start + 1) if (_set_location_value(&ret, key, value) == FAILED) throw std::invalid_argument("bad config file arguments"); } } return (ret); } // not convinced i want to return an int... // but could be helpful to get more feed back // maybe use a throw... but catch in parse_server... int ConfigParser::_set_server_value(Server *server, const std::string key, const std::string value) { switch (key) { case "server_name": server->server_name = value; case "listen": if (value.find_first_of(":") == std::string::npos) { // why not store as vector [4] ? server->host = "0.0.0.0"; server->value = value; } else { // maybe do this differently? std::vector tmp = split(value, ':'); // i might take issue with this, will see if (server->host != "" && server->host != tmp[0]) throw std::invalid_argument("bad config file arguments"); server->host = tmp[0]; server->port = tmp[1]; } case "root": server->root = value; case "index": std::vector tmp = split(value, ' '); for (unsigned long i = 0; i != tmp.size(); i++) server->index.push_back(tmp[i]); case "allow_methods": std::vector tmp = split(value, ' '); // might do something different here // like change how methods are stored? for (unsigned long i = 0; i != tmp.size(); i++) server->allow_methods.push_back(str_to_method_type(tmp[i])); case "autoindex": server->autoindex = (value == "on" ? true : false); case "client_body_limit": server->client_body_limit = atoi(value.c_str()); case "recv_timeout": // what is tv_sec and do i need it? server->recv_timeout.tv_sec = atoi(value.c_str()); case "send_timeout": server->send_timeout.tv_sec = atoi(value.c_str()); case "return": std::vector tmp = split(value, ' '); server->redirect_status = atoi(tmp[0].c_str()); server->redirect_uri = tmp[1]; case "error_page": std::vector tmp = split(value, ' '); std::string path = tmp[tmp.size() - 1]; for (unsigned long i = 0; i != tmp.size() - 1; i++) { int status_code = atoi(tmp[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; } default : throw std::invalid_argument("bad config file arguments"); } return (1); // for now but prolly will change... } // again not sure i want an int ret int ConfigParser::_set_location_value(Location *location, const std::string key, const std::string value) { switch (key) { case "root": location->root = value; case "index": std::vector tmp = split(value, ' '); for (unsigned long i = 0; i != tmp.size(); i++) location->index.push_back(tmp[i]); case "allow_methods": std::vector tmp = split(value, ' '); for (unsigned long i = 0; i != tmp.size(); i++) location->allow_methods.push_back(str_to_methodtype(tmp[i])); case "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"); // 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()); case "client_body_limit": location->client_body_limit = atoi(value.c_str()); default : throw std::invalid_argument("bad config file arguments"); } return (1); // again prolly want to change this... } // shit, this doesn't work... // wait it might... void ConfigParser::_check_proper_line_end(size_t *prev, size_t *curr) { if ((*prev = _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", *prev)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); } // i'm not sure i like this... // and it needs to be _check_... // rework this whole thing... int ConfigParser::check_line_syntax(std::string line) { // line must be end with semicolon size_t semicol; size_t find; semicol = line.find_first_of(";"); if (semicol == std::string::npos) return FAILED; find = line.find_first_not_of(" \t\n", semicol + 1, line.length() - semicol - 1); if (find != std::string::npos) return FAILED; find = line.find_last_not_of(" \t", semicol - 1); return find; } // I might need to make my own Exceptions to throw...