/* ************************************************************************** */ /* */ /* ::: :::::::: */ /* 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 I don't love the use of EMPTY and FAILURE and those slowly transitioning away... I would rather throw exceptions everywhere... where do i check if there is only a ";" need to figure out why return std::vector * rather than just simple not a pointer... is there a good reason? I need to better understand what check_proper_line does */ // 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() { // is this the best way to do this? new? std::vector *ret = new std::vector(); // yea i could do 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 (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); // or // return (&ret); // wait no, that doesn't work... } // could we return a ref instead? // i think no // 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) { _get_key(&word_start, &blank_start); _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); std::string key = _get_key(&word_start, &blank_start); 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: // ok i figured it out, there's a slight difference! // curr below is this actually, she does go all the way to the end of the line... // there has to be a better way of doing this, just like // get me the first word, get me all the rest... but like in sub functions... if ((curr = _content.find_first_of("\n", prev)) == std::string::npos) throw std::invalid_argument("bad config file arguments"); _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_for_semicolon(_content.substr(key_start, curr - key_start))) == FAILED) throw std::invalid_argument("bad config file arguments"); // if ((int)value_end == EMPTY) // continue ; // could i shove this in the _set_server_value() func call? std::string value = _content.substr(prev, value_end - prev + key_start + 1) // since there's not return anymore // if (_set_server_value(&ret, key, value) == FAILED) // throw std::invalid_argument("bad config file arguments"); _set_server_value(&ret, key, value); // 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); 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... // ok no we need the if cuz have to set value_end if ((value_end = _check_for_semicolon(_content.substr(key_start, curr - key_start))) == FAILED) throw std::invalid_argument("bad config file arguments"); // if we remove all empty lines at the start no need for this // if ((int)value_end == EMPTY) // continue ; // shove this in _set_location_value() func call? 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"); _set_location_value(&ret, key, value); } } return (ret); } // yea ok new plan, this is where a lot of the checks will happen... void ConfigParser::_set_server_value(Server *server, const std::string key, const std::string value) { // so turns out here is where i should be checking for the semicolon i think // and also making sure there are the right number of values depending on which key it is... // like for error page there can be a bunch but you can't have say 1 error code and several error files // it's 1 error file per line and // i could do some checks at the start for value being bad or empty? or is // that unnecessary since in theory i've already checked that it's good... 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... // definitely need a better name... // _check_proper_word_break ? // get next word ? 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"); } // no longer returning an int // fuck no we have to return an int... // ok keep FAILED as being -1 // i mean we could send a pointer to value_end and then ret void, but why // bother chaning it now... int ConfigParser::_check_for_semicolon(std::string line) { // yea ok i just don't like this... // 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; } void ConfigParser::_print_content() const { std::cout << _content; } // I might need to make my own Exceptions to throw...