瀏覽代碼

Add Cookie parser

xcbosa-itx 2 年之前
父節點
當前提交
1a3fe7d6b3

+ 5 - 0
CMakeLists.txt

@@ -23,6 +23,11 @@ execute_process(
         WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/data
 )
 
+execute_process(
+        COMMAND ${CMAKE_SOURCE_DIR}/data/Generate-Assets.py sha256.js
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/data
+)
+
 aux_source_directory(. DIR_SRCS)
 aux_source_directory(controller DIR_CONTROLLERS)
 aux_source_directory(controller/auto-generated DIR_CONTROLLERS_AUTOGENERATED)

+ 4 - 10
controller/EntryController.cpp

@@ -5,6 +5,7 @@
 #include "../processor/processor.h"
 #include "../webuiconf.h"
 #include "../processor/templates/framework7/Framework7Document.hpp"
+#include "../user.hpp"
 
 using namespace std;
 using namespace xc::processor;
@@ -15,22 +16,15 @@ using namespace configor;
 namespace xc::controller {
 
     ResponseData *EntryController(RequestData request) {
-        bool isLogin = false;
         return new TemplateResponseData({
-            If(isLogin, {
-                Framework7Document({
-                    p("登陆成功")
-                }, {
-                    a("2023 © Frp-WebUI by XCBOSA")
-                        .classAdd("link")
-                        .onclick("window.open('https://github.com/XCBOSA/frp-webui-500k.git')")
-                })
+            If(user::isLogin(request.getCookie("Token")), {
+                ContentGeneratorReference("PortListController", request)
             }, {
                 ContentGeneratorReference("LoginController", request)
             })
         });
     }
 
-    ContentGeneratorDefineS(request.getURL() == "/" || request.getURL().length() == 0, EntryController(request))
+    ContentGeneratorDefineS(request.getURLPath() == "/", EntryController(request))
 
 }

+ 34 - 11
controller/LoginController.cpp

@@ -5,6 +5,8 @@
 #include "../processor/processor.h"
 #include "../webuiconf.h"
 #include "../processor/templates/framework7/Framework7Document.hpp"
+#include "../fs.hpp"
+#include "../user.hpp"
 
 using namespace std;
 using namespace xc::processor;
@@ -17,25 +19,46 @@ namespace xc::controller {
     ResponseData *LoginController(RequestData request) {
         return new TemplateResponseData({
             Framework7Document({
+                BlockTitleView("需要登陆"),
                 FormView({
-                    FormInputView("userName", "用户名", "text", "输入您的用户名"),
-                    FormInputView("password", "密码", "password", "输入您的密码")
-                }),
-                BlockView({
-                    BlockColumnView({
-                        ButtonView("登陆"),
-                        VerticalSpacer(10),
-                        p("如果您需要注册,请联系管理员")
+                    FormInputView("username", "用户名", "text", "输入您的用户名").id("username"),
+                    FormInputView("password", "密码", "password", "输入您的密码").id("password"),
+                    FormItemView({
+                        BlockView({
+                            ButtonView("登陆").onclick("doLogin('" + conf::userPasswordSalt + "')"),
+                            VerticalSpacer(10),
+                            Label("如果您需要注册,请联系管理员")
+                        })
                     })
-                })
+                }).action("/").method("get").id("loginForm"),
                 }, {
-                a("2023 © Frp-WebUI by XCBOSA")
+                Link("2023 © Frp-WebUI by XCBOSA")
                     .classAdd("link")
                     .onclick("window.open('https://github.com/XCBOSA/frp-webui-500k.git')")
             })
         });
     }
 
-    ContentGeneratorDefineWithNameS("LoginController", false, LoginController(request))
+    ContentGeneratorDefineWithNameS("LoginController", request.getURLPath() == "/", LoginController(request))
+
+    ResponseData *ValidAuthController(RequestData request) {
+        string arg = request.getURLArgument("v");
+        JsonModel model = json::parse(arg);
+        string username = model["username"];
+        string password = model["password"];
+        string token = user::tryLogin(username, password);
+//        TemplateResponseData *resp = new TemplateResponseData({
+//            If(token.empty(), {
+//                ContentGeneratorReference("LoginController", request)
+//            }, {
+//                ContentGeneratorReference("PortListController", request)
+//            })
+//        });
+        auto resp = new RedirectResponse("/");
+        resp->addCookie("Token", token);
+        return resp;
+    }
+
+    ContentGeneratorDefineS(request.getURLPath() == "/login", ValidAuthController(request))
 
 }

+ 31 - 0
controller/PortListController.cpp

@@ -0,0 +1,31 @@
+//
+// Created by xcbosa on 2023/1/30.
+//
+
+#include "../processor/processor.h"
+#include "../webuiconf.h"
+#include "../processor/templates/framework7/Framework7Document.hpp"
+
+using namespace std;
+using namespace xc::processor;
+using namespace xc::processor::templates;
+using namespace xc::processor::templates::framework7;
+using namespace configor;
+
+namespace xc::controller {
+
+    ResponseData *PortListController(RequestData request) {
+        return new TemplateResponseData({
+            Framework7Document({
+                p("登陆成功")
+            }, {
+                a("2023 © Frp-WebUI by XCBOSA")
+                .classAdd("link")
+                .onclick("window.open('https://github.com/XCBOSA/frp-webui-500k.git')")
+            })
+        });
+    }
+
+    ContentGeneratorDefineWithNameS("PortListController", false, PortListController(request))
+
+}

+ 7 - 8
controller/StaticWebPageController.cpp

@@ -20,13 +20,12 @@ namespace xc::controller {
     ContentGeneratorDefine({
         if (!conf::enableStaticAssetsController) { return false; }
         if (request.getMethod() != "GET") { return false; }
-        if (request.getURL().length() == 0) { return false; }
         struct stat buffer;
         string filePath = "html";
-        if (request.getURL()[0] == '/') {
-            filePath += request.getURL();
+        if (request.getURLPath()[0] == '/') {
+            filePath += request.getURLPath();
         } else {
-            filePath += "/" + request.getURL();
+            filePath += "/" + request.getURLPath();
         }
         if (stat(filePath.c_str(), &buffer) == 0) {
             if (S_ISREG(buffer.st_mode)) {
@@ -49,10 +48,10 @@ namespace xc::controller {
         }, {
         struct stat buffer;
         string filePath = "html";
-        if (request.getURL()[0] == '/') {
-            filePath += request.getURL();
+        if (request.getURLPath()[0] == '/') {
+            filePath += request.getURLPath();
         } else {
-            filePath += "/" + request.getURL();
+            filePath += "/" + request.getURLPath();
         }
         if (stat(filePath.c_str(), &buffer) == 0) {
             if (S_ISREG(buffer.st_mode)) {
@@ -74,7 +73,7 @@ namespace xc::controller {
                 }
             }
         }
-        return (ResponseData *) new FileResponseData(conf::errorPage404);
+        return (ResponseData *) new TemplateResponseData(conf::errorPage404);
     })
 
 }

+ 1 - 1
controller/auto-generated/framework7-framework7-bundle.min.css.cpp

@@ -33,6 +33,6 @@ namespace xc::controller {
         , mimeTypeOfFile(ControllerPath));
     }
 
-    ContentGeneratorDefineS(request.getURL() == ControllerPath, controllerResponse(request))
+    ContentGeneratorDefineS(request.getURLPath() == ControllerPath, controllerResponse(request))
 
 }

+ 1 - 1
controller/auto-generated/framework7-framework7-bundle.min.js.cpp

@@ -34,6 +34,6 @@ namespace xc::controller {
         , mimeTypeOfFile(ControllerPath));
     }
 
-    ContentGeneratorDefineS(request.getURL() == ControllerPath, controllerResponse(request))
+    ContentGeneratorDefineS(request.getURLPath() == ControllerPath, controllerResponse(request))
 
 }

+ 1 - 1
controller/auto-generated/index.css.cpp

@@ -21,6 +21,6 @@ namespace xc::controller {
         , mimeTypeOfFile(ControllerPath));
     }
 
-    ContentGeneratorDefineS(request.getURL() == ControllerPath, controllerResponse(request))
+    ContentGeneratorDefineS(request.getURLPath() == ControllerPath, controllerResponse(request))
 
 }

+ 18 - 4
controller/auto-generated/index.js.cpp

@@ -34,12 +34,26 @@ namespace xc::controller {
             string("            path: \'/about/\',\n") + 
             string("            url: \'about.html\',\n") + 
             string("        },\n") + 
-            string("    ],\n") + 
-            string("    // ... other parameters\n") + 
-            string("});\n")
+            string("    ]\n") + 
+            string("});\n") + 
+            string("\n") + 
+            string("function doLogin(salt) {\n") + 
+            string("    let data = app.form.convertToData(\"#loginForm\")\n") + 
+            string("    if (data.username.length == 0) {\n") + 
+            string("        app.dialog.alert(\"请输入用户名\")\n") + 
+            string("        return\n") + 
+            string("    }\n") + 
+            string("    if (data.password.length == 0) {\n") + 
+            string("        app.dialog.alert(\"请输入密码\")\n") + 
+            string("        return\n") + 
+            string("    }\n") + 
+            string("    data.password = sha256_digest(data.password + salt)\n") + 
+            string("    window.location = \"/login?v=\" + JSON.stringify(data)\n") + 
+            string("    return\n") + 
+            string("}\n")
         , mimeTypeOfFile(ControllerPath));
     }
 
