/* * (C) 2003-2022 Anope Team * Contact us at team@anope.org * * Please read COPYING and README for further details. */ #include "webcpanel.h" #include #include #include #include #include #include struct ForLoop { static std::vector Stack; size_t start; /* Index of start of this loop */ std::vector vars; /* User defined variables */ typedef std::pair range; std::vector ranges; /* iterator ranges for each variable */ ForLoop(size_t s, TemplateFileServer::Replacements &r, const std::vector &v, const std::vector &r_names) : start(s), vars(v) { for (unsigned i = 0; i < r_names.size(); ++i) ranges.push_back(r.equal_range(r_names[i])); } void increment(const TemplateFileServer::Replacements &r) { for (unsigned i = 0; i < ranges.size(); ++i) { range &ra = ranges[i]; if (ra.first != r.end() && ra.first != ra.second) ++ra.first; } } bool finished(const TemplateFileServer::Replacements &r) const { for (unsigned i = 0; i < ranges.size(); ++i) { const range &ra = ranges[i]; if (ra.first != r.end() && ra.first != ra.second) return false; } return true; } }; std::vector ForLoop::Stack; std::stack IfStack; static Anope::string FindReplacement(const TemplateFileServer::Replacements &r, const Anope::string &key) { /* Search first through for loop stack then global replacements */ for (unsigned i = ForLoop::Stack.size(); i > 0; --i) { ForLoop &fl = ForLoop::Stack[i - 1]; for (unsigned j = 0; j < fl.vars.size(); ++j) { const Anope::string &var_name = fl.vars[j]; if (key == var_name) { const ForLoop::range &range = fl.ranges[j]; if (range.first != r.end() && range.first != range.second) { return range.first->second; } } } } TemplateFileServer::Replacements::const_iterator it = r.find(key); if (it != r.end()) return it->second; return ""; } TemplateFileServer::TemplateFileServer(const Anope::string &f_n) : file_name(f_n) { } void TemplateFileServer::Serve(HTTPProvider *server, const Anope::string &page_name, HTTPClient *client, HTTPMessage &message, HTTPReply &reply, Replacements &r) { int fd = open((template_base + "/" + this->file_name).c_str(), O_RDONLY); if (fd < 0) { Log(LOG_NORMAL, "httpd") << "Error serving file " << page_name << " (" << (template_base + "/" + this->file_name) << "): " << strerror(errno); client->SendError(HTTP_PAGE_NOT_FOUND, "Page not found"); return; } Anope::string buf; int i; char buffer[BUFSIZE]; while ((i = read(fd, buffer, sizeof(buffer) - 1)) > 0) { buffer[i] = 0; buf += buffer; } close(fd); Anope::string finished; bool escaped = false; for (unsigned j = 0; j < buf.length(); ++j) { if (buf[j] == '\\' && j + 1 < buf.length() && (buf[j + 1] == '{' || buf[j + 1] == '}')) escaped = true; else if (buf[j] == '{' && !escaped) { size_t f = buf.substr(j).find('}'); if (f == Anope::string::npos) break; const Anope::string &content = buf.substr(j + 1, f - 1); if (content.find("IF ") == 0) { std::vector tokens; spacesepstream(content).GetTokens(tokens); if (tokens.size() == 4 && tokens[1] == "EQ") { Anope::string first = FindReplacement(r, tokens[2]), second = FindReplacement(r, tokens[3]); if (first.empty()) first = tokens[2]; if (second.empty()) second = tokens[3]; bool stackok = IfStack.empty() || IfStack.top(); IfStack.push(stackok && first == second); } else if (tokens.size() == 3 && tokens[1] == "EXISTS") { bool stackok = IfStack.empty() || IfStack.top(); IfStack.push(stackok && r.count(tokens[2]) > 0); } else Log() << "Invalid IF in web template " << this->file_name; } else if (content == "ELSE") { if (IfStack.empty()) Log() << "Invalid ELSE with no stack in web template" << this->file_name; else { bool old = IfStack.top(); IfStack.pop(); // Pop off previous if() bool stackok = IfStack.empty() || IfStack.top(); IfStack.push(stackok && !old); // Push back the opposite of what was popped } } else if (content == "END IF") { if (IfStack.empty()) Log() << "END IF with empty stack?"; else IfStack.pop(); } else if (content.find("FOR ") == 0) { std::vector tokens; spacesepstream(content).GetTokens(tokens); if (tokens.size() != 4 || tokens[2] != "IN") Log() << "Invalid FOR in web template " << this->file_name; else { std::vector temp_variables, real_variables; commasepstream(tokens[1]).GetTokens(temp_variables); commasepstream(tokens[3]).GetTokens(real_variables); if (temp_variables.size() != real_variables.size()) Log() << "Invalid FOR in web template " << this->file_name << " variable mismatch"; else ForLoop::Stack.push_back(ForLoop(j + f, r, temp_variables, real_variables)); } } else if (content == "END FOR") { if (ForLoop::Stack.empty()) Log() << "END FOR with empty stack?"; else { ForLoop &fl = ForLoop::Stack.back(); if (fl.finished(r)) ForLoop::Stack.pop_back(); else { fl.increment(r); if (fl.finished(r)) ForLoop::Stack.pop_back(); else { j = fl.start; // Move pointer back to start of the loop continue; // To prevent skipping over this block which doesn't exist anymore } } } } else if (content.find("INCLUDE ") == 0) { std::vector tokens; spacesepstream(content).GetTokens(tokens); if (tokens.size() != 2) Log() << "Invalid INCLUDE in web template " << this->file_name; else { if (!finished.empty()) { reply.Write(finished); // Write out what we have currently so we insert this files contents here finished.clear(); } TemplateFileServer tfs(tokens[1]); tfs.Serve(server, page_name, client, message, reply, r); } } else { // If the if stack is empty or we are in a true statement bool ifok = IfStack.empty() || IfStack.top(); bool forok = ForLoop::Stack.empty() || !ForLoop::Stack.back().finished(r); if (ifok && forok) { Anope::string replacement = FindReplacement(r, content.substr(0, f - 1)); // htmlescape all text replaced onto the page replacement = HTTPUtils::Escape(replacement); finished += replacement; } } j += f; // Skip over this whole block } else { escaped = false; // If the if stack is empty or we are in a true statement bool ifok = IfStack.empty() || IfStack.top(); bool forok = ForLoop::Stack.empty() || !ForLoop::Stack.back().finished(r); if (ifok && forok) finished += buf[j]; } } if (!finished.empty()) reply.Write(finished); }