/* ************************************************************************** */ /* */ /* ::: :::::::: */ /* 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; // 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()) { // if we remove the Emtpy lines as well it simplifies things later... 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")) != 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); } */ // ok what exactly is the benefit of returning a pointer here... //std::vector * ConfigParser::parse() std::vector ConfigParser::parse() { // is this the best way to do this? new? // std::vector *ret = new std::vector(); std::vector ret; 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 (curr != 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); } // might need new names for Prev and Curr, not super descriptive... ServerConfig ConfigParser::parse_server(size_t *start) { ServerConfig 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); // 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. switch (key) { case "}": // why +1 curr is already after it no? *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: 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); // it handles the throws } } return (ret); } LocationConfig ConfigParser::parse_location(size_t *start) { LocationConfig 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); // 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. switch (key) { case "}": *start = curr; break ; default: 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); // it handles the throws } } return (ret); } // ok you need to think through these throws, when will each occur? void ConfigParser::_set_server_values(Server *server, const std::string key, const 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"); // 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"); 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"); // we Trim value. // is this valid? value = value.substr(0, i - 1); std::vector tmp = ::split(value, ' '); if (tmp.size() == 1) { switch (key) { case "server_name": server->server_name = value; case "listen": // ok yea i don't get this one anymore.. 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 tmp2 = split(value, ':'); // i might take issue with this, will see if (server->host != "" && server->host != tmp2[0]) throw std::invalid_argument("bad listen"); server->host = tmp[0]; server->port = tmp[1]; } case "root": server->root = value; 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()); default : throw std::invalid_argument("should only have 1 value"); // yea ok but it could also be something else like too many // args } } else if (tmp.size() > 1) { switch (key) { case "index": // could run more tests on value content but meh... for (unsigned long i = 0; i != tmp.size(); i++) server->index.push_back(tmp[i]); case "allow_methods": // 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 "return": // could run more checks here too // like tmp.size() must be 2 // and tmp[0] should be a number and tmp[1] a string? server->redirect_status = atoi(tmp[0].c_str()); server->redirect_uri = tmp[1]; case "error_page": // something more complicated? // like make sure ints then 1 string? 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"); } } else throw std::invalid_argument("missing value"); } // again not sure i want an int ret int ConfigParser::_set_location_values(Location *location, const std::string key, const 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"); // 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"); 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"); // we Trim value. // is this valid? value = value.substr(0, i - 1); std::vector tmp = ::split(value, ' '); if (tmp.size() == 1) { switch (key) { case "root": location->root = value; case "client_body_limit": location->client_body_limit = atoi(value.c_str()); default : throw std::invalid_argument("should only have 1 argument"); } } else if (tmp.size() > 1) { switch (key) { 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()); default : throw std::invalid_argument("bad config file arguments"); } } else throw std::invalid_argument("missing a value"); } // 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"); 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); return (values); } void ConfigParser::_print_content() const { std::cout << _content; } // I might need to make my own Exceptions to throw...