-    ContentGeneratorDefineS(request.getURL() == ControllerPath, controllerResponse(request))
+    ContentGeneratorDefineS(request.getURLPath() == ControllerPath, controllerResponse(request))
 
 }

+ 266 - 0
controller/auto-generated/sha256.js.cpp

@@ -0,0 +1,266 @@
+//
+// Created by xcbosa on 2023-01-30
+//
+
+#include "../../processor/processor.h"
+#include "../../utils/utils.h"
+#include "../../webuiconf.h"
+
+using namespace std;
+using namespace xc::processor;
+using namespace xc::utils;
+using namespace xc::processor::templates;
+
+namespace xc::controller {
+
+    static string ControllerPath = "/sha256.js";
+
+    static ResponseData *controllerResponse(RequestData request) {
+        return new TextResponseData(200,
+            string("/*\n") + 
+            string("* A JavaScript implementation of the SHA256 hash function.\n") + 
+            string("*\n") + 
+            string("* FILE:	sha256.js\n") + 
+            string("* VERSION:	0.8\n") + 
+            string("* AUTHOR:	Christoph Bichlmeier <informatik@zombiearena.de>\n") + 
+            string("*\n") + 
+            string("* NOTE: This version is not tested thoroughly!\n") + 
+            string("*\n") + 
+            string("* Copyright (c) 2003, Christoph Bichlmeier\n") + 
+            string("* All rights reserved.\n") + 
+            string("*\n") + 
+            string("* Redistribution and use in source and binary forms, with or without\n") + 
+            string("* modification, are permitted provided that the following conditions\n") + 
+            string("* are met:\n") + 
+            string("* 1. Redistributions of source code must retain the above copyright\n") + 
+            string("*    notice, this list of conditions and the following disclaimer.\n") + 
+            string("* 2. Redistributions in binary form must reproduce the above copyright\n") + 
+            string("*    notice, this list of conditions and the following disclaimer in the\n") + 
+            string("*    documentation and/or other materials provided with the distribution.\n") + 
+            string("* 3. Neither the name of the copyright holder nor the names of contributors\n") + 
+            string("*    may be used to endorse or promote products derived from this software\n") + 
+            string("*    without specific prior written permission.\n") + 
+            string("*\n") + 
+            string("* ======================================================================\n") + 
+            string("*\n") + 
+            string("* THIS SOFTWARE IS PROVIDED BY THE AUTHORS \'\'AS IS\'\' AND ANY EXPRESS\n") + 
+            string("* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n") + 
+            string("* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n") + 
+            string("* ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE\n") + 
+            string("* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n") + 
+            string("* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n") + 
+            string("* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR\n") + 
+            string("* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\n") + 
+            string("* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n") + 
+            string("* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\n") + 
+            string("* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n") + 
+            string("*/\n") + 
+            string("\n") + 
+            string("/* SHA256 logical functions */\n") + 
+            string("function rotateRight(n,x) {\n") + 
+            string("    return ((x >>> n) | (x << (32 - n)));\n") + 
+            string("}\n") + 
+            string("function choice(x,y,z) {\n") + 
+            string("    return ((x & y) ^ (~x & z));\n") + 
+            string("}\n") + 
+            string("function majority(x,y,z) {\n") + 
+            string("    return ((x & y) ^ (x & z) ^ (y & z));\n") + 
+            string("}\n") + 
+            string("function sha256_Sigma0(x) {\n") + 
+            string("    return (rotateRight(2, x) ^ rotateRight(13, x) ^ rotateRight(22, x));\n") + 
+            string("}\n") + 
+            string("function sha256_Sigma1(x) {\n") + 
+            string("    return (rotateRight(6, x) ^ rotateRight(11, x) ^ rotateRight(25, x));\n") + 
+            string("}\n") + 
+            string("function sha256_sigma0(x) {\n") + 
+            string("    return (rotateRight(7, x) ^ rotateRight(18, x) ^ (x >>> 3));\n") + 
+            string("}\n") + 
+            string("function sha256_sigma1(x) {\n") + 
+            string("    return (rotateRight(17, x) ^ rotateRight(19, x) ^ (x >>> 10));\n") + 
+            string("}\n") + 
+            string("function sha256_expand(W, j) {\n") + 
+            string("    return (W[j&0x0f] += sha256_sigma1(W[(j+14)&0x0f]) + W[(j+9)&0x0f] +\n") + 
+            string("        sha256_sigma0(W[(j+1)&0x0f]));\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Hash constant words K: */\n") + 
+            string("let K256 = new Array(\n") + 
+            string("    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,\n") + 
+            string("    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n") + 
+            string("    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,\n") + 
+            string("    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n") + 
+            string("    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,\n") + 
+            string("    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n") + 
+            string("    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,\n") + 
+            string("    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n") + 
+            string("    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,\n") + 
+            string("    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n") + 
+            string("    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,\n") + 
+            string("    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n") + 
+            string("    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,\n") + 
+            string("    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n") + 
+            string("    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n") + 
+            string("    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n") + 
+            string(");\n") + 
+            string("\n") + 
+            string("/* global arrays */\n") + 
+            string("let ihash, count, buffer;\n") + 
+            string("let sha256_hex_digits = \"0123456789abcdef\";\n") + 
+            string("\n") + 
+            string("/* Add 32-bit integers with 16-bit operations (bug in some JS-interpreters:\n") + 
+            string("overflow) */\n") + 
+            string("function safe_add(x, y)\n") + 
+            string("{\n") + 
+            string("    let lsw = (x & 0xffff) + (y & 0xffff);\n") + 
+            string("    let msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n") + 
+            string("    return (msw << 16) | (lsw & 0xffff);\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Initialise the SHA256 computation */\n") + 
+            string("function sha256_init() {\n") + 
+            string("    ihash = new Array(8);\n") + 
+            string("    count = new Array(2);\n") + 
+            string("    buffer = new Array(64);\n") + 
+            string("    count[0] = count[1] = 0;\n") + 
+            string("    ihash[0] = 0x6a09e667;\n") + 
+            string("    ihash[1] = 0xbb67ae85;\n") + 
+            string("    ihash[2] = 0x3c6ef372;\n") + 
+            string("    ihash[3] = 0xa54ff53a;\n") + 
+            string("    ihash[4] = 0x510e527f;\n") + 
+            string("    ihash[5] = 0x9b05688c;\n") + 
+            string("    ihash[6] = 0x1f83d9ab;\n") + 
+            string("    ihash[7] = 0x5be0cd19;\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Transform a 512-bit message block */\n") + 
+            string("function sha256_transform() {\n") + 
+            string("    let a, b, c, d, e, f, g, h, T1, T2;\n") + 
+            string("    let W = new Array(16);\n") + 
+            string("\n") + 
+            string("    /* Initialize registers with the previous intermediate value */\n") + 
+            string("    a = ihash[0];\n") + 
+            string("    b = ihash[1];\n") + 
+            string("    c = ihash[2];\n") + 
+            string("    d = ihash[3];\n") + 
+            string("    e = ihash[4];\n") + 
+            string("    f = ihash[5];\n") + 
+            string("    g = ihash[6];\n") + 
+            string("    h = ihash[7];\n") + 
+            string("\n") + 
+            string("    /* make 32-bit words */\n") + 
+            string("    for(let i=0; i<16; i++)\n") + 
+            string("        W[i] = ((buffer[(i<<2)+3]) | (buffer[(i<<2)+2] << 8) | (buffer[(i<<2)+1]\n") + 
+            string("            << 16) | (buffer[i<<2] << 24));\n") + 
+            string("\n") + 
+            string("    for(let j=0; j<64; j++) {\n") + 
+            string("        T1 = h + sha256_Sigma1(e) + choice(e, f, g) + K256[j];\n") + 
+            string("        if(j < 16) T1 += W[j];\n") + 
+            string("        else T1 += sha256_expand(W, j);\n") + 
+            string("        T2 = sha256_Sigma0(a) + majority(a, b, c);\n") + 
+            string("        h = g;\n") + 
+            string("        g = f;\n") + 
+            string("        f = e;\n") + 
+            string("        e = safe_add(d, T1);\n") + 
+            string("        d = c;\n") + 
+            string("        c = b;\n") + 
+            string("        b = a;\n") + 
+            string("        a = safe_add(T1, T2);\n") + 
+            string("    }\n") + 
+            string("\n") + 
+            string("    /* Compute the current intermediate hash value */\n") + 
+            string("    ihash[0] += a;\n") + 
+            string("    ihash[1] += b;\n") + 
+            string("    ihash[2] += c;\n") + 
+            string("    ihash[3] += d;\n") + 
+            string("    ihash[4] += e;\n") + 
+            string("    ihash[5] += f;\n") + 
+            string("    ihash[6] += g;\n") + 
+            string("    ihash[7] += h;\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Read the next chunk of data and update the SHA256 computation */\n") + 
+            string("function sha256_update(data, inputLen) {\n") + 
+            string("    let i, index, curpos = 0;\n") + 
+            string("    /* Compute number of bytes mod 64 */\n") + 
+            string("    index = ((count[0] >> 3) & 0x3f);\n") + 
+            string("    let remainder = (inputLen & 0x3f);\n") + 
+            string("\n") + 
+            string("    /* Update number of bits */\n") + 
+            string("    if ((count[0] += (inputLen << 3)) < (inputLen << 3)) count[1]++;\n") + 
+            string("    count[1] += (inputLen >> 29);\n") + 
+            string("\n") + 
+            string("    /* Transform as many times as possible */\n") + 
+            string("    for(i=0; i+63<inputLen; i+=64) {\n") + 
+            string("        for(let j=index; j<64; j++)\n") + 
+            string("            buffer[j] = data.charCodeAt(curpos++);\n") + 
+            string("        sha256_transform();\n") + 
+            string("        index = 0;\n") + 
+            string("    }\n") + 
+            string("\n") + 
+            string("    /* Buffer remaining input */\n") + 
+            string("    for(let j=0; j<remainder; j++)\n") + 
+            string("        buffer[j] = data.charCodeAt(curpos++);\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Finish the computation by operations such as padding */\n") + 
+            string("function sha256_final() {\n") + 
+            string("    let index = ((count[0] >> 3) & 0x3f);\n") + 
+            string("    buffer[index++] = 0x80;\n") + 
+            string("    if(index <= 56) {\n") + 
+            string("        for(let i=index; i<56; i++)\n") + 
+            string("            buffer[i] = 0;\n") + 
+            string("    } else {\n") + 
+            string("        for(let i=index; i<64; i++)\n") + 
+            string("            buffer[i] = 0;\n") + 
+            string("        sha256_transform();\n") + 
+            string("        for(let i=0; i<56; i++)\n") + 
+            string("            buffer[i] = 0;\n") + 
+            string("    }\n") + 
+            string("    buffer[56] = (count[1] >>> 24) & 0xff;\n") + 
+            string("    buffer[57] = (count[1] >>> 16) & 0xff;\n") + 
+            string("    buffer[58] = (count[1] >>> 8) & 0xff;\n") + 
+            string("    buffer[59] = count[1] & 0xff;\n") + 
+            string("    buffer[60] = (count[0] >>> 24) & 0xff;\n") + 
+            string("    buffer[61] = (count[0] >>> 16) & 0xff;\n") + 
+            string("    buffer[62] = (count[0] >>> 8) & 0xff;\n") + 
+            string("    buffer[63] = count[0] & 0xff;\n") + 
+            string("    sha256_transform();\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Split the internal hash values into an array of bytes */\n") + 
+            string("function sha256_encode_bytes() {\n") + 
+            string("    let j=0;\n") + 
+            string("    let output = new Array(32);\n") + 
+            string("    for(let i=0; i<8; i++) {\n") + 
+            string("        output[j++] = ((ihash[i] >>> 24) & 0xff);\n") + 
+            string("        output[j++] = ((ihash[i] >>> 16) & 0xff);\n") + 
+            string("        output[j++] = ((ihash[i] >>> 8) & 0xff);\n") + 
+            string("        output[j++] = (ihash[i] & 0xff);\n") + 
+            string("    }\n") + 
+            string("    return output;\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Get the internal hash as a hex string */\n") + 
+            string("function sha256_encode_hex() {\n") + 
+            string("    let output = new String();\n") + 
+            string("    for(let i=0; i<8; i++) {\n") + 
+            string("        for(let j=28; j>=0; j-=4)\n") + 
+            string("            output += sha256_hex_digits.charAt((ihash[i] >>> j) & 0x0f);\n") + 
+            string("    }\n") + 
+            string("    return output;\n") + 
+            string("}\n") + 
+            string("\n") + 
+            string("/* Main function: returns a hex string representing the SHA256 value of the\n") + 
+            string("given data */\n") + 
+            string("function sha256_digest(data) {\n") + 
+            string("    sha256_init();\n") + 
+            string("    sha256_update(data, data.length);\n") + 
+            string("    sha256_final();\n") + 
+            string("    return sha256_encode_hex();\n") + 
+            string("}\n")
+        , mimeTypeOfFile(ControllerPath));
+    }
+
+    ContentGeneratorDefineS(request.getURLPath() == ControllerPath, controllerResponse(request))
+
+}

