diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..2bbe336 --- /dev/null +++ b/.clang-format @@ -0,0 +1,21 @@ +--- +BasedOnStyle: Google +BreakBeforeBraces: Custom +BraceWrapping: + BeforeCatch: true + BeforeElse: true + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +IndentWidth: 4 +AccessModifierOffset: -4 +IndentCaseLabels: false +NamespaceIndentation: Inner +ColumnLimit: 0 +AlignConsecutiveMacros: Consecutive +DerivePointerAlignment: false +SpacesBeforeTrailingComments: 1 +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: AllIfsAndElse +... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f426e1a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run lint + uses: jidicula/clang-format-action@v4.11.0 + with: + clang-format-version: "13" + fallback-style: "LLVM" + + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y g++ make + - name: Build + run: make + env: + CC: gcc + CXX: g++ + + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Build + run: make + env: + CC: gcc + CXX: g++ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 67765a8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -before_install: - - "eval \"${MATRIX_EVAL}\"" -language: cpp -matrix: - include: - - - env: - - "MATRIX_EVAL=\"TESTENV=lint && STYLE=LLVM\"" - os: osx - script: - - "find . -name *.h -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" - - "find . -name *.hpp -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" - - "find . -name *.c -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" - - "find . -name *.cpp -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" - - - before_install: - - "sudo apt-get update" - - "sudo apt-get install -y g++ make" - env: - - "MATRIX_EVAL=\"TESTENV=build && CC=gcc && CXX=g++\"" - os: linux - script: - - make - - - env: - - "MATRIX_EVAL=\"TESTENV=build && CC=gcc && CXX=g++\"" - os: osx - script: - - make diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..972a459 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +CXX = g++ +CXXFLAGS = -std=c++11 -Wall -pedantic + +BUILD_DIR = build +TEMPLATE_DIR = .template +OUT_EXE = myserver.out + +ifeq ($(OS),Windows_NT) + LDLIBS += -l Ws2_32 +endif + +all: $(BUILD_DIR) $(OUT_EXE) + +$(OUT_EXE): $(BUILD_DIR)/main.o $(BUILD_DIR)/handlers.o $(BUILD_DIR)/response.o $(BUILD_DIR)/request.o $(BUILD_DIR)/utilities.o $(BUILD_DIR)/strutils.o $(BUILD_DIR)/server.o $(BUILD_DIR)/route.o $(BUILD_DIR)/template_parser.o + $(CXX) $(CXXFLAGS) $^ $(LDLIBS) -o $@ + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(BUILD_DIR)/template_parser.o: utils/template_parser.cpp utils/template_parser.hpp utils/request.cpp utils/request.hpp utils/utilities.hpp utils/utilities.cpp utils/strutils.hpp utils/strutils.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/response.o: utils/response.cpp utils/response.hpp utils/include.hpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/request.o: utils/request.cpp utils/request.hpp utils/include.hpp utils/utilities.hpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/utilities.o: utils/utilities.cpp utils/utilities.hpp utils/strutils.hpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/strutils.o: utils/strutils.cpp utils/strutils.hpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/server.o: server/server.cpp server/server.hpp server/route.hpp utils/utilities.hpp utils/strutils.hpp utils/response.hpp utils/request.hpp utils/include.hpp utils/template_parser.hpp utils/template_parser.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/route.o: server/route.cpp server/route.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/handlers.o: examples/handlers.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/main.o: examples/main.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +.PHONY: all clean +clean: + rm -rf $(BUILD_DIR) $(TEMPLATE_DIR) *.o *.out &> /dev/null diff --git a/README.md b/README.md index c959749..ddbbee5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ AP HTTP === -[![Travis (.org)](https://travis-ci.com/UTAP/APHTTP.svg)](https://travis-ci.com/UTAP/APHTTP) -[![code style: LLVM](https://img.shields.io/badge/code_style-LLVM-brightgreen.svg)](https://llvm.org/docs/CodingStandards.html) -[![Release](https://img.shields.io/github/release/UTAP/APHTTP.svg)](https://github.com/UTAP/APHTTP/releases/latest) -[![Wiki](https://img.shields.io/badge/GitHub-Wiki-yellowgreen.svg)](https://github.com/UTAP/APHTTP/wiki) -**AP HTTP::_server_** is a simple web application server-side blocking framework for C++ based on simplified versions of [W++](http://konteck.github.io/wpp/), [HappyHTTP](http://scumways.com/happyhttp/happyhttp.html), and [cpp-netlib](http://cpp-netlib.org/). +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/AP-ECE-UT/APHTTP/license.md) +[![Release](https://img.shields.io/github/release/AP-ECE-UT/APHTTP.svg?color=brightgreen)](https://github.com/AP-ECE-UT/APHTTP/releases/latest) +[![Wiki](https://img.shields.io/badge/GitHub-Wiki-red.svg)](https://github.com/AP-ECE-UT/APHTTP/wiki) + +**AP HTTP::_server_** is a simple web application server-side blocking framework for C++. +This library is based on simplified versions of [W++](http://konteck.github.io/wpp/), [HappyHTTP](http://scumways.com/happyhttp/happyhttp.html), and [cpp-netlib](http://cpp-netlib.org/). diff --git a/examples/handlers.cpp b/examples/handlers.cpp index e88ddba..ce10ab7 100644 --- a/examples/handlers.cpp +++ b/examples/handlers.cpp @@ -1,54 +1,60 @@ #include "handlers.hpp" -using namespace std; - -Response *RandomNumberHandler::callback(Request *req) { - Response *res = new Response; - res->setHeader("Content-Type", "text/html"); - string body; - body += ""; - body += ""; - body += ""; - body += "

AP HTTP

"; - body += "

"; - body += "a random number in [1, 10] is: "; - body += to_string(rand() % 10 + 1); - body += "

"; - body += "

"; - body += "SeddionId: "; - body += req->getSessionId(); - body += "

"; - body += ""; - body += ""; - res->setBody(body); - return res; +#include +#include + +Response* RandomNumberHandler::callback(Request* req) { + Response* res = new Response(); + res->setHeader("Content-Type", "text/html"); + + std::string randomNumber = std::to_string(std::rand() % 10 + 1); + std::string body; + + body += ""; + body += ""; + + body += ""; + body += " Random Number Page"; + body += ""; + + body += ""; + body += "

AP HTTP

"; + body += "

A random number in [1, 10] is: " + randomNumber + "

"; + body += "

SessionId: " + req->getSessionId() + "

"; + body += ""; + + body += ""; + res->setBody(body); + return res; } -Response *LoginHandler::callback(Request *req) { - string username = req->getBodyParam("username"); - string password = req->getBodyParam("password"); - if (username == "root") - throw Server::Exception("Remote root access has been disabled."); - cout << "username: " << username << ",\tpassword: " << password << endl; - Response *res = Response::redirect("/rand"); - res->setSessionId("SID"); - return res; +Response* LoginHandler::callback(Request* req) { + std::string username = req->getBodyParam("username"); + std::string password = req->getBodyParam("password"); + if (username == "root") { + throw Server::Exception("Remote root access has been disabled."); + } + std::cout << "username: " << username << ",\tpassword: " << password << std::endl; + Response* res = Response::redirect("/rand"); + res->setSessionId("SID"); + return res; } -Response *UploadHandler::callback(Request *req) { - string name = req->getBodyParam("file_name"); - string file = req->getBodyParam("file"); - cout << name << " (" << file.size() << "B):\n" << file << endl; - Response *res = Response::redirect("/"); - return res; +Response* UploadHandler::callback(Request* req) { + std::string name = req->getBodyParam("file_name"); + std::string file = req->getBodyParam("file"); + utils::writeToFile(file, name); + Response* res = Response::redirect("/"); + return res; } -ColorHandler::ColorHandler(string filePath) : TemplateHandler(filePath) {} +ColorHandler::ColorHandler(const std::string& filePath) + : TemplateHandler(filePath) {} -map ColorHandler::handle(Request *req) { - map context; - string newName = "I am " + req->getQueryParam("name"); - context["name"] = newName; - context["color"] = req->getQueryParam("color"); - return context; +std::map ColorHandler::handle(Request* req) { + std::string newName = "I am " + req->getQueryParam("name"); + std::map context; + context["name"] = newName; + context["color"] = req->getQueryParam("color"); + return context; } diff --git a/examples/handlers.hpp b/examples/handlers.hpp index 987d310..ed82807 100644 --- a/examples/handlers.hpp +++ b/examples/handlers.hpp @@ -1,30 +1,30 @@ -#ifndef _MY_HANDLERS_ -#define _MY_HANDLERS_ +#ifndef HANDLERS_HPP_INCLUDE +#define HANDLERS_HPP_INCLUDE + +#include +#include #include "../server/server.hpp" -#include // for rand and srand -#include // for time -#include class RandomNumberHandler : public RequestHandler { public: - Response *callback(Request *); + Response* callback(Request*) override; }; class LoginHandler : public RequestHandler { public: - Response *callback(Request *); + Response* callback(Request*) override; }; class UploadHandler : public RequestHandler { public: - Response *callback(Request *); + Response* callback(Request*) override; }; class ColorHandler : public TemplateHandler { public: - ColorHandler(std::string filePath); - std::map handle(Request *req); + ColorHandler(const std::string& filePath); + std::map handle(Request* req) override; }; -#endif +#endif // HANDLERS_HPP_INCLUDE diff --git a/examples/main.cpp b/examples/main.cpp index 1e5c549..dfa21c5 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -1,26 +1,36 @@ -#include "handlers.hpp" -#include "my_server.hpp" -#include // for rand and srand -#include // for time #include +#include -using namespace std; +#include "../server/server.hpp" +#include "handlers.hpp" -int main(int argc, char **argv) { - srand(time(NULL)); // for rand - try { - MyServer server(argc > 1 ? atoi(argv[1]) : 5000); +void mapServerPaths(Server& server) { server.setNotFoundErrPage("static/404.html"); + server.get("/", new ShowPage("static/home.html")); + server.get("/home.png", new ShowImage("static/home.png")); + server.get("/rand", new RandomNumberHandler()); server.get("/login", new ShowPage("static/logincss.html")); server.post("/login", new LoginHandler()); server.get("/up", new ShowPage("static/upload_form.html")); server.post("/up", new UploadHandler()); - server.get("/rand", new RandomNumberHandler()); - server.get("/home.png", new ShowImage("static/home.png")); - server.get("/", new ShowPage("static/home.html")); server.get("/colors", new ColorHandler("template/colors.html")); - server.run(); - } catch (const Server::Exception& e) { - cerr << e.getMessage() << endl; - } + server.get("/music", new ShowPage("static/music.html")); + server.get("/music/moonlight.mp3", new ShowFile("static/moonlight.mp3", "audio/mpeg")); +} + +int main(int argc, char** argv) { + try { + int port = argc > 1 ? std::stoi(argv[1]) : 5000; + Server server(port); + mapServerPaths(server); + std::cout << "Server running on port: " << port << std::endl; + server.run(); + } + catch (const std::invalid_argument& e) { + std::cerr << e.what() << std::endl; + } + catch (const Server::Exception& e) { + std::cerr << e.getMessage() << std::endl; + } + return 0; } diff --git a/examples/my_server.cpp b/examples/my_server.cpp deleted file mode 100644 index ab13843..0000000 --- a/examples/my_server.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "my_server.hpp" - -MyServer::MyServer(int port) : Server(port) {} diff --git a/examples/my_server.hpp b/examples/my_server.hpp deleted file mode 100644 index 4543073..0000000 --- a/examples/my_server.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef __MY_SERVER__ -#define __MY_SERVER__ - -#include "../server/server.hpp" - -class MyServer : public Server { -public: - MyServer(int port = 5000); -}; - -#endif diff --git a/makefile b/makefile deleted file mode 100644 index 6d7f6f3..0000000 --- a/makefile +++ /dev/null @@ -1,48 +0,0 @@ -CC=g++ -STD=-std=c++11 -Wall -pedantic -CF=$(STD) -BUILD_DIR=build -TEMPLATE_DIR=.template - -ifeq ($(OS),Windows_NT) - LDLIBS += -l Ws2_32 -endif - -all: $(BUILD_DIR) myserver.out - -$(BUILD_DIR): - mkdir -p $(BUILD_DIR) - -$(BUILD_DIR)/template_parser.o: utils/template_parser.cpp utils/template_parser.hpp utils/request.cpp utils/request.hpp utils/utilities.hpp utils/utilities.cpp - $(CC) $(CF) -c utils/template_parser.cpp -o $(BUILD_DIR)/template_parser.o - -$(BUILD_DIR)/response.o: utils/response.cpp utils/response.hpp utils/include.hpp - $(CC) $(CF) -c utils/response.cpp -o $(BUILD_DIR)/response.o - -$(BUILD_DIR)/request.o: utils/request.cpp utils/request.hpp utils/include.hpp utils/utilities.hpp - $(CC) $(CF) -c utils/request.cpp -o $(BUILD_DIR)/request.o - -$(BUILD_DIR)/utilities.o: utils/utilities.cpp utils/utilities.hpp - $(CC) $(CF) -c utils/utilities.cpp -o $(BUILD_DIR)/utilities.o - -$(BUILD_DIR)/server.o: server/server.cpp server/server.hpp server/route.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp utils/template_parser.hpp utils/template_parser.cpp - $(CC) $(CF) -c server/server.cpp -o $(BUILD_DIR)/server.o - -$(BUILD_DIR)/route.o: server/route.cpp server/route.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp - $(CC) $(CF) -c server/route.cpp -o $(BUILD_DIR)/route.o - -$(BUILD_DIR)/handlers.o: examples/handlers.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp - $(CC) $(CF) -c examples/handlers.cpp -o $(BUILD_DIR)/handlers.o - -$(BUILD_DIR)/my_server.o: examples/my_server.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp - $(CC) $(CF) -c examples/my_server.cpp -o $(BUILD_DIR)/my_server.o - -$(BUILD_DIR)/main.o: examples/main.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp - $(CC) $(CF) -c examples/main.cpp -o $(BUILD_DIR)/main.o - -myserver.out: $(BUILD_DIR)/my_server.o $(BUILD_DIR)/main.o $(BUILD_DIR)/handlers.o $(BUILD_DIR)/response.o $(BUILD_DIR)/request.o $(BUILD_DIR)/utilities.o $(BUILD_DIR)/server.o $(BUILD_DIR)/route.o $(BUILD_DIR)/template_parser.o - $(CC) $(CF) $(BUILD_DIR)/my_server.o $(BUILD_DIR)/main.o $(BUILD_DIR)/handlers.o $(BUILD_DIR)/response.o $(BUILD_DIR)/request.o $(BUILD_DIR)/utilities.o $(BUILD_DIR)/server.o $(BUILD_DIR)/route.o $(BUILD_DIR)/template_parser.o $(LDLIBS) -o myserver.out - -.PHONY: clean -clean: - rm -rf $(BUILD_DIR) $(TEMPLATE_DIR) *.o *.out &> /dev/null diff --git a/server/route.cpp b/server/route.cpp index 3a551d5..b1f1a22 100644 --- a/server/route.cpp +++ b/server/route.cpp @@ -1,19 +1,24 @@ #include "route.hpp" + #include "server.hpp" -using namespace std; +Route::Route(Request::Method method, const std::string& path) + : method_(method), + path_(path), + handler_(nullptr) {} -Route::Route(Method _method, string _path) { - method = _method; - path = _path; +Route::~Route() { + delete handler_; } -void Route::setHandler(RequestHandler *_handler) { handler = _handler; } - -bool Route::isMatch(Method _method, string url) { - return (url == path) && (_method == method); +void Route::setHandler(RequestHandler* handler) { + handler_ = handler; } -Response *Route::handle(Request *req) { return handler->callback(req); } +Response* Route::handle(Request* req) { + return handler_->callback(req); +} -Route::~Route() { delete handler; } +bool Route::isMatch(Request::Method method, const std::string& url) { + return (method_ == method) && (url == path_); +} diff --git a/server/route.hpp b/server/route.hpp index 8417f75..9bfb921 100644 --- a/server/route.hpp +++ b/server/route.hpp @@ -1,23 +1,26 @@ -#ifndef __ROUTE__ -#define __ROUTE__ -#include "../utils/include.hpp" -#include "../utils/request.hpp" -#include "../utils/response.hpp" +#ifndef ROUTE_HPP_INCLUDE +#define ROUTE_HPP_INCLUDE + #include +#include "../utils/request.hpp" + +class Response; class RequestHandler; class Route { -private: - Method method; - std::string path; - RequestHandler *handler; - public: - Route(Method _method, std::string _path); - ~Route(); - bool isMatch(Method, std::string url); - Response *handle(Request *req); - void setHandler(RequestHandler *_handler); + Route(Request::Method method, const std::string& path); + ~Route(); + + void setHandler(RequestHandler* handler); + Response* handle(Request* req); + bool isMatch(Request::Method, const std::string& url); + +private: + Request::Method method_; + std::string path_; + RequestHandler* handler_; }; -#endif + +#endif // ROUTE_HPP_INCLUDE diff --git a/server/server.cpp b/server/server.cpp index da418e1..262a754 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -13,29 +13,29 @@ #include #include +#include "../utils/strutils.hpp" #include "../utils/utilities.hpp" - #ifdef _WIN32 #ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 //win xp +#define _WIN32_WINNT 0x0501 // win xp #endif -#include #include +#include #else -//POSIX sockets -#include +// POSIX sockets #include +#include #include //close() #endif #ifdef _WIN32 #define ISVALIDSOCKET(s) ((s) != INVALID_SOCKET) -#define CLOSESOCKET(s) closesocket(s) +#define CLOSESOCKET(s) closesocket(s) #define GETSOCKETERRNO() (WSAGetLastError()) #else #define ISVALIDSOCKET(s) ((s) >= 0) -#define CLOSESOCKET(s) close(s) +#define CLOSESOCKET(s) close(s) #define GETSOCKETERRNO() (errno) #endif @@ -43,10 +43,12 @@ static const char* getSocketError() { #ifdef _WIN32 static char message[256]; message[0] = '\0'; - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, WSAGetLastError(), 0, (LPSTR)&message, sizeof(message), NULL); - char *newline = strrchr(message, '\n'); - if (newline) *newline = '\0'; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, WSAGetLastError(), 0, (LPSTR)&message, sizeof(message), + NULL); + char* newline = strrchr(message, '\n'); + if (newline) + *newline = '\0'; return message; #else return strerror(errno); @@ -56,351 +58,334 @@ static const char* getSocketError() { using namespace std; class NotFoundHandler : public RequestHandler { - string notFoundErrPage; - public: - NotFoundHandler(string notFoundErrPage = "") - : notFoundErrPage(notFoundErrPage) {} - Response *callback(Request *req) { - Response *res = new Response(404); - if (!notFoundErrPage.empty()) { - res->setHeader("Content-Type", "text/" + getExtension(notFoundErrPage)); - res->setBody(readFile(notFoundErrPage.c_str())); + NotFoundHandler(const string& notFoundErrPage = "") + : notFoundErrPage_(notFoundErrPage) {} + Response* callback(Request* req) { + Response* res = new Response(Response::Status::notFound); + if (!notFoundErrPage_.empty()) { + res->setHeader("Content-Type", "text/" + utils::getExtension(notFoundErrPage_)); + res->setBody(utils::readFile(notFoundErrPage_)); + } + return res; } - return res; - } + +private: + string notFoundErrPage_; }; class ServerErrorHandler { public: - static Response *callback(string msg) { - Response *res = new Response(500); - res->setHeader("Content-Type", "application/json"); - res->setBody("{ \"code\": \"500\", \"message\": \"" + msg + "\" }\n"); - return res; - } + static Response* callback(const string& msg) { + Response* res = new Response(Response::Status::internalServerError); + res->setHeader("Content-Type", "application/json"); + res->setBody("{ \"code\": \"500\", \"message\": \"" + msg + "\" }\n"); + return res; + } }; -void split(string str, string separator, int max, vector &results) { - int i = 0; - size_t found = str.find_first_of(separator); - - while (found != string::npos) { - if (found > 0) - results.push_back(str.substr(0, found)); - str = str.substr(found + 1); - found = str.find_first_of(separator); - - if (max > -1 && ++i == max) - break; - } - if (str.length() > 0) - results.push_back(str); -} +Request* parseRawReq(char* reqData, size_t length) { + Request* req = nullptr; + string boundary; + string lastFieldKey; + string lastFieldValue; + string reqDataStr(reqData, reqData + length); + try { + size_t endOfHeader = reqDataStr.find("\r\n\r\n"); + string reqHeader = reqDataStr.substr(0, endOfHeader); + string reqBody = reqDataStr.substr(endOfHeader + 4, reqDataStr.size()); + if (endOfHeader == string::npos) { + throw Server::Exception("End of request header not found."); + } + vector headers = strutils::split(reqHeader, "\r\n"); + if (reqHeader.find('\0') != string::npos) { + throw Server::Exception("Binary data in header."); + } + size_t realBodySize = length - endOfHeader - 4; // string("\r\n\r\n").size(); -Request *parseRawReq(char *headersRaw, size_t length) { - Request *req = nullptr; - string boundary; - string lastFieldKey; - string lastFieldValue; - bool shouldBeEmpty; - try { - enum State { REQ, HEADER, BODY, BODY_HEADER, BODY_BODY }; - State state = REQ; - vector headers = split(string(headersRaw), "\r\n", false); - for (size_t i = 0; i < length; i++) { - if (!headersRaw[i]) - throw Server::Exception("Unsupported binary data in request."); - } - size_t realBodySize = - string(headersRaw).size() - - split(string(headersRaw), "\r\n\r\n", false)[0].size() - - string("\r\n\r\n").size(); - for (size_t headerIndex = 0; headerIndex < headers.size(); headerIndex++) { - string line = headers[headerIndex]; - switch (state) { - case REQ: { - vector R = split(line, " ", false); + vector R = strutils::split(headers[0], ' '); if (R.size() != 3) { - throw Server::Exception("Invalid header (request line)"); + throw Server::Exception("Invalid header (request line)"); } req = new Request(R[0]); req->setPath(R[1]); size_t pos = req->getPath().find('?'); if (pos != string::npos && pos != req->getPath().size() - 1) { - vector Q1 = split(req->getPath().substr(pos + 1), "&", false); - for (vector::size_type q = 0; q < Q1.size(); q++) { - vector Q2 = split(Q1[q], "=", false); - if (Q2.size() == 2) - req->setQueryParam(Q2[0], Q2[1], false); - else - throw Server::Exception("Invalid query"); - } + vector Q1 = strutils::split(req->getPath().substr(pos + 1), '&'); + for (vector::size_type q = 0; q < Q1.size(); q++) { + vector Q2 = strutils::split(Q1[q], '='); + if (Q2.size() == 2) + req->setQueryParam(Q2[0], Q2[1], false); + else + throw Server::Exception("Invalid query"); + } } req->setPath(req->getPath().substr(0, pos)); - state = HEADER; - } break; - case HEADER: { - if (line == "") { - state = BODY; - if (req->getHeader("Content-Type") - .substr(0, string("multipart/form-data").size()) == - "multipart/form-data") { - boundary = - req->getHeader("Content-Type") - .substr(req->getHeader("Content-Type").find("boundary=") + - string("boundary=").size()); - } - break; - } - vector R = split(line, ": ", false); - if (R.size() != 2) - throw Server::Exception("Invalid header"); - req->setHeader(R[0], R[1], false); - if (toLowerCase(R[0]) == toLowerCase("Content-Length")) - if (realBodySize != (size_t)atol(R[1].c_str())) - return NULL; - } break; - case BODY: { - if (req->getHeader("Content-Type") == "") { - } else if (req->getHeader("Content-Type") == - "application/x-www-form-urlencoded") { - vector body = split(line, "&", false); - for (size_t i = 0; i < body.size(); i++) { - vector field = split(body[i], "=", false); - if (field.size() == 2) - req->setBodyParam(field[0], field[1], false); - else if (field.size() == 1) - req->setBodyParam(field[0], "", false); - else - throw Server::Exception("Invalid body"); - } - } else if (req->getHeader("Content-Type") - .substr(0, string("multipart/form-data").size()) == - "multipart/form-data") { - if (line == "--" + boundary || line == "--" + boundary + "--") { - lastFieldKey = ""; - lastFieldValue = ""; - shouldBeEmpty = false; - state = BODY_HEADER; - } - } else { - throw Server::Exception("Unsupported body type: " + - req->getHeader("Content-Type")); - } - } break; - case BODY_HEADER: { - if (line == "") { - state = BODY_BODY; - break; + + for (size_t headerIndex = 1; headerIndex < headers.size(); headerIndex++) { + string line = headers[headerIndex]; + vector R = strutils::split(line, ": "); + if (R.size() != 2) + throw Server::Exception("Invalid header"); + req->setHeader(R[0], R[1], false); + if (strutils::tolower(R[0]) == strutils::tolower("Content-Length")) + if (realBodySize != (size_t)atol(R[1].c_str())) + return nullptr; } - vector R = split(line, ": ", false); - if (R.size() != 2) - throw Server::Exception("Invalid header"); - if (toLowerCase(R[0]) == toLowerCase("Content-Disposition")) { - vector A = split(R[1], "; ", false); - for (size_t i = 0; i < A.size(); i++) { - vector attr = split(A[i], "=", false); - if (attr.size() == 2) { - if (toLowerCase(attr[0]) == toLowerCase("name")) { - lastFieldKey = attr[1].substr(1, attr[1].size() - 2); - } - } else if (attr.size() == 1) { - } else - throw Server::Exception("Invalid body attribute"); - } - } else if (toLowerCase(R[0]) == toLowerCase("Content-Type")) { - if (toLowerCase(R[1]) == toLowerCase("application/octet-stream")) - shouldBeEmpty = true; - else if (toLowerCase(R[1].substr(0, R[1].find("/"))) != - toLowerCase("text")) - throw Server::Exception("Unsupported file type: " + R[1]); + + string contentType = req->getHeader("Content-Type"); + if (realBodySize != 0 && !contentType.empty()) { + if (strutils::startsWith(contentType, "application/x-www-form-urlencoded")) { + vector urlencodedParts = strutils::split(reqBody, "\r\n"); + for (const string& part : urlencodedParts) { + vector body = strutils::split(part, '&'); + for (size_t i = 0; i < body.size(); i++) { + vector field = strutils::split(body[i], '='); + if (field.size() == 2) + req->setBodyParam(field[0], field[1], "application/x-www-form-urlencoded", false); + else if (field.size() == 1) + req->setBodyParam(field[0], "", "application/x-www-form-urlencoded", false); + else + throw Server::Exception("Invalid body"); + } + } + } + else if (strutils::startsWith(contentType, "multipart/form-data")) { + boundary = contentType.substr(contentType.find("boundary=") + 9); + size_t firstBoundary = reqBody.find("--" + boundary); + if (firstBoundary == string::npos) { + throw Server::Exception("Boundary data not found."); + } + reqBody.erase(reqBody.begin(), reqBody.begin() + firstBoundary + 2 + boundary.size()); + + vector boundaries = strutils::split(reqBody, "--" + boundary); + boundaries.pop_back(); + + for (string b : boundaries) { + b.pop_back(); // remove "\r\n" from start and end of each boundary + b.pop_back(); + b.erase(b.begin(), b.begin() + 2); + string boundaryContentType = "text/plain"; + + size_t endOfBoundaryHeader = b.find("\r\n\r\n") + 4; + vector abc = strutils::split(b.substr(0, endOfBoundaryHeader - 4), "\r\n"); + for (const string& line : abc) { + if (line.empty()) { + break; + } + vector R = strutils::split(line, ": "); + if (R.size() != 2) throw Server::Exception("Invalid header"); + if (strutils::tolower(R[0]) == strutils::tolower("Content-Disposition")) { + vector A = strutils::split(R[1], "; "); + for (size_t i = 0; i < A.size(); i++) { + vector attr = strutils::split(A[i], '='); + if (attr.size() == 2) { + if (strutils::tolower(attr[0]) == strutils::tolower("name")) { + lastFieldKey = attr[1].substr(1, attr[1].size() - 2); + } + } + else if (attr.size() != 1) { + throw Server::Exception("Invalid body attribute"); + } + } + } + else if (strutils::tolower(R[0]) == strutils::tolower("Content-Type")) { + boundaryContentType = strutils::tolower(R[1]); + } + } + lastFieldValue = b.substr(endOfBoundaryHeader); + req->setBodyParam(lastFieldKey, lastFieldValue, boundaryContentType, false); + } + } + else { + throw Server::Exception("Unsupported body type: " + contentType); + } } - } break; - case BODY_BODY: { - if (line == "--" + boundary || line == "--" + boundary + "--") { - req->setBodyParam(lastFieldKey, - lastFieldValue.substr(string("\r\n").size()), - false); - lastFieldKey = ""; - lastFieldValue = ""; - state = BODY_HEADER; - shouldBeEmpty = false; - } else if (shouldBeEmpty && !line.empty()) - throw Server::Exception("Unsupported file type: " + - string("application/octet-stream")); - else - lastFieldValue += "\r\n" + line; - } break; - } } - } catch (const Server::Exception&) { - throw; - } catch (...) { - throw Server::Exception("Error on parsing request"); - } - return req; + catch (const Server::Exception&) { + throw; + } + catch (const std::exception& e) { + throw Server::Exception("Error on parsing request: " + std::string(e.what())); + } + return req; } -Server::Server(int _port) : port(_port) { +Server::Server(int port) : port_(port) { #ifdef _WIN32 - WSADATA wsa_data; - int initializeResult = WSAStartup(MAKEWORD(2, 2), &wsa_data); - if (initializeResult != 0) { - throw Exception("Error: WinSock WSAStartup failed: " + string(getSocketError())); - } + WSADATA wsa_data; + int initializeResult = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (initializeResult != 0) { + throw Exception("Error: WinSock WSAStartup failed: " + + string(getSocketError())); + } #endif - notFoundHandler = new NotFoundHandler(); + notFoundHandler_ = new NotFoundHandler(); - sc = socket(AF_INET, SOCK_STREAM, 0); - int sc_option = 1; + sc_ = socket(AF_INET, SOCK_STREAM, 0); + int sc_option = 1; #ifdef _WIN32 - setsockopt(sc, SOL_SOCKET, SO_REUSEADDR, (char*)&sc_option, sizeof(sc_option)); + setsockopt(sc_, SOL_SOCKET, SO_REUSEADDR, (char*)&sc_option, sizeof(sc_option)); #else - setsockopt(sc, SOL_SOCKET, SO_REUSEADDR, &sc_option, sizeof(sc_option)); + setsockopt(sc_, SOL_SOCKET, SO_REUSEADDR, &sc_option, sizeof(sc_option)); #endif - if (!ISVALIDSOCKET(sc)) - throw Exception("Error on opening socket: " + string(getSocketError())); + if (!ISVALIDSOCKET(sc_)) { + throw Exception("Error on opening socket: " + string(getSocketError())); + } - struct sockaddr_in serv_addr; - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = INADDR_ANY; - serv_addr.sin_port = htons(port); + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(port_); + + if (::bind(sc_, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) != 0) { + throw Exception("Error on binding: " + string(getSocketError())); + } +} - if (::bind(sc, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { - throw Exception("Error on binding: " + string(getSocketError())); - } +void Server::mapRequest(const string& path, RequestHandler* handler, Request::Method method) { + Route* route = new Route(method, path); + route->setHandler(handler); + routes_.push_back(route); } -void Server::get(string path, RequestHandler *handler) { - Route *route = new Route(GET, path); - route->setHandler(handler); - routes.push_back(route); +void Server::get(const std::string& path, RequestHandler* handler) { + mapRequest(path, handler, Request::Method::GET); } -void Server::post(string path, RequestHandler *handler) { - Route *route = new Route(POST, path); - route->setHandler(handler); - routes.push_back(route); +void Server::post(const std::string& path, RequestHandler* handler) { + mapRequest(path, handler, Request::Method::POST); +} + +void Server::put(const std::string& path, RequestHandler* handler) { + mapRequest(path, handler, Request::Method::PUT); +} + +void Server::del(const std::string& path, RequestHandler* handler) { + mapRequest(path, handler, Request::Method::DEL); } void Server::run() { - ::listen(sc, 10); - - struct sockaddr_in cli_addr; - socklen_t clilen; - clilen = sizeof(cli_addr); - SOCKET newsc; - - while (true) { - newsc = ::accept(sc, (struct sockaddr *)&cli_addr, &clilen); - if (!ISVALIDSOCKET(newsc)) - throw Exception("Error on accept: " + string(getSocketError())); - Response *res = NULL; - try { - char* data = new char[BUFSIZE + 1]; - size_t recv_len, recv_total_len = 0; - Request *req = NULL; - while (!req) { - recv_len = - recv(newsc, data + recv_total_len, BUFSIZE - recv_total_len, 0); - if (recv_len > 0) { - recv_total_len += recv_len; - data[recv_total_len >= 0 ? recv_total_len : 0] = 0; - req = parseRawReq(data, recv_total_len); - } else - break; - } - delete[] data; - if (!recv_total_len) { - CLOSESOCKET(newsc); - continue; - } - req->log(); - size_t i = 0; - for (; i < routes.size(); i++) { - if (routes[i]->isMatch(req->getMethod(), req->getPath())) { - res = routes[i]->handle(req); - break; + ::listen(sc_, 10); + + struct sockaddr_in cli_addr; + socklen_t clilen; + clilen = sizeof(cli_addr); + _SOCKET newsc; + + while (true) { + newsc = ::accept(sc_, (struct sockaddr*)&cli_addr, &clilen); + if (!ISVALIDSOCKET(newsc)) + throw Exception("Error on accept: " + string(getSocketError())); + Response* res = nullptr; + try { + char* data = new char[BUFSIZE + 1]; + size_t recv_len, recv_total_len = 0; + Request* req = nullptr; + while (!req) { + recv_len = recv(newsc, data + recv_total_len, BUFSIZE - recv_total_len, 0); + if (recv_len > 0) { + recv_total_len += recv_len; + data[recv_total_len >= 0 ? recv_total_len : 0] = 0; + req = parseRawReq(data, recv_total_len); + } + else + break; + } + delete[] data; + if (!recv_total_len) { + CLOSESOCKET(newsc); + continue; + } + req->log(); + size_t i = 0; + for (; i < routes_.size(); i++) { + if (routes_[i]->isMatch(req->getMethod(), req->getPath())) { + res = routes_[i]->handle(req); + break; + } + } + if (i == routes_.size() && notFoundHandler_) { + res = notFoundHandler_->callback(req); + } + delete req; + } + catch (const Exception& exc) { + delete res; + res = ServerErrorHandler::callback(exc.getMessage()); } - } - if (i == routes.size() && notFoundHandler) { - res = notFoundHandler->callback(req); - } - delete req; - } catch (const Exception& exc) { - delete res; - res = ServerErrorHandler::callback(exc.getMessage()); + res->log(); + string res_data = res->getResponse(); + int si = res_data.size(); + delete res; + int wr = send(newsc, res_data.c_str(), si, 0); + if (wr != si) + throw Exception("Send error: " + string(getSocketError())); + CLOSESOCKET(newsc); } - int si; - res->log(); - string res_data = res->print(si); - delete res; - int wr = send(newsc, res_data.c_str(), si, 0); - if (wr != si) - throw Exception("Send error: " + string(getSocketError())); - CLOSESOCKET(newsc); - } } Server::~Server() { - if (sc >= 0) - CLOSESOCKET(sc); - delete notFoundHandler; - for (size_t i = 0; i < routes.size(); ++i) - delete routes[i]; - + if (sc_ >= 0) { + CLOSESOCKET(sc_); + } + delete notFoundHandler_; + for (size_t i = 0; i < routes_.size(); ++i) { + delete routes_[i]; + } #ifdef _WIN32 - WSACleanup(); + WSACleanup(); #endif } -Server::Exception::Exception(const string msg) { message = msg; } +Server::Exception::Exception(const string message) : message_(message) {} -string Server::Exception::getMessage() const { return message; } +string Server::Exception::getMessage() const { return message_; } -ShowFile::ShowFile(string _filePath, string _fileType) { - filePath = _filePath; - fileType = _fileType; -} +ShowFile::ShowFile(const string& filePath, const string& fileType) + : filePath_(filePath), + fileType_(fileType) {} -Response *ShowFile::callback(Request *req) { - Response *res = new Response; - res->setHeader("Content-Type", fileType); - res->setBody(readFile(filePath.c_str())); - return res; +Response* ShowFile::callback(Request* req) { + Response* res = new Response(); + res->setHeader("Content-Type", fileType_); + res->setBody(utils::readFile(filePath_)); + return res; } -ShowPage::ShowPage(string filePath) - : ShowFile(filePath, "text/" + getExtension(filePath)) {} +ShowPage::ShowPage(const string& filePath) + : ShowFile(filePath, "text/" + utils::getExtension(filePath)) {} -ShowImage::ShowImage(string filePath) - : ShowFile(filePath, "image/" + getExtension(filePath)) {} +ShowImage::ShowImage(const string& filePath) + : ShowFile(filePath, "image/" + utils::getExtension(filePath)) {} -void Server::setNotFoundErrPage(std::string notFoundErrPage) { - delete notFoundHandler; - notFoundHandler = new NotFoundHandler(notFoundErrPage); +void Server::setNotFoundErrPage(const std::string& notFoundErrPage) { + delete notFoundHandler_; + notFoundHandler_ = new NotFoundHandler(notFoundErrPage); } RequestHandler::~RequestHandler() {} -TemplateHandler::TemplateHandler(string _filePath) { - filePath = _filePath; - parser = new TemplateParser(filePath); +TemplateHandler::TemplateHandler(const string& filePath) + : filePath_(filePath), + parser_(new TemplateParser(filePath)) {} + +TemplateHandler::~TemplateHandler() { + delete parser_; } -Response *TemplateHandler::callback(Request *req) { - map context; - context = this->handle(req); - Response *res = new Response; - res->setHeader("Content-Type", "text/html"); - res->setBody(parser->getHtml(context)); - return res; +Response* TemplateHandler::callback(Request* req) { + map context; + context = this->handle(req); + Response* res = new Response(); + res->setHeader("Content-Type", "text/html"); + res->setBody(parser_->getHtml(context)); + return res; } -map TemplateHandler::handle(Request *req) { - map context; - return context; +map TemplateHandler::handle(Request* req) { + map context; + return context; } diff --git a/server/server.hpp b/server/server.hpp index 507c098..0bb9433 100644 --- a/server/server.hpp +++ b/server/server.hpp @@ -1,82 +1,93 @@ -#ifndef __SERVER__ -#define __SERVER__ +#ifndef SERVER_HPP_INCLUDE +#define SERVER_HPP_INCLUDE + +#include +#include +#include + #include "../utils/include.hpp" #include "../utils/request.hpp" #include "../utils/response.hpp" #include "../utils/template_parser.hpp" #include "route.hpp" -#include -#include -#include #ifdef _WIN32 -typedef unsigned SOCKET; +typedef unsigned _SOCKET; #else -typedef int SOCKET; +typedef int _SOCKET; #endif class TemplateParser; class RequestHandler { public: - virtual ~RequestHandler(); - virtual Response *callback(Request *req) = 0; + virtual ~RequestHandler(); + virtual Response* callback(Request* req) = 0; }; class ShowFile : public RequestHandler { - std::string filePath; - std::string fileType; - public: - ShowFile(std::string filePath, std::string fileType); - Response *callback(Request *req); + ShowFile(const std::string& filePath, const std::string& fileType); + Response* callback(Request* req) override; + +private: + std::string filePath_; + std::string fileType_; }; class ShowPage : public ShowFile { - public: - ShowPage(std::string _filePath); + ShowPage(const std::string& filePath); }; class ShowImage : public ShowFile { - public: - ShowImage(std::string _filePath); + ShowImage(const std::string& filePath); }; class TemplateHandler : public RequestHandler { - std::string filePath; - TemplateParser *parser; - public: - TemplateHandler(std::string _filePath); - Response *callback(Request *req); - virtual std::map handle(Request *req); + TemplateHandler(const std::string& filePath); + ~TemplateHandler(); + + Response* callback(Request* req) override; + virtual std::map handle(Request* req); + +private: + std::string filePath_; + TemplateParser* parser_; }; class Server { public: - Server(int port = 5000); - ~Server(); - void run(); - void get(std::string path, RequestHandler *handler); - void post(std::string path, RequestHandler *handler); - void setNotFoundErrPage(std::string); - - class Exception : public std::exception { - public: - Exception() {} - Exception(const std::string); - std::string getMessage() const; - - private: - std::string message; - }; + Server(int port = 5000); + ~Server(); + + void run(); + + void get(const std::string& path, RequestHandler* handler); + void post(const std::string& path, RequestHandler* handler); + void put(const std::string& path, RequestHandler* handler); + void del(const std::string& path, RequestHandler* handler); + void setNotFoundErrPage(const std::string& notFoundErrPage); + + class Exception : public std::exception { + public: + Exception() = default; + Exception(const std::string message); + std::string getMessage() const; + + private: + std::string message_; + }; private: - SOCKET sc; - int port; - std::vector routes; - RequestHandler *notFoundHandler; + _SOCKET sc_; + int port_; + std::vector routes_; + RequestHandler* notFoundHandler_; + + void mapRequest(const std::string& path, RequestHandler* handler, Request::Method method); }; -#endif + +#endif // SERVER_HPP_INCLUDE diff --git a/static/404.html b/static/404.html index ad4466f..8132b5d 100644 --- a/static/404.html +++ b/static/404.html @@ -1,8 +1,16 @@ - - -

AP HTTP

-

Err: 404

-

Requested page was not found!

- + + + + + + Page Not Found! + + + +

AP HTTP

+

Err: 404

+

Requested page was not found!

+ + diff --git a/static/home.html b/static/home.html index c0f67dd..63ad7e8 100644 --- a/static/home.html +++ b/static/home.html @@ -1,13 +1,20 @@ - - -

AP HTTP

- -
- Go to login page -
- Go to upload page -
- Go to colors page - + + + + + + AP HTTP + + + +

AP HTTP

+ Home Logo
+ Go to rand page
+ Go to login page
+ Go to upload page
+ Go to colors page
+ Go to music page + + diff --git a/static/login.html b/static/login.html index edbd72a..4aae4b4 100644 --- a/static/login.html +++ b/static/login.html @@ -1,14 +1,22 @@ - - -

AP HTTP

+ + + + + + Login Page + + + +

AP HTTP

+

Login

-
-
- - - -
-
- +
+ + + +
+
+ + diff --git a/static/logincss.html b/static/logincss.html index 964f468..e5f6ee1 100644 --- a/static/logincss.html +++ b/static/logincss.html @@ -1,14 +1,22 @@ - - -

AP HTTP

-
-
-

Login

- - - -
-
- + + + + + + Login Page + + + +

AP HTTP

+
+

Login

+
+ + + +
+
+ + diff --git a/static/moonlight.mp3 b/static/moonlight.mp3 new file mode 100644 index 0000000..e253b93 Binary files /dev/null and b/static/moonlight.mp3 differ diff --git a/static/music.html b/static/music.html new file mode 100644 index 0000000..8478749 --- /dev/null +++ b/static/music.html @@ -0,0 +1,16 @@ + + + + + + Music Page + + + +

Music

+ + + + diff --git a/static/upload_form.html b/static/upload_form.html index 5636664..670d4c4 100644 --- a/static/upload_form.html +++ b/static/upload_form.html @@ -1,14 +1,22 @@ - - -

AP HTTP

+ + + + + + Upload Form + + + +

AP HTTP

+

Upload

-
-
- - - -
-
- +
+ + + +
+
+ + diff --git a/template/colors.html b/template/colors.html index f055243..5b79a78 100644 --- a/template/colors.html +++ b/template/colors.html @@ -2,65 +2,65 @@ - - - - Document + + + Colors Page -
- Choose color : - - Red - Navy - Green - -
- - Your name : - - - -
+

Colors

+
+ Choose color: + + + + +
+