+ 1 - 1
data/Generate-Assets.py

@@ -47,7 +47,7 @@ if __name__ == "__main__":
             w.write(fileText + "\n")
             w.write("        , mimeTypeOfFile(ControllerPath));\n")
             w.write("    }\n\n")
-            w.write("    ContentGeneratorDefineS(request.getURL() == ControllerPath, controllerResponse(request))\n")
+            w.write("    ContentGeneratorDefineS(request.getURLPath() == ControllerPath, controllerResponse(request))\n")
             w.write("\n")
             w.write("}\n")
 

+ 0 - 11
data/html/error.html

@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Error {{errorCode}}</title>
-</head>
-<body>
-    <p style="text-align: center">{{errorMessage}} ({{errorCode}})</p>
-    <p style="text-align: center">FRPCWebUI / XCHttpServer 1.0</p>
-</body>
-</html>

+ 0 - 57
data/html/index.html

@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <!-- Required meta tags-->
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover">
-    <meta name="apple-mobile-web-app-capable" content="yes">
-    <!-- Color theme for statusbar (Android only) -->
-    <meta name="theme-color" content="#2196f3">
-    <!-- Your app title -->
-    <title>My App</title>
-    <!-- Path to Framework7 Library Bundle CSS -->
-    <link rel="stylesheet" href="framework7/framework7-bundle.min.css">
-    <!-- Path to your custom app styles-->
-    <link rel="stylesheet" href="index.css">
-</head>
-<body>
-<!-- App root element -->
-<div id="app">
-
-    <!-- Your main view, should have "view-main" class -->
-    <div class="view view-main">
-        <!-- Initial Page, "data-name" contains page name -->
-        <div data-name="home" class="page">
-
-            <!-- Top Navbar -->
-            <div class="navbar">
-                <div class="navbar-bg"></div>
-                <div class="navbar-inner">
-                    <div class="title">Awesome App</div>
-                </div>
-            </div>
-
-            <!-- Bottom Toolbar -->
-            <div class="toolbar toolbar-bottom">
-                <div class="toolbar-inner">
-                    <!-- Toolbar links -->
-                    <a href="#" class="link">Link 1</a>
-                    <a href="#" class="link">Link 2</a>
-                </div>
-            </div>
-
-            <!-- Scrollable page content -->
-            <div class="page-content">
-                <p>Page content goes here</p>
-                <!-- Link to another page -->
-                <a href="/about/">About app</a>
-            </div>
-        </div>
-    </div>
-</div>
-<!-- Path to Framework7 Library Bundle JS-->
-<script type="text/javascript" src="framework7/framework7-bundle.min.js"></script>
-<!-- Path to your app js-->
-<script type="text/javascript" src="index.js"></script>
-</body>
-</html>

+ 16 - 2
data/html/index.js

@@ -15,6 +15,20 @@ let app = new Framework7({
             path: '/about/',
             url: 'about.html',
         },
-    ],
-    // ... other parameters
+    ]
 });
+
+function doLogin(salt) {
+    let data = app.form.convertToData("#loginForm")
+    if (data.username.length == 0) {
+        app.dialog.alert("请输入用户名")
+        return
+    }
+    if (data.password.length == 0) {
+        app.dialog.alert("请输入密码")
+        return
+    }
+    data.password = sha256_digest(data.password + salt)
+    window.location = "/login?v=" + JSON.stringify(data)
+    return
+}

+ 241 - 0
data/html/sha256.js

@@ -0,0 +1,241 @@
+/*
+* A JavaScript implementation of the SHA256 hash function.
+*
+* FILE:	sha256.js
+* VERSION:	0.8
+* AUTHOR:	Christoph Bichlmeier <informatik@zombiearena.de>
+*
+* NOTE: This version is not tested thoroughly!
+*
+* Copyright (c) 2003, Christoph Bichlmeier
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+* 3. Neither the name of the copyright holder nor the names of contributors
+*    may be used to endorse or promote products derived from this software
+*    without specific prior written permission.
+*
+* ======================================================================
+*
+* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
+* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* SHA256 logical functions */
+function rotateRight(n,x) {
+    return ((x >>> n) | (x << (32 - n)));
+}
+function choice(x,y,z) {
+    return ((x & y) ^ (~x & z));
+}
+function majority(x,y,z) {
+    return ((x & y) ^ (x & z) ^ (y & z));
+}
+function sha256_Sigma0(x) {
+    return (rotateRight(2, x) ^ rotateRight(13, x) ^ rotateRight(22, x));
+}
+function sha256_Sigma1(x) {
+    return (rotateRight(6, x) ^ rotateRight(11, x) ^ rotateRight(25, x));
+}
+function sha256_sigma0(x) {
+    return (rotateRight(7, x) ^ rotateRight(18, x) ^ (x >>> 3));
+}
+function sha256_sigma1(x) {
+    return (rotateRight(17, x) ^ rotateRight(19, x) ^ (x >>> 10));
+}
+function sha256_expand(W, j) {
+    return (W[j&0x0f] += sha256_sigma1(W[(j+14)&0x0f]) + W[(j+9)&0x0f] +
+        sha256_sigma0(W[(j+1)&0x0f]));
+}
+
+/* Hash constant words K: */
+let K256 = new Array(
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+);
+
+/* global arrays */
+let ihash, count, buffer;
+let sha256_hex_digits = "0123456789abcdef";
+
+/* Add 32-bit integers with 16-bit operations (bug in some JS-interpreters:
+overflow) */
+function safe_add(x, y)
+{
+    let lsw = (x & 0xffff) + (y & 0xffff);
+    let msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+    return (msw << 16) | (lsw & 0xffff);
+}
+
+/* Initialise the SHA256 computation */
+function sha256_init() {
+    ihash = new Array(8);
+    count = new Array(2);
+    buffer = new Array(64);
+    count[0] = count[1] = 0;
+    ihash[0] = 0x6a09e667;
+    ihash[1] = 0xbb67ae85;
+    ihash[2] = 0x3c6ef372;
+    ihash[3] = 0xa54ff53a;
+    ihash[4] = 0x510e527f;
+    ihash[5] = 0x9b05688c;
+    ihash[6] = 0x1f83d9ab;
+    ihash[7] = 0x5be0cd19;
+}
+
+/* Transform a 512-bit message block */
+function sha256_transform() {
+    let a, b, c, d, e, f, g, h, T1, T2;
+    let W = new Array(16);
+
+    /* Initialize registers with the previous intermediate value */
+    a = ihash[0];
+    b = ihash[1];
+    c = ihash[2];
+    d = ihash[3];
+    e = ihash[4];
+    f = ihash[5];
+    g = ihash[6];
+    h = ihash[7];
+
+    /* make 32-bit words */
+    for(let i=0; i<16; i++)
+        W[i] = ((buffer[(i<<2)+3]) | (buffer[(i<<2)+2] << 8) | (buffer[(i<<2)+1]
+            << 16) | (buffer[i<<2] << 24));
+
+    for(let j=0; j<64; j++) {
+        T1 = h + sha256_Sigma1(e) + choice(e, f, g) + K256[j];
+        if(j < 16) T1 += W[j];
+        else T1 += sha256_expand(W, j);
+        T2 = sha256_Sigma0(a) + majority(a, b, c);
+        h = g;
+        g = f;
+        f = e;
+        e = safe_add(d, T1);
+        d = c;
+        c = b;
+        b = a;
+        a = safe_add(T1, T2);
+    }
+
+    /* Compute the current intermediate hash value */
+    ihash[0] += a;
+    ihash[1] += b;
+    ihash[2] += c;
+    ihash[3] += d;
+    ihash[4] += e;
+    ihash[5] += f;
+    ihash[6] += g;
+    ihash[7] += h;
+}
+
+/* Read the next chunk of data and update the SHA256 computation */
+function sha256_update(data, inputLen) {
+    let i, index, curpos = 0;
+    /* Compute number of bytes mod 64 */
+    index = ((count[0] >> 3) & 0x3f);
+    let remainder = (inputLen & 0x3f);
+
+    /* Update number of bits */
+    if ((count[0] += (inputLen << 3)) < (inputLen << 3)) count[1]++;
+    count[1] += (inputLen >> 29);
+
+    /* Transform as many times as possible */
+    for(i=0; i+63<inputLen; i+=64) {
+        for(let j=index; j<64; j++)
+            buffer[j] = data.charCodeAt(curpos++);
+        sha256_transform();
+        index = 0;
+    }
+
+    /* Buffer remaining input */
+    for(let j=0; j<remainder; j++)
+        buffer[j] = data.charCodeAt(curpos++);
+}
+
+/* Finish the computation by operations such as padding */
+function sha256_final() {
+    let index = ((count[0] >> 3) & 0x3f);
+    buffer[index++] = 0x80;
+    if(index <= 56) {
+        for(let i=index; i<56; i++)
+            buffer[i] = 0;
+    } else {
+        for(let i=index; i<64; i++)
+            buffer[i] = 0;
+        sha256_transform();
+        for(let i=0; i<56; i++)
+            buffer[i] = 0;
+    }
+    buffer[56] = (count[1] >>> 24) & 0xff;
+    buffer[57] = (count[1] >>> 16) & 0xff;
+    buffer[58] = (count[1] >>> 8) & 0xff;
+    buffer[59] = count[1] & 0xff;
+    buffer[60] = (count[0] >>> 24) & 0xff;
+    buffer[61] = (count[0] >>> 16) & 0xff;
+    buffer[62] = (count[0] >>> 8) & 0xff;
+    buffer[63] = count[0] & 0xff;
+    sha256_transform();
+}
+
+/* Split the internal hash values into an array of bytes */
+function sha256_encode_bytes() {
+    let j=0;
+    let output = new Array(32);
+    for(let i=0; i<8; i++) {
+        output[j++] = ((ihash[i] >>> 24) & 0xff);
+        output[j++] = ((ihash[i] >>> 16) & 0xff);
+        output[j++] = ((ihash[i] >>> 8) & 0xff);
+        output[j++] = (ihash[i] & 0xff);
+    }
+    return output;
+}
+
+/* Get the internal hash as a hex string */
+function sha256_encode_hex() {
+    let output = new String();
+    for(let i=0; i<8; i++) {
+        for(let j=28; j>=0; j-=4)
+            output += sha256_hex_digits.charAt((ihash[i] >>> j) & 0x0f);
+    }
+    return output;
+}
+
+/* Main function: returns a hex string representing the SHA256 value of the
+given data */
+function sha256_digest(data) {
+    sha256_init();
+    sha256_update(data, data.length);
+    sha256_final();
+    return sha256_encode_hex();
+}

+ 37 - 0
fs.hpp

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <sys/stat.h>
+#include <string>
+#include "utils/utils.h"
+
+using namespace std;
+
+namespace fs {
+
+    inline bool existsNothing(string filePath) {
+        struct stat buffer;
+        return stat(filePath.c_str(), &buffer) != 0;
+    }
+
+    inline bool existsAnything(string filePath) {
+        struct stat buffer;
+        return stat(filePath.c_str(), &buffer) == 0;
+    }
+
+    inline bool existsFile(string filePath) {
+        struct stat buffer;
+        if (stat(filePath.c_str(), &buffer) == 0) {
+            return S_ISREG(buffer.st_mode);
+        }
+        return false;
+    }
+
+    inline bool existsDirectory(string filePath) {
+        struct stat buffer;
+        if (stat(filePath.c_str(), &buffer) == 0) {
+            return !S_ISREG(buffer.st_mode);
+        }
+        return false;
+    }
+
+}

+ 3 - 4
httpserver/ClientConnection.cpp

@@ -151,10 +151,9 @@ namespace xc {
                 cleanUpAndDestroy();
                 return;
             } else {
-                conf::errorPage.applyReplacements(400, {
-                    Replacement("errorCode", "400"),
-                    Replacement("errorMessage", "未知的协议 " + method)
-                }).writeTo(clWrite);
+                conf::ErrorView view(400, "");
+                view.setMessage("未知的协议 " + method);
+                TemplateResponseData({ view }).writeTo(clWrite);
                 cleanUpAndDestroy();
                 return;
             }

+ 1 - 1
processor/RequestProcessWorker.cpp

@@ -21,7 +21,7 @@ namespace xc {
                 }
                 ResponseData *resp = nullptr;
                 if (generator == nullptr) {
-                    resp = new FileResponseData(conf::errorPage404);
+                    resp = new TemplateResponseData(conf::errorPage404);
                 } else {
                     resp = generator->generateResponse(task->getRequest());
                 }

+ 5 - 0
processor/templates/TemplateResponseData.cpp

@@ -12,6 +12,11 @@ namespace xc {
                 this->generateBody(prototypes);
             }
 
+            TemplateResponseData::TemplateResponseData(int statusCode, vector<ViewTemplatePrototype> prototypes): TextResponseData(200, "") {
+                this->setStatusCode(statusCode);
+                this->generateBody(prototypes);
+            }
+
             void TemplateResponseData::generateBody(const vector<ViewTemplatePrototype> &prototypes) {
                 ostringstream oss;
                 for (auto it : prototypes) {

+ 1 - 0
processor/templates/TemplateResponseData.h

@@ -14,6 +14,7 @@ namespace xc {
             class TemplateResponseData: public TextResponseData {
             public:
                 TemplateResponseData(vector<ViewTemplatePrototype> prototypes);
+                TemplateResponseData(int statusCode, vector<ViewTemplatePrototype> prototypes);
                 void generateBody(const vector<ViewTemplatePrototype> &prototypes);
             };
 

+ 13 - 7
processor/templates/ViewTemplatePrototypes.cpp

@@ -63,14 +63,16 @@ namespace xc {
                             writeTo << "\" ";
                         }
 
-                        writeTo << "style=\"";
-                        for (auto it: this->styles) {
-                            writeTo << it.first;
-                            writeTo << ": ";
-                            writeTo << it.second;
-                            writeTo << "; ";
+                        if (!this->styles.empty()) {
+                            writeTo << "style=\"";
+                            for (auto it: this->styles) {
+                                writeTo << it.first;
+                                writeTo << ": ";
+                                writeTo << it.second;
+                                writeTo << "; ";
+                            }
+                            writeTo << '\"';
                         }
-                        writeTo << '\"';
 
                         writeTo << '>';
                     }
@@ -155,6 +157,10 @@ namespace xc {
             ViewTemplatePrototype& ViewTemplatePrototype::type(string value) { return this->prop("type", value); }
             ViewTemplatePrototype& ViewTemplatePrototype::src(string value) { return this->prop("src", value); }
             ViewTemplatePrototype& ViewTemplatePrototype::charset(string value) { return this->prop("charset", value); }
+            ViewTemplatePrototype& ViewTemplatePrototype::action(string value) { return this->prop("action", value); }
+            ViewTemplatePrototype& ViewTemplatePrototype::method(string value) { return this->prop("method", value); }
+            ViewTemplatePrototype& ViewTemplatePrototype::value(string value) { return this->prop("value", value); }
+            ViewTemplatePrototype& ViewTemplatePrototype::onsubmit(string value) { return this->prop("onsubmit", value); }
 
             View ContentGeneratorReference(string name, RequestData request) {
                 const ContentGenerator *generator = processor::findContentGenerator(name);

+ 4 - 0
processor/templates/ViewTemplatePrototypes.h

@@ -45,6 +45,10 @@ namespace xc {
                 ViewTemplatePrototype& type(string value);
                 ViewTemplatePrototype& src(string value);
                 ViewTemplatePrototype& charset(string value);
+                ViewTemplatePrototype& action(string value);
+                ViewTemplatePrototype& method(string value);
+                ViewTemplatePrototype& value(string value);
+                ViewTemplatePrototype& onsubmit(string value);
 
                 ViewTemplatePrototype& pointer(ViewTemplatePrototype **ptr);
             protected:

+ 20 - 4
processor/templates/framework7/Framework7Document.hpp

@@ -39,6 +39,7 @@ namespace xc::processor::templates::framework7 {
                 }).id("app"),
                 script().type("text/javascript").src("framework7/framework7-bundle.min.js"),
                 script().type("text/javascript").src("index.js"),
+                script().type("text/javascript").src("sha256.js"),
             });
             this->inner(html);
         }
@@ -74,18 +75,30 @@ namespace xc::processor::templates::framework7 {
         }) { }
     };
 
+    class FormInputButtonView: public input {
+    public:
+        FormInputButtonView(string text): input() {
+            this->type("submit");
+            this->value(text);
+            this->classAdd("button button-fill");
+        }
+    };
+
     class BlockView: public div {
     public:
         BlockView(ViewCollection content): div(content) {
-            this->classAdd("block block-strong row");
+            this->classAdd("block block-strong");
         }
+        BlockView& inset() { this->classAdd("inset"); return *this; }
     };
 
-    class BlockColumnView: public div {
+    class BlockTitleView: public div {
     public:
-        BlockColumnView(ViewCollection content): div(content) {
-            this->classAdd("col");
+        BlockTitleView(string text): div(text) {
+            this->classAdd("block-title");
         }
+        BlockTitleView& large() { this->classAdd("block-title-large"); return *this; }
+        BlockTitleView& medium() { this->classAdd("block-title-medium"); return *this; }
     };
 
     const string ClassButtonViewTypeFill("button-fill");
@@ -125,6 +138,9 @@ namespace xc::processor::templates::framework7 {
         }
     };
 
+    typedef p Label;
+    typedef a Link;
+
 
 
 } // framework7

+ 130 - 0
thirdparty/sha256.hpp

@@ -0,0 +1,130 @@
+// sha256.c
+
+#pragma once
+
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string>
+#include <ostream>
+#include <sstream>
+
+using namespace std;
+
+void __sha256(const unsigned char *data, size_t len, unsigned char *out) __attribute__((weak));
+
+string sha256(string str) __attribute__((weak)) {
+    unsigned char buff[32];
+    ::memset(buff, 0, 32);
+    const char *c_str = str.c_str();
+    __sha256((unsigned char *)c_str, ::strlen(c_str), buff);
+    ostringstream oss;
+    oss << hex;
+    for (int i = 0; i < 32; i++) {
+        oss << (int)(buff[i]);
+    }
+    return oss.str();
+}
+
+#define rightrotate(w, n) ((w >> n) | (w) << (32-(n)))
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define copy_uint32(p, val) *((uint32_t *)p) = __builtin_bswap32((val))//gcc 内建函数__builtin_bswap32,
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define copy_uint32(p, val) *((uint32_t *)p) = (val)
+#else
+#error "Unsupported target architecture endianess!"
+#endif
+
+static const uint32_t k[64] = {
+        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+void __sha256(const unsigned char *data, size_t len, unsigned char *out) __attribute__((weak)) {
+    uint32_t h0 = 0x6a09e667;
+    uint32_t h1 = 0xbb67ae85;
+    uint32_t h2 = 0x3c6ef372;
+    uint32_t h3 = 0xa54ff53a;
+    uint32_t h4 = 0x510e527f;
+    uint32_t h5 = 0x9b05688c;
+    uint32_t h6 = 0x1f83d9ab;
+    uint32_t h7 = 0x5be0cd19;
+    int r = (int)(len * 8 % 512);
+    int append = ((r < 448) ? (448 - r) : (448 + 512 - r)) / 8;
+    size_t new_len = len + append + 8;
+    unsigned char buf[new_len];
+    bzero(buf + len, append);
+    if (len > 0) {
+        memcpy(buf, data, len);
+    }
+    buf[len] = (unsigned char)0x80;
+    uint64_t bits_len = len * 8;
+    for (int i = 0; i < 8; i++) {
+        buf[len + append + i] = (bits_len >> ((7 - i) * 8)) & 0xff;
+    }
+    uint32_t w[64];
+    bzero(w, 64);
+    size_t chunk_len = new_len / 64;
+    for (int idx = 0; idx < chunk_len; idx++) {
+        uint32_t val = 0;
+        for (int i = 0; i < 64; i++) {
+            val =  val | (*(buf + idx * 64 + i) << (8 * (3 - i)));
+            if (i % 4 == 3) {
+                w[i / 4] = val;
+                val = 0;
+            }
+        }
+        for (int i = 16; i < 64; i++) {
+            uint32_t s0 = rightrotate(w[i - 15], 7) ^ rightrotate(w[i - 15], 18) ^ (w[i - 15] >> 3);
+            uint32_t s1 = rightrotate(w[i - 2], 17) ^ rightrotate(w[i - 2], 19) ^ (w[i - 2] >> 10);
+            w[i] = w[i - 16] + s0 + w[i - 7] + s1;
+        }
+
+        uint32_t a = h0;
+        uint32_t b = h1;
+        uint32_t c = h2;
+        uint32_t d = h3;
+        uint32_t e = h4;
+        uint32_t f = h5;
+        uint32_t g = h6;
+        uint32_t h = h7;
+        for (int i = 0; i < 64; i++) {
+            uint32_t s_1 = rightrotate(e, 6) ^ rightrotate(e, 11) ^ rightrotate(e, 25);
+            uint32_t ch = (e & f) ^ (~e & g);
+            uint32_t temp1 = h + s_1 + ch + k[i] + w[i];
+            uint32_t s_0 = rightrotate(a, 2) ^ rightrotate(a, 13) ^ rightrotate(a, 22);
+            uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
+            uint32_t temp2 = s_0 + maj;
+            h = g;
+            g = f;
+            f = e;
+            e = d + temp1;
+            d = c;
+            c = b;
+            b = a;
+            a = temp1 + temp2;
+        }
+        h0 += a;
+        h1 += b;
+        h2 += c;
+        h3 += d;
+        h4 += e;
+        h5 += f;
+        h6 += g;
+        h7 += h;
+    }
+    copy_uint32(out, h0);
+    copy_uint32(out + 1, h1);
+    copy_uint32(out + 2, h2);
+    copy_uint32(out + 3, h3);
+    copy_uint32(out + 4, h4);
+    copy_uint32(out + 5, h5);
+    copy_uint32(out + 6, h6);
+    copy_uint32(out + 7, h7);
+}

+ 106 - 0
user.hpp

@@ -0,0 +1,106 @@
+//
+// Created by xcbosa on 2023/1/30.
+//
+
+#pragma once
+
+#include <sys/stat.h>
+#include <string>
+#include <time.h>
+#include "utils/utils.h"
+#include "webuiconf.h"
+#include "fs.hpp"
+#include "thirdparty/sha256.hpp"
+
+using namespace std;
+using namespace xc;
+using namespace xc::utils;
+
+namespace user {
+
+    inline string generateToken(string payload) __attribute__((weak)) {
+        ostringstream oss;
+        oss << payload;
+        oss << "/";
+        ::time_t t;
+        ::time(&t);
+        oss << (t + conf::userTokenExpireSeconds);
+        oss << "/";
+        oss << sha256(oss.str());
+        return oss.str();
+    }
+
+    inline string tryLogin(string username, string password) __attribute__((weak)) {
+        string userInfoFile = conf::getUserDataDir() + "/" + username;
+        if (fs::existsFile(userInfoFile)) {
+            INIFile ini(userInfoFile);
+            string iniPwd = ini.getMust("info")->get("password");
+            if (iniPwd == password) {
+                return generateToken(username);
+            }
+        }
+        return "";
+    }
+
+    inline vector<string> split(const string& str, const string& delim) __attribute__((weak)) {
+        vector<string> res;
+        if ("" == str) return res;
+        char * strs = new char[str.length() + 1];
+        strcpy(strs, str.c_str());
+        char * d = new char[delim.length() + 1];
+        strcpy(d, delim.c_str());
+        char *p = strtok(strs, d);
+        while(p) {
+            string s = p;
+            res.push_back(s);
+            p = strtok(NULL, d);
+        }
+        return res;
+    }
+
+    inline std::string& trim(std::string &s) __attribute__((weak)) {
+        if (s.empty()) { return s; }
+        s.erase(0,s.find_first_not_of(" "));
+        s.erase(s.find_last_not_of(" ") + 1);
+        return s;
+    }
+
+    inline string getTokenUserName(string token) __attribute__((weak)) {
+        auto list = split(token, "/");
+        if (list.size() != 3) {
+            return "";
+        }
+        string username = list[0];
+        string time = list[1];
+        string hash = list[2];
+        trim(username);
+        trim(time);
+        trim(hash);
+        try {
+            long timeSec = stol(time);
+            ::time_t t;
+            ::time(&t);
+            if (t > timeSec) {
+                return "";
+            }
+        }
+        catch (...) {
+            return "";
+        }
+        ostringstream oss;
+        oss << username;
+        oss << "/";
+        oss << time;
+        oss << "/";
+        string rhash = sha256(oss.str());
+        if (hash != rhash) {
+            return "";
+        }
+        return username;
+    }
+
+    inline bool isLogin(string token) __attribute__((weak)) {
+        return !getTokenUserName(token).empty();
+    }
+
+}

+ 1 - 1
utils/BinaryResponseData.cpp

@@ -34,7 +34,7 @@ namespace xc {
         }
 
         void BinaryResponseData::writeTo(::FILE *fp) const {
-            ::fprintf(fp, "HTTP/1.1 %d FRPCWebUI\r\n", this->statusCode);
+            ::fprintf(fp, "HTTP/1.1 %d XCHttpServer\r\n", this->statusCode);
             for (auto item : this->headers) {
                 ::fprintf(fp, "%s: %s\r\n", item.first.c_str(), item.second.c_str());
             }

+ 2 - 6
utils/INI.cpp

@@ -46,15 +46,11 @@ namespace xc {
         INISection::INISection(string title, map<string, string> data): title(title), data(data) { }
 
         string INISection::get(string key) {
-            if (this->has(key)) {
-                return this->data[key];
-            } else {
-                return string();
-            }
+            return this->data[key];
         }
 
         bool INISection::has(string key) {
-            return this->data.count(key) != 0;
+            return this->data.count(key) == 1;
         }
 
         void INISection::set(string key, string value) {

+ 13 - 0
utils/RedirectResponse.cpp

@@ -0,0 +1,13 @@
+//
+// Created by xcbosa on 2023/1/30.
+//
+
+#include "RedirectResponse.h"
+
+namespace xc {
+    namespace utils {
+        RedirectResponse::RedirectResponse(string location): TextResponseData(302, "") {
+            this->setHeader("Location", location);
+        }
+    } // xc
+} // utils

+ 19 - 0
utils/RedirectResponse.h

@@ -0,0 +1,19 @@
+//
+// Created by xcbosa on 2023/1/30.
+//
+
+#pragma once
+
+#include "utils-private.h"
+#include "TextResponseData.h"
+
+namespace xc {
+    namespace utils {
+
+        class RedirectResponse: public TextResponseData {
+        public:
+            RedirectResponse(string location);
+        };
+
+    } // xc
+} // utils

+ 115 - 0
utils/RequestData.cpp

@@ -3,6 +3,9 @@
 //
 
 #include "RequestData.h"
+#include "regex"
+
+using namespace std;
 
 namespace xc {
     namespace utils {
@@ -11,6 +14,13 @@ namespace xc {
             this->method = method;
             this->headers = headers;
             this->body = body;
+            if (this->url.length() == 0) {
+                this->url = "/";
+            } else {
+                if (this->url[0] != '/') {
+                    this->url = '/' + this->url;
+                }
+            }
         }
 
         string RequestData::getURL() const {
@@ -28,5 +38,110 @@ namespace xc {
         string RequestData::getBody() const {
             return this->body;
         }
+
+        string RequestData::getURLPath() const {
+            return this->url.substr(0, this->url.find_first_of('?'));
+        }
+
+        static unsigned char dec_tab[256] = {
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0,  0,  0,  0,  0,
+                0, 10, 11, 12, 13, 14, 15,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0, 10, 11, 12, 13, 14, 15,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+                0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+        };
+
+        static char *urlDecode(const char *str){
+            int len = (int) strlen(str);
+            char *tmp = (char *)malloc(len + 1);
+            int i = 0, pos = 0;
+            for (i = 0; i < len; i++) {
+                if (str[i] != '%')
+                    tmp[pos] = str[i];
+                else if (i + 2 >= len) {
+                    tmp[pos++] = '%';
+                    if (++i >= len)
+                        break;
+                    tmp[pos] = str[i];
+                    break;
+                } else if (isalnum(str[i + 1]) && isalnum(str[i + 2])) {
+                    tmp[pos] = (dec_tab[(unsigned char) str[i + 1]] << 4)
+                               + dec_tab[(unsigned char) str[i + 2]];
+                    i += 2;
+                } else
+                    tmp[pos] = str[i];
+                pos++;
+            }
+            tmp[pos] = '\0';
+            return tmp;
+        }
+
+        string RequestData::getURLArgument(string key) const {
+            smatch result;
+            string res;
+            if (regex_search(this->url.cbegin(), this->url.cend(), result, regex(key + "=(.*?)&"))) {
+                res = result[1];
+            } else if (regex_search(this->url.cbegin(), this->url.cend(), result, regex(key + "=(.*)"))) {
+                res = result[1];
+            } else {
+                return string();
+            }
+            char *newStr = urlDecode(res.c_str());
+            res = string(newStr);
+            ::free(newStr);
+            return res;
+        }
+
+        vector<string> split(const string& str, const string& delim) {
+            vector<string> res;
+            if ("" == str) return res;
+            char * strs = new char[str.length() + 1];
+            strcpy(strs, str.c_str());
+            char * d = new char[delim.length() + 1];
+            strcpy(d, delim.c_str());
+            char *p = strtok(strs, d);
+            while(p) {
+                string s = p;
+                res.push_back(s);
+                p = strtok(NULL, d);
+            }
+            return res;
+        }
+
+        std::string& trim(std::string &s) {
+            if (s.empty()) { return s; }
+            s.erase(0,s.find_first_not_of(" "));
+            s.erase(s.find_last_not_of(" ") + 1);
+            return s;
+        }
+
+        string RequestData::getCookie(string key) {
+            string cookies = this->getHeader("Cookie");
+            if (cookies.empty()) { return ""; }
+            vector<string> cookieList = split(cookies, ";");
+            for (string cookie : cookieList) {
+                auto items = split(cookies, "=");
+                if (items.size() == 2) {
+                    string name = items[0];
+                    string value = items[1];
+                    trim(name);
+                    if (name == key) {
+                        return value;
+                    }
+                }
+            }
+            return "";
+        }
     } // xc
 } // utils

+ 3 - 0
utils/RequestData.h

@@ -13,9 +13,12 @@ namespace xc {
         public:
             RequestData(string url, string method, map<string, string> headers, string body);
             string getURL() const;
+            string getURLPath() const;
+            string getURLArgument(string key) const;
             string getMethod() const;
             string getHeader(string name);
             string getBody() const;
+            string getCookie(string key);
         private:
             string url;
             string method;

+ 17 - 2
utils/TextResponseData.cpp

@@ -56,8 +56,19 @@ namespace xc {
         }
 
         void TextResponseData::writeTo(::FILE *fp) const {
-            ::fprintf(fp, "HTTP/1.1 %d FRPCWebUI\r\n", this->statusCode);
-            for (auto item : this->headers) {
+            ::fprintf(fp, "HTTP/1.1 %d XCHttpServer\r\n", this->statusCode);
+            map<string, string> headers(this->headers);
+            if (!this->cookies.empty()) {
+                ostringstream oss;
+                for (auto it : this->cookies) {
+                    oss << it.first;
+                    oss << "=";
+                    oss << it.second;
+                    oss << "; ";
+                }
+                headers["Set-Cookie"] = oss.str();
+            }
+            for (auto item : headers) {
                 ::fprintf(fp, "%s: %s\r\n", item.first.c_str(), item.second.c_str());
             }
             ::fputs("\r\n", fp);
@@ -85,5 +96,9 @@ namespace xc {
         void TextResponseData::writeResponseBodyTo(ostream &fp) const {
             fp << this->body;
         }
+
+        void TextResponseData::addCookie(string key, string value) {
+            this->cookies[key] = value;
+        }
     } // xc
 } // utils

+ 2 - 0
utils/TextResponseData.h

@@ -26,9 +26,11 @@ namespace xc {
             string getBody();
             void writeTo(::FILE *fp) const;
             void writeResponseBodyTo(ostream &fp) const;
+            void addCookie(string key, string value);
         private:
             int statusCode;
             map<string, string> headers;
+            map<string, string> cookies;
             string body;
         };
 

+ 1 - 0
utils/utils.h

@@ -10,6 +10,7 @@
 #include "FileResponseData.h"
 #include "BinaryResponseData.h"
 #include "INI.h"
+#include "RedirectResponse.h"
 
 using namespace std;
 

+ 49 - 21
webuiconf.h

@@ -7,11 +7,13 @@
 #include <vector>
 #include <map>
 #include <sys/stat.h>
-#include "webui.h"
 #include "utils/utils.h"
+#include "processor/processor.h"
 
 using namespace std;
 using namespace xc::utils;
+using namespace xc::processor;
+using namespace xc::processor::templates;
 
 namespace xc::conf {
     const int clientSocketTimeoutSeconds = 3;
@@ -23,14 +25,22 @@ namespace xc::conf {
 
     const string userPasswordSalt("eDGJ&v,.W0U(66.lVQFsKfWb*bm*M+Lj");
     const string userJWTSecret("r)xB=P-6A4dpqXLk%03=f+*8TlXDM@%r");
-    const string userDataDir = "data/users";
+    const string userDataDir = "/etc/frpcwebui/users";
+    const int userTokenExpireSeconds = 60 * 60 * 24;
 
     inline string getUserDataDir() {
         struct stat buffer;
         if (stat(userDataDir.c_str(), &buffer) == 0) {
-            assertx(!S_ISREG(buffer.st_mode), "xc::conf::userDataDir must be configured to a folder, but a file located there.");
+            assert(!S_ISREG(buffer.st_mode));
+            return userDataDir;
+        }
+        mode_t old_mask = umask(0);
+        int n_ret = mkdir(userDataDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
+        umask(old_mask);
+        if (n_ret != 0) {
+            ::perror("");
+            assert(n_ret == 0);
         }
-        mkdir(userDataDir.c_str(), S_IRWXU);
         return userDataDir;
     }
 
@@ -61,25 +71,43 @@ namespace xc::conf {
             "index.htm"
     };
 
-    const IncompleteFileResponseData errorPage(FileResponseData(500, "html/error.html", "text/html"));
+    class ErrorView: public View {
+    public:
+        ErrorView(int errorCode, string errorMessage): View("") {
+            ostringstream oss;
+            oss << "Error ";
+            oss << errorMessage;
+            oss << " ";
+            oss << errorCode;
+            html html({
+                head({
+                    meta().charset("UTF-8"),
+                    title(oss.str())
+                }),
+                body({
+                    p(oss.str())
+                    .style("text-align", "center")
+                    .pointer(&this->messageView),
+                    p("FRPC-WebUI / XCHttpServer 1.0")
+                    .style("text-align", "center")
+                })
+            });
+            this->inner(html);
+        }
+
+        void setMessage(string text) {
+            this->messageView->inner(text);
+        }
+    private:
+        View *messageView;
+    };
+
+    const TemplateResponseData errorPage400(400, { ErrorView(400, "请求格式错误,无法解析请求") });
 
-    const auto errorPage400 = errorPage.applyReplacements(400, {
-        Replacement("errorMessage", "请求格式错误,无法解析请求"),
-        Replacement("errorCode", "400")
-    });
+    const TemplateResponseData errorPage404(400, { ErrorView(404, "不存在指定的资源") });
 
-    const auto errorPage404 = errorPage.applyReplacements(404, {
-        Replacement("errorMessage", "不存在指定的资源"),
-        Replacement("errorCode", "404")
-    });
+    const TemplateResponseData errorPage500(400, { ErrorView(500, "服务器内部错误,可能是服务器访问量过大,请稍后重试") });
 
-    const auto errorPage500 = errorPage.applyReplacements(500, {
-        Replacement("errorMessage", "服务器内部错误,可能是服务器访问量过大,请稍后重试"),
-        Replacement("errorCode", "500")
-    });
+    const TemplateResponseData errorPageTimeout(550, { ErrorView(550, "服务器任务处理已超时,可能服务器访问量过大,请稍后重试") });
 
-    const auto errorPageTimeout = errorPage.applyReplacements(550, {
-        Replacement("errorMessage", "服务器任务处理已超时,可能服务器访问量过大,请稍后重试"),
-        Replacement("errorCode", "550")
-    });
 }