Compare commits
196 Commits
Author | SHA1 | Date | |
---|---|---|---|
1b44752f91
|
|||
5eeb9ef3c1
|
|||
dd15b9d906
|
|||
12922a0661
|
|||
c0799101b1
|
|||
c1d076db04
|
|||
10464f3f30
|
|||
63781472fa
|
|||
81931d287d
|
|||
531ddb4880
|
|||
e0d8ab31d5
|
|||
3a36d54e9d
|
|||
33d9aa3a5d
|
|||
f4d30206b0
|
|||
5b094ba98d
|
|||
f60cdc8228
|
|||
ab1c4d6fd4
|
|||
26d54e9968
|
|||
b6c7d8f58e
|
|||
53fcceeafb
|
|||
96567909db
|
|||
4b3c067a75
|
|||
77b80ca67b
|
|||
70e76d8783
|
|||
6b1bc54cf3
|
|||
e1edb48a3c
|
|||
dc5d1bebcc
|
|||
7b562c4b78
|
|||
413ab2aa5b
|
|||
68315b9765
|
|||
820a232a96
|
|||
b676388018
|
|||
debac11f90
|
|||
9297788cdf
|
|||
e61d16fb41
|
|||
ebf3258092
|
|||
59d0c485fd
|
|||
c90df2397f
|
|||
c4eb6709cf
|
|||
a73cbac7a1
|
|||
95946b1666
|
|||
785ab31890
|
|||
5481d314c9
|
|||
5986f39802
|
|||
a972340209
|
|||
e6dd8b84f9
|
|||
c3942b3382
|
|||
fde2b7aabb
|
|||
d0be587a36
|
|||
351568004f
|
|||
be757add02
|
|||
037c150868
|
|||
576d0a0dba
|
|||
ee6f9115a8
|
|||
6bbf79245b
|
|||
080d729f31
|
|||
2f6ba62715
|
|||
4d4d94fc81
|
|||
55028bd9cd
|
|||
8b0eb45854
|
|||
df49236130
|
|||
c7ca62a7e9
|
|||
5d01f7e219
|
|||
ceb0167742
|
|||
8fa9f2528f
|
|||
2e16fdee96
|
|||
f9b7e83ac8
|
|||
9b9ddbb913
|
|||
566ac0ca93
|
|||
bcdf36527f
|
|||
75ef4110c8
|
|||
f1064692a8
|
|||
f0ec64b629
|
|||
0db781e823
|
|||
798c41f1c8
|
|||
bc4a764bd5
|
|||
105e11d31d
|
|||
15530b642a
|
|||
e856f3f091
|
|||
b04c787df4
|
|||
ee7d1e086b
|
|||
cf8862100a
|
|||
4cc7b1c18a
|
|||
6fa62aeb4d
|
|||
3663a6064b
|
|||
ed233f8b5e
|
|||
6e8875d528
|
|||
640f3abc1f
|
|||
7d8f065ec9
|
|||
dd44ffee95
|
|||
c0962b90a6
|
|||
161952441d
|
|||
09b2118863
|
|||
b64828b01a
|
|||
8a640acd24
|
|||
609fd34ab7
|
|||
7cb4f40d22
|
|||
156c7d6621
|
|||
f39e1a6cb6
|
|||
c708633197
|
|||
b7f574e2ed
|
|||
be4475c336
|
|||
c6fbf25192
|
|||
b6ecb68b1a
|
|||
492e6a94cb
|
|||
1733ad3c1d
|
|||
885ec2226f
|
|||
1429961e44
|
|||
d88c70c978
|
|||
bfe08623c2
|
|||
2ed8451db1
|
|||
495381ab3f
|
|||
9d1594dc01
|
|||
463568182d
|
|||
fbd9a2667f
|
|||
d39f69d665
|
|||
c6f364d6e8
|
|||
8cccf48826
|
|||
7e2c9bea6d
|
|||
562cfb04e6
|
|||
14bcc47db2
|
|||
815fa32d14
|
|||
31fa9195a1
|
|||
caa09c3f05
|
|||
00abf345b9
|
|||
f0338209f5
|
|||
f6ac55adaf
|
|||
5e9c98d67f
|
|||
ba57509461
|
|||
4e079e094f
|
|||
a4901f4c23
|
|||
f5a42b8c28
|
|||
4f33e1d809
|
|||
c7902ea5b5
|
|||
50f5438654
|
|||
a5c1516468
|
|||
db224c7452
|
|||
812d5ab384
|
|||
1be4929c6c
|
|||
80ed6dea3e
|
|||
14b4f71af7
|
|||
a6789a7f3e
|
|||
7c2e3f2f9b
|
|||
93973c6e77
|
|||
97b04173ee
|
|||
8002ab3ed7
|
|||
35b924eea5
|
|||
3dbe356b87
|
|||
84c3e87a08
|
|||
51531ff78e
|
|||
e0ecc35d0c
|
|||
c2de0ce439
|
|||
2bcfa97e1b
|
|||
7c5a57b3d8
|
|||
99328996fa
|
|||
8249b7ed89
|
|||
db94388e69
|
|||
a5919807a3
|
|||
cc53b43c8a
|
|||
1ea0c95ebb
|
|||
9f3d8cc0c0
|
|||
8b9b32b9af
|
|||
81b5a569db
|
|||
dcdf91d5bf
|
|||
2d6277dc23
|
|||
8335a20e32
|
|||
8ac29b3815
|
|||
687d918677
|
|||
b4a4f12e9c
|
|||
38448401d0
|
|||
352742d26c
|
|||
e71786716e
|
|||
bce6f64c6e
|
|||
43bf946cce
|
|||
49f1a5f429
|
|||
3ba1848e40
|
|||
f7fc4fa801
|
|||
e116b940b2
|
|||
37f3e4eae1
|
|||
bbbde82a46
|
|||
77852d9626
|
|||
13fb362d12
|
|||
0bf8c0ccc3
|
|||
f98ffc7077
|
|||
e6760cf665
|
|||
ae430c340c
|
|||
42b8aee4bd
|
|||
24ce5d8cd6
|
|||
00382a71e2
|
|||
feaadf31bf
|
|||
a16c66c7e7
|
|||
7500476dc5
|
|||
a417efdece
|
|||
d104a43d1b
|
|||
e68e0239c3
|
|||
f148578154
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,5 +4,4 @@
|
||||
!run.sh
|
||||
!Makefile
|
||||
!.gitignore
|
||||
!CppNet
|
||||
!CppNet/**
|
||||
!README.md
|
||||
|
21
Makefile
21
Makefile
@ -2,21 +2,18 @@
|
||||
|
||||
packages:
|
||||
@echo "Installing packages..."
|
||||
sudo apt-get install g++ libmagic-dev libssl-dev php-cgi
|
||||
sudo apt-get install gcc libmagic-dev libssl-dev php-fpm
|
||||
@echo "Finished downloading!"
|
||||
|
||||
update:
|
||||
@echo "Updating imported git repos..."
|
||||
cd CppNet
|
||||
git pull
|
||||
cd ..
|
||||
@echo "Finished updating!"
|
||||
|
||||
compile:
|
||||
@echo "Compiling..."
|
||||
@mkdir -p bin
|
||||
g++ src/necronda-server.cpp -o bin/necronda-server -std=c++17 -fPIC -pthread -lz -lmagic -lssl -ldl -lcrypto
|
||||
@echo "Finished compiling!"
|
||||
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz -lmaxminddb -Wall
|
||||
|
||||
install: | packages update compile
|
||||
compile-debian:
|
||||
@mkdir -p bin
|
||||
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz -lmaxminddb -Wall \
|
||||
-D MAGIC_FILE="\"/usr/share/file/magic.mgc\"" \
|
||||
-D PHP_FPM_SOCKET="\"/var/run/php/php7.3-fpm.sock\""
|
||||
|
||||
install: | packages compile
|
||||
@echo "Finished!"
|
||||
|
9
run.sh
Normal file → Executable file
9
run.sh
Normal file → Executable file
@ -1,7 +1,6 @@
|
||||
#!/bin/bash
|
||||
echo "-- Building and starting Necronda Server..."
|
||||
make update && make compile && \
|
||||
echo -e "-- Successfully finished compiling!\n" && \
|
||||
sleep 0.0625 && \
|
||||
echo -e "-- Starting Server...\n" && \
|
||||
authbind ./bin/necronda-server
|
||||
make compile && \
|
||||
echo "-- Successfully finished compiling!" && \
|
||||
echo "-- Starting Server..." && \
|
||||
./bin/necronda-server $@
|
||||
|
196
src/URI.cpp
196
src/URI.cpp
@ -1,196 +0,0 @@
|
||||
|
||||
#include "URI.h"
|
||||
#include "necronda-server.h"
|
||||
#include <utility>
|
||||
#include <sys/stat.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
string getExtension(string path) {
|
||||
long pos = path.find_last_of('.');
|
||||
if (pos == string::npos) {
|
||||
return "";
|
||||
}
|
||||
return path.substr(pos + 1, path.length() - pos);
|
||||
}
|
||||
|
||||
string getFilename(string path) {
|
||||
long pos = path.find_last_of('/');
|
||||
if (pos == string::npos) {
|
||||
return "";
|
||||
}
|
||||
return path.substr(pos + 1, path.length() - pos);
|
||||
}
|
||||
|
||||
bool isDirectory(string path) {
|
||||
struct stat statbuf;
|
||||
return stat(path.c_str(), &statbuf) == 0 && S_ISDIR(statbuf.st_mode) != 0;
|
||||
}
|
||||
|
||||
bool isFile(string path) {
|
||||
struct stat statbuf;
|
||||
return stat(path.c_str(), &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
|
||||
}
|
||||
|
||||
bool fileExists(string path) {
|
||||
struct stat statbuf;
|
||||
return stat(path.c_str(), &statbuf) == 0;
|
||||
}
|
||||
|
||||
URI::URI() = default;
|
||||
|
||||
URI::URI(string webroot, string reqpath) {
|
||||
unsigned long pos = reqpath.find('?');
|
||||
if (pos != string::npos) {
|
||||
queryinit = true;
|
||||
query = reqpath.substr(pos + 1, reqpath.length() - pos);
|
||||
reqpath.erase(pos, reqpath.length() - pos);
|
||||
} else {
|
||||
query = "";
|
||||
queryinit = false;
|
||||
}
|
||||
if (webroot.length() >= 1 && webroot[webroot.length() - 1] == '/') {
|
||||
webroot.erase(webroot.length() - 1);
|
||||
}
|
||||
reqpath = url_decode(reqpath);
|
||||
if (reqpath.find("/../") != string::npos) {
|
||||
throw (char *) "Invalid path";
|
||||
}
|
||||
if (reqpath[0] != '/') {
|
||||
reqpath = '/' + reqpath;
|
||||
}
|
||||
this->webroot = webroot;
|
||||
this->reqpath = reqpath;
|
||||
|
||||
info = "";
|
||||
relpath = reqpath;
|
||||
|
||||
while ((!fileExists(webroot + relpath) || (isDirectory(webroot + relpath) && !fileExists(webroot + relpath + "/index.php")))
|
||||
&& (!fileExists(webroot + relpath + ".php") || (isDirectory(webroot + relpath + ".php") && !fileExists(webroot + relpath + ".php/index.php")))
|
||||
&& (!fileExists(webroot + relpath + ".html") || (isDirectory(webroot + relpath + ".html") && !fileExists(webroot + relpath + ".html/index.php")))) {
|
||||
long slash = relpath.find_last_of('/');
|
||||
if (slash == string::npos || relpath == "/") {
|
||||
break;
|
||||
}
|
||||
info = relpath.substr(slash) + info;
|
||||
relpath.erase(slash);
|
||||
}
|
||||
|
||||
if (!info.empty() && isDirectory(webroot + relpath)) {
|
||||
relpath.append("/");
|
||||
}
|
||||
|
||||
string abs = relpath;
|
||||
if (fileExists(webroot + abs)) {
|
||||
string ext = getExtension(abs);
|
||||
if (ext == "php" || ext == "html") {
|
||||
abs.erase(abs.length() - ext.length() - 1, abs.length());
|
||||
}
|
||||
}
|
||||
|
||||
string fname = getFilename(abs);
|
||||
if (fname == "index") {
|
||||
abs.erase(abs.length() - fname.length() - 1, abs.length());
|
||||
}
|
||||
|
||||
this->filepath = webroot + relpath;
|
||||
|
||||
if (isDirectory(webroot + abs)) {
|
||||
if (abs[abs.length() - 1] != '/') {
|
||||
abs += "/";
|
||||
}
|
||||
this->relpath = abs;
|
||||
abs += "index";
|
||||
if (fileExists(webroot + abs + ".php")) {
|
||||
this->filepath = webroot + abs + ".php";
|
||||
} else if (fileExists(webroot + abs + ".html")) {
|
||||
this->filepath = webroot + abs + ".html";
|
||||
}
|
||||
} else {
|
||||
if (abs[abs.length() - 1] == '/') {
|
||||
abs.erase(abs.length() - 1, abs.length() - 1);
|
||||
}
|
||||
this->relpath = abs;
|
||||
if (fileExists(webroot + abs + ".php")) {
|
||||
this->filepath = webroot + abs + ".php";
|
||||
} else if (fileExists(webroot + abs + ".html")) {
|
||||
this->filepath = webroot + abs + ".html";
|
||||
}
|
||||
}
|
||||
|
||||
if (isStatic() && !info.empty()) {
|
||||
if (relpath[relpath.length() - 1] == '/') {
|
||||
relpath.erase(relpath.length() - 1);
|
||||
}
|
||||
newpath = relpath + info;
|
||||
filepath = "";
|
||||
} else if (relpath != reqpath) {
|
||||
if (!info.empty() && relpath[relpath.length() - 1] == '/') {
|
||||
info.erase(0,1);
|
||||
}
|
||||
newpath = relpath + info;
|
||||
} else {
|
||||
newpath = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string URI::getWebRoot() {
|
||||
return webroot;
|
||||
}
|
||||
|
||||
string URI::getRelativePath() {
|
||||
return relpath;
|
||||
}
|
||||
|
||||
string URI::getAbsolutePath() {
|
||||
return webroot + relpath;
|
||||
}
|
||||
|
||||
string URI::getFilePath() {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
string URI::getRelativeFilePath() {
|
||||
string str = getFilePath();
|
||||
long len = getWebRoot().length();
|
||||
return str.substr(len, str.length() - len);
|
||||
}
|
||||
|
||||
string URI::getNewPath() {
|
||||
if (isStatic()) {
|
||||
if (hasQuery()) {
|
||||
return getRelativePath();
|
||||
}
|
||||
}
|
||||
if (!newpath.empty() && newpath != reqpath) {
|
||||
return url_encode(newpath) + (queryinit? "?" + query : "");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
FILE *URI::openFile() {
|
||||
return fopen64(getFilePath().c_str(), "rb");
|
||||
}
|
||||
|
||||
string URI::getFilePathInfo() {
|
||||
return info; //getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length());
|
||||
}
|
||||
|
||||
string URI::getFileType() {
|
||||
return getMimeType(getFilePath());
|
||||
}
|
||||
|
||||
bool URI::isStatic() {
|
||||
return getExtension(filepath) != "php";
|
||||
}
|
||||
|
||||
string URI::getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
bool URI::hasQuery() {
|
||||
return queryinit;
|
||||
}
|
||||
|
51
src/URI.h
51
src/URI.h
@ -1,51 +0,0 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#ifndef NECRONDA_PATH
|
||||
#define NECRONDA_PATH
|
||||
|
||||
using namespace std;
|
||||
|
||||
class URI {
|
||||
private:
|
||||
string webroot;
|
||||
string reqpath;
|
||||
string relpath;
|
||||
string query;
|
||||
string info;
|
||||
string filepath;
|
||||
string newpath;
|
||||
bool queryinit{};
|
||||
|
||||
public:
|
||||
URI();
|
||||
|
||||
URI(string webroot, string reqpath);
|
||||
|
||||
string getWebRoot();
|
||||
|
||||
string getRelativePath();
|
||||
|
||||
string getAbsolutePath();
|
||||
|
||||
string getFilePath();
|
||||
|
||||
string getRelativeFilePath();
|
||||
|
||||
string getNewPath();
|
||||
|
||||
FILE *openFile();
|
||||
|
||||
string getFilePathInfo();
|
||||
|
||||
string getFileType();
|
||||
|
||||
bool isStatic();
|
||||
|
||||
string getQuery();
|
||||
|
||||
bool hasQuery();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
348
src/cache.c
Normal file
348
src/cache.c
Normal file
@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* File cache implementation
|
||||
* src/cache.c
|
||||
* Lorenz Stechauner, 2020-12-19
|
||||
*/
|
||||
|
||||
#include "cache.h"
|
||||
|
||||
|
||||
int magic_init() {
|
||||
magic = magic_open(MAGIC_MIME);
|
||||
if (magic == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (magic_load(magic, MAGIC_FILE) != 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
|
||||
return -2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cache_process_term() {
|
||||
cache_continue = 0;
|
||||
}
|
||||
|
||||
int cache_process() {
|
||||
signal(SIGINT, cache_process_term);
|
||||
signal(SIGTERM, cache_process_term);
|
||||
|
||||
int shm_id = shmget(SHM_KEY_CACHE, FILE_CACHE_SIZE * sizeof(cache_entry), 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
shmdt(cache);
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
if (mkdir("/var/necronda-server/", 0755) < 0) {
|
||||
if (errno != EEXIST) {
|
||||
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda-server/': %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
|
||||
FILE *cache_file = fopen("/var/necronda-server/cache", "rb");
|
||||
if (cache_file != NULL) {
|
||||
fread(cache, sizeof(cache_entry), FILE_CACHE_SIZE, cache_file);
|
||||
fclose(cache_file);
|
||||
}
|
||||
|
||||
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
cache[i].is_updating = 0;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
char buf[16384];
|
||||
char comp_buf[16384];
|
||||
char filename_comp[256];
|
||||
unsigned long read;
|
||||
int compress;
|
||||
SHA_CTX ctx;
|
||||
unsigned char hash[SHA_DIGEST_LENGTH];
|
||||
int cache_changed = 0;
|
||||
int p_len;
|
||||
while (cache_continue) {
|
||||
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] != 0 && cache[i].meta.etag[0] == 0 && !cache[i].is_updating) {
|
||||
cache[i].is_updating = 1;
|
||||
fprintf(stdout, "[cache] Hashing file %s\n", cache[i].filename);
|
||||
SHA1_Init(&ctx);
|
||||
file = fopen(cache[i].filename, "rb");
|
||||
compress = strncmp(cache[i].meta.type, "text/", 5) == 0 ||
|
||||
(strncmp(cache[i].meta.type, "application/", 12) == 0 &&
|
||||
strstr(cache[i].meta.type, "+xml") != NULL);
|
||||
|
||||
int level = NECRONDA_ZLIB_LEVEL;
|
||||
z_stream strm;
|
||||
FILE *comp_file = NULL;
|
||||
if (compress) {
|
||||
sprintf(buf, "%.*s/.necronda-server", cache[i].webroot_len, cache[i].filename);
|
||||
mkdir(buf, 0755);
|
||||
sprintf(buf, "%.*s/.necronda-server/cache", cache[i].webroot_len, cache[i].filename);
|
||||
mkdir(buf, 0700);
|
||||
char *rel_path = cache[i].filename + cache[i].webroot_len + 1;
|
||||
for (int j = 0; j < strlen(rel_path); j++) {
|
||||
char ch = rel_path[j];
|
||||
if (ch == '/') {
|
||||
ch = '_';
|
||||
}
|
||||
buf[j] = ch;
|
||||
}
|
||||
buf[strlen(rel_path)] = 0;
|
||||
p_len = snprintf(filename_comp, sizeof(filename_comp), "%.*s/.necronda-server/cache/%s.z",
|
||||
cache[i].webroot_len, cache[i].filename, buf);
|
||||
if (p_len < 0 || p_len >= sizeof(filename_comp)) {
|
||||
fprintf(stderr, ERR_STR "Unable to open cached file: "
|
||||
"File name for compressed file too long" CLR_STR "\n");
|
||||
goto comp_err;
|
||||
}
|
||||
fprintf(stdout, "[cache] Compressing file %s\n", cache[i].filename);
|
||||
comp_file = fopen(filename_comp, "wb");
|
||||
if (comp_file == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open cached file: %s" CLR_STR "\n", strerror(errno));
|
||||
comp_err:
|
||||
compress = 0;
|
||||
} else {
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
if (deflateInit(&strm, level) != Z_OK) {
|
||||
fprintf(stderr, ERR_STR "Unable to init deflate: %s" CLR_STR "\n", strerror(errno));
|
||||
compress = 0;
|
||||
fclose(comp_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((read = fread(buf, 1, sizeof(buf), file)) > 0) {
|
||||
SHA1_Update(&ctx, buf, read);
|
||||
if (compress) {
|
||||
strm.avail_in = read;
|
||||
strm.next_in = (unsigned char *) buf;
|
||||
do {
|
||||
strm.avail_out = sizeof(comp_buf);
|
||||
strm.next_out = (unsigned char *) comp_buf;
|
||||
deflate(&strm, feof(file) ? Z_FINISH : Z_NO_FLUSH);
|
||||
fwrite(comp_buf, 1, sizeof(comp_buf) - strm.avail_out, comp_file);
|
||||
strm.avail_in = 0;
|
||||
} while (strm.avail_out == 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (compress) {
|
||||
deflateEnd(&strm);
|
||||
fclose(comp_file);
|
||||
fprintf(stdout, "[cache] Finished compressing file %s\n", cache[i].filename);
|
||||
strcpy(cache[i].meta.filename_comp, filename_comp);
|
||||
} else {
|
||||
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
|
||||
}
|
||||
SHA1_Final(hash, &ctx);
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
for (int j = 0; j < SHA_DIGEST_LENGTH; j++) {
|
||||
sprintf(cache[i].meta.etag + j * 2, "%02x", hash[j]);
|
||||
}
|
||||
fclose(file);
|
||||
fprintf(stdout, "[cache] Finished hashing file %s\n", cache[i].filename);
|
||||
cache[i].is_updating = 0;
|
||||
cache_changed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cache_changed) {
|
||||
cache_changed = 0;
|
||||
cache_file = fopen("/var/necronda-server/cache", "wb");
|
||||
fwrite(cache, sizeof(cache_entry), FILE_CACHE_SIZE, cache_file);
|
||||
fclose(cache_file);
|
||||
} else {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_init() {
|
||||
if (magic_init() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int shm_id = shmget(SHM_KEY_CACHE, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL | 0600);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
|
||||
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
|
||||
if (shm == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
cache = shm;
|
||||
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -4;
|
||||
}
|
||||
cache = shm_rw;
|
||||
memset(cache, 0, FILE_CACHE_SIZE * sizeof(cache_entry));
|
||||
shmdt(shm_rw);
|
||||
cache = shm;
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
// child
|
||||
if (cache_process() == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return -6;
|
||||
}
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
|
||||
children[0] = pid;
|
||||
} else {
|
||||
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
|
||||
return -5;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_unload() {
|
||||
int shm_id = shmget(SHM_KEY_CACHE, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(cache);
|
||||
return -1;
|
||||
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(cache);
|
||||
return -1;
|
||||
}
|
||||
shmdt(cache);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_update_entry(int entry_num, const char *filename, const char *webroot) {
|
||||
void *cache_ro = cache;
|
||||
int shm_id = shmget(SHM_KEY_CACHE, 0, 0);
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
struct stat statbuf;
|
||||
stat(filename, &statbuf);
|
||||
memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf));
|
||||
|
||||
cache[entry_num].webroot_len = (unsigned char) strlen(webroot);
|
||||
strcpy(cache[entry_num].filename, filename);
|
||||
|
||||
magic_setflags(magic, MAGIC_MIME_TYPE);
|
||||
const char *type = magic_file(magic, filename);
|
||||
char type_new[24];
|
||||
sprintf(type_new, "%s", type);
|
||||
if (strcmp(type, "text/plain") == 0) {
|
||||
if (strcmp(filename + strlen(filename) - 4, ".css") == 0) {
|
||||
sprintf(type_new, "text/css");
|
||||
} else if (strcmp(filename + strlen(filename) - 3, ".js") == 0) {
|
||||
sprintf(type_new, "text/javascript");
|
||||
}
|
||||
}
|
||||
strcpy(cache[entry_num].meta.type, type_new);
|
||||
|
||||
magic_setflags(magic, MAGIC_MIME_ENCODING);
|
||||
strcpy(cache[entry_num].meta.charset, magic_file(magic, filename));
|
||||
|
||||
memset(cache[entry_num].meta.etag, 0, sizeof(cache[entry_num].meta.etag));
|
||||
memset(cache[entry_num].meta.filename_comp, 0, sizeof(cache[entry_num].meta.filename_comp));
|
||||
cache[entry_num].is_updating = 0;
|
||||
|
||||
shmdt(shm_rw);
|
||||
cache = cache_ro;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_filename_comp_invalid(const char *filename) {
|
||||
void *cache_ro = cache;
|
||||
int shm_id = shmget(SHM_KEY_CACHE, 0, 0);
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(filename) &&
|
||||
strcmp(cache[i].filename, filename) == 0) {
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
|
||||
cache[i].is_updating = 0;
|
||||
|
||||
shmdt(shm_rw);
|
||||
cache = cache_ro;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uri_cache_init(http_uri *uri) {
|
||||
if (uri->filename == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(uri->filename) &&
|
||||
strcmp(cache[i].filename, uri->filename) == 0) {
|
||||
uri->meta = &cache[i].meta;
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uri->meta == NULL) {
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] == 0) {
|
||||
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
|
||||
return -1;
|
||||
}
|
||||
uri->meta = &cache[i].meta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
struct stat statbuf;
|
||||
stat(uri->filename, &statbuf);
|
||||
if (memcmp(&uri->meta->stat.st_mtime, &statbuf.st_mtime, sizeof(statbuf.st_mtime)) != 0) {
|
||||
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
49
src/cache.h
Normal file
49
src/cache.h
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* File cache implementation (header file)
|
||||
* src/cache.h
|
||||
* Lorenz Stechauner, 2020-12-19
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_CACHE_H
|
||||
#define NECRONDA_SERVER_CACHE_H
|
||||
|
||||
#include "uri.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <zlib.h>
|
||||
#include <magic.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
|
||||
|
||||
magic_t magic;
|
||||
|
||||
typedef struct {
|
||||
char filename[256];
|
||||
unsigned char webroot_len;
|
||||
unsigned char is_updating:1;
|
||||
meta_data meta;
|
||||
} cache_entry;
|
||||
|
||||
cache_entry *cache;
|
||||
|
||||
int cache_continue = 1;
|
||||
|
||||
int magic_init();
|
||||
|
||||
void cache_process_term();
|
||||
|
||||
int cache_process();
|
||||
|
||||
int cache_init();
|
||||
|
||||
int cache_unload();
|
||||
|
||||
int cache_update_entry(int entry_num, const char *filename, const char *webroot);
|
||||
|
||||
int cache_filename_comp_invalid(const char *filename);
|
||||
|
||||
int uri_cache_init(http_uri *uri);
|
||||
|
||||
#endif //NECRONDA_SERVER_CACHE_H
|
685
src/client.c
Normal file
685
src/client.c
Normal file
@ -0,0 +1,685 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Client connection and request handlers
|
||||
* src/client.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#include "client.h"
|
||||
|
||||
|
||||
host_config *get_host_config(const char *host) {
|
||||
for (int i = 0; i < MAX_HOST_CONFIG; i++) {
|
||||
host_config *hc = &config[i];
|
||||
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||
if (strcmp(hc->name, host) == 0) return hc;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void client_terminate() {
|
||||
server_keep_alive = 0;
|
||||
}
|
||||
|
||||
int client_websocket_handler() {
|
||||
// TODO implement client_websocket_handler
|
||||
return 0;
|
||||
}
|
||||
|
||||
int client_request_handler(sock *client, unsigned long client_num, unsigned int req_num) {
|
||||
struct timespec begin, end;
|
||||
long ret;
|
||||
int client_keep_alive;
|
||||
char buf0[1024], buf1[1024];
|
||||
char msg_buf[4096], msg_pre_buf[4096], err_msg[256];
|
||||
char buffer[CHUNK_SIZE];
|
||||
err_msg[0] = 0;
|
||||
char host[256], *host_ptr, *hdr_connection;
|
||||
host_config *conf = NULL;
|
||||
long content_length = 0;
|
||||
FILE *file = NULL;
|
||||
msg_buf[0] = 0;
|
||||
int accept_if_modified_since = 0;
|
||||
int use_fastcgi = 0;
|
||||
int use_rev_proxy = 0;
|
||||
int p_len;
|
||||
fastcgi_conn php_fpm = {.socket = 0, .req_id = 0};
|
||||
http_status custom_status;
|
||||
|
||||
http_res res;
|
||||
sprintf(res.version, "1.1");
|
||||
res.status = http_get_status(501);
|
||||
res.hdr.field_num = 0;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
||||
|
||||
fd_set socket_fds;
|
||||
FD_ZERO(&socket_fds);
|
||||
FD_SET(client->socket, &socket_fds);
|
||||
client_timeout.tv_sec = CLIENT_TIMEOUT;
|
||||
client_timeout.tv_usec = 0;
|
||||
ret = select(client->socket + 1, &socket_fds, NULL, NULL, &client_timeout);
|
||||
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
||||
http_add_header_field(&res.hdr, "Server", SERVER_STR);
|
||||
if (ret <= 0) {
|
||||
if (errno != 0) {
|
||||
return 1;
|
||||
}
|
||||
client_keep_alive = 0;
|
||||
res.status = http_get_status(408);
|
||||
goto respond;
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
||||
|
||||
http_req req;
|
||||
ret = http_receive_request(client, &req);
|
||||
if (ret != 0) {
|
||||
client_keep_alive = 0;
|
||||
if (ret < 0) {
|
||||
goto abort;
|
||||
} else if (ret == 1) {
|
||||
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
||||
} else if (ret == 2) {
|
||||
sprintf(err_msg, "Unable to parse header: Invalid method.");
|
||||
} else if (ret == 3) {
|
||||
sprintf(err_msg, "Unable to parse header: Invalid version.");
|
||||
} else if (ret == 4) {
|
||||
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
|
||||
} else if (ret == 5) {
|
||||
sprintf(err_msg, "Unable to parse header: End of header not found.");
|
||||
}
|
||||
res.status = http_get_status(400);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
hdr_connection = http_get_header_field(&req.hdr, "Connection");
|
||||
client_keep_alive = hdr_connection != NULL &&
|
||||
(strcmp(hdr_connection, "keep-alive") == 0 || strcmp(hdr_connection, "Keep-Alive") == 0);
|
||||
host_ptr = http_get_header_field(&req.hdr, "Host");
|
||||
if (host_ptr != NULL && strlen(host_ptr) > 255) {
|
||||
host[0] = 0;
|
||||
res.status = http_get_status(400);
|
||||
sprintf(err_msg, "Host header field is too long.");
|
||||
goto respond;
|
||||
} else if (host_ptr == NULL || strchr(host_ptr, '/') != NULL) {
|
||||
if (strchr(client_addr_str, ':') == NULL) {
|
||||
strcpy(host, client_addr_str);
|
||||
} else {
|
||||
sprintf(host, "[%s]", client_addr_str);
|
||||
}
|
||||
res.status = http_get_status(400);
|
||||
sprintf(err_msg, "The client provided no or an invalid Host header field.");
|
||||
goto respond;
|
||||
} else {
|
||||
strcpy(host, host_ptr);
|
||||
}
|
||||
|
||||
sprintf(log_req_prefix, "[%s%24s%s]%s ", BLD_STR, host, CLR_STR, log_client_prefix);
|
||||
log_prefix = log_req_prefix;
|
||||
print(BLD_STR "%s %s" CLR_STR, req.method, req.uri);
|
||||
|
||||
conf = get_host_config(host);
|
||||
if (conf == NULL) {
|
||||
print("Host unknown, redirecting to default");
|
||||
res.status = http_get_status(307);
|
||||
sprintf(buf0, "https://%s%s", DEFAULT_HOST, req.uri);
|
||||
http_add_header_field(&res.hdr, "Location", buf0);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
http_uri uri;
|
||||
unsigned char dir_mode = conf->type == CONFIG_TYPE_LOCAL ? conf->local.dir_mode : URI_DIR_MODE_NO_VALIDATION;
|
||||
ret = uri_init(&uri, conf->local.webroot, req.uri, dir_mode);
|
||||
if (ret != 0) {
|
||||
if (ret == 1) {
|
||||
sprintf(err_msg, "Invalid URI: has to start with slash.");
|
||||
res.status = http_get_status(400);
|
||||
} else if (ret == 2) {
|
||||
sprintf(err_msg, "Invalid URI: contains relative path change (/../).");
|
||||
res.status = http_get_status(400);
|
||||
} else if (ret == 3) {
|
||||
sprintf(err_msg, "The specified webroot directory does not exist.");
|
||||
res.status = http_get_status(404);
|
||||
} else {
|
||||
res.status = http_get_status(500);
|
||||
}
|
||||
goto respond;
|
||||
}
|
||||
|
||||
if (dir_mode != URI_DIR_MODE_NO_VALIDATION) {
|
||||
ssize_t size = sizeof(buf0);
|
||||
url_decode(req.uri, buf0, &size);
|
||||
int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc;
|
||||
if (strcmp(uri.uri, buf0) != 0 || change_proto) {
|
||||
res.status = http_get_status(308);
|
||||
size = sizeof(buf0);
|
||||
url_encode(uri.uri, buf0, &size);
|
||||
if (change_proto) {
|
||||
p_len = snprintf(buf1, sizeof(buf1), "https://%s%s", host, buf0);
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||
res.status = http_get_status(500);
|
||||
print(ERR_STR "Header field 'Location' too long" CLR_STR);
|
||||
goto respond;
|
||||
}
|
||||
http_add_header_field(&res.hdr, "Location", buf1);
|
||||
} else {
|
||||
http_add_header_field(&res.hdr, "Location", buf0);
|
||||
}
|
||||
goto respond;
|
||||
}
|
||||
} else if (!client->enc) {
|
||||
res.status = http_get_status(308);
|
||||
sprintf(buf0, "https://%s%s", host, req.uri);
|
||||
http_add_header_field(&res.hdr, "Location", buf0);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
if (conf->type == CONFIG_TYPE_LOCAL) {
|
||||
if (strncmp(uri.req_path, "/.well-known/", 13) != 0 && strstr(uri.path, "/.") != NULL) {
|
||||
res.status = http_get_status(403);
|
||||
sprintf(err_msg, "Parts of this URI are hidden.");
|
||||
goto respond;
|
||||
} else if (uri.filename == NULL && (int) uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
|
||||
res.status = http_get_status(403);
|
||||
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
|
||||
goto respond;
|
||||
} else if (uri.filename == NULL && (int) !uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
|
||||
// TODO list directory contents
|
||||
res.status = http_get_status(501);
|
||||
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
|
||||
goto respond;
|
||||
} else if (uri.filename == NULL || (strlen(uri.pathinfo) > 0 && (int) uri.is_static)) {
|
||||
res.status = http_get_status(404);
|
||||
goto respond;
|
||||
} else if (strlen(uri.pathinfo) != 0 && conf->local.dir_mode != URI_DIR_MODE_INFO) {
|
||||
res.status = http_get_status(404);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
if (uri.is_static) {
|
||||
res.status = http_get_status(200);
|
||||
http_add_header_field(&res.hdr, "Allow", "GET, HEAD");
|
||||
http_add_header_field(&res.hdr, "Accept-Ranges", "bytes");
|
||||
if (strcmp(req.method, "GET") != 0 && strcmp(req.method, "HEAD") != 0) {
|
||||
res.status = http_get_status(405);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
ret = uri_cache_init(&uri);
|
||||
if (ret != 0) {
|
||||
res.status = http_get_status(500);
|
||||
sprintf(err_msg, "Unable to communicate with internal file cache.");
|
||||
goto respond;
|
||||
}
|
||||
char *last_modified = http_format_date(uri.meta->stat.st_mtime, buf0, sizeof(buf0));
|
||||
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
|
||||
sprintf(buf1, "%s; charset=%s", uri.meta->type, uri.meta->charset);
|
||||
http_add_header_field(&res.hdr, "Content-Type", buf1);
|
||||
if (uri.meta->etag[0] != 0) {
|
||||
http_add_header_field(&res.hdr, "ETag", uri.meta->etag);
|
||||
}
|
||||
if (strncmp(uri.meta->type, "text/", 5) == 0) {
|
||||
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=3600");
|
||||
} else {
|
||||
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=86400");
|
||||
}
|
||||
|
||||
char *if_modified_since = http_get_header_field(&req.hdr, "If-Modified-Since");
|
||||
char *if_none_match = http_get_header_field(&req.hdr, "If-None-Match");
|
||||
if ((if_none_match != NULL && strstr(if_none_match, uri.meta->etag) == NULL) ||
|
||||
(accept_if_modified_since && if_modified_since != NULL &&
|
||||
strcmp(if_modified_since, last_modified) == 0)) {
|
||||
res.status = http_get_status(304);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
char *range = http_get_header_field(&req.hdr, "Range");
|
||||
if (range != NULL) {
|
||||
if (strlen(range) <= 6 || strncmp(range, "bytes=", 6) != 0) {
|
||||
res.status = http_get_status(416);
|
||||
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "Last-Modified", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "ETag", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
|
||||
goto respond;
|
||||
}
|
||||
range += 6;
|
||||
char *ptr = strchr(range, '-');
|
||||
if (ptr == NULL) {
|
||||
res.status = http_get_status(416);
|
||||
goto respond;
|
||||
}
|
||||
file = fopen(uri.filename, "rb");
|
||||
fseek(file, 0, SEEK_END);
|
||||
unsigned long file_len = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
if (file_len == 0) {
|
||||
content_length = 0;
|
||||
goto respond;
|
||||
}
|
||||
long num1 = 0;
|
||||
long num2 = (long) file_len - 1;
|
||||
|
||||
if (ptr != range) num1 = (long) strtoul(range, NULL, 10);
|
||||
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
|
||||
|
||||
if (num1 >= file_len || num2 >= file_len || num1 > num2) {
|
||||
res.status = http_get_status(416);
|
||||
goto respond;
|
||||
}
|
||||
sprintf(buf0, "bytes %li-%li/%li", num1, num2, file_len);
|
||||
http_add_header_field(&res.hdr, "Content-Range", buf0);
|
||||
|
||||
res.status = http_get_status(206);
|
||||
fseek(file, num1, SEEK_SET);
|
||||
content_length = num2 - num1 + 1;
|
||||
|
||||
goto respond;
|
||||
}
|
||||
|
||||
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
|
||||
if (uri.meta->filename_comp[0] != 0 && accept_encoding != NULL &&
|
||||
strstr(accept_encoding, "deflate") != NULL) {
|
||||
file = fopen(uri.meta->filename_comp, "rb");
|
||||
if (file == NULL) {
|
||||
cache_filename_comp_invalid(uri.filename);
|
||||
goto not_compressed;
|
||||
}
|
||||
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
|
||||
} else {
|
||||
not_compressed:
|
||||
file = fopen(uri.filename, "rb");
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
content_length = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
} else {
|
||||
struct stat statbuf;
|
||||
stat(uri.filename, &statbuf);
|
||||
char *last_modified = http_format_date(statbuf.st_mtime, buf0, sizeof(buf0));
|
||||
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
|
||||
|
||||
res.status = http_get_status(200);
|
||||
if (fastcgi_init(&php_fpm, client_num, req_num, client, &req, &uri) != 0) {
|
||||
res.status = http_get_status(502);
|
||||
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
|
||||
goto respond;
|
||||
}
|
||||
|
||||
if (strcmp(req.method, "POST") == 0 || strcmp(req.method, "PUT") == 0) {
|
||||
char *client_content_length = http_get_header_field(&req.hdr, "Content-Length");
|
||||
unsigned long client_content_len;
|
||||
if (client_content_length == NULL) {
|
||||
goto fastcgi_end;
|
||||
}
|
||||
client_content_len = strtoul(client_content_length, NULL, 10);
|
||||
ret = fastcgi_receive(&php_fpm, client, client_content_len);
|
||||
if (ret != 0) {
|
||||
if (ret < 0) {
|
||||
goto abort;
|
||||
} else {
|
||||
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
|
||||
}
|
||||
res.status = http_get_status(502);
|
||||
goto respond;
|
||||
}
|
||||
}
|
||||
fastcgi_end:
|
||||
fastcgi_close_stdin(&php_fpm);
|
||||
|
||||
ret = fastcgi_header(&php_fpm, &res, err_msg);
|
||||
if (ret != 0) {
|
||||
if (ret < 0) {
|
||||
goto abort;
|
||||
}
|
||||
goto respond;
|
||||
}
|
||||
char *status = http_get_header_field(&res.hdr, "Status");
|
||||
if (status != NULL) {
|
||||
int status_code = (int) strtoul(status, NULL, 10);
|
||||
res.status = http_get_status(status_code);
|
||||
http_remove_header_field(&res.hdr, "Status", HTTP_REMOVE_ALL);
|
||||
if (res.status == NULL && status_code >= 100 && status_code <= 999) {
|
||||
custom_status.code = status_code;
|
||||
strcpy(custom_status.type, "");
|
||||
strcpy(custom_status.msg, status + 4);
|
||||
res.status = &custom_status;
|
||||
} else if (res.status == NULL) {
|
||||
res.status = http_get_status(500);
|
||||
sprintf(err_msg, "The status code was set to an invalid or unknown value.");
|
||||
goto respond;
|
||||
}
|
||||
}
|
||||
|
||||
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
|
||||
if (accept_encoding != NULL && strstr(accept_encoding, "deflate") != NULL) {
|
||||
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
|
||||
}
|
||||
|
||||
content_length = -1;
|
||||
use_fastcgi = 1;
|
||||
if (http_get_header_field(&res.hdr, "Content-Length") == NULL) {
|
||||
http_add_header_field(&res.hdr, "Transfer-Encoding", "chunked");
|
||||
}
|
||||
}
|
||||
} else if (conf->type == CONFIG_TYPE_REVERSE_PROXY) {
|
||||
print("Reverse proxy for " BLD_STR "%s:%i" CLR_STR, conf->rev_proxy.hostname, conf->rev_proxy.port);
|
||||
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
|
||||
ret = rev_proxy_init(&req, &res, conf, client, &custom_status, err_msg);
|
||||
use_rev_proxy = ret == 0;
|
||||
} else {
|
||||
print(ERR_STR "Unknown host type: %i" CLR_STR, conf->type);
|
||||
res.status = http_get_status(501);
|
||||
}
|
||||
|
||||
respond:
|
||||
if (!use_rev_proxy) {
|
||||
if (http_get_header_field(&res.hdr, "Accept-Ranges") == NULL) {
|
||||
http_add_header_field(&res.hdr, "Accept-Ranges", "none");
|
||||
}
|
||||
if (!use_fastcgi && !use_rev_proxy && file == NULL &&
|
||||
((res.status->code >= 400 && res.status->code < 600) || err_msg[0] != 0)) {
|
||||
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
||||
http_add_header_field(&res.hdr, "Server", SERVER_STR);
|
||||
|
||||
// TODO list Locations on 3xx Redirects
|
||||
const http_doc_info *info = http_get_status_info(res.status);
|
||||
const http_status_msg *http_msg = http_get_error_msg(res.status);
|
||||
|
||||
sprintf(msg_pre_buf, info->doc, res.status->code, res.status->msg,
|
||||
http_msg != NULL ? http_msg->msg : "", err_msg[0] != 0 ? err_msg : "");
|
||||
content_length = snprintf(msg_buf, sizeof(msg_buf), http_default_document, res.status->code,
|
||||
res.status->msg, msg_pre_buf, info->mode, info->icon, info->color, host);
|
||||
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
|
||||
}
|
||||
if (content_length >= 0) {
|
||||
sprintf(buf0, "%li", content_length);
|
||||
http_add_header_field(&res.hdr, "Content-Length", buf0);
|
||||
} else if (http_get_header_field(&res.hdr, "Transfer-Encoding") == NULL) {
|
||||
server_keep_alive = 0;
|
||||
}
|
||||
}
|
||||
|
||||
char *conn = http_get_header_field(&res.hdr, "Connection");
|
||||
int close_proxy = conn == NULL || (strcmp(conn, "keep-alive") != 0 && strcmp(conn, "Keep-Alive") != 0);
|
||||
http_remove_header_field(&res.hdr, "Connection", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "Keep-Alive", HTTP_REMOVE_ALL);
|
||||
if (server_keep_alive && client_keep_alive) {
|
||||
http_add_header_field(&res.hdr, "Connection", "keep-alive");
|
||||
sprintf(buf0, "timeout=%i, max=%i", CLIENT_TIMEOUT, REQ_PER_CONNECTION);
|
||||
http_add_header_field(&res.hdr, "Keep-Alive", buf0);
|
||||
} else {
|
||||
http_add_header_field(&res.hdr, "Connection", "close");
|
||||
}
|
||||
|
||||
http_send_response(client, &res);
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
char *location = http_get_header_field(&res.hdr, "Location");
|
||||
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
|
||||
print("%s%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), use_rev_proxy ? "-> " : "", res.status->code,
|
||||
res.status->msg, location != NULL ? " -> " : "", location != NULL ? location : "",
|
||||
format_duration(micros, buf0), CLR_STR);
|
||||
|
||||
// TODO access/error log file
|
||||
|
||||
if (strcmp(req.method, "HEAD") != 0) {
|
||||
unsigned long snd_len = 0;
|
||||
unsigned long len;
|
||||
if (msg_buf[0] != 0) {
|
||||
ret = sock_send(client, msg_buf, content_length, 0);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
||||
}
|
||||
snd_len += ret;
|
||||
} else if (file != NULL) {
|
||||
while (snd_len < content_length) {
|
||||
len = fread(buffer, 1, CHUNK_SIZE, file);
|
||||
if (snd_len + len > content_length) {
|
||||
len = content_length - snd_len;
|
||||
}
|
||||
ret = sock_send(client, buffer, len, feof(file) ? 0 : MSG_MORE);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
||||
break;
|
||||
}
|
||||
snd_len += ret;
|
||||
}
|
||||
} else if (use_fastcgi) {
|
||||
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
|
||||
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
|
||||
char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
|
||||
int comp = content_encoding != NULL && strcmp(content_encoding, "deflate") == 0;
|
||||
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (comp ? FASTCGI_COMPRESS : 0);
|
||||
fastcgi_send(&php_fpm, client, flags);
|
||||
} else if (use_rev_proxy) {
|
||||
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
|
||||
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
|
||||
char *content_len = http_get_header_field(&res.hdr, "Content-Length");
|
||||
unsigned long len_to_send = 0;
|
||||
if (content_len != NULL) {
|
||||
len_to_send = strtol(content_len, NULL, 10);
|
||||
}
|
||||
rev_proxy_send(client, chunked, len_to_send);
|
||||
}
|
||||
}
|
||||
|
||||
if (close_proxy && rev_proxy.socket != 0) {
|
||||
print(BLUE_STR "Closing proxy connection" CLR_STR);
|
||||
sock_close(&rev_proxy);
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
|
||||
print("Transfer complete: %s", format_duration(micros, buf0));
|
||||
|
||||
uri_free(&uri);
|
||||
abort:
|
||||
if (php_fpm.socket != 0) {
|
||||
shutdown(php_fpm.socket, SHUT_RDWR);
|
||||
close(php_fpm.socket);
|
||||
php_fpm.socket = 0;
|
||||
}
|
||||
http_free_req(&req);
|
||||
http_free_res(&res);
|
||||
if (client->buf != NULL) {
|
||||
free(client->buf);
|
||||
client->buf = NULL;
|
||||
client->buf_off = 0;
|
||||
client->buf_len = 0;
|
||||
}
|
||||
return !client_keep_alive;
|
||||
}
|
||||
|
||||
int client_connection_handler(sock *client, unsigned long client_num) {
|
||||
struct timespec begin, end;
|
||||
int ret, req_num;
|
||||
char buf[1024];
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
||||
|
||||
if (dns_server[0] != 0) {
|
||||
sprintf(buf, "dig @%s +short +time=1 -x %s", dns_server, client_addr_str);
|
||||
FILE *dig = popen(buf, "r");
|
||||
if (dig == NULL) {
|
||||
print(ERR_STR "Unable to start dig: %s" CLR_STR "\n", strerror(errno));
|
||||
goto dig_err;
|
||||
}
|
||||
unsigned long read = fread(buf, 1, sizeof(buf), dig);
|
||||
ret = pclose(dig);
|
||||
if (ret != 0) {
|
||||
print(ERR_STR "Dig terminated with exit code %i" CLR_STR "\n", ret);
|
||||
goto dig_err;
|
||||
}
|
||||
char *ptr = memchr(buf, '\n', read);
|
||||
if (ptr == buf || ptr == NULL) {
|
||||
goto dig_err;
|
||||
}
|
||||
ptr[-1] = 0;
|
||||
client_host_str = malloc(strlen(buf) + 1);
|
||||
strcpy(client_host_str, buf);
|
||||
} else {
|
||||
dig_err:
|
||||
client_host_str = NULL;
|
||||
}
|
||||
|
||||
client_geoip = malloc(GEOIP_MAX_SIZE);
|
||||
long str_off = 0;
|
||||
for (int i = 0; i < MAX_MMDB && mmdbs[i].filename != NULL; i++) {
|
||||
int gai_error, mmdb_res;
|
||||
MMDB_lookup_result_s result = MMDB_lookup_string(&mmdbs[i], client_addr_str, &gai_error, &mmdb_res);
|
||||
if (mmdb_res != MMDB_SUCCESS) {
|
||||
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
|
||||
continue;
|
||||
} else if (gai_error != 0) {
|
||||
print(ERR_STR "Unable to lookup geoip info" CLR_STR "\n");
|
||||
continue;
|
||||
} else if (!result.found_entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MMDB_entry_data_list_s *list;
|
||||
mmdb_res = MMDB_get_entry_data_list(&result.entry, &list);
|
||||
if (mmdb_res != MMDB_SUCCESS) {
|
||||
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
|
||||
continue;
|
||||
}
|
||||
|
||||
long prev = str_off;
|
||||
if (str_off != 0) {
|
||||
str_off--;
|
||||
}
|
||||
mmdb_json(list, client_geoip, &str_off, GEOIP_MAX_SIZE);
|
||||
if (prev != 0) {
|
||||
client_geoip[prev - 1] = ',';
|
||||
}
|
||||
|
||||
MMDB_free_entry_data_list(list);
|
||||
}
|
||||
|
||||
char client_cc[3];
|
||||
client_cc[0] = 0;
|
||||
if (str_off == 0) {
|
||||
free(client_geoip);
|
||||
client_geoip = NULL;
|
||||
} else {
|
||||
char *pos = client_geoip;
|
||||
pos = strstr(pos, "\"country\":");
|
||||
if (pos != NULL) {
|
||||
pos = strstr(pos, "\"iso_code\":");
|
||||
pos += 12;
|
||||
strncpy(client_cc, pos, 2);
|
||||
}
|
||||
}
|
||||
|
||||
print("Connection accepted from %s %s%s%s[%s]", client_addr_str, client_host_str != NULL ? "(" : "",
|
||||
client_host_str != NULL ? client_host_str : "", client_host_str != NULL ? ") " : "",
|
||||
client_cc[0] != 0 ? client_cc : "N/A");
|
||||
|
||||
client_timeout.tv_sec = CLIENT_TIMEOUT;
|
||||
client_timeout.tv_usec = 0;
|
||||
if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout)) < 0)
|
||||
goto set_timeout_err;
|
||||
if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &client_timeout, sizeof(client_timeout)) < 0) {
|
||||
set_timeout_err:
|
||||
print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client->enc) {
|
||||
client->ssl = SSL_new(client->ctx);
|
||||
SSL_set_fd(client->ssl, client->socket);
|
||||
SSL_set_accept_state(client->ssl);
|
||||
|
||||
ret = SSL_accept(client->ssl);
|
||||
client->_last_ret = ret;
|
||||
client->_errno = errno;
|
||||
client->_ssl_error = ERR_get_error();
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(client));
|
||||
goto close;
|
||||
}
|
||||
}
|
||||
|
||||
req_num = 0;
|
||||
ret = 0;
|
||||
while (ret == 0 && server_keep_alive && req_num < REQ_PER_CONNECTION) {
|
||||
ret = client_request_handler(client, client_num, req_num++);
|
||||
log_prefix = log_conn_prefix;
|
||||
}
|
||||
|
||||
close:
|
||||
sock_close(client);
|
||||
|
||||
if (rev_proxy.socket != 0) {
|
||||
print(BLUE_STR "Closing proxy connection" CLR_STR);
|
||||
sock_close(&rev_proxy);
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
|
||||
|
||||
print("Connection closed (%s)", format_duration(micros, buf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *client_addr) {
|
||||
int ret;
|
||||
struct sockaddr_in6 *server_addr;
|
||||
struct sockaddr_storage server_addr_storage;
|
||||
|
||||
char *color_table[] = {"\x1B[31m", "\x1B[32m", "\x1B[33m", "\x1B[34m", "\x1B[35m", "\x1B[36m"};
|
||||
|
||||
signal(SIGINT, client_terminate);
|
||||
signal(SIGTERM, client_terminate);
|
||||
|
||||
client_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
|
||||
inet_ntop(client_addr->sin6_family, (void *) &client_addr->sin6_addr, client_addr_str_ptr, INET6_ADDRSTRLEN);
|
||||
if (strncmp(client_addr_str_ptr, "::ffff:", 7) == 0) {
|
||||
client_addr_str = client_addr_str_ptr + 7;
|
||||
} else {
|
||||
client_addr_str = client_addr_str_ptr;
|
||||
}
|
||||
|
||||
socklen_t len = sizeof(server_addr_storage);
|
||||
getsockname(client->socket, (struct sockaddr *) &server_addr_storage, &len);
|
||||
server_addr = (struct sockaddr_in6 *) &server_addr_storage;
|
||||
server_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
|
||||
inet_ntop(server_addr->sin6_family, (void *) &server_addr->sin6_addr, server_addr_str_ptr, INET6_ADDRSTRLEN);
|
||||
if (strncmp(server_addr_str_ptr, "::ffff:", 7) == 0) {
|
||||
server_addr_str = server_addr_str_ptr + 7;
|
||||
} else {
|
||||
server_addr_str = server_addr_str_ptr;
|
||||
}
|
||||
|
||||
log_req_prefix = malloc(256);
|
||||
log_client_prefix = malloc(256);
|
||||
sprintf(log_client_prefix, "[%s%4i%s]%s[%*s][%5i]%s", (int) client->enc ? HTTPS_STR : HTTP_STR,
|
||||
ntohs(server_addr->sin6_port), CLR_STR, color_table[client_num % 6], INET_ADDRSTRLEN, client_addr_str,
|
||||
ntohs(client_addr->sin6_port), CLR_STR);
|
||||
|
||||
log_conn_prefix = malloc(256);
|
||||
sprintf(log_conn_prefix, "[%24s]%s ", server_addr_str, log_client_prefix);
|
||||
log_prefix = log_conn_prefix;
|
||||
|
||||
print("Started child process with PID %i", getpid());
|
||||
|
||||
ret = client_connection_handler(client, client_num);
|
||||
free(client_addr_str_ptr);
|
||||
client_addr_str_ptr = NULL;
|
||||
free(server_addr_str_ptr);
|
||||
server_addr_str_ptr = NULL;
|
||||
if (client_host_str != NULL) {
|
||||
free(client_host_str);
|
||||
client_host_str = NULL;
|
||||
}
|
||||
free(log_conn_prefix);
|
||||
log_conn_prefix = NULL;
|
||||
free(log_req_prefix);
|
||||
log_req_prefix = NULL;
|
||||
free(log_client_prefix);
|
||||
log_client_prefix = NULL;
|
||||
return ret;
|
||||
}
|
673
src/client.cpp
673
src/client.cpp
@ -1,673 +0,0 @@
|
||||
#include <utility>
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* client.cpp - Client and Connection handler
|
||||
* Lorenz Stechauner, 2018-05-16
|
||||
*/
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <zlib.h>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <openssl/md5.h>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include "network/Socket.h"
|
||||
#include "network/http/HttpRequest.h"
|
||||
#include "network/http/HttpConnection.h"
|
||||
#include "necronda-server.h"
|
||||
#include "network/http/HttpStatusCode.h"
|
||||
#include "URI.h"
|
||||
#include "procopen.h"
|
||||
#include "network/Address.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
string host;
|
||||
string cc;
|
||||
string country;
|
||||
string prov;
|
||||
string provname;
|
||||
string city;
|
||||
string timezone;
|
||||
string localdate;
|
||||
} IpAddressInfo;
|
||||
|
||||
|
||||
void log_to_file(const char *prefix, const string &str, string host) {
|
||||
//FILE *file = fopen((getWebRoot(std::move(host)) + ".access.log").c_str(), "a");
|
||||
//fprintf(file, "%s%s\r\n", prefix, str.c_str());
|
||||
//fflush(file);
|
||||
//fclose(file);
|
||||
}
|
||||
|
||||
void log_error_to_file(const char *prefix, const string &str, string host) {
|
||||
log_to_file(prefix, "\x1B[1;31m" + str + "\x1B[0m", std::move(host));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes log messages to the console
|
||||
* @param prefix The connection prefix
|
||||
* @param str The string to be written
|
||||
*/
|
||||
void log(const char *prefix, const string &str) {
|
||||
printf("%s%s\r\n", prefix, str.c_str());
|
||||
flush(cout);
|
||||
}
|
||||
|
||||
void log_error(const char *prefix, const string &str) {
|
||||
log(prefix, "\x1B[1;31m" + str + "\x1B[0m");
|
||||
}
|
||||
|
||||
void php_error_handler(const char *prefix, FILE *stderr) {
|
||||
string line;
|
||||
while (!(line = read_line(stderr)).empty()) {
|
||||
log_error(prefix, line);
|
||||
}
|
||||
fclose(stderr);
|
||||
}
|
||||
|
||||
IpAddressInfo get_ip_address_info(Address* addr) {
|
||||
FILE *name = popen(("/opt/ipinfo/ipinfo.py " + addr->toString()).c_str(), "r");
|
||||
char hostbuffer[1024];
|
||||
memset(hostbuffer, 0, 1024);
|
||||
size_t size = fread(hostbuffer, 1, 1024, name);
|
||||
istringstream buffer(hostbuffer);
|
||||
string line;
|
||||
|
||||
IpAddressInfo info;
|
||||
int num = 0;
|
||||
while (std::getline(buffer, line)) {
|
||||
switch (num) {
|
||||
case 0: info.host = line; break;
|
||||
case 1: info.cc = line; break;
|
||||
case 2: info.country = line; break;
|
||||
case 3: info.prov = line; break;
|
||||
case 4: info.provname = line; break;
|
||||
case 5: info.city = line; break;
|
||||
case 6: info.timezone = line; break;
|
||||
case 7: info.localdate = line; break;
|
||||
}
|
||||
num++;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
string get_os_info(int fd) {
|
||||
struct tcp_info ti;
|
||||
socklen_t tisize = sizeof(ti);
|
||||
getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &tisize);
|
||||
|
||||
int ttl;
|
||||
socklen_t ttlsize = sizeof(ttl);
|
||||
getsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, &ttlsize);
|
||||
|
||||
return "win_size=" + to_string(ti.tcpi_rcv_space) + ", ttl=" + to_string(ttl);
|
||||
}
|
||||
|
||||
string getETag(string filename) {
|
||||
ifstream etags = ifstream("/var/necronda/ETags");
|
||||
|
||||
ifstream a = ifstream();
|
||||
string line;
|
||||
int index = 0;
|
||||
int i = 0;
|
||||
string timestamp = getTimestamp(filename);
|
||||
long size = getFileSize(filename);
|
||||
while (getline(etags, line)) {
|
||||
i++;
|
||||
if (line == filename) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
long p1 = line.find(':');
|
||||
if (p1 == string::npos) continue;
|
||||
long p2 = line.find(':', (unsigned) p1 + 1);
|
||||
if (p2 == string::npos) continue;
|
||||
long p3 = line.find(':', (unsigned) p2 + 1);
|
||||
if (p3 == string::npos) continue;
|
||||
string FILENAME = line.substr(0, (unsigned) p1);
|
||||
string HASH = line.substr((unsigned) p1 + 1, (unsigned) (p2 - p1));
|
||||
string TIMESTAMP = line.substr((unsigned) p2 + 1, (unsigned) (p3 - p2));
|
||||
long SIZE = strtol(line.substr((unsigned) p3 + 1, line.length() - p3).c_str(), nullptr, 10);
|
||||
if (FILENAME == filename) {
|
||||
index = i;
|
||||
if (timestamp != TIMESTAMP || size != SIZE) {
|
||||
break;
|
||||
} else {
|
||||
etags.close();
|
||||
return HASH;
|
||||
}
|
||||
}
|
||||
}
|
||||
etags.close();
|
||||
|
||||
MD5_CTX mdContext;
|
||||
MD5_Init(&mdContext);
|
||||
size_t bytes;
|
||||
char buffer[4096];
|
||||
FILE *file = fopen(filename.c_str(), "rb");
|
||||
if (file == nullptr) {
|
||||
throw (char *) "Invalid file";
|
||||
}
|
||||
while ((bytes = fread(buffer, 1, 4096, file)) != 0) {
|
||||
MD5_Update(&mdContext, buffer, bytes);
|
||||
}
|
||||
fclose(file);
|
||||
unsigned char md[16];
|
||||
MD5_Final(md, &mdContext);
|
||||
char md5buff[32];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
sprintf(md5buff + i * 2, "%02x", md[i]);
|
||||
}
|
||||
string md5 = string(md5buff);
|
||||
|
||||
if (index == 0) {
|
||||
char buff[256];
|
||||
sprintf(buff, "%s:%s:%s:%ld\n", filename.c_str(), md5.c_str(), timestamp.c_str(), size);
|
||||
FILE *f = fopen("/var/necronda/ETags", "a");
|
||||
if (f == nullptr) {
|
||||
throw (char *) strerror(errno);
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
fwrite(buff, 1, strlen(buff), f);
|
||||
fflush(f);
|
||||
fclose(f);
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
return md5;
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
#include <wait.h>
|
||||
#include <thread>
|
||||
|
||||
|
||||
long getPosition(std::string str, char c, int occurence) {
|
||||
int tempOccur = 0;
|
||||
int num = 0;
|
||||
for (auto it : str) {
|
||||
num++;
|
||||
if (it == c) {
|
||||
if (++tempOccur == occurence) {
|
||||
return num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int websocket_handler(Socket *socket, stds *pipes) {
|
||||
fd_set readfd;
|
||||
int maxfd = (socket->getFd() > pipes->stdout->_fileno) ? socket->getFd() : pipes->stdout->_fileno;
|
||||
FD_ZERO(&readfd);
|
||||
FD_SET(socket->getFd(), &readfd);
|
||||
FD_SET(pipes->stdout->_fileno, &readfd);
|
||||
|
||||
/*while (true) {
|
||||
int ret = ::select(maxfd + 1, &readfd, nullptr, nullptr, nullptr);
|
||||
if (ret < 0) {
|
||||
throw (char *) strerror(errno);
|
||||
}
|
||||
|
||||
int c = fgetc(pipes->stdout);
|
||||
if (c == -1) {
|
||||
long rec = socket->receive(pipes->stdin);
|
||||
} else {
|
||||
ungetc(c, pipes->stdout);
|
||||
socket->send(pipes->stdout);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles (keep-alive) HTTP connections
|
||||
* @param prefix The connection prefix
|
||||
* @param socket The socket
|
||||
* @param id The client ID
|
||||
* @param num The Connection Number in the client
|
||||
* @return Should the server wait for another header?
|
||||
*/
|
||||
bool connection_handler(const char *preprefix, const char *col1, const char *col2, Socket *socket, long id, long num, IpAddressInfo *info) {
|
||||
bool error = false;
|
||||
char buffer[1024];
|
||||
char *prefix = (char *) preprefix;
|
||||
|
||||
HttpConnection req;
|
||||
try {
|
||||
req = HttpConnection(socket);
|
||||
} catch (char *msg) {
|
||||
try {
|
||||
if (msg == "Malformed header") {
|
||||
log(prefix, "Unable to parse header: Malformed header");
|
||||
socket->send("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
||||
return false;
|
||||
} else if (msg == "timeout") {
|
||||
log(prefix, "Timeout!");
|
||||
socket->send("HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n");
|
||||
return false;
|
||||
} else {
|
||||
log(prefix, (string) "Unable to receive from socket: " + msg);
|
||||
return false;
|
||||
}
|
||||
} catch (char *msg2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
bool noRedirect, redir, invalidMethod, etag, compress, wantsWebsocket, websocket = false;
|
||||
URI path;
|
||||
pid_t childpid;
|
||||
FILE *file;
|
||||
int statuscode;
|
||||
string hash, type, host;
|
||||
thread *t;
|
||||
long pos;
|
||||
stds pipes;
|
||||
|
||||
if (req.isExistingField("Connection") && req.getField("Connection") == "keep-alive") {
|
||||
req.setField("Connection", "keep-alive");
|
||||
req.setField("Keep-Alive", "timeout=3600, max=100");
|
||||
} else {
|
||||
req.setField("Connection", "close");
|
||||
error = true;
|
||||
}
|
||||
|
||||
host = "";
|
||||
|
||||
if (!req.isExistingField("Host")) {
|
||||
req.respond(400);
|
||||
goto respond;
|
||||
}
|
||||
host = req.getField("Host");
|
||||
pos = host.find(':');
|
||||
if (pos != string::npos) {
|
||||
host.erase(pos, host.length() - pos);
|
||||
}
|
||||
|
||||
/*
|
||||
FILE *name = popen(("dig @8.8.8.8 +time=1 -x " + socket->getPeerAddress()->toString() +
|
||||
" | grep -oP \"^[^;].*\\t\\K([^ ]*)\\w\"").c_str(), "r");
|
||||
char hostbuffer[1024];
|
||||
memset(hostbuffer, 0, 1024);
|
||||
size_t size = fread(hostbuffer, 1, 1024, name);
|
||||
hostbuffer[size - 1] = 0; // remove \n
|
||||
if (size <= 1) {
|
||||
sprintf(hostbuffer, "%s", socket->getPeerAddress()->toString().c_str());
|
||||
}
|
||||
*/
|
||||
|
||||
sprintf(buffer, "[\x1B[1m%s\x1B[0m][%i]%s[%s][%i]%s ", host.c_str(), socket->getSocketPort(), col1,
|
||||
info->host.c_str(), socket->getPeerPort(), col2);
|
||||
prefix = buffer;
|
||||
|
||||
log(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m");
|
||||
log_to_file(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m", host);
|
||||
|
||||
noRedirect = req.getPath().find("/.well-known/") == 0 || (req.getPath().find("/files/") == 0);
|
||||
redir = true;
|
||||
if (!noRedirect) {
|
||||
if (getWebRoot(host).empty()) {
|
||||
req.redirect(303, "https://www.necronda.net" + req.getPath());
|
||||
} else if (socket->getSocketPort() != 443) {
|
||||
req.redirect(302, "https://" + host + req.getPath());
|
||||
} else {
|
||||
redir = false;
|
||||
}
|
||||
} else {
|
||||
redir = false;
|
||||
}
|
||||
|
||||
path = URI(getWebRoot(host), req.getPath());
|
||||
childpid = 0;
|
||||
|
||||
if (redir) {
|
||||
goto respond;
|
||||
} else if (!path.getNewPath().empty() && req.getMethod() != "POST") {
|
||||
req.redirect(303, path.getNewPath());
|
||||
goto respond;
|
||||
}
|
||||
|
||||
file = path.openFile();
|
||||
if (file == nullptr) {
|
||||
req.setField("Cache-Control", "public, max-age=60");
|
||||
req.respond(404);
|
||||
goto respond;
|
||||
}
|
||||
type = path.getFileType();
|
||||
|
||||
if (type.find("inode/") == 0 || (path.getRelativeFilePath().find("/.") != string::npos && !noRedirect)) {
|
||||
req.respond(403);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
req.setField("Content-Type", type);
|
||||
req.setField("Last-Modified", getHttpDate(path.getFilePath()));
|
||||
|
||||
invalidMethod = false;
|
||||
etag = false;
|
||||
|
||||
if (path.isStatic()) {
|
||||
hash = getETag(path.getFilePath());
|
||||
req.setField("ETag", hash);
|
||||
req.setField("Accept-Ranges", "bytes");
|
||||
if (type.find("text/") == 0) {
|
||||
req.setField("Cache-Control", "public, max-age=3600");
|
||||
} else {
|
||||
req.setField("Cache-Control", "public, max-age=86400");
|
||||
}
|
||||
req.setField("Allow", "GET");
|
||||
if (req.getMethod() != "GET") {
|
||||
invalidMethod = true;
|
||||
}
|
||||
if (req.isExistingField("If-None-Match") && req.getField("If-None-Match") == hash) {
|
||||
etag = true;
|
||||
}
|
||||
} else {
|
||||
req.setField("Accept-Ranges", "none");
|
||||
req.setField("Cache-Control", "private, no-cache");
|
||||
req.setField("Allow", "GET, POST, PUT");
|
||||
if (req.getMethod() != "GET" && req.getMethod() != "POST" && req.getMethod() != "PUT") {
|
||||
invalidMethod = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidMethod) {
|
||||
req.respond(405);
|
||||
goto respond;
|
||||
} else if (etag) {
|
||||
req.respond(304);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
statuscode = 0;
|
||||
if (!path.isStatic()) {
|
||||
string cmd = (string) "env -i" +
|
||||
" REDIRECT_STATUS=" + cli_encode("CGI") +
|
||||
" DOCUMENT_ROOT=" + cli_encode(getWebRoot(host)) +
|
||||
" " + req.cgiExport() +
|
||||
(req.isExistingField("Content-Length") ? " CONTENT_LENGTH=" +
|
||||
cli_encode(req.getField(
|
||||
"Content-Length"))
|
||||
: "") +
|
||||
(req.isExistingField("Content-Type") ? " CONTENT_TYPE=" + cli_encode(
|
||||
req.getField("Content-Type")) : "") +
|
||||
((socket->isSecured()) ? " HTTPS=on" : "") +
|
||||
" PATH_INFO=" + cli_encode(path.getFilePathInfo()) +
|
||||
" PATH_TRANSLATED=" + cli_encode(path.getAbsolutePath()) +
|
||||
" QUERY_STRING=" + cli_encode(path.getQuery()) +
|
||||
" REMOTE_ADDR=" + cli_encode(socket->getPeerAddress()->toString()) +
|
||||
" REMOTE_HOST=" + cli_encode(info->host) +
|
||||
" REMOTE_PORT=" + cli_encode(to_string(socket->getPeerPort())) +
|
||||
" REQUEST_METHOD=" + cli_encode(req.getMethod()) +
|
||||
" REQUEST_URI=" + cli_encode(req.getPath()) +
|
||||
" SCRIPT_FILENAME=" + cli_encode(path.getFilePath()) +
|
||||
" SCRIPT_NAME=" + cli_encode(path.getRelativePath()) +
|
||||
" SERVER_ADMIN=" + cli_encode("lorenz.stechauner@gmail.com") +
|
||||
" SERVER_NAME=" + cli_encode(host) +
|
||||
" SERVER_PORT=" + cli_encode(to_string(socket->getSocketPort())) +
|
||||
" SERVER_SOFTWARE=" + cli_encode("Necronda 3.0") +
|
||||
" SERVER_PROTOCOL=" + cli_encode("HTTP/1.1") +
|
||||
" GATEWAY_INTERFACE=" + cli_encode("CGI/1.1") +
|
||||
" /usr/bin/php-cgi";
|
||||
|
||||
pipes = procopen(cmd.c_str());
|
||||
childpid = pipes.pid;
|
||||
|
||||
long len = req.isExistingField("Content-Length")
|
||||
? strtol(req.getField("Content-Length").c_str(), nullptr, 10)
|
||||
: ((req.getMethod() == "POST" || req.getMethod() == "PUT") ? -1 : 0);
|
||||
|
||||
socket->receive(pipes.stdin, len);
|
||||
wantsWebsocket = req.getMethod() == "GET" &&
|
||||
req.isExistingResponseField("Connection") && req.getField("Connection") == "Upgrade" &&
|
||||
req.isExistingResponseField("Upgrade") && req.getField("Upgrade") == "websocket";
|
||||
if (!wantsWebsocket) {
|
||||
// Close only if no Websocket upgrade is possible
|
||||
fclose(pipes.stdin);
|
||||
}
|
||||
t = new thread(php_error_handler, prefix, pipes.stderr);
|
||||
|
||||
string line;
|
||||
while (!(line = read_line(pipes.stdout)).empty()) {
|
||||
pos = line.find(':');
|
||||
string index = line.substr(0, pos);
|
||||
string data = line.substr(pos + 1, line.length() - pos);
|
||||
|
||||
while (index[0] == ' ') index.erase(index.begin() + 0);
|
||||
while (index[index.length() - 1] == ' ') index.erase(index.end() - 1);
|
||||
while (data[0] == ' ') data.erase(data.begin() + 0);
|
||||
while (data[data.length() - 1] == ' ') data.erase(data.end() - 1);
|
||||
|
||||
if (index == "Status") {
|
||||
statuscode = (int) strtol(data.substr(0, 3).c_str(), nullptr, 10);
|
||||
} else {
|
||||
if (index == "Location" && statuscode == 0) {
|
||||
statuscode = 303;
|
||||
} else if (index == "Content-Type") {
|
||||
type = data;
|
||||
}
|
||||
req.setField(index, data);
|
||||
}
|
||||
}
|
||||
|
||||
websocket = statuscode == 101 &&
|
||||
req.isExistingResponseField("Connection") && req.getResponseField("Connection") == "Upgrade" &&
|
||||
req.isExistingResponseField("Upgrade") && req.getResponseField("Upgrade") == "websocket";
|
||||
|
||||
fclose(file);
|
||||
file = pipes.stdout;
|
||||
if (websocket) {
|
||||
log(prefix, "Upgrade to WebSocket!");
|
||||
req.respond(statuscode);
|
||||
goto respond;
|
||||
} else {
|
||||
int c = fgetc(pipes.stdout);
|
||||
if (c == -1) {
|
||||
// No Data -> Error
|
||||
req.respond((statuscode == 0) ? 500 : statuscode);
|
||||
goto respond;
|
||||
} else {
|
||||
ungetc(c, pipes.stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statuscode = (statuscode == 0) ? 200 : statuscode;
|
||||
|
||||
compress = (type.find("text/") == 0 ||
|
||||
(type.find("application/") == 0 && type.find("+xml") != string::npos) ||
|
||||
type == "application/json" ||
|
||||
type == "application/javascript") &&
|
||||
req.isExistingField("Accept-Encoding") &&
|
||||
req.getField("Accept-Encoding").find("deflate") != string::npos;
|
||||
|
||||
if (compress) {
|
||||
req.setField("Accept-Ranges", "none");
|
||||
}
|
||||
|
||||
if (compress && req.isExistingField("Range")) {
|
||||
req.respond(416);
|
||||
} else if (req.isExistingField("Range")) {
|
||||
string range = req.getField("Range");
|
||||
if (range.find("bytes=") != 0 || !path.isStatic()) {
|
||||
req.respond(416);
|
||||
} else {
|
||||
fseek(file, 0L, SEEK_END);
|
||||
long len = ftell(file);
|
||||
fseek(file, 0L, SEEK_SET);
|
||||
long p = range.find('-');
|
||||
if (p == string::npos) {
|
||||
req.respond(416);
|
||||
} else {
|
||||
string part1 = range.substr(6, (unsigned long) (p - 6));
|
||||
string part2 = range.substr((unsigned long) (p + 1),
|
||||
range.length() - p - 1);
|
||||
long num1 = stol(part1, nullptr, 10);
|
||||
long num2 = len - 1;
|
||||
if (!part2.empty()) {
|
||||
num2 = stol(part2, nullptr, 10);
|
||||
}
|
||||
if (num1 < 0 || num1 >= len || num2 < 0 || num2 >= len) {
|
||||
req.respond(416);
|
||||
} else {
|
||||
req.setField("Content-Range",
|
||||
(string) "bytes " + to_string(num1) + "-" +
|
||||
to_string(num2) +
|
||||
"/" + to_string(len));
|
||||
req.respond(206, file, compress, num1, num2);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
req.respond(statuscode, file, compress);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
if (childpid > 0) {
|
||||
waitpid(childpid, nullptr, 0);
|
||||
}
|
||||
|
||||
respond:
|
||||
|
||||
HttpStatusCode status = req.getStatusCode();
|
||||
int code = status.code;
|
||||
string color;
|
||||
string comment;
|
||||
if ((code >= 200 && code < 300) || code == 304 || code == 101) {
|
||||
color = "\x1B[1;32m"; // Success (Cached, Switching Protocols): Green
|
||||
} else if (code >= 100 && code < 200) {
|
||||
color = "\x1B[1;93m"; // Continue: Yellow
|
||||
} else if (code >= 300 && code < 400) {
|
||||
color = "\x1B[1;93m"; // Redirect: Yellow
|
||||
comment = " -> " +
|
||||
(req.isExistingResponseField("Location") ? req.getResponseField("Location") : "<invalid>");
|
||||
} else if (code >= 400 && code < 500) {
|
||||
color = "\x1B[1;31m"; // Client Error: Red
|
||||
//comment = " -> " + req.getPath();
|
||||
} else if (code >= 500 & code < 600) {
|
||||
color = "\x1B[1;31m"; // Server Error: Red
|
||||
//comment = " -> " + req.getPath();
|
||||
}
|
||||
string msg = color + to_string(status.code) + " " + status.message + comment + " (" + formatTime(req.getDuration()) + ")\x1B[0m";
|
||||
log(prefix, msg);
|
||||
if (!host.empty()) {
|
||||
log_to_file(prefix, msg, host);
|
||||
}
|
||||
|
||||
if (websocket) {
|
||||
websocket_handler(socket, &pipes);
|
||||
log(prefix, "\x1B[1mClosing WebSocket (" + formatTime(req.getDuration()) + ")\x1B[0m");
|
||||
}
|
||||
} catch (char *msg) {
|
||||
HttpStatusCode status = req.getStatusCode();
|
||||
log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")");
|
||||
try {
|
||||
if (strncmp(msg, "timeout", strlen(msg)) == 0) {
|
||||
log(prefix, "Timeout!");
|
||||
req.setField("Connection", "close");
|
||||
req.respond(408);
|
||||
error = true;
|
||||
} else if (strncmp(msg, "Invalid path", strlen(msg)) == 0) {
|
||||
log(prefix, "Timeout!");
|
||||
req.setField("Connection", "close");
|
||||
req.respond(400);
|
||||
} else {
|
||||
log(prefix, (string) "Unable to receive from socket: " + msg);
|
||||
error = true;
|
||||
}
|
||||
} catch (char *msg2) {
|
||||
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles HTTP clients
|
||||
* @param socket The socket
|
||||
* @param id The client ID
|
||||
*/
|
||||
void client_handler(Socket *socket, long id, bool ssl) {
|
||||
const char *prefix;
|
||||
char const *col1;
|
||||
char const *col2 = "\x1B[0m";
|
||||
IpAddressInfo info = get_ip_address_info(socket->getPeerAddress());
|
||||
auto os = get_os_info(socket->getFd());
|
||||
{
|
||||
auto group = (int) (id % 6);
|
||||
if (group == 0) {
|
||||
col1 = "\x1B[0;31m"; // Red
|
||||
} else if (group == 1) {
|
||||
col1 = "\x1B[0;32m"; // Green
|
||||
} else if (group == 2) {
|
||||
col1 = "\x1B[0;34m"; // Blue
|
||||
} else if (group == 3) {
|
||||
col1 = "\x1B[0;33m"; // Yellow
|
||||
} else if (group == 4) {
|
||||
col1 = "\x1B[0;35m"; // Magenta
|
||||
} else {
|
||||
col1 = "\x1B[0;36m"; // Cyan
|
||||
}
|
||||
|
||||
string *a = new string("[" + socket->getSocketAddress()->toString() + "][" +
|
||||
to_string(socket->getSocketPort()) + "]" + col1 +
|
||||
"[" + info.host + "][" + to_string(socket->getPeerPort()) +
|
||||
"]" + col2 + " ");
|
||||
prefix = a->c_str();
|
||||
}
|
||||
|
||||
log(prefix, "Connection established");
|
||||
log(prefix, string("Host: ") + info.host + " (" + socket->getPeerAddress()->toString() + ")");
|
||||
log(prefix, string("OS: ") + os);
|
||||
log(prefix, string("Location: ") + info.cc + "/" + info.country + ", " + info.prov + "/" + info.provname + ", " + info.city);
|
||||
log(prefix, string("Local Date: ") + info.localdate + " (" + info.timezone + ")");
|
||||
|
||||
|
||||
bool err = false;
|
||||
try {
|
||||
socket->setReceiveTimeout(3600000);
|
||||
socket->setSendTimeout(36000000);
|
||||
} catch (char *msg) {
|
||||
log(prefix, (string) "Unable to set timeout on socket: " + msg);
|
||||
err = true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (ssl) {
|
||||
//socket->sslHandshake("/home/lorenz/Documents/Projects/Necronda-Server/necronda-server-3.0/privkey.pem",
|
||||
// "/home/lorenz/Documents/Projects/Necronda-Server/necronda-server-3.0/fullchain.pem");
|
||||
socket->sslHandshake("/cert/necronda.net/privkey.pem",
|
||||
"/cert/necronda.net/fullchain.pem");
|
||||
}
|
||||
} catch (char *msg) {
|
||||
log(prefix, (string) "Unable to perform handshake: " + msg);
|
||||
err = true;
|
||||
}
|
||||
|
||||
long reqnum = 0;
|
||||
if (!err) {
|
||||
while (connection_handler(prefix, col1, col2, socket, id, ++reqnum, &info));
|
||||
reqnum--;
|
||||
}
|
||||
|
||||
log(prefix,
|
||||
"Connection terminated (#:" + to_string(reqnum) + ", R: " + formatSize(socket->getBytesReceived()) + ", S: " +
|
||||
formatSize(socket->getBytesSent()) + ", T: " + formatTime(socket->getDuration()) + ")");
|
||||
socket->close();
|
||||
}
|
||||
|
||||
|
23
src/client.h
Normal file
23
src/client.h
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Client connection and request handlers (header file)
|
||||
* src/client.h
|
||||
* Lorenz Stechauner, 2021-01-17
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_CLIENT_H
|
||||
#define NECRONDA_SERVER_CLIENT_H
|
||||
|
||||
#include "necronda-server.h"
|
||||
#include "utils.h"
|
||||
#include "uri.h"
|
||||
#include "http.h"
|
||||
#include "fastcgi.h"
|
||||
|
||||
|
||||
int server_keep_alive = 1;
|
||||
char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
|
||||
|
||||
struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0};
|
||||
|
||||
#endif //NECRONDA_SERVER_CLIENT_H
|
205
src/config.c
Normal file
205
src/config.c
Normal file
@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Configuration file loader
|
||||
* src/config.c
|
||||
* Lorenz Stechauner, 2021-01-05
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
||||
int config_init() {
|
||||
int shm_id = shmget(SHM_KEY_CONFIG, MAX_HOST_CONFIG * sizeof(host_config), IPC_CREAT | IPC_EXCL | 0640);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
|
||||
if (shm == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
config = shm;
|
||||
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
config = shm_rw;
|
||||
memset(config, 0, MAX_HOST_CONFIG * sizeof(host_config));
|
||||
shmdt(shm_rw);
|
||||
config = shm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_unload() {
|
||||
int shm_id = shmget(SHM_KEY_CONFIG, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
return -1;
|
||||
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
return -1;
|
||||
}
|
||||
shmdt(config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_load(const char *filename) {
|
||||
FILE *file = fopen(filename, "r");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open config file: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
unsigned long len = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
char *conf = malloc(len);
|
||||
fread(conf, 1, len, file);
|
||||
fclose(file);
|
||||
|
||||
host_config *tmp_config = malloc(MAX_HOST_CONFIG * sizeof(host_config));
|
||||
memset(tmp_config, 0, MAX_HOST_CONFIG * sizeof(host_config));
|
||||
|
||||
int i = 0;
|
||||
int mode = 0;
|
||||
char *ptr = NULL;
|
||||
char *source, *target;
|
||||
while ((ptr = strtok(ptr == NULL ? conf : NULL, "\n")) != NULL) {
|
||||
char *comment = strchr(ptr, '#');
|
||||
if (comment != NULL) comment[0] = 0;
|
||||
len = strlen(ptr);
|
||||
if (ptr[0] == '[') {
|
||||
if (ptr[len - 1] != ']') goto err;
|
||||
strncpy(tmp_config[i].name, ptr + 1, len - 2);
|
||||
i++;
|
||||
continue;
|
||||
} else if (i == 0) {
|
||||
if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
|
||||
source = ptr + 11;
|
||||
target = cert_file;
|
||||
} else if (len > 12 && strncmp(ptr, "private_key", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
|
||||
source = ptr + 11;
|
||||
target = key_file;
|
||||
} else if (len > 10 && strncmp(ptr, "geoip_dir", 9) == 0 && (ptr[9] == ' ' || ptr[9] == '\t')) {
|
||||
source = ptr + 9;
|
||||
target = geoip_dir;
|
||||
} else if (len > 11 && strncmp(ptr, "dns_server", 10) == 0 && (ptr[10] == ' ' || ptr[10] == '\t')) {
|
||||
source = ptr + 10;
|
||||
target = dns_server;
|
||||
}
|
||||
} else {
|
||||
host_config *hc = &tmp_config[i - 1];
|
||||
if (len > 8 && strncmp(ptr, "webroot", 7) == 0 && (ptr[7] == ' ' || ptr[7] == '\t')) {
|
||||
source = ptr + 7;
|
||||
target = hc->local.webroot;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_LOCAL;
|
||||
}
|
||||
} else if (len > 9 && strncmp(ptr, "dir_mode", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
|
||||
source = ptr + 8;
|
||||
target = NULL;
|
||||
mode = 1;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_LOCAL;
|
||||
}
|
||||
} else if (len > 9 && strncmp(ptr, "hostname", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
|
||||
source = ptr + 8;
|
||||
target = hc->rev_proxy.hostname;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
}
|
||||
} else if (len > 5 && strncmp(ptr, "port", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
|
||||
source = ptr + 4;
|
||||
target = NULL;
|
||||
mode = 2;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
}
|
||||
} else if (strcmp(ptr, "http") == 0) {
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
hc->rev_proxy.enc = 0;
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(ptr, "https") == 0) {
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
hc->rev_proxy.enc = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
char *end_ptr = source + strlen(source) - 1;
|
||||
while (source[0] == ' ' || source[0] == '\t') source++;
|
||||
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') end_ptr--;
|
||||
if (end_ptr <= source) {
|
||||
err:
|
||||
free(conf);
|
||||
free(tmp_config);
|
||||
fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n");
|
||||
return -2;
|
||||
}
|
||||
end_ptr[1] = 0;
|
||||
if (target != NULL) {
|
||||
strcpy(target, source);
|
||||
} else if (mode == 1) {
|
||||
if (strcmp(source, "forbidden") == 0) {
|
||||
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
|
||||
} else if (strcmp(source, "info") == 0) {
|
||||
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_INFO;
|
||||
} else if (strcmp(source, "list") == 0) {
|
||||
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_LIST;
|
||||
} else {
|
||||
goto err;
|
||||
}
|
||||
} else if (mode == 2) {
|
||||
tmp_config[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
|
||||
}
|
||||
}
|
||||
free(conf);
|
||||
|
||||
for (int j = 0; j < i; j++) {
|
||||
if (tmp_config[j].type == CONFIG_TYPE_LOCAL) {
|
||||
char *webroot = tmp_config[j].local.webroot;
|
||||
if (webroot[strlen(webroot) - 1] == '/') {
|
||||
webroot[strlen(webroot) - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int shm_id = shmget(SHM_KEY_CONFIG, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
return -3;
|
||||
}
|
||||
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
free(tmp_config);
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -4;
|
||||
}
|
||||
memcpy(shm_rw, tmp_config, MAX_HOST_CONFIG * sizeof(host_config));
|
||||
free(tmp_config);
|
||||
shmdt(shm_rw);
|
||||
return 0;
|
||||
}
|
50
src/config.h
Normal file
50
src/config.h
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Configuration file loader (header file)
|
||||
* src/config.h
|
||||
* Lorenz Stechauner, 2021-01-05
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NECRONDA_SERVER_CONFIG_H
|
||||
#define NECRONDA_SERVER_CONFIG_H
|
||||
|
||||
#define CONFIG_TYPE_UNSET 0
|
||||
#define CONFIG_TYPE_LOCAL 1
|
||||
#define CONFIG_TYPE_REVERSE_PROXY 2
|
||||
|
||||
#include "uri.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
char name[256];
|
||||
union {
|
||||
struct {
|
||||
char hostname[256];
|
||||
unsigned short port;
|
||||
unsigned char enc:1;
|
||||
} rev_proxy;
|
||||
struct {
|
||||
char webroot[256];
|
||||
unsigned char dir_mode:2;
|
||||
} local;
|
||||
};
|
||||
} host_config;
|
||||
|
||||
|
||||
host_config *config;
|
||||
char cert_file[256], key_file[256], geoip_dir[256], dns_server[256];
|
||||
|
||||
|
||||
int config_init();
|
||||
|
||||
int config_load(const char *filename);
|
||||
|
||||
int config_unload();
|
||||
|
||||
#endif //NECRONDA_SERVER_CONFIG_H
|
532
src/fastcgi.c
Normal file
532
src/fastcgi.c
Normal file
@ -0,0 +1,532 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* FastCGI interface implementation
|
||||
* src/fastcgi.c
|
||||
* Lorenz Stechauner, 2020-12-26
|
||||
*/
|
||||
|
||||
#include "fastcgi.h"
|
||||
|
||||
|
||||
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
|
||||
char *ptr = buf;
|
||||
unsigned long key_len = strlen(key);
|
||||
unsigned long val_len = strlen(value);
|
||||
|
||||
|
||||
if (key_len <= 127) {
|
||||
ptr[0] = (char) (key_len & 0x7F);
|
||||
ptr++;
|
||||
} else {
|
||||
ptr[0] = (char) (0x80 | (key_len >> 24));
|
||||
ptr[1] = (char) ((key_len >> 16) & 0xFF);
|
||||
ptr[2] = (char) ((key_len >> 8) & 0xFF);
|
||||
ptr[3] = (char) (key_len & 0xFF);
|
||||
ptr += 4;
|
||||
}
|
||||
if (val_len <= 127) {
|
||||
ptr[0] = (char) (val_len & 0x7F);
|
||||
ptr++;
|
||||
} else {
|
||||
ptr[0] = (char) (0x80 | (val_len >> 24));
|
||||
ptr[1] = (char) ((val_len >> 16) & 0xFF);
|
||||
ptr[2] = (char) ((val_len >> 8) & 0xFF);
|
||||
ptr[3] = (char) (val_len & 0xFF);
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
memcpy(ptr, key, key_len);
|
||||
ptr += key_len;
|
||||
memcpy(ptr, value, val_len);
|
||||
ptr += val_len;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_num, const sock *client,
|
||||
const http_req *req, const http_uri *uri) {
|
||||
unsigned short req_id = (client_num & 0xFFF) << 4;
|
||||
if (client_num == 0) {
|
||||
req_id |= (req_num + 1) & 0xF;
|
||||
} else {
|
||||
req_id |= req_num & 0xF;
|
||||
}
|
||||
conn->req_id = req_id;
|
||||
conn->out_buf = NULL;
|
||||
conn->out_off = 0;
|
||||
|
||||
int php_fpm = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (php_fpm < 0) {
|
||||
print(ERR_STR "Unable to create unix socket: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
conn->socket = php_fpm;
|
||||
|
||||
struct sockaddr_un php_fpm_addr = {AF_UNIX, PHP_FPM_SOCKET};
|
||||
if (connect(conn->socket, (struct sockaddr *) &php_fpm_addr, sizeof(php_fpm_addr)) < 0) {
|
||||
print(ERR_STR "Unable to connect to unix socket of PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
FCGI_Header header = {
|
||||
.version = FCGI_VERSION_1,
|
||||
.requestIdB1 = req_id >> 8,
|
||||
.requestIdB0 = req_id & 0xFF,
|
||||
.paddingLength = 0,
|
||||
.reserved = 0
|
||||
};
|
||||
|
||||
header.type = FCGI_BEGIN_REQUEST;
|
||||
header.contentLengthB1 = 0;
|
||||
header.contentLengthB0 = sizeof(FCGI_BeginRequestBody);
|
||||
FCGI_BeginRequestRecord begin = {
|
||||
header,
|
||||
{.roleB1 = (FCGI_RESPONDER >> 8) & 0xFF, .roleB0 = FCGI_RESPONDER & 0xFF, .flags = 0}
|
||||
};
|
||||
if (send(conn->socket, &begin, sizeof(begin), 0) != sizeof(begin)) {
|
||||
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
|
||||
char param_buf[4096];
|
||||
char buf0[256];
|
||||
char *param_ptr = param_buf + sizeof(header);
|
||||
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REDIRECT_STATUS", "CGI");
|
||||
param_ptr = fastcgi_add_param(param_ptr, "DOCUMENT_ROOT", uri->webroot);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "GATEWAY_INTERFACE", "CGI/1.1");
|
||||
param_ptr = fastcgi_add_param(param_ptr, "SERVER_SOFTWARE", SERVER_STR);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PROTOCOL", "HTTP/1.1");
|
||||
param_ptr = fastcgi_add_param(param_ptr, "SERVER_NAME", http_get_header_field(&req->hdr, "Host"));
|
||||
if (client->enc) {
|
||||
param_ptr = fastcgi_add_param(param_ptr, "HTTPS", "on");
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr_storage;
|
||||
struct sockaddr_in6 *addr;
|
||||
socklen_t len = sizeof(addr_storage);
|
||||
getsockname(client->socket, (struct sockaddr *) &addr_storage, &len);
|
||||
addr = (struct sockaddr_in6 *) &addr_storage;
|
||||
sprintf(buf0, "%i", addr->sin6_port);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PORT", buf0);
|
||||
|
||||
len = sizeof(addr_storage);
|
||||
getpeername(client->socket, (struct sockaddr *) &addr_storage, &len);
|
||||
addr = (struct sockaddr_in6 *) &addr_storage;
|
||||
sprintf(buf0, "%i", addr->sin6_port);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_PORT", buf0);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_ADDR", client_addr_str);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", client_host_str != NULL ? client_host_str : client_addr_str);
|
||||
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
|
||||
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
|
||||
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_METHOD", req->method);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_URI", req->uri);
|
||||
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_NAME", uri->filename + strlen(uri->webroot));
|
||||
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_FILENAME", uri->filename);
|
||||
//param_ptr = fastcgi_add_param(param_ptr, "PATH_TRANSLATED", uri->filename);
|
||||
|
||||
param_ptr = fastcgi_add_param(param_ptr, "QUERY_STRING", uri->query != NULL ? uri->query : "");
|
||||
if (uri->pathinfo != NULL && strlen(uri->pathinfo) > 0) {
|
||||
sprintf(buf0, "/%s", uri->pathinfo);
|
||||
} else {
|
||||
buf0[0] = 0;
|
||||
}
|
||||
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
|
||||
|
||||
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
|
||||
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
|
||||
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
|
||||
char *content_type = http_get_header_field(&req->hdr, "Content-Type");
|
||||
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
|
||||
if (client_geoip != NULL) {
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", client_geoip);
|
||||
}
|
||||
|
||||
for (int i = 0; i < req->hdr.field_num; i++) {
|
||||
char *ptr = buf0;
|
||||
ptr += sprintf(ptr, "HTTP_");
|
||||
for (int j = 0; j < strlen(req->hdr.fields[i][0]); j++, ptr++) {
|
||||
char ch = req->hdr.fields[i][0][j];
|
||||
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
|
||||
ch = ch;
|
||||
} else if (ch >= 'a' && ch <= 'z') {
|
||||
ch &= 0x5F;
|
||||
} else {
|
||||
ch = '_';
|
||||
}
|
||||
ptr[0] = ch;
|
||||
ptr[1] = 0;
|
||||
}
|
||||
param_ptr = fastcgi_add_param(param_ptr, buf0, req->hdr.fields[i][1]);
|
||||
}
|
||||
|
||||
unsigned short param_len = param_ptr - param_buf - sizeof(header);
|
||||
header.type = FCGI_PARAMS;
|
||||
header.contentLengthB1 = param_len >> 8;
|
||||
header.contentLengthB0 = param_len & 0xFF;
|
||||
memcpy(param_buf, &header, sizeof(header));
|
||||
if (send(conn->socket, param_buf, param_len + sizeof(header), 0) != param_len + sizeof(header)) {
|
||||
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
|
||||
header.type = FCGI_PARAMS;
|
||||
header.contentLengthB1 = 0;
|
||||
header.contentLengthB0 = 0;
|
||||
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
|
||||
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fastcgi_close_stdin(fastcgi_conn *conn) {
|
||||
FCGI_Header header = {
|
||||
.version = FCGI_VERSION_1,
|
||||
.type = FCGI_STDIN,
|
||||
.requestIdB1 = conn->req_id >> 8,
|
||||
.requestIdB0 = conn->req_id & 0xFF,
|
||||
.contentLengthB1 = 0,
|
||||
.contentLengthB0 = 0,
|
||||
.paddingLength = 0,
|
||||
.reserved = 0
|
||||
};
|
||||
|
||||
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
|
||||
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fastcgi_php_error(const char *msg, int msg_len, char *err_msg) {
|
||||
char *msg_str = malloc(msg_len + 1);
|
||||
char *ptr0 = msg_str;
|
||||
strncpy(msg_str, msg, msg_len);
|
||||
char *ptr1 = NULL;
|
||||
int len;
|
||||
int err = 0;
|
||||
// FIXME *msg is part of a stream, handle fragmented lines
|
||||
while (1) {
|
||||
int msg_type = 0;
|
||||
int msg_pre_len = 0;
|
||||
ptr1 = strstr(ptr0, "PHP message: ");
|
||||
if (ptr1 == NULL) {
|
||||
len = (int) (msg_len - (ptr0 - msg_str));
|
||||
if (ptr0 == msg_str) msg_type = 2;
|
||||
} else {
|
||||
len = (int) (ptr1 - ptr0);
|
||||
}
|
||||
if (len == 0) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (len >= 14 && strncmp(ptr0, "PHP Warning: ", 14) == 0) {
|
||||
msg_type = 1;
|
||||
msg_pre_len = 14;
|
||||
} else if (len >= 18 && strncmp(ptr0, "PHP Fatal error: ", 18) == 0) {
|
||||
msg_type = 2;
|
||||
msg_pre_len = 18;
|
||||
} else if (len >= 18 && strncmp(ptr0, "PHP Parse error: ", 18) == 0) {
|
||||
msg_type = 2;
|
||||
msg_pre_len = 18;
|
||||
} else if (len >= 18 && strncmp(ptr0, "PHP Notice: ", 13) == 0) {
|
||||
msg_type = 1;
|
||||
msg_pre_len = 13;
|
||||
}
|
||||
|
||||
char *ptr2 = ptr0;
|
||||
char *ptr3;
|
||||
int len2;
|
||||
while (ptr2 - ptr0 < len) {
|
||||
ptr3 = strchr(ptr2, '\n');
|
||||
len2 = (int) (len - (ptr2 - ptr0));
|
||||
if (ptr3 != NULL && (ptr3 - ptr2) < len2) {
|
||||
len2 = (int) (ptr3 - ptr2);
|
||||
}
|
||||
print("%s%.*s%s", msg_type == 1 ? WRN_STR : msg_type == 2 ? ERR_STR : "", len2, ptr2, msg_type != 0 ? CLR_STR : "");
|
||||
if (msg_type == 2 && ptr2 == ptr0) {
|
||||
sprintf(err_msg, "%.*s", len2, ptr2);
|
||||
err = 1;
|
||||
}
|
||||
if (ptr3 == NULL) {
|
||||
break;
|
||||
}
|
||||
ptr2 = ptr3 + 1;
|
||||
}
|
||||
|
||||
next:
|
||||
if (ptr1 == NULL) {
|
||||
break;
|
||||
}
|
||||
ptr0 = ptr1 + 13;
|
||||
}
|
||||
free(msg_str);
|
||||
return err;
|
||||
}
|
||||
|
||||
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
|
||||
FCGI_Header header;
|
||||
char *content;
|
||||
unsigned short content_len, req_id;
|
||||
int ret;
|
||||
int err = 0;
|
||||
|
||||
while (1) {
|
||||
ret = recv(conn->socket, &header, sizeof(header), 0);
|
||||
if (ret < 0) {
|
||||
res->status = http_get_status(502);
|
||||
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
|
||||
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return 1;
|
||||
} else if (ret != sizeof(header)) {
|
||||
res->status = http_get_status(502);
|
||||
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
|
||||
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
|
||||
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
|
||||
content = malloc(content_len + header.paddingLength);
|
||||
ret = recv(conn->socket, content, content_len + header.paddingLength, 0);
|
||||
if (ret < 0) {
|
||||
res->status = http_get_status(502);
|
||||
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
|
||||
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
free(content);
|
||||
return 1;
|
||||
} else if (ret != (content_len + header.paddingLength)) {
|
||||
res->status = http_get_status(502);
|
||||
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
|
||||
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
|
||||
free(content);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (req_id != conn->req_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (header.type == FCGI_END_REQUEST) {
|
||||
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
|
||||
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
|
||||
body->appStatusB0;
|
||||
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
|
||||
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
|
||||
}
|
||||
if (app_status != 0) {
|
||||
print(ERR_STR "Script terminated with exit code %i" CLR_STR, app_status);
|
||||
}
|
||||
close(conn->socket);
|
||||
conn->socket = 0;
|
||||
free(content);
|
||||
return 1;
|
||||
} else if (header.type == FCGI_STDERR) {
|
||||
err = err || fastcgi_php_error(content, content_len, err_msg);
|
||||
} else if (header.type == FCGI_STDOUT) {
|
||||
break;
|
||||
} else {
|
||||
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
|
||||
}
|
||||
|
||||
free(content);
|
||||
}
|
||||
if (err) {
|
||||
res->status = http_get_status(500);
|
||||
return 2;
|
||||
}
|
||||
|
||||
conn->out_buf = content;
|
||||
conn->out_len = content_len;
|
||||
conn->out_off = (unsigned short) (strstr(content, "\r\n\r\n") - content + 4);
|
||||
|
||||
char *buf = content;
|
||||
unsigned short header_len = conn->out_off;
|
||||
if (header_len <= 0) {
|
||||
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < header_len; i++) {
|
||||
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
|
||||
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
char *ptr = buf;
|
||||
while (header_len != (ptr - buf)) {
|
||||
char *pos0 = strstr(ptr, "\r\n");
|
||||
if (pos0 == NULL) {
|
||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = http_parse_header_field(&res->hdr, ptr, pos0);
|
||||
if (ret != 0) return ret;
|
||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||
return 0;
|
||||
}
|
||||
ptr = pos0 + 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
||||
FCGI_Header header;
|
||||
int ret;
|
||||
char buf0[256];
|
||||
int len;
|
||||
char *content, *ptr;
|
||||
unsigned short req_id, content_len;
|
||||
char comp_out[4096];
|
||||
int finish_comp = 0;
|
||||
|
||||
z_stream strm;
|
||||
if (flags & FASTCGI_COMPRESS) {
|
||||
int level = NECRONDA_ZLIB_LEVEL;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
if (deflateInit(&strm, level) != Z_OK) {
|
||||
print(ERR_STR "Unable to init deflate: %s" CLR_STR, strerror(errno));
|
||||
flags &= !FASTCGI_COMPRESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (conn->out_buf != NULL && conn->out_len > conn->out_off) {
|
||||
content = conn->out_buf;
|
||||
ptr = content + conn->out_off;
|
||||
content_len = conn->out_len - conn->out_off;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
ret = recv(conn->socket, &header, sizeof(header), 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (ret != sizeof(header)) {
|
||||
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
|
||||
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
|
||||
content = malloc(content_len + header.paddingLength);
|
||||
ptr = content;
|
||||
ret = recv(conn->socket, content, content_len + header.paddingLength, 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
free(content);
|
||||
return -1;
|
||||
} else if (ret != (content_len + header.paddingLength)) {
|
||||
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
|
||||
free(content);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (header.type == FCGI_END_REQUEST) {
|
||||
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
|
||||
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
|
||||
body->appStatusB0;
|
||||
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
|
||||
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
|
||||
}
|
||||
if (app_status != 0) {
|
||||
print(ERR_STR "Script terminated with exit code %i" CLR_STR, app_status);
|
||||
}
|
||||
close(conn->socket);
|
||||
conn->socket = 0;
|
||||
free(content);
|
||||
|
||||
if (flags & FASTCGI_COMPRESS) {
|
||||
finish_comp = 1;
|
||||
content_len = 0;
|
||||
goto out;
|
||||
finish:
|
||||
deflateEnd(&strm);
|
||||
}
|
||||
|
||||
if (flags & FASTCGI_CHUNKED) {
|
||||
sock_send(client, "0\r\n\r\n", 5, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else if (header.type == FCGI_STDERR) {
|
||||
fastcgi_php_error(content, content_len, buf0);
|
||||
} else if (header.type == FCGI_STDOUT) {
|
||||
out:
|
||||
if (flags & FASTCGI_COMPRESS) {
|
||||
strm.avail_in = content_len;
|
||||
strm.next_in = (unsigned char *) ptr;
|
||||
}
|
||||
do {
|
||||
int buf_len = content_len;
|
||||
if (flags & FASTCGI_COMPRESS) {
|
||||
strm.avail_out = sizeof(comp_out);
|
||||
strm.next_out = (unsigned char *) comp_out;
|
||||
deflate(&strm, finish_comp ? Z_FINISH : Z_NO_FLUSH);
|
||||
strm.avail_in = 0;
|
||||
ptr = comp_out;
|
||||
buf_len = (int) (sizeof(comp_out) - strm.avail_out);
|
||||
}
|
||||
if (buf_len != 0) {
|
||||
len = sprintf(buf0, "%X\r\n", buf_len);
|
||||
if (flags & FASTCGI_CHUNKED) sock_send(client, buf0, len, 0);
|
||||
sock_send(client, ptr, buf_len, 0);
|
||||
if (flags & FASTCGI_CHUNKED) sock_send(client, "\r\n", 2, 0);
|
||||
}
|
||||
} while ((flags & FASTCGI_COMPRESS) && strm.avail_out == 0);
|
||||
if (finish_comp) goto finish;
|
||||
} else {
|
||||
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
|
||||
}
|
||||
free(content);
|
||||
}
|
||||
}
|
||||
|
||||
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
|
||||
unsigned long rcv_len = 0;
|
||||
char *buf[16384];
|
||||
long ret;
|
||||
FCGI_Header header = {
|
||||
.version = FCGI_VERSION_1,
|
||||
.type = FCGI_STDIN,
|
||||
.requestIdB1 = conn->req_id >> 8,
|
||||
.requestIdB0 = conn->req_id & 0xFF,
|
||||
.contentLengthB1 = 0,
|
||||
.contentLengthB0 = 0,
|
||||
.paddingLength = 0,
|
||||
.reserved = 0
|
||||
};
|
||||
|
||||
if (client->buf != NULL && client->buf_len - client->buf_off > 0) {
|
||||
ret = (int) (client->buf_len - client->buf_off);
|
||||
memcpy(buf, client->buf + client->buf_off, ret);
|
||||
goto send;
|
||||
}
|
||||
|
||||
while (rcv_len < len) {
|
||||
ret = sock_recv(client, buf, sizeof(buf), 0);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to receive: %s" CLR_STR, sock_strerror(client));
|
||||
return -1;
|
||||
}
|
||||
|
||||
send:
|
||||
rcv_len += ret;
|
||||
header.contentLengthB1 = (ret >> 8) & 0xFF;
|
||||
header.contentLengthB0 = ret & 0xFF;
|
||||
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) goto err;
|
||||
if (send(conn->socket, buf, ret, 0) != ret) {
|
||||
err:
|
||||
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
158
src/fastcgi.h
Normal file
158
src/fastcgi.h
Normal file
@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* FastCGI interface implementation (header file)
|
||||
* src/fastcgi.h
|
||||
* Lorenz Stechauner, 2020-12-26
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_FASTCGI_H
|
||||
#define NECRONDA_SERVER_FASTCGI_H
|
||||
|
||||
#define FASTCGI_CHUNKED 1
|
||||
#define FASTCGI_COMPRESS 2
|
||||
|
||||
#include "necronda-server.h"
|
||||
#include "http.h"
|
||||
#include "uri.h"
|
||||
#include "client.h"
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <zlib.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
int socket;
|
||||
unsigned short req_id;
|
||||
char *out_buf;
|
||||
unsigned short out_len;
|
||||
unsigned short out_off;
|
||||
} fastcgi_conn;
|
||||
|
||||
char *fastcgi_add_param(char *buf, const char *key, const char *value);
|
||||
|
||||
int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_num, const sock *client,
|
||||
const http_req *req, const http_uri *uri);
|
||||
|
||||
int fastcgi_close_stdin(fastcgi_conn *conn);
|
||||
|
||||
int fastcgi_php_error(const char *msg, int msg_len, char *err_msg);
|
||||
|
||||
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
|
||||
|
||||
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
|
||||
|
||||
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
|
||||
|
||||
|
||||
/*
|
||||
* Listening socket file number
|
||||
*/
|
||||
#define FCGI_LISTENSOCK_FILENO 0
|
||||
|
||||
typedef struct {
|
||||
unsigned char version;
|
||||
unsigned char type;
|
||||
unsigned char requestIdB1;
|
||||
unsigned char requestIdB0;
|
||||
unsigned char contentLengthB1;
|
||||
unsigned char contentLengthB0;
|
||||
unsigned char paddingLength;
|
||||
unsigned char reserved;
|
||||
} FCGI_Header;
|
||||
|
||||
/*
|
||||
* Number of bytes in a FCGI_Header. Future versions of the protocol
|
||||
* will not reduce this number.
|
||||
*/
|
||||
#define FCGI_HEADER_LEN 8
|
||||
|
||||
/*
|
||||
* Value for version component of FCGI_Header
|
||||
*/
|
||||
#define FCGI_VERSION_1 1
|
||||
|
||||
/*
|
||||
* Values for type component of FCGI_Header
|
||||
*/
|
||||
#define FCGI_BEGIN_REQUEST 1
|
||||
#define FCGI_ABORT_REQUEST 2
|
||||
#define FCGI_END_REQUEST 3
|
||||
#define FCGI_PARAMS 4
|
||||
#define FCGI_STDIN 5
|
||||
#define FCGI_STDOUT 6
|
||||
#define FCGI_STDERR 7
|
||||
#define FCGI_DATA 8
|
||||
#define FCGI_GET_VALUES 9
|
||||
#define FCGI_GET_VALUES_RESULT 10
|
||||
#define FCGI_UNKNOWN_TYPE 11
|
||||
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
|
||||
|
||||
/*
|
||||
* Value for requestId component of FCGI_Header
|
||||
*/
|
||||
#define FCGI_NULL_REQUEST_ID 0
|
||||
|
||||
typedef struct {
|
||||
unsigned char roleB1;
|
||||
unsigned char roleB0;
|
||||
unsigned char flags;
|
||||
unsigned char reserved[5];
|
||||
} FCGI_BeginRequestBody;
|
||||
|
||||
typedef struct {
|
||||
FCGI_Header header;
|
||||
FCGI_BeginRequestBody body;
|
||||
} FCGI_BeginRequestRecord;
|
||||
|
||||
/*
|
||||
* Mask for flags component of FCGI_BeginRequestBody
|
||||
*/
|
||||
#define FCGI_KEEP_CONN 1
|
||||
|
||||
/*
|
||||
* Values for role component of FCGI_BeginRequestBody
|
||||
*/
|
||||
#define FCGI_RESPONDER 1
|
||||
#define FCGI_AUTHORIZER 2
|
||||
#define FCGI_FILTER 3
|
||||
|
||||
typedef struct {
|
||||
unsigned char appStatusB3;
|
||||
unsigned char appStatusB2;
|
||||
unsigned char appStatusB1;
|
||||
unsigned char appStatusB0;
|
||||
unsigned char protocolStatus;
|
||||
unsigned char reserved[3];
|
||||
} FCGI_EndRequestBody;
|
||||
|
||||
typedef struct {
|
||||
FCGI_Header header;
|
||||
FCGI_EndRequestBody body;
|
||||
} FCGI_EndRequestRecord;
|
||||
|
||||
/*
|
||||
* Values for protocolStatus component of FCGI_EndRequestBody
|
||||
*/
|
||||
#define FCGI_REQUEST_COMPLETE 0
|
||||
#define FCGI_CANT_MPX_CONN 1
|
||||
#define FCGI_OVERLOADED 2
|
||||
#define FCGI_UNKNOWN_ROLE 3
|
||||
|
||||
/*
|
||||
* Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
|
||||
*/
|
||||
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
|
||||
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
|
||||
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
|
||||
|
||||
typedef struct {
|
||||
unsigned char type;
|
||||
unsigned char reserved[7];
|
||||
} FCGI_UnknownTypeBody;
|
||||
|
||||
typedef struct {
|
||||
FCGI_Header header;
|
||||
FCGI_UnknownTypeBody body;
|
||||
} FCGI_UnknownTypeRecord;
|
||||
|
||||
#endif //NECRONDA_SERVER_FASTCGI_H
|
312
src/http.c
Normal file
312
src/http.c
Normal file
@ -0,0 +1,312 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* HTTP implementation
|
||||
* src/net/http.c
|
||||
* Lorenz Stechauner, 2020-12-09
|
||||
*/
|
||||
|
||||
#include "http.h"
|
||||
|
||||
|
||||
void http_to_camel_case(char *str, int mode) {
|
||||
char last = '-';
|
||||
char ch;
|
||||
for (int i = 0; i < strlen(str); i++) {
|
||||
ch = str[i];
|
||||
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
|
||||
str[i] = (char) ((int) ch & 0x5F);
|
||||
} else if (mode == HTTP_LOWER && ch >= 'A' && ch <= 'Z') {
|
||||
str[i] = (char) ((int) ch | 0x20);
|
||||
}
|
||||
last = str[i];
|
||||
}
|
||||
}
|
||||
|
||||
void http_free_hdr(http_hdr *hdr) {
|
||||
for (int i = 0; i < hdr->field_num; i++) {
|
||||
free(hdr->fields[i][0]);
|
||||
free(hdr->fields[i][1]);
|
||||
}
|
||||
hdr->field_num = 0;
|
||||
}
|
||||
|
||||
void http_free_req(http_req *req) {
|
||||
if (req->uri == NULL) free(req->uri);
|
||||
req->uri = NULL;
|
||||
http_free_hdr(&req->hdr);
|
||||
}
|
||||
|
||||
void http_free_res(http_res *res) {
|
||||
http_free_hdr(&res->hdr);
|
||||
}
|
||||
|
||||
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) {
|
||||
char *pos1 = memchr(buf, ':', end_ptr - buf);
|
||||
char *pos2;
|
||||
if (pos1 == NULL) {
|
||||
print(ERR_STR "Unable to parse header" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
|
||||
long len = pos1 - buf;
|
||||
hdr->fields[(int) hdr->field_num][0] = malloc(len + 1);
|
||||
sprintf(hdr->fields[(int) hdr->field_num][0], "%.*s", (int) len, buf);
|
||||
http_to_camel_case(hdr->fields[(int) hdr->field_num][0], HTTP_CAMEL);
|
||||
|
||||
pos1++;
|
||||
pos2 = (char *) end_ptr - 1;
|
||||
while (pos1[0] == ' ') pos1++;
|
||||
while (pos2[0] == ' ') pos2--;
|
||||
len = pos2 - pos1 + 1;
|
||||
|
||||
if (len <= 0) {
|
||||
hdr->fields[(int) hdr->field_num][1] = malloc(1);
|
||||
hdr->fields[(int) hdr->field_num][1][0] = 0;
|
||||
} else {
|
||||
hdr->fields[(int) hdr->field_num][1] = malloc(len + 1);
|
||||
sprintf(hdr->fields[(int) hdr->field_num][1], "%.*s", (int) len, pos1);
|
||||
}
|
||||
hdr->field_num++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_receive_request(sock *client, http_req *req) {
|
||||
long rcv_len, len;
|
||||
char *ptr, *pos0, *pos1, *pos2;
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
memset(req->method, 0, sizeof(req->method));
|
||||
memset(req->version, 0, sizeof(req->version));
|
||||
req->uri = NULL;
|
||||
req->hdr.field_num = 0;
|
||||
|
||||
while (1) {
|
||||
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, 0);
|
||||
if (rcv_len <= 0) {
|
||||
print("Unable to receive: %s", sock_strerror(client));
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned long header_len = strstr(buf, "\r\n\r\n") - buf + 4;
|
||||
if (header_len <= 0) {
|
||||
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
|
||||
return 5;
|
||||
}
|
||||
|
||||
for (int i = 0; i < header_len; i++) {
|
||||
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
|
||||
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
ptr = buf;
|
||||
while (header_len > (ptr - buf + 2)) {
|
||||
pos0 = strstr(ptr, "\r\n");
|
||||
if (pos0 == NULL) {
|
||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (req->version[0] == 0) {
|
||||
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
|
||||
if (pos1 == NULL) goto err_hdr_fmt;
|
||||
|
||||
if (pos1 - ptr - 1 >= sizeof(req->method)) {
|
||||
print(ERR_STR "Unable to parse header: Method name too long" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (pos1 - ptr - 1); i++) {
|
||||
if (ptr[i] < 'A' || ptr[i] > 'Z') {
|
||||
print(ERR_STR "Unable to parse header: Invalid method" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
strncpy(req->method, ptr, pos1 - ptr - 1);
|
||||
|
||||
pos2 = memchr(pos1, ' ', rcv_len - (pos1 - buf)) + 1;
|
||||
if (pos2 == NULL) {
|
||||
err_hdr_fmt:
|
||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (memcmp(pos2, "HTTP/", 5) != 0 || memcmp(pos2 + 8, "\r\n", 2) != 0) {
|
||||
print(ERR_STR "Unable to parse header: Invalid version" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
|
||||
len = pos2 - pos1 - 1;
|
||||
req->uri = malloc(len + 1);
|
||||
sprintf(req->uri, "%.*s", (int) len, pos1);
|
||||
sprintf(req->version, "%.3s", pos2 + 5);
|
||||
} else {
|
||||
int ret = http_parse_header_field(&req->hdr, ptr, pos0);
|
||||
if (ret != 0) return ret;
|
||||
}
|
||||
ptr = pos0 + 2;
|
||||
}
|
||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
client->buf_len = rcv_len - (pos0 - buf + 4);
|
||||
if (client->buf_len > 0) {
|
||||
client->buf = malloc(client->buf_len);
|
||||
client->buf_off = 0;
|
||||
memcpy(client->buf, pos0 + 4, client->buf_len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
|
||||
char field_name_1[256], field_name_2[256];
|
||||
strcpy(field_name_1, field_name);
|
||||
http_to_camel_case(field_name_1, HTTP_LOWER);
|
||||
for (int i = 0; i < hdr->field_num; i++) {
|
||||
strcpy(field_name_2, hdr->fields[i][0]);
|
||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
||||
if (strcmp(field_name_1, field_name_2) == 0) {
|
||||
return hdr->fields[i][1];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
|
||||
size_t len_name = strlen(field_name);
|
||||
size_t len_value = strlen(field_value);
|
||||
char *_field_name = malloc(len_name + 1);
|
||||
char *_field_value = malloc(len_value + 1);
|
||||
strcpy(_field_name, field_name);
|
||||
strcpy(_field_value, field_value);
|
||||
http_to_camel_case(_field_name, HTTP_PRESERVE);
|
||||
hdr->fields[(int) hdr->field_num][0] = _field_name;
|
||||
hdr->fields[(int) hdr->field_num][1] = _field_value;
|
||||
hdr->field_num++;
|
||||
}
|
||||
|
||||
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
|
||||
char field_name_1[256], field_name_2[256];
|
||||
strcpy(field_name_1, field_name);
|
||||
http_to_camel_case(field_name_1, HTTP_LOWER);
|
||||
|
||||
int i = 0;
|
||||
int diff = 1;
|
||||
if (mode == HTTP_REMOVE_LAST) {
|
||||
i = hdr->field_num - 1;
|
||||
diff = -1;
|
||||
}
|
||||
for (; i < hdr->field_num && i >= 0; i += diff) {
|
||||
strcpy(field_name_2, hdr->fields[i][0]);
|
||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
||||
if (strcmp(field_name_1, field_name_2) == 0) {
|
||||
for (int j = i; j < hdr->field_num - 1; j++) {
|
||||
memcpy(hdr->fields[j], hdr->fields[j + 1], sizeof(hdr->fields[0]));
|
||||
}
|
||||
hdr->field_num--;
|
||||
if (mode == HTTP_REMOVE_ALL) {
|
||||
i -= diff;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int http_send_response(sock *client, http_res *res) {
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
|
||||
for (int i = 0; i < res->hdr.field_num; i++) {
|
||||
off += sprintf(buf + off, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
|
||||
}
|
||||
off += sprintf(buf + off, "\r\n");
|
||||
if (sock_send(client, buf, off, 0) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_send_request(sock *server, http_req *req) {
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
long off = sprintf(buf, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
|
||||
for (int i = 0; i < req->hdr.field_num; i++) {
|
||||
off += sprintf(buf + off, "%s: %s\r\n", req->hdr.fields[i][0], req->hdr.fields[i][1]);
|
||||
}
|
||||
off += sprintf(buf + off, "\r\n");
|
||||
long ret = sock_send(server, buf, off, 0);
|
||||
if (ret <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const http_status *http_get_status(unsigned short status_code) {
|
||||
for (int i = 0; i < sizeof(http_statuses) / sizeof(http_status); i++) {
|
||||
if (http_statuses[i].code == status_code) {
|
||||
return &http_statuses[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const http_status_msg *http_get_error_msg(const http_status *status) {
|
||||
unsigned short code = status->code;
|
||||
for (int i = 0; i < sizeof(http_status_messages) / sizeof(http_status_msg); i++) {
|
||||
if (http_status_messages[i].code == code) {
|
||||
return &http_status_messages[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *http_get_status_color(const http_status *status) {
|
||||
unsigned short code = status->code;
|
||||
if (code >= 100 && code < 200) {
|
||||
return HTTP_1XX_STR;
|
||||
} else if ((code >= 200 && code < 300) || code == 304) {
|
||||
return HTTP_2XX_STR;
|
||||
} else if (code >= 300 && code < 400) {
|
||||
return HTTP_3XX_STR;
|
||||
} else if (code >= 400 && code < 500) {
|
||||
return HTTP_4XX_STR;
|
||||
} else if (code >= 500 && code < 600) {
|
||||
return HTTP_5XX_STR;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char *http_format_date(time_t time, char *buf, size_t size) {
|
||||
struct tm *timeinfo = gmtime(&time);
|
||||
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *http_get_date(char *buf, size_t size) {
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
return http_format_date(rawtime, buf, size);
|
||||
}
|
||||
|
||||
const http_doc_info *http_get_status_info(const http_status *status) {
|
||||
unsigned short code = status->code;
|
||||
static http_doc_info info[] = {
|
||||
{"info", HTTP_COLOR_INFO, http_info_icon, http_info_document},
|
||||
{"success", HTTP_COLOR_SUCCESS, http_success_icon, http_success_document},
|
||||
{"warning", HTTP_COLOR_WARNING, http_warning_icon, http_warning_document},
|
||||
{"error", HTTP_COLOR_ERROR, http_error_icon, http_error_document}
|
||||
};
|
||||
if (code >= 100 && code < 200) {
|
||||
return &info[0];
|
||||
} else if ((code >= 200 && code < 300) || code == 304) {
|
||||
return &info[1];
|
||||
} else if (code >= 300 && code < 400) {
|
||||
return &info[2];
|
||||
} else if (code >= 400 && code < 600) {
|
||||
return &info[3];
|
||||
}
|
||||
return NULL;
|
||||
}
|
285
src/http.h
Normal file
285
src/http.h
Normal file
@ -0,0 +1,285 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* HTTP implementation (header file)
|
||||
* src/net/http.h
|
||||
* Lorenz Stechauner, 2020-12-09
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_HTTP_H
|
||||
#define NECRONDA_SERVER_HTTP_H
|
||||
|
||||
#define HTTP_PRESERVE 0
|
||||
#define HTTP_LOWER 1
|
||||
#define HTTP_CAMEL 2
|
||||
|
||||
#define HTTP_REMOVE_ONE 0
|
||||
#define HTTP_REMOVE_ALL 1
|
||||
#define HTTP_REMOVE_LAST 2
|
||||
|
||||
#define HTTP_COLOR_SUCCESS "#008000"
|
||||
#define HTTP_COLOR_INFO "#606060"
|
||||
#define HTTP_COLOR_WARNING "#E0C000"
|
||||
#define HTTP_COLOR_ERROR "#C00000"
|
||||
|
||||
#include "sock.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned short code;
|
||||
char type[16];
|
||||
char msg[32];
|
||||
} http_status;
|
||||
|
||||
typedef struct {
|
||||
unsigned short code;
|
||||
const char *msg;
|
||||
} http_status_msg;
|
||||
|
||||
typedef struct {
|
||||
char mode[8];
|
||||
char color[8];
|
||||
const char *icon;
|
||||
const char *doc;
|
||||
} http_doc_info;
|
||||
|
||||
typedef struct {
|
||||
char field_num;
|
||||
char *fields[64][2];
|
||||
} http_hdr;
|
||||
|
||||
typedef struct {
|
||||
char method[16];
|
||||
char *uri;
|
||||
char version[3];
|
||||
http_hdr hdr;
|
||||
} http_req;
|
||||
|
||||
typedef struct {
|
||||
const http_status *status;
|
||||
char version[3];
|
||||
http_hdr hdr;
|
||||
} http_res;
|
||||
|
||||
static const http_status http_statuses[] = {
|
||||
{100, "Informational", "Continue"},
|
||||
{101, "Informational", "Switching Protocols"},
|
||||
|
||||
{200, "Success", "OK"},
|
||||
{201, "Success", "Created"},
|
||||
{202, "Success", "Accepted"},
|
||||
{203, "Success", "Non-Authoritative Information"},
|
||||
{204, "Success", "No Content"},
|
||||
{205, "Success", "Reset Content"},
|
||||
{206, "Success", "Partial Content"},
|
||||
|
||||
{300, "Redirection", "Multiple Choices"},
|
||||
{301, "Redirection", "Moved Permanently"},
|
||||
{302, "Redirection", "Found"},
|
||||
{303, "Redirection", "See Other"},
|
||||
{304, "Success", "Not Modified"},
|
||||
{305, "Redirection", "Use Proxy"},
|
||||
{307, "Redirection", "Temporary Redirect"},
|
||||
{308, "Redirection", "Permanent Redirect"},
|
||||
|
||||
{400, "Client Error", "Bad Request"},
|
||||
{401, "Client Error", "Unauthorized"},
|
||||
{402, "Client Error", "Payment Required"},
|
||||
{403, "Client Error", "Forbidden"},
|
||||
{404, "Client Error", "Not Found"},
|
||||
{405, "Client Error", "Method Not Allowed"},
|
||||
{406, "Client Error", "Not Acceptable"},
|
||||
{407, "Client Error", "Proxy Authentication Required"},
|
||||
{408, "Client Error", "Request Timeout"},
|
||||
{409, "Client Error", "Conflict"},
|
||||
{410, "Client Error", "Gone"},
|
||||
{411, "Client Error", "Length Required"},
|
||||
{412, "Client Error", "Precondition Failed"},
|
||||
{413, "Client Error", "Request Entity Too Large"},
|
||||
{414, "Client Error", "Request-URI Too Long"},
|
||||
{415, "Client Error", "Unsupported Media Type"},
|
||||
{416, "Client Error", "Range Not Satisfiable"},
|
||||
{417, "Client Error", "Expectation Failed"},
|
||||
|
||||
{500, "Server Error", "Internal Server Error"},
|
||||
{501, "Server Error", "Not Implemented"},
|
||||
{502, "Server Error", "Bad Gateway"},
|
||||
{503, "Server Error", "Service Unavailable"},
|
||||
{504, "Server Error", "Gateway Timeout"},
|
||||
{505, "Server Error", "HTTP Version Not Supported"},
|
||||
};
|
||||
|
||||
static const http_status_msg http_status_messages[] = {
|
||||
{100, "The client SHOULD continue with its request."},
|
||||
{101, "The server understands and is willing to comply with the clients request, via the Upgrade message header field, for a change in the application protocol being used on this connection."},
|
||||
|
||||
{200, "The request has succeeded."},
|
||||
{201, "The request has been fulfilled and resulted in a new resource being created."},
|
||||
{202, "The request has been accepted for processing, but the processing has not been completed."},
|
||||
{203, "The returned meta information in the entity-header is not the definitive set as available from the origin server, but is gathered from a local or a third-party copy."},
|
||||
{204, "The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta information."},
|
||||
{205, "The server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent."},
|
||||
{206, "The server has fulfilled the partial GET request for the resource."},
|
||||
|
||||
{300, "The requested resource corresponds to any one of a set of representations, each with its own specific location, and agent-driven negotiation information is being provided so that the user (or user agent) can select a preferred representation and redirect its request to that location."},
|
||||
{301, "The requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs."},
|
||||
{302, "The requested resource resides temporarily under a different URI."},
|
||||
{303, "The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource."},
|
||||
{304, "The request has been fulfilled and the requested resource has not been modified."},
|
||||
{305, "The requested resource MUST be accessed through the proxy given by the Location field."},
|
||||
{307, "The requested resource resides temporarily under a different URI."},
|
||||
{308, "The requested resource has been assigned a new permanent URI and any future references to this resource ought to use one of the enclosed URIs."},
|
||||
|
||||
{400, "The request could not be understood by the server due to malformed syntax."},
|
||||
{401, "The request requires user authentication."},
|
||||
{403, "The server understood the request, but is refusing to fulfill it."},
|
||||
{404, "The server has not found anything matching the Request-URI."},
|
||||
{405, "The method specified in the Request-Line is not allowed for the resource identified by the Request-URI."},
|
||||
{406, "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request."},
|
||||
{407, "The request requires user authentication on the proxy."},
|
||||
{408, "The client did not produce a request within the time that the server was prepared to wait."},
|
||||
{409, "The request could not be completed due to a conflict with the current state of the resource."},
|
||||
{410, "The requested resource is no longer available at the server and no forwarding address is known."},
|
||||
{411, "The server refuses to accept the request without a defined Content-Length."},
|
||||
{412, "The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server."},
|
||||
{413, "The server is refusing to process a request because the request entity is larger than the server is willing or able to process."},
|
||||
{414, "The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret."},
|
||||
{415, "The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method."},
|
||||
{416, "None of the ranges in the requests Range header field overlap the current extent of the selected resource or that the set of ranges requested has been rejected due to invalid ranges or an excessive request of small or overlapping ranges."},
|
||||
{417, "The expectation given in an Expect request-header field could not be met by this server, or, if the server is a proxy, the server has unambiguous evidence that the request could not be met by the next-hop server."},
|
||||
|
||||
{500, "The server encountered an unexpected condition which prevented it from fulfilling the request."},
|
||||
{501, "The server does not support the functionality required to fulfill the request."},
|
||||
{502, "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."},
|
||||
{503, "The server is currently unable to handle the request due to a temporary overloading or maintenance of the server."},
|
||||
{504, "The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI or some other auxiliary server it needed to access in attempting to complete the request."},
|
||||
{505, "The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."}
|
||||
};
|
||||
|
||||
static const char http_default_document[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang=\"en\">\n"
|
||||
"<head>\n"
|
||||
"\t<title>%1$i %2$s - %7$s</title>\n"
|
||||
"\t<meta charset=\"UTF-8\"/>\n"
|
||||
"\t<meta name=\"theme-color\" content=\"%6$s\"/>\n"
|
||||
"\t<meta name=\"color-scheme\" content=\"light dark\"/>\n"
|
||||
"\t<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
|
||||
"\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
|
||||
"\t<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicon.ico\"/>\n"
|
||||
"%5$s"
|
||||
"\t<style>\n"
|
||||
"\t\thtml{font-family:\"Arial\",sans-serif;--error:" HTTP_COLOR_ERROR ";--warning:" HTTP_COLOR_WARNING ";--success:" HTTP_COLOR_SUCCESS ";--info:" HTTP_COLOR_INFO ";--color:var(--%4$s);}\n"
|
||||
"\t\tbody{background-color:#F0F0F0;margin:0;}\n"
|
||||
"\t\tmain{max-width:650px;margin:2em auto;}\n"
|
||||
"\t\tsection{margin:1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em;}\n"
|
||||
"\t\th1,h2,h3,h4,h5,h6,h7{text-align:center;color:var(--color);font-weight:normal;}\n"
|
||||
"\t\th1{font-size:3em;margin:0.125em 0 0.125em 0;}\n"
|
||||
"\t\th2{font-size:1.5em;margin:0.25em 0 1em 0;}\n"
|
||||
"\t\tp{text-align:center;font-size:0.875em;}\n"
|
||||
"\t\tdiv.footer{color:#808080;font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}\n"
|
||||
"\t\tdiv.footer a{color:#808080;}\n"
|
||||
"\t\t@media(prefers-color-scheme:dark){\n"
|
||||
"\t\t\thtml{color:#FFFFFF;}\n"
|
||||
"\t\t\tbody{background-color:#101010;}\n"
|
||||
"\t\t\tsection{background-color:#181818;}\n"
|
||||
"\t\t}\n"
|
||||
"\t</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"\t<main>\n"
|
||||
"\t\t<section>\n"
|
||||
"%3$s"
|
||||
"\t\t\t<div class=\"footer\"><a href=\"https://%7$s/\">%7$s</a> - Necronda web server " NECRONDA_VERSION "</div>\n"
|
||||
"\t\t</section>\n"
|
||||
"\t</main>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
|
||||
static const char http_error_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :(</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
static const char http_error_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNDMDAwMDAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjooPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
|
||||
static const char http_warning_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :o</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
static const char http_warning_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNFMEMwMDAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjpvPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
|
||||
static const char http_success_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :)</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
static const char http_success_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiMwMDgwMDAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
|
||||
static const char http_info_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :)</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
static const char http_info_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiM2MDYwNjAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
|
||||
void http_to_camel_case(char *str, int mode);
|
||||
|
||||
void http_free_hdr(http_hdr *hdr);
|
||||
|
||||
void http_free_req(http_req *req);
|
||||
|
||||
void http_free_res(http_res *res);
|
||||
|
||||
int http_receive_request(sock *client, http_req *req);
|
||||
|
||||
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) ;
|
||||
|
||||
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
|
||||
|
||||
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
|
||||
|
||||
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode);
|
||||
|
||||
int http_send_response(sock *client, http_res *res);
|
||||
|
||||
int http_send_request(sock *server, http_req *req);
|
||||
|
||||
const http_status *http_get_status(unsigned short status_code);
|
||||
|
||||
const http_status_msg *http_get_error_msg(const http_status *status);
|
||||
|
||||
const char *http_get_status_color(const http_status *status);
|
||||
|
||||
char *http_format_date(time_t time, char *buf, size_t size);
|
||||
|
||||
char *http_get_date(char *buf, size_t size);
|
||||
|
||||
const http_doc_info *http_get_status_info(const http_status *status);
|
||||
|
||||
#endif //NECRONDA_SERVER_HTTP_H
|
383
src/necronda-server.c
Normal file
383
src/necronda-server.c
Normal file
@ -0,0 +1,383 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Main executable
|
||||
* src/necronda-server.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
|
||||
#include "necronda-server.h"
|
||||
|
||||
#include "config.c"
|
||||
#include "utils.c"
|
||||
#include "uri.c"
|
||||
#include "cache.c"
|
||||
#include "sock.c"
|
||||
#include "http.c"
|
||||
#include "rev_proxy.c"
|
||||
#include "client.c"
|
||||
#include "fastcgi.c"
|
||||
|
||||
|
||||
int active = 1;
|
||||
const char *config_file;
|
||||
|
||||
|
||||
void openssl_init() {
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
ERR_load_BIO_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n");
|
||||
int status = 0;
|
||||
int ret;
|
||||
int kills = 0;
|
||||
for (int i = 0; i < MAX_CHILDREN; i++) {
|
||||
if (children[i] != 0) {
|
||||
ret = waitpid(children[i], &status, WNOHANG);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
}
|
||||
} else {
|
||||
kill(children[i], SIGKILL);
|
||||
kills++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kills > 0) {
|
||||
fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills);
|
||||
}
|
||||
cache_unload();
|
||||
config_unload();
|
||||
exit(2);
|
||||
}
|
||||
|
||||
void terminate() {
|
||||
fprintf(stderr, "\nTerminating gracefully...\n");
|
||||
active = 0;
|
||||
|
||||
signal(SIGINT, destroy);
|
||||
signal(SIGTERM, destroy);
|
||||
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
shutdown(sockets[i], SHUT_RDWR);
|
||||
close(sockets[i]);
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
int wait_num = 0;
|
||||
int ret;
|
||||
for (int i = 0; i < MAX_CHILDREN; i++) {
|
||||
if (children[i] != 0) {
|
||||
ret = waitpid(children[i], &status, WNOHANG);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
}
|
||||
} else {
|
||||
kill(children[i], SIGTERM);
|
||||
wait_num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wait_num > 0) {
|
||||
fprintf(stderr, "Waiting for %i child process(es)...\n", wait_num);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_CHILDREN; i++) {
|
||||
if (children[i] != 0) {
|
||||
ret = waitpid(children[i], &status, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wait_num > 0) {
|
||||
// Wait another 50 ms to let child processes write to stdout/stderr
|
||||
signal(SIGINT, SIG_IGN);
|
||||
signal(SIGTERM, SIG_IGN);
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 50000000};
|
||||
nanosleep(&ts, &ts);
|
||||
fprintf(stderr, "\nGoodbye\n");
|
||||
} else {
|
||||
fprintf(stderr, "Goodbye\n");
|
||||
}
|
||||
cache_unload();
|
||||
config_unload();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
const int YES = 1;
|
||||
fd_set socket_fds, read_socket_fds;
|
||||
int max_socket_fd = 0;
|
||||
int ready_sockets_num;
|
||||
long client_num = 0;
|
||||
char buf[1024];
|
||||
int ret;
|
||||
|
||||
int client_fd;
|
||||
sock client;
|
||||
struct sockaddr_in6 client_addr;
|
||||
unsigned int client_addr_len = sizeof(client_addr);
|
||||
|
||||
memset(sockets, 0, sizeof(sockets));
|
||||
memset(children, 0, sizeof(children));
|
||||
memset(mmdbs, 0, sizeof(mmdbs));
|
||||
|
||||
struct timeval timeout;
|
||||
|
||||
const struct sockaddr_in6 addresses[2] = {
|
||||
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(80)},
|
||||
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)}
|
||||
};
|
||||
|
||||
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to set stdout to line-buffered mode: %s" CLR_STR, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
printf("Necronda Web Server " NECRONDA_VERSION "\n");
|
||||
|
||||
ret = config_init();
|
||||
if (ret != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
config_file = NULL;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
|
||||
printf("Usage: necronda-server [-h] [-c <CONFIG-FILE>]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -c, --config <CONFIG-FILE> path to the config file. If not provided, default will be used\n"
|
||||
" -h, --help print this dialogue\n");
|
||||
config_unload();
|
||||
return 0;
|
||||
} else if (strcmp(arg, "-c") == 0 || strcmp(arg, "--config") == 0) {
|
||||
if (i == argc - 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --config <CONFIG-FILE>" CLR_STR "\n", arg);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
config_file = argv[++i];
|
||||
} else {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument '%s'" CLR_STR "\n", arg);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file);
|
||||
if (ret != 0) {
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
sockets[0] = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (sockets[0] < 0) goto socket_err;
|
||||
sockets[1] = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (sockets[1] < 0) {
|
||||
socket_err:
|
||||
fprintf(stderr, ERR_STR "Unable to create socket: %s" CLR_STR "\n", strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to set options for socket %i: %s" CLR_STR "\n", i, strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(sockets[0], (struct sockaddr *) &addresses[0], sizeof(addresses[0])) < 0) goto bind_err;
|
||||
if (bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) < 0) {
|
||||
bind_err:
|
||||
fprintf(stderr, ERR_STR "Unable to bind socket to address: %s" CLR_STR "\n", strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
signal(SIGINT, terminate);
|
||||
signal(SIGTERM, terminate);
|
||||
|
||||
if (geoip_dir[0] != 0) {
|
||||
DIR *geoip = opendir(geoip_dir);
|
||||
if (geoip == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open GeoIP dir: %s" CLR_STR "\n", strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
struct dirent *dir;
|
||||
int i = 0;
|
||||
while ((dir = readdir(geoip)) != NULL) {
|
||||
if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".mmdb") != 0) continue;
|
||||
if (i >= MAX_MMDB) {
|
||||
fprintf(stderr, ERR_STR "Too many .mmdb files" CLR_STR "\n");
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
sprintf(buf, "%s/%s", geoip_dir, dir->d_name);
|
||||
ret = MMDB_open(buf, 0, &mmdbs[i]);
|
||||
if (ret != MMDB_SUCCESS) {
|
||||
fprintf(stderr, ERR_STR "Unable to open .mmdb file: %s" CLR_STR "\n", MMDB_strerror(ret));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i == 0) {
|
||||
fprintf(stderr, ERR_STR "No .mmdb files found in %s" CLR_STR "\n", geoip_dir);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
closedir(geoip);
|
||||
}
|
||||
|
||||
openssl_init();
|
||||
|
||||
client.buf = NULL;
|
||||
client.buf_len = 0;
|
||||
client.buf_off = 0;
|
||||
client.ctx = SSL_CTX_new(TLS_server_method());
|
||||
SSL_CTX_set_options(client.ctx, SSL_OP_SINGLE_DH_USE);
|
||||
SSL_CTX_set_verify(client.ctx, SSL_VERIFY_NONE, NULL);
|
||||
SSL_CTX_set_min_proto_version(client.ctx, TLS1_VERSION);
|
||||
SSL_CTX_set_mode(client.ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
SSL_CTX_set_cipher_list(client.ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
|
||||
SSL_CTX_set_ecdh_auto(client.ctx, 1);
|
||||
|
||||
rev_proxy.buf = NULL;
|
||||
rev_proxy.buf_len = 0;
|
||||
rev_proxy.buf_off = 0;
|
||||
rev_proxy.ctx = SSL_CTX_new(TLS_client_method());
|
||||
|
||||
if (SSL_CTX_use_certificate_chain_file(client.ctx, cert_file) != 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n",
|
||||
ERR_reason_error_string(ERR_get_error()), cert_file);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
if (SSL_CTX_use_PrivateKey_file(client.ctx, key_file, SSL_FILETYPE_PEM) != 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n",
|
||||
ERR_reason_error_string(ERR_get_error()), key_file);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to listen on socket %i: %s" CLR_STR "\n", i, strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
FD_ZERO(&socket_fds);
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
FD_SET(sockets[i], &socket_fds);
|
||||
if (sockets[i] > max_socket_fd) {
|
||||
max_socket_fd = sockets[i];
|
||||
}
|
||||
}
|
||||
|
||||
ret = cache_init();
|
||||
if (ret < 0) {
|
||||
config_unload();
|
||||
return 1;
|
||||
} else if (ret != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Ready to accept connections\n");
|
||||
|
||||
while (active) {
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
read_socket_fds = socket_fds;
|
||||
ready_sockets_num = select(max_socket_fd + 1, &read_socket_fds, NULL, NULL, &timeout);
|
||||
if (ready_sockets_num < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to select sockets: %s" CLR_STR "\n", strerror(errno));
|
||||
terminate();
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
if (FD_ISSET(sockets[i], &read_socket_fds)) {
|
||||
client_fd = accept(sockets[i], (struct sockaddr *) &client_addr, &client_addr_len);
|
||||
if (client_fd < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to accept connection: %s" CLR_STR "\n", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
// child
|
||||
signal(SIGINT, SIG_IGN);
|
||||
signal(SIGTERM, SIG_IGN);
|
||||
|
||||
client.socket = client_fd;
|
||||
client.enc = i == 1;
|
||||
return client_handler(&client, client_num, &client_addr);
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
client_num++;
|
||||
close(client_fd);
|
||||
for (int j = 0; j < MAX_CHILDREN; j++) {
|
||||
if (children[j] == 0) {
|
||||
children[j] = pid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
for (int i = 0; i < MAX_CHILDREN; i++) {
|
||||
if (children[i] != 0) {
|
||||
ret = waitpid(children[i], &status, WNOHANG);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* necronda-server.cpp - Main Executable
|
||||
* Lorenz Stechauner, 2018-05-09
|
||||
*/
|
||||
|
||||
|
||||
#include "necronda-server.h"
|
||||
#include <magic.h>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
#include <csignal>
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
const char* webroot = "/srv/necronda/";
|
||||
|
||||
|
||||
string getMimeType(string path) {
|
||||
|
||||
unsigned long pos = path.find_last_of('.');
|
||||
string ext;
|
||||
if (pos != string::npos) {
|
||||
ext = path.substr(pos + 1, path.length() - pos);
|
||||
}
|
||||
|
||||
magic_t magic = magic_open(MAGIC_MIME_TYPE);
|
||||
magic_load(magic, "/usr/share/misc/magic.mgc");
|
||||
string type = magic_file(magic, path.c_str());
|
||||
magic_setflags(magic, MAGIC_MIME_ENCODING);
|
||||
string charset = magic_file(magic, path.c_str());
|
||||
|
||||
if (type == "text/plain") {
|
||||
if (ext == "css") {
|
||||
type = "text/css";
|
||||
} else if (ext == "js") {
|
||||
type = "text/javascript";
|
||||
}
|
||||
}
|
||||
|
||||
magic_close(magic);
|
||||
|
||||
return type + "; charset=" + charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sun, 06 Nov 1994 08:49:37 GMT
|
||||
* @return
|
||||
*/
|
||||
|
||||
std::string getTimestamp(string path) {
|
||||
struct stat attrib;
|
||||
stat(path.c_str(), &attrib);
|
||||
return getTimestamp(attrib.st_ctime);
|
||||
}
|
||||
|
||||
std::string getTimestamp(time_t time) {
|
||||
char buffer[64];
|
||||
struct tm *timeinfo = gmtime(&time);
|
||||
strftime(buffer, sizeof(buffer), "%Y%m%d%H%M%S", timeinfo);
|
||||
return string(buffer);
|
||||
}
|
||||
|
||||
long getFileSize(string filename) {
|
||||
struct stat stat_buf;
|
||||
int rc = stat(filename.c_str(), &stat_buf);
|
||||
return rc == 0 ? stat_buf.st_size : -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a formatted time string
|
||||
* @param micros Delta time to be formatted
|
||||
* @return A formatted time string
|
||||
*/
|
||||
std::string formatTime(long micros) {
|
||||
char buffer[64];
|
||||
if (micros < 1000) {
|
||||
sprintf(buffer, "%.3f ms", micros / 1000.0);
|
||||
} else if (micros < 10000) {
|
||||
sprintf(buffer, "%.2f ms", micros / 1000.0);
|
||||
} else if (micros < 100000) {
|
||||
sprintf(buffer, "%.1f ms", micros / 1000.0);
|
||||
} else if (micros < 1000000) {
|
||||
sprintf(buffer, "%.0f ms", micros / 1000.0);
|
||||
} else {
|
||||
sprintf(buffer, "%.1f s", micros / 1000000.0);
|
||||
}
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
std::string formatSize(unsigned long bytes) {
|
||||
char buffer[64];
|
||||
if (bytes > 0x10000000000) {
|
||||
sprintf(buffer, "%.1f TiB", (double) bytes / 0x10000000000);
|
||||
} else if (bytes > 0x40000000) {
|
||||
sprintf(buffer, "%.1f GiB", (double) bytes / 0x40000000);
|
||||
} else if (bytes > 0x100000) {
|
||||
sprintf(buffer, "%.1f MiB", (double) bytes / 0x100000);
|
||||
} else if (bytes > 0x400) {
|
||||
sprintf(buffer, "%.1f KiB", (double) bytes / 0x400);
|
||||
} else {
|
||||
sprintf(buffer, "%ld B", bytes);
|
||||
}
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
string url_decode(string url) {
|
||||
long pos = 0;
|
||||
while ((pos = url.find('+', pos + 1)) != string::npos) {
|
||||
url.replace(pos, 1, 1, ' ');
|
||||
}
|
||||
pos = 0;
|
||||
while ((pos = url.find('%', pos + 1)) != string::npos) {
|
||||
const char *num = url.substr(pos + 1, 2).c_str();
|
||||
auto c = (char) strtol(num, nullptr, 16);
|
||||
url.erase(pos, 3);
|
||||
url.insert(pos, 1, c);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
string url_encode(string url) {
|
||||
char buff[4];
|
||||
for (long pos = 0; pos < url.length(); pos++) {
|
||||
auto c = (unsigned char) url[pos];
|
||||
if (c < ' ' || c > '~' || c == ' ' || c == '#' || c == '?' || c == '&' || c == '=' || c == '\\' || c == '%') {
|
||||
sprintf(buff, "%%%02X", c);
|
||||
url.replace(pos, 1, buff);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
string html_decode(string text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
string html_encode(string text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
string cli_encode(string text) {
|
||||
char buff[5];
|
||||
for (long pos = 0; pos < text.length(); pos++) {
|
||||
auto c = (unsigned char) text[pos];
|
||||
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == ',' || c == '.' || c == '_' || c == '+' || c == ':' || c == '@' || c == '%' || c == '/' || c == '-')) {
|
||||
sprintf(buff, "\\%.1s", &c);
|
||||
text.replace(pos, 1, buff);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
string read_line(FILE* file) {
|
||||
char *line;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
if ((read = getline(&line, &len, file)) < 0 || line == nullptr) {
|
||||
return "";
|
||||
}
|
||||
string l = string(line);
|
||||
if (l[l.length()-1] == '\n') {
|
||||
l.erase(l.length()-1);
|
||||
}
|
||||
if (l[l.length()-1] == '\r') {
|
||||
l.erase(l.length()-1);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
#include "procopen.cpp"
|
||||
#include "network/Address.cpp"
|
||||
#include "network/Socket.cpp"
|
||||
#include "URI.cpp"
|
||||
#include "network/http/Http.cpp"
|
||||
#include "network/http/HttpStatusCode.cpp"
|
||||
#include "network/http/HttpHeader.cpp"
|
||||
#include "network/http/HttpRequest.cpp"
|
||||
#include "network/http/HttpResponse.cpp"
|
||||
#include "network/http/HttpConnection.cpp"
|
||||
|
||||
string getWebRoot(string host) {
|
||||
string root = webroot + host;
|
||||
if (fileExists(root)) {
|
||||
return root;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#include "client.cpp"
|
||||
|
||||
|
||||
long clientnum = 0;
|
||||
|
||||
int main() {
|
||||
cout << "Necronda Server 3.0" << endl << "by Lorenz Stechauner" << endl << endl;
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
ERR_load_crypto_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
|
||||
int ret = system("mkdir -p /var/necronda /etc/necronda /tmp/necronda; touch /var/necronda/ETags");
|
||||
|
||||
if (ret != 0) {
|
||||
cout << "Unable to create server files" << endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
list<unsigned short> ports = {80, 443};
|
||||
|
||||
list<Socket> servers = {};
|
||||
auto it = ports.begin();
|
||||
|
||||
for (int i = 0; i < ports.size(); i++) {
|
||||
unsigned short port = *it;
|
||||
advance(it, 1);
|
||||
Socket server = Socket();
|
||||
servers.push_back(server);
|
||||
|
||||
try {
|
||||
server.setReuseAddress(true);
|
||||
server.setReceiveTimeout(0);
|
||||
server.setSendTimeout(0);
|
||||
} catch (char *msg) {
|
||||
cout << "Unable to set socket option: " << msg << endl;
|
||||
exit(2);
|
||||
}
|
||||
|
||||
try {
|
||||
server.bind(port);
|
||||
} catch (char *msg) {
|
||||
cout << "Unable to bind socket to port " << port << ": " << msg << endl;
|
||||
exit(3);
|
||||
}
|
||||
|
||||
try {
|
||||
server.listen(256);
|
||||
} catch (char *msg) {
|
||||
cout << "Unable to listen on socket: " << msg << endl;
|
||||
exit(4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cout << "Ready for connections" << endl;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
Socket::select(servers, {});
|
||||
for (Socket server : servers) {
|
||||
try {
|
||||
Socket *socket = server.accept();
|
||||
clientnum++;
|
||||
thread *t = new thread(client_handler, socket, clientnum, server.getSocketPort() == 443);
|
||||
} catch (char *msg) {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
} catch (char *msg) {
|
||||
cout << "Select: " << msg << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1,41 +1,86 @@
|
||||
//
|
||||
// Created by lorenz on 5/17/18.
|
||||
//
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Main executable (header file)
|
||||
* src/necronda-server.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#ifndef NECRONDA_SERVER_NECRONDA_SERVER_H
|
||||
#define NECRONDA_SERVER_NECRONDA_SERVER_H
|
||||
|
||||
#ifndef NECRONDA_SERVER
|
||||
#define NECRONDA_SERVER
|
||||
|
||||
using namespace std;
|
||||
|
||||
unsigned long getMicros();
|
||||
|
||||
string formatTime(long micros);
|
||||
|
||||
string formatSize(unsigned long bytes);
|
||||
|
||||
string getWebRoot(string host);
|
||||
|
||||
string getMimeType(string path);
|
||||
|
||||
string getTimestamp(string path);
|
||||
|
||||
string getTimestamp(time_t time);
|
||||
|
||||
long getFileSize(string filename);
|
||||
|
||||
string url_decode(string url);
|
||||
|
||||
string url_encode(string url);
|
||||
|
||||
string html_decode(string text);
|
||||
|
||||
string html_encode(string text);
|
||||
|
||||
string cli_encode(string text);
|
||||
|
||||
string read_line(FILE *file);
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/dh.h>
|
||||
#include <maxminddb.h>
|
||||
#include <dirent.h>
|
||||
|
||||
|
||||
#define NUM_SOCKETS 2
|
||||
#define MAX_CHILDREN 1024
|
||||
#define MAX_MMDB 3
|
||||
#define MAX_HOST_CONFIG 64
|
||||
#define LISTEN_BACKLOG 16
|
||||
#define REQ_PER_CONNECTION 100
|
||||
#define CLIENT_TIMEOUT 3600
|
||||
#define SERVER_TIMEOUT 4
|
||||
|
||||
#define CHUNK_SIZE 8192
|
||||
#define CLIENT_MAX_HEADER_SIZE 8192
|
||||
#define FILE_CACHE_SIZE 1024
|
||||
#define GEOIP_MAX_SIZE 8192
|
||||
|
||||
#define SHM_KEY_CACHE 255641
|
||||
#define SHM_KEY_CONFIG 255642
|
||||
|
||||
#define ERR_STR "\x1B[1;31m"
|
||||
#define CLR_STR "\x1B[0m"
|
||||
#define BLD_STR "\x1B[1m"
|
||||
#define WRN_STR "\x1B[1;33m"
|
||||
#define BLUE_STR "\x1B[34m"
|
||||
#define HTTP_STR "\x1B[1;31m"
|
||||
#define HTTPS_STR "\x1B[1;32m"
|
||||
|
||||
#define HTTP_1XX_STR "\x1B[1;32m"
|
||||
#define HTTP_2XX_STR "\x1B[1;32m"
|
||||
#define HTTP_3XX_STR "\x1B[1;33m"
|
||||
#define HTTP_4XX_STR "\x1B[1;31m"
|
||||
#define HTTP_5XX_STR "\x1B[1;31m"
|
||||
|
||||
#define NECRONDA_VERSION "4.3"
|
||||
#define SERVER_STR "Necronda/" NECRONDA_VERSION
|
||||
#define NECRONDA_ZLIB_LEVEL 9
|
||||
|
||||
#ifndef DEFAULT_HOST
|
||||
#define DEFAULT_HOST "www.necronda.net"
|
||||
#endif
|
||||
#ifndef MAGIC_FILE
|
||||
#define MAGIC_FILE "/usr/share/file/misc/magic.mgc"
|
||||
#endif
|
||||
#ifndef PHP_FPM_SOCKET
|
||||
#define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
|
||||
#endif
|
||||
#ifndef DEFAULT_CONFIG_FILE
|
||||
#define DEFAULT_CONFIG_FILE "/etc/necronda-server/necronda-server.conf"
|
||||
#endif
|
||||
|
||||
int sockets[NUM_SOCKETS];
|
||||
pid_t children[MAX_CHILDREN];
|
||||
MMDB_s mmdbs[MAX_MMDB];
|
||||
|
||||
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
|
||||
|
||||
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H
|
||||
|
@ -1,68 +0,0 @@
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <cstring>
|
||||
#include "Address.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
Address::Address() {
|
||||
|
||||
}
|
||||
|
||||
Address::Address(string addr) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
Address::Address(struct sockaddr_in *addr) {
|
||||
address = ntohl(addr->sin_addr.s_addr);
|
||||
}
|
||||
|
||||
|
||||
struct sockaddr_in Address::toStruct(unsigned short port)const {
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(address);
|
||||
addr.sin_port = htons(port);
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
string Address::toString() const {
|
||||
struct sockaddr_in addr = toStruct(0);
|
||||
struct in_addr ipAddr = addr.sin_addr;
|
||||
return inet_ntoa(ipAddr);
|
||||
}
|
||||
|
||||
bool Address::isLocal() {
|
||||
string a = toString();
|
||||
return a.find("127.0.0.") == 0;
|
||||
}
|
||||
|
||||
|
||||
ostream& operator<<(ostream &str, const Address &addr) {
|
||||
return str << addr.toString();
|
||||
}
|
||||
|
||||
string operator+(string &str, const Address &addr) {
|
||||
return str + addr.toString();
|
||||
}
|
||||
|
||||
string operator+(string &str, const Address *addr) {
|
||||
return str + addr->toString();
|
||||
}
|
||||
|
||||
string operator+(const Address &addr, string &str) {
|
||||
return addr.toString() + str;
|
||||
}
|
||||
|
||||
string operator+(const Address *addr, string &str) {
|
||||
return addr->toString() + str;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* HttpHeader.h - HttpHeader Class definition
|
||||
* Lorenz Stechauner, 2018-05-09
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_ADDRESS
|
||||
#define NECRONDA_ADDRESS
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Address {
|
||||
private:
|
||||
unsigned int address;
|
||||
|
||||
public:
|
||||
Address();
|
||||
|
||||
explicit Address(string address);
|
||||
|
||||
explicit Address(struct sockaddr_in *address);
|
||||
|
||||
struct sockaddr_in toStruct(unsigned short port) const;
|
||||
|
||||
string toString() const;
|
||||
|
||||
bool isLocal();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -1,661 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* Socket.cpp - Socket Class methods
|
||||
* Lorenz Stechauner, 2018-05-09
|
||||
*/
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <unistd.h>
|
||||
#include <sstream>
|
||||
#include <ctime>
|
||||
#include <poll.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <list>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "Address.h"
|
||||
#include "Socket.h"
|
||||
#include "http/Http.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
static void multi_ssl_init() {
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
ERR_load_crypto_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
}
|
||||
|
||||
|
||||
char *multi_ssl_get_error(SSL *ssl, int ret) {
|
||||
if (ret > 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned long ret2 = ERR_get_error();
|
||||
const char *err2 = strerror(errno);
|
||||
const char *err1 = ERR_reason_error_string(ret2);
|
||||
|
||||
switch (SSL_get_error(ssl, ret)) {
|
||||
case SSL_ERROR_NONE:
|
||||
return (char *) "none";
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
return (char *) "closed";
|
||||
case SSL_ERROR_WANT_READ:
|
||||
return (char *) "want_read";
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
return (char *) "want_write";
|
||||
case SSL_ERROR_WANT_CONNECT:
|
||||
return (char *) "want_connect";
|
||||
case SSL_ERROR_WANT_ACCEPT:
|
||||
return (char *) "want_accept";
|
||||
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||
return (char *) "want_x509_lookup";
|
||||
case SSL_ERROR_SYSCALL:
|
||||
return (char *) ((ret2 == 0) ? (ret == 0) ? "protocol violation" : err2 : err1);
|
||||
case SSL_ERROR_SSL:
|
||||
return (char *) err1;
|
||||
default:
|
||||
return (char *) "unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
char *strerror_socket(int nr) {
|
||||
if (nr == EAGAIN || nr == EWOULDBLOCK) {
|
||||
return (char *) "timeout";
|
||||
} else if (nr == ECONNRESET) {
|
||||
return (char *) "closed";
|
||||
} else {
|
||||
return strerror(nr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Socket::Socket(int fd) {
|
||||
this->fd = fd;
|
||||
microsStart = getMicros();
|
||||
microsLast = microsStart;
|
||||
bytesSent = 0;
|
||||
bytesReceived = 0;
|
||||
enc = false;
|
||||
ssl = nullptr;
|
||||
ctx = nullptr;
|
||||
clients = false;
|
||||
servers = false;
|
||||
}
|
||||
|
||||
Socket::Socket() {
|
||||
fd = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
enc = false;
|
||||
microsStart = getMicros();
|
||||
microsLast = microsStart;
|
||||
bytesSent = 0;
|
||||
bytesReceived = 0;
|
||||
ssl = nullptr;
|
||||
ctx = nullptr;
|
||||
clients = false;
|
||||
servers = false;
|
||||
}
|
||||
|
||||
int Socket::getFd() {
|
||||
return fd;
|
||||
}
|
||||
|
||||
void Socket::setSocketOption(int option, bool value = true) {
|
||||
int val = value ? 1 : 0;
|
||||
|
||||
if (::setsockopt(fd, SOL_SOCKET, option, &val, sizeof(val)) != 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::bind(Address *address, unsigned short port) {
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY; // address.
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::bind(unsigned short port) {
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::listen(int num) {
|
||||
if (::listen(fd, num) != 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::connect(Address, unsigned short) {
|
||||
|
||||
}
|
||||
|
||||
Socket* Socket::accept() {
|
||||
int newfd = ::accept(fd, nullptr, nullptr);
|
||||
if (newfd < 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
Socket *socket = new Socket(newfd);
|
||||
socket->servers = true;
|
||||
return socket;
|
||||
}
|
||||
|
||||
void Socket::close() {
|
||||
if (isSecured()) {
|
||||
//SSL_shutdown(ssl);
|
||||
SSL_free(ssl);
|
||||
SSL_CTX_free(ctx);
|
||||
}
|
||||
|
||||
if (::close(fd) != 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
Address *Socket::getPeerAddress() const {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
getpeername(fd, (struct sockaddr *) &addr, &len);
|
||||
struct sockaddr_in *s = (struct sockaddr_in *) &addr;
|
||||
return new Address(s);
|
||||
}
|
||||
|
||||
unsigned short Socket::getPeerPort() const {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
getpeername(fd, (struct sockaddr *) &addr, &len);
|
||||
return ntohs(((struct sockaddr_in *) &addr)->sin_port);
|
||||
}
|
||||
|
||||
Address *Socket::getSocketAddress() const {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
getsockname(fd, (struct sockaddr *) &addr, &len);
|
||||
struct sockaddr_in *s = (struct sockaddr_in *) &addr;
|
||||
return new Address(s);
|
||||
}
|
||||
|
||||
unsigned short Socket::getSocketPort() const {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
getsockname(fd, (struct sockaddr *) &addr, &len);
|
||||
return ntohs(((struct sockaddr_in *) &addr)->sin_port);
|
||||
}
|
||||
|
||||
|
||||
void Socket::setReuseAddress(bool value) {
|
||||
setSocketOption(SO_REUSEADDR, value);
|
||||
}
|
||||
|
||||
void Socket::setReusePort(bool value) {
|
||||
setSocketOption(SO_REUSEPORT, value);
|
||||
}
|
||||
|
||||
|
||||
string Socket::toString() const {
|
||||
return "{[Socket]" + getSocketAddress()->toString() + ":" + to_string(getSocketPort()) + "<->" +
|
||||
getPeerAddress()->toString() + ":" + to_string(getPeerPort()) + "}";
|
||||
}
|
||||
|
||||
long Socket::send(string *str) {
|
||||
return send(str->c_str(), str->length());
|
||||
}
|
||||
|
||||
long Socket::send(string str) {
|
||||
return send(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
long Socket::send(const char *str, long length) {
|
||||
return send((void*) str, length);
|
||||
}
|
||||
|
||||
long Socket::send(const char *str) {
|
||||
return send(str, strlen(str));
|
||||
}
|
||||
|
||||
long Socket::send(FILE *file) {
|
||||
char buffer[CPPNET_CHUNK];
|
||||
long all_len = 0;
|
||||
long len = 0;
|
||||
do {
|
||||
len = fread(buffer, 1, CPPNET_CHUNK, file);
|
||||
send(buffer, len);
|
||||
all_len += len;
|
||||
} while (len > 0 && len == CPPNET_CHUNK);
|
||||
return all_len;
|
||||
}
|
||||
|
||||
Socket::~Socket() {
|
||||
|
||||
}
|
||||
|
||||
long Socket::receive(void *buffer, int size) {
|
||||
long len;
|
||||
if (isSecured()) {
|
||||
len = SSL_read(ssl, buffer, size);
|
||||
if (len < 0) {
|
||||
throw multi_ssl_get_error(ssl, (int) len);
|
||||
}
|
||||
} else {
|
||||
len = recv(fd, buffer, (size_t) size, 0);
|
||||
if (len < 0) {
|
||||
throw strerror_socket(errno);
|
||||
}
|
||||
}
|
||||
bytesReceived += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
long Socket::peek(void *buffer, int size) {
|
||||
long len;
|
||||
if (isSecured()) {
|
||||
len = SSL_peek(ssl, buffer, size);
|
||||
if (len < 0) {
|
||||
throw multi_ssl_get_error(ssl, (int) len);
|
||||
}
|
||||
} else {
|
||||
len = recv(fd, buffer, (size_t) size, MSG_PEEK);
|
||||
if (len < 0) {
|
||||
throw strerror_socket(errno);
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
long Socket::send(void *buffer, int size) {
|
||||
long len;
|
||||
if (isSecured()) {
|
||||
if (size != 0) {
|
||||
len = SSL_write(ssl, buffer, size);
|
||||
if (len <= 0) {
|
||||
throw multi_ssl_get_error(ssl, (int) len);
|
||||
}
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
} else {
|
||||
len = ::send(fd, buffer, (size_t) size, 0);
|
||||
if (len < 0) {
|
||||
throw strerror_socket(errno);
|
||||
}
|
||||
}
|
||||
bytesSent += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
string Socket::receive() {
|
||||
string *str = new string();
|
||||
|
||||
char buffer[CPPNET_CHUNK];
|
||||
long len = 0;
|
||||
do {
|
||||
len = receive((void*) buffer, CPPNET_CHUNK);
|
||||
str->append(buffer, (unsigned) len);
|
||||
} while (len > 0 && len == CPPNET_CHUNK);
|
||||
|
||||
return *str;
|
||||
}
|
||||
|
||||
string Socket::receive(long length) {
|
||||
string *str = new string();
|
||||
|
||||
char buffer[CPPNET_CHUNK];
|
||||
long len = 0;
|
||||
long reclen = 0;
|
||||
do {
|
||||
len = receive((void*) buffer, CPPNET_CHUNK);
|
||||
reclen += len;
|
||||
str->append(buffer, (unsigned) len);
|
||||
} while (reclen < length);
|
||||
|
||||
return *str;
|
||||
}
|
||||
|
||||
string Socket::receive(string until) {
|
||||
string *str = new string();
|
||||
|
||||
struct pollfd ufds[1];
|
||||
ufds[0].fd = fd;
|
||||
ufds[0].events = POLLIN | POLLOUT;
|
||||
|
||||
char buffer[CPPNET_CHUNK];
|
||||
long len = 0;
|
||||
do {
|
||||
len = peek((void*) buffer, CPPNET_CHUNK);
|
||||
if (len != 0) {
|
||||
string s = string(buffer, (size_t) len);
|
||||
size_t found = s.find(until);
|
||||
long l = (found != string::npos) ? found + 1 : len;
|
||||
long l2 = (found != string::npos) ? found : len;
|
||||
str->append(buffer, (unsigned) l2);
|
||||
receive((void *) buffer, (int) l);
|
||||
if (found != string::npos) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (poll(ufds, 1, 0) < 0) {
|
||||
throw strerror_socket(errno);
|
||||
} else if ((ufds[0].revents & POLLIN) == 0) {
|
||||
if ((ufds[0].revents & POLLOUT) != 0) {
|
||||
throw (char *) "error";
|
||||
} else {
|
||||
throw (char *) "want_write";
|
||||
}
|
||||
} else if ((ufds[0].revents & POLLERR) != 0) {
|
||||
throw (char *) "error";
|
||||
} else if (ufds[0].revents & (POLLRDHUP | POLLHUP | POLLNVAL) != 0) {
|
||||
throw (char *) "closed";
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return *str;
|
||||
}
|
||||
|
||||
string Socket::receive(const char *until) {
|
||||
return receive(until, (int) (strlen(until)));
|
||||
}
|
||||
|
||||
string Socket::receive(const char *until, unsigned long strlen) {
|
||||
return receive(string(until, strlen));
|
||||
}
|
||||
|
||||
long Socket::receive(FILE *file) {
|
||||
char buffer[CPPNET_CHUNK];
|
||||
long len;
|
||||
long rec = 0;
|
||||
do {
|
||||
len = receive((void*) buffer, CPPNET_CHUNK);
|
||||
fwrite(buffer, 1, CPPNET_CHUNK, file);
|
||||
rec += len;
|
||||
} while (len > 0 && len == CPPNET_CHUNK);
|
||||
return len;
|
||||
}
|
||||
|
||||
long Socket::receive(FILE *file, long size) {
|
||||
char buffer[CPPNET_CHUNK];
|
||||
long len = 0;
|
||||
long rec = 0;
|
||||
while (size > rec) {
|
||||
len = receive((void*) buffer, (CPPNET_CHUNK > (size - rec) && size >= 0)?(size - rec):CPPNET_CHUNK);
|
||||
fwrite(buffer, 1, len, file);
|
||||
rec += len;
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
string Socket::receiveLine() {
|
||||
string str = receive("\n");
|
||||
if (str.length() > 0 && str.at(str.length() - 1) == '\r') {
|
||||
str = str.substr(0, str.length() - 1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
long Socket::getDuration() {
|
||||
return getMicros() - microsStart;
|
||||
}
|
||||
|
||||
|
||||
void Socket::setReceiveTimeout(unsigned long ms) {
|
||||
struct timeval timeout;
|
||||
if (ms == 0) {
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 1;
|
||||
} else {
|
||||
timeout.tv_sec = ms / 1000;
|
||||
timeout.tv_usec = (ms % 1000) * 1000;
|
||||
}
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)) < 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::setSendTimeout(unsigned long ms) {
|
||||
struct timeval timeout;
|
||||
if (ms == 0) {
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 1;
|
||||
} else {
|
||||
timeout.tv_sec = ms / 1000;
|
||||
timeout.tv_usec = (ms % 1000) * 1000;
|
||||
}
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(timeout)) < 0) {
|
||||
throw strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
bool Socket::isServerSide() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
bool Socket::isSecured() {
|
||||
return enc;
|
||||
}
|
||||
|
||||
bool Socket::isClientSide() {
|
||||
return clients;
|
||||
}
|
||||
|
||||
void Socket::sslHandshake(map<string, KeyPair> sni) {
|
||||
/*if (isSecured()) {
|
||||
throw (char *) "Socket already secured";
|
||||
}
|
||||
|
||||
const SSL_METHOD *method;
|
||||
if (isServerSide()) {
|
||||
method = TLSv1_2_server_method();
|
||||
} else if (isClientSide()) {
|
||||
method = TLSv1_2_client_method();
|
||||
} else {
|
||||
method = TLSv1_2_method();
|
||||
}
|
||||
|
||||
SSL_CTX *ctx = SSL_CTX_new(method);
|
||||
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
|
||||
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||
|
||||
const char *certfile = keypair.fullchain.c_str();
|
||||
const char *keyfile = keypair.privkey.c_str();
|
||||
|
||||
if (isServerSide()) {
|
||||
if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) != 1) {
|
||||
throw (char *) ERR_reason_error_string(ERR_get_error());
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) {
|
||||
throw (char *) ERR_reason_error_string(ERR_get_error());
|
||||
}
|
||||
}
|
||||
|
||||
SSL_CTX_set_tlsext_servername_callback
|
||||
|
||||
this->ctx = ctx;
|
||||
this->ssl = SSL_new(ctx);
|
||||
SSL_set_fd(ssl, fd);
|
||||
enc = true;
|
||||
|
||||
while (true) {
|
||||
int ret = 0;
|
||||
if (isServerSide()) {
|
||||
ret = SSL_accept(ssl);
|
||||
} else if (isClientSide()) {
|
||||
ret = SSL_connect(ssl);
|
||||
} else {
|
||||
ret = SSL_do_handshake(ssl);
|
||||
}
|
||||
|
||||
if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) ||
|
||||
(isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) {
|
||||
throw multi_ssl_get_error(ssl, ret);
|
||||
} else if (ret == 1) {
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
void Socket::sslHandshake() {
|
||||
sslHandshake(KeyPair{"", ""});
|
||||
}
|
||||
|
||||
void Socket::sslHandshake(KeyPair keypair) {
|
||||
if (isSecured()) {
|
||||
throw (char *) "Socket already secured";
|
||||
}
|
||||
|
||||
const SSL_METHOD *method;
|
||||
if (isServerSide()) {
|
||||
method = TLSv1_2_server_method();
|
||||
} else if (isClientSide()) {
|
||||
method = TLSv1_2_client_method();
|
||||
} else {
|
||||
method = TLSv1_2_method();
|
||||
}
|
||||
|
||||
SSL_CTX *ctx = SSL_CTX_new(method);
|
||||
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
|
||||
SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); // TLS1_VERSION
|
||||
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
|
||||
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||
|
||||
const char *certfile = keypair.fullchain.c_str();
|
||||
const char *keyfile = keypair.privkey.c_str();
|
||||
|
||||
if (isServerSide()) {
|
||||
if (SSL_CTX_use_certificate_chain_file(ctx, certfile) != 1) {
|
||||
throw (char *) ERR_reason_error_string(ERR_get_error());
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) {
|
||||
throw (char *) ERR_reason_error_string(ERR_get_error());
|
||||
}
|
||||
}
|
||||
|
||||
this->ctx = ctx;
|
||||
this->ssl = SSL_new(ctx);
|
||||
SSL_set_fd(ssl, fd);
|
||||
enc = true;
|
||||
|
||||
while (true) {
|
||||
int ret = 0;
|
||||
if (isServerSide()) {
|
||||
ret = SSL_accept(ssl);
|
||||
} else if (isClientSide()) {
|
||||
ret = SSL_connect(ssl);
|
||||
} else {
|
||||
ret = SSL_do_handshake(ssl);
|
||||
}
|
||||
|
||||
if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) ||
|
||||
(isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) {
|
||||
throw multi_ssl_get_error(ssl, ret);
|
||||
} else if (ret == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Socket::sslHandshake(string privkey, string fullchain) {
|
||||
sslHandshake(KeyPair{std::move(privkey), std::move(fullchain)});
|
||||
}
|
||||
|
||||
long Socket::select(list<Socket> read, list<Socket> write, long millis) {
|
||||
fd_set readfd, writefd;
|
||||
int maxfd = 0;
|
||||
FD_ZERO(&readfd);
|
||||
FD_ZERO(&writefd);
|
||||
|
||||
for (Socket s : read) {
|
||||
if (s.fd > maxfd) {
|
||||
maxfd = s.fd;
|
||||
}
|
||||
FD_SET(s.fd, &readfd);
|
||||
}
|
||||
|
||||
for (Socket s : write) {
|
||||
if (s.fd > maxfd) {
|
||||
maxfd = s.fd;
|
||||
}
|
||||
FD_SET(s.fd, &writefd);
|
||||
}
|
||||
|
||||
struct timeval *tv = new struct timeval;
|
||||
if (millis < 0) {
|
||||
tv = nullptr;
|
||||
} else if (millis == 0) {
|
||||
tv->tv_sec = 0;
|
||||
tv->tv_usec = 1;
|
||||
} else {
|
||||
tv->tv_sec = millis / 1000;
|
||||
tv->tv_usec = (millis % 1000) * 1000;
|
||||
}
|
||||
|
||||
int ret = ::select(maxfd + 1, &readfd, &writefd, nullptr, tv);
|
||||
if (ret < 0) {
|
||||
throw (char *) strerror(errno);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
long Socket::select(list<Socket> read, list<Socket> write) {
|
||||
Socket::select(std::move(read), std::move(write), -1);
|
||||
}
|
||||
|
||||
unsigned long Socket::getBytesSent() {
|
||||
return bytesSent;
|
||||
}
|
||||
|
||||
unsigned long Socket::getBytesReceived() {
|
||||
return bytesReceived;
|
||||
}
|
||||
|
||||
|
||||
ostream &operator<<(ostream &str, const Socket &socket) {
|
||||
return str << socket.toString();
|
||||
}
|
||||
|
||||
ostream &operator<<(ostream &str, const Socket *socket) {
|
||||
return str << socket->toString();
|
||||
}
|
||||
|
||||
string operator+(string &str, const Socket &socket) {
|
||||
return str + socket.toString();
|
||||
}
|
||||
|
||||
string operator+(const Socket &socket, string &str) {
|
||||
return socket.toString() + str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,174 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* Socket.h - Socket Class definition
|
||||
* Lorenz Stechauner, 2018-05-09
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SOCKET
|
||||
#define NECRONDA_SOCKET
|
||||
|
||||
#include <map>
|
||||
|
||||
#define CPPNET_CHUNK 16384
|
||||
|
||||
typedef struct {
|
||||
string privkey;
|
||||
string fullchain;
|
||||
} KeyPair;
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
class Socket {
|
||||
private:
|
||||
int fd;
|
||||
SSL *ssl;
|
||||
SSL_CTX *ctx;
|
||||
bool enc;
|
||||
bool servers;
|
||||
bool clients;
|
||||
unsigned long bytesSent;
|
||||
unsigned long bytesReceived;
|
||||
long microsStart;
|
||||
long microsLast;
|
||||
|
||||
void setSocketOption(int, bool);
|
||||
|
||||
long send(void *buffer, int size);
|
||||
|
||||
long receive(void *buffer, int size);
|
||||
|
||||
long peek(void *buffer, int size);
|
||||
|
||||
public:
|
||||
Socket();
|
||||
|
||||
explicit Socket(int filedescriptor);
|
||||
|
||||
~Socket();
|
||||
|
||||
void bind(Address *address, unsigned short port);
|
||||
|
||||
void bind(unsigned short port);
|
||||
|
||||
void listen(int count = 1);
|
||||
|
||||
void connect(Address address, unsigned short port);
|
||||
|
||||
Socket* accept();
|
||||
|
||||
void sslHandshake();
|
||||
|
||||
void sslHandshake(map<string, KeyPair> sni);
|
||||
|
||||
void sslHandshake(KeyPair keypair);
|
||||
|
||||
void sslHandshake(string privkey, string fullchain);
|
||||
|
||||
long send(string *str);
|
||||
|
||||
long send(string str);
|
||||
|
||||
long send(const char *str);
|
||||
|
||||
long send(const char *str, long length);
|
||||
|
||||
long send(FILE *file);
|
||||
|
||||
string receive();
|
||||
|
||||
string receive(long length);
|
||||
|
||||
string receive(string until);
|
||||
|
||||
string receive(const char *until, unsigned long strlen);
|
||||
|
||||
string receive(const char *until);
|
||||
|
||||
long receive(FILE *file);
|
||||
|
||||
string receiveLine();
|
||||
|
||||
void shutdown();
|
||||
|
||||
void close();
|
||||
|
||||
int getFd();
|
||||
|
||||
long getDuration();
|
||||
|
||||
Address *getSocketAddress() const;
|
||||
|
||||
unsigned short getSocketPort() const;
|
||||
|
||||
Address *getPeerAddress() const;
|
||||
|
||||
unsigned short getPeerPort() const;
|
||||
|
||||
string toString() const;
|
||||
|
||||
|
||||
bool isServerSide();
|
||||
|
||||
bool isClientSide();
|
||||
|
||||
bool isSecured();
|
||||
|
||||
|
||||
void setReuseAddress(bool value = true);
|
||||
|
||||
void setReusePort(bool value = true);
|
||||
|
||||
void setSendBufferSize(int value);
|
||||
|
||||
void setReceiveBufferSize(int value);
|
||||
|
||||
void setMinReceiveBytes(int value);
|
||||
|
||||
void setMinSendBytes(int value);
|
||||
|
||||
void setSendTimeout(unsigned long ms);
|
||||
|
||||
void setReceiveTimeout(unsigned long ms);
|
||||
|
||||
|
||||
bool getReuseAddress();
|
||||
|
||||
bool getReusePort();
|
||||
|
||||
int getSendBufferSize();
|
||||
|
||||
int getReceiveBufferSize();
|
||||
|
||||
int getMinReceiveBytes();
|
||||
|
||||
int getMinSendBytes();
|
||||
|
||||
long getSendTimeout();
|
||||
|
||||
long getReceiveTimeout();
|
||||
|
||||
unsigned long getBytesSent();
|
||||
|
||||
unsigned long getBytesReceived();
|
||||
|
||||
static long select(list<Socket> read, list<Socket> write, long millis);
|
||||
|
||||
static long select(list<Socket> read, list<Socket> write);
|
||||
|
||||
long receive(FILE *file, long size);
|
||||
};
|
||||
|
||||
Socket operator<<(Socket sock, const char *str);
|
||||
|
||||
Socket operator<<(Socket sock, string str);
|
||||
|
||||
ostream &operator<<(ostream &str, const Socket &socket);
|
||||
|
||||
ostream &operator<<(ostream &str, const Socket *socket);
|
||||
|
||||
string operator+(string &str, const Socket &socket);
|
||||
|
||||
string operator+(const Socket &socket, string &str);
|
||||
|
||||
#endif
|
@ -1,32 +0,0 @@
|
||||
//
|
||||
// Created by lorenz on 7/10/18.
|
||||
//
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include "Http.h"
|
||||
|
||||
unsigned long getMicros() {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
return (unsigned long) (1000000 * tv.tv_sec + tv.tv_usec);
|
||||
}
|
||||
|
||||
string getHttpDate() {
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
return getHttpDate(rawtime);
|
||||
}
|
||||
|
||||
string getHttpDate(string filename) {
|
||||
struct stat attrib;
|
||||
stat(filename.c_str(), &attrib);
|
||||
return getHttpDate(attrib.st_ctime);
|
||||
}
|
||||
|
||||
string getHttpDate(time_t time) {
|
||||
char buffer[64];
|
||||
struct tm *timeinfo = gmtime(&time);
|
||||
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
|
||||
return string(buffer);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// Created by lorenz on 7/10/18.
|
||||
//
|
||||
|
||||
#ifndef CPPNET_HTTP_H
|
||||
#define CPPNET_HTTP_H
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
unsigned long getMicros();
|
||||
|
||||
string getHttpDate(time_t time);
|
||||
|
||||
string getHttpDate();
|
||||
|
||||
string getHttpDate(string filename);
|
||||
|
||||
|
||||
#endif //CPPNET_HTTP_H
|
@ -1,194 +0,0 @@
|
||||
|
||||
|
||||
#include <zlib.h>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include "HttpConnection.h"
|
||||
#include "../Socket.h"
|
||||
#include "HttpStatusCode.h"
|
||||
#include "Http.h"
|
||||
|
||||
|
||||
HttpConnection::HttpConnection() = default;
|
||||
|
||||
HttpConnection::HttpConnection(Socket *socket) {
|
||||
this->socket = socket;
|
||||
this->request = new HttpRequest(socket);
|
||||
this->response = new HttpResponse();
|
||||
microsStart = getMicros();
|
||||
response->setVersion("1.1");
|
||||
response->setField("Server", "Necronda/3.0");
|
||||
}
|
||||
|
||||
void HttpConnection::respond(int statuscode) {
|
||||
if (statuscode >= 400 && statuscode < 600) {
|
||||
respond(statuscode,
|
||||
"<!DOCTYPE html><html><head><title>" + to_string(statuscode) + " " +
|
||||
::getStatusCode(statuscode).message +
|
||||
"</title></head><body><center><h1>" + to_string(statuscode) + " " +
|
||||
::getStatusCode(statuscode).message +
|
||||
"</h1>" +
|
||||
((request->isExistingField("Host")) ?
|
||||
(request->isExistingField("Referer") &&
|
||||
request->getField("Referer").find(request->getField("Host")) != string::npos) ?
|
||||
"<p>Go back to the last page you visited: <a href=\"" + request->getField("Referer") + "\">" +
|
||||
request->getField("Referer") + "</a></p>" :
|
||||
"<p>Go back to the home page of <a href=\"//" +
|
||||
request->getField("Host") + "/\">" +
|
||||
request->getField("Host") +
|
||||
"</a></p>" : "") + "</center></body></html>\r\n"
|
||||
);
|
||||
} else {
|
||||
respond(statuscode, "");
|
||||
}
|
||||
}
|
||||
|
||||
void HttpConnection::respond(int statuscode, string payload) {
|
||||
response->setStatusCode(statuscode);
|
||||
response->setField("Date", getHttpDate());
|
||||
response->setField("Content-Length", to_string(payload.length()));
|
||||
response->sendHeader(socket);
|
||||
socket->send(std::move(payload));
|
||||
}
|
||||
|
||||
void HttpConnection::respond(int statuscode, FILE *file, bool compress, long start, long end) {
|
||||
response->setStatusCode(statuscode);
|
||||
response->setField("Transfer-Encoding", "chunked");
|
||||
response->setField("Date", getHttpDate());
|
||||
|
||||
long shouldTransfer;
|
||||
long transfered = 0;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long len = ftell(file);
|
||||
|
||||
if (start != -1 && end != -1) {
|
||||
fseek(file, start, SEEK_SET);
|
||||
response->setField("Content-Length", to_string(end - start + 1));
|
||||
shouldTransfer = end - start + 1;
|
||||
compress = false;
|
||||
} else {
|
||||
fseek(file, 0, SEEK_SET);
|
||||
shouldTransfer = len;
|
||||
if (len >= 0 && !compress) {
|
||||
response->setField("Content-Length", to_string(len));
|
||||
}
|
||||
}
|
||||
|
||||
if (compress) {
|
||||
response->setField("Content-Encoding", "deflate");
|
||||
}
|
||||
|
||||
response->sendHeader(socket);
|
||||
|
||||
if (compress) {
|
||||
int level = 1;
|
||||
int ret, flush;
|
||||
unsigned have;
|
||||
z_stream strm;
|
||||
unsigned char in[CPPNET_CHUNK];
|
||||
unsigned char out[CPPNET_CHUNK];
|
||||
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
ret = deflateInit(&strm, level);
|
||||
if (ret != Z_OK) {
|
||||
throw (char *) "Unable to open file";
|
||||
}
|
||||
|
||||
do {
|
||||
strm.avail_in = (uInt) fread(in, 1, CPPNET_CHUNK, file);
|
||||
|
||||
if (ferror(file)) {
|
||||
(void) deflateEnd(&strm);
|
||||
throw (char *) strerror(errno);
|
||||
}
|
||||
flush = feof(file) ? Z_FINISH : Z_NO_FLUSH;
|
||||
strm.next_in = in;
|
||||
do {
|
||||
strm.avail_out = CPPNET_CHUNK;
|
||||
strm.next_out = out;
|
||||
ret = deflate(&strm, flush);
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
have = CPPNET_CHUNK - strm.avail_out;
|
||||
|
||||
if (have != 0) {
|
||||
char buffer[64];
|
||||
sprintf(buffer, "%X\r\n", have);
|
||||
socket->send(buffer);
|
||||
socket->send((const char *) out, have);
|
||||
socket->send("\r\n");
|
||||
}
|
||||
} while (strm.avail_out == 0);
|
||||
assert(strm.avail_in == 0);
|
||||
} while (flush != Z_FINISH);
|
||||
assert(ret == Z_STREAM_END);
|
||||
socket->send("0\r\n\r\n");
|
||||
deflateEnd(&strm);
|
||||
} else {
|
||||
char buffer[CPPNET_CHUNK];
|
||||
char buff[64];
|
||||
while (true) {
|
||||
unsigned long size = fread(buffer, 1, (size_t) ((CPPNET_CHUNK > (shouldTransfer - transfered) && shouldTransfer > 0) ? (shouldTransfer - transfered) : CPPNET_CHUNK), file);
|
||||
transfered += size;
|
||||
sprintf(buff, "%lX\r\n", size);
|
||||
socket->send(buff);
|
||||
socket->send((const char *) buffer, size);
|
||||
socket->send("\r\n");
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string HttpConnection::getField(string index) {
|
||||
return request->getField(std::move(index));
|
||||
}
|
||||
|
||||
string HttpConnection::getPath() {
|
||||
return request->getPath();
|
||||
}
|
||||
|
||||
void HttpConnection::setField(string index, string data) {
|
||||
response->setField(std::move(index), std::move(data));
|
||||
}
|
||||
|
||||
bool HttpConnection::isExistingField(string index) {
|
||||
return request->isExistingField(std::move(index));
|
||||
}
|
||||
|
||||
string HttpConnection::getMethod() {
|
||||
return request->getMethod();
|
||||
}
|
||||
|
||||
long HttpConnection::getDuration() {
|
||||
return getMicros() - microsStart;
|
||||
}
|
||||
|
||||
HttpStatusCode HttpConnection::getStatusCode() {
|
||||
return response->getStatusCode();
|
||||
}
|
||||
|
||||
void HttpConnection::redirect(int statuscode, string location) {
|
||||
setField("Location", std::move(location));
|
||||
respond(statuscode, "");
|
||||
}
|
||||
|
||||
string HttpConnection::getResponseField(string index) {
|
||||
return response->getField(std::move(index));
|
||||
}
|
||||
|
||||
bool HttpConnection::isExistingResponseField(string index) {
|
||||
return response->isExistingField(std::move(index));
|
||||
}
|
||||
|
||||
string HttpConnection::cgiExport() {
|
||||
return request->cgiExport();
|
||||
}
|
||||
|
||||
void HttpConnection::removeField(string index) {
|
||||
response->removeField(std::move(index));
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
|
||||
|
||||
#ifndef NECRONDA_HTTP_CONNECTION
|
||||
#define NECRONDA_HTTP_CONNECTION
|
||||
|
||||
|
||||
#include "../Socket.h"
|
||||
#include "HttpResponse.h"
|
||||
#include "HttpRequest.h"
|
||||
|
||||
|
||||
class HttpConnection {
|
||||
private:
|
||||
Socket *socket{};
|
||||
HttpRequest *request{};
|
||||
HttpResponse *response{};
|
||||
long microsStart{};
|
||||
|
||||
public:
|
||||
explicit HttpConnection();
|
||||
|
||||
explicit HttpConnection(Socket *socket);
|
||||
|
||||
void respond(int statuscode);
|
||||
|
||||
void respond(int statuscode, string payload);
|
||||
|
||||
void respond(int statuscode, FILE *file, bool compress = false, long start = -1, long end = -1);
|
||||
|
||||
void redirect(int statuscode, string location);
|
||||
|
||||
bool isExistingField(string index);
|
||||
|
||||
bool isExistingResponseField(string index);
|
||||
|
||||
string getField(string index);
|
||||
|
||||
string getResponseField(string index);
|
||||
|
||||
string getPath();
|
||||
|
||||
string getMethod();
|
||||
|
||||
void setField(string index, string data);
|
||||
|
||||
long getDuration();
|
||||
|
||||
HttpStatusCode getStatusCode();
|
||||
|
||||
string cgiExport();
|
||||
|
||||
void removeField(string index);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,122 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* HttpHeader.cpp - HttpHeader Class methods
|
||||
* Lorenz Stechauner, 2018-05-09
|
||||
*/
|
||||
|
||||
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include "../Socket.h"
|
||||
|
||||
#include "HttpHeader.h"
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
string to_cgi(string text) {
|
||||
for (auto & c: text) c = (char) toupper(c);
|
||||
long pos = 0;
|
||||
while ((pos = text.find('-', pos + 1)) != string::npos) {
|
||||
text.replace(pos, 1, 1, '_');
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*/
|
||||
HttpHeader::HttpHeader() {
|
||||
fields = fields;
|
||||
}
|
||||
|
||||
HttpHeader::HttpHeader(Socket *socket) : HttpHeader::HttpHeader() {
|
||||
parse(socket);
|
||||
}
|
||||
|
||||
|
||||
void HttpHeader::parse(Socket *socket) {
|
||||
while (true) {
|
||||
string line = socket->receiveLine();
|
||||
if (line.length() == 0) {
|
||||
break;
|
||||
} else {
|
||||
unsigned long pos = line.find(':');
|
||||
if (pos == string::npos) {
|
||||
throw (char *) "Malformed header";
|
||||
}
|
||||
string index = line.substr(0, pos);
|
||||
string data = line.substr(pos + 1, line.length() - pos);
|
||||
while (index[0] == ' ') index.erase(index.begin() + 0);
|
||||
while (index[index.length() - 1] == ' ') index.erase(index.end() - 1);
|
||||
while (data[0] == ' ') data.erase(data.begin() + 0);
|
||||
while (data[data.length() - 1] == ' ') data.erase(data.end() - 1);
|
||||
setField(index, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default Destructor
|
||||
*/
|
||||
HttpHeader::~HttpHeader() {
|
||||
fields.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a field in the HTTP header
|
||||
* e.g. Content-Length: 42
|
||||
* @param index The field index
|
||||
* @param data The field data
|
||||
*/
|
||||
void HttpHeader::setField(string index, string data) {
|
||||
removeField(index);
|
||||
fields.insert(make_pair(index, data));
|
||||
}
|
||||
|
||||
void HttpHeader::removeField(string index) {
|
||||
fields.erase(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field from the HTTP header
|
||||
* e.g. Content-Length: 42
|
||||
* @param index The field index
|
||||
* @return The field data
|
||||
*/
|
||||
string HttpHeader::getField(string index) {
|
||||
auto i = fields.find(index);
|
||||
if (i != fields.end()) {
|
||||
return fields.at(index);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool HttpHeader::isExistingField(string index) {
|
||||
auto i = fields.find(index);
|
||||
return i != fields.end();
|
||||
}
|
||||
|
||||
string HttpHeader::toString() {
|
||||
string header = "";
|
||||
for (auto it = fields.begin(); it != fields.end(); it++ ) {
|
||||
header += it->first + ": " + it->second + "\r\n";
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
string HttpHeader::cgiExport() {
|
||||
string header = "";
|
||||
for (auto it = fields.begin(); it != fields.end(); it++ ) {
|
||||
header += "HTTP_" + to_cgi(it->first) + "=" + cli_encode(it->second) + " ";
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* HttpHeader.h - HttpHeader Class definition
|
||||
* Lorenz Stechauner, 2018-05-09
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_HTTP_HEADER
|
||||
#define NECRONDA_HTTP_HEADER
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct comp {
|
||||
bool operator()(const std::string& lhs, const std::string& rhs) const {
|
||||
return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores Key-Value Pairs for a HTTP header
|
||||
* e.g.
|
||||
* Content-Length: 64
|
||||
* Host: example.org
|
||||
*/
|
||||
class HttpHeader {
|
||||
private:
|
||||
map<string, string, comp> fields;
|
||||
|
||||
public:
|
||||
HttpHeader();
|
||||
|
||||
explicit HttpHeader(Socket *socket);
|
||||
|
||||
~HttpHeader();
|
||||
|
||||
void setField(string index, string data);
|
||||
|
||||
string getField(string index);
|
||||
|
||||
void removeField(string index);
|
||||
|
||||
bool isExistingField(string index);
|
||||
|
||||
void parse(Socket *socket);
|
||||
|
||||
string toString();
|
||||
|
||||
string cgiExport();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -1,107 +0,0 @@
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include "../Socket.h"
|
||||
#include "HttpHeader.h"
|
||||
#include "HttpRequest.h"
|
||||
|
||||
|
||||
HttpRequest::HttpRequest() {
|
||||
this->header = HttpHeader();
|
||||
}
|
||||
|
||||
HttpRequest::HttpRequest(Socket *socket) : HttpRequest::HttpRequest() {
|
||||
parseHeader(socket);
|
||||
}
|
||||
|
||||
HttpRequest::HttpRequest(string method, string path, string version) : HttpRequest::HttpRequest() {
|
||||
this->method = std::move(method);
|
||||
this->path = std::move(path);
|
||||
this->version = std::move(version);
|
||||
}
|
||||
|
||||
void HttpRequest::parseHeader(Socket *socket) {
|
||||
string line = socket->receiveLine();
|
||||
|
||||
unsigned long pos1 = line.find(' ');
|
||||
unsigned long pos2;
|
||||
|
||||
bool invalid = false;
|
||||
|
||||
if (pos1 != string::npos) {
|
||||
pos2 = line.find(' ', pos1 + 1);
|
||||
if (pos2 != string::npos) {
|
||||
method = line.substr(0, pos1);
|
||||
for (auto &c: method) c = (char) toupper(c);
|
||||
path = line.substr(pos1 + 1, pos2 - pos1 - 1);
|
||||
version = line.substr(pos2 + 6, 3);
|
||||
} else {
|
||||
invalid = true;
|
||||
}
|
||||
} else {
|
||||
pos2 = string::npos;
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
|
||||
if (!invalid && (line.substr(pos2 + 1, 5) != "HTTP/" || version[1] != '.' || path[0] != '/' || !(version[0] >= '0' && version[0] <= '9') || !(version[2] >= '0' && version[2] <= '9'))) {
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
if (invalid) {
|
||||
method = "";
|
||||
path = "";
|
||||
version = "";
|
||||
throw (char *) "Malformed header";
|
||||
}
|
||||
|
||||
header.parse(socket);
|
||||
}
|
||||
|
||||
string HttpRequest::getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
string HttpRequest::getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
string HttpRequest::getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
void HttpRequest::setMethod(string method) {
|
||||
this->method = std::move(method);
|
||||
}
|
||||
|
||||
void HttpRequest::setPath(string path) {
|
||||
this->path = std::move(path);
|
||||
}
|
||||
|
||||
void HttpRequest::setVersion(string version) {
|
||||
this->version = std::move(version);
|
||||
}
|
||||
|
||||
string HttpRequest::getField(string index) {
|
||||
return header.getField(std::move(index));
|
||||
}
|
||||
|
||||
void HttpRequest::setField(string index, string data) {
|
||||
header.setField(std::move(index), std::move(data));
|
||||
}
|
||||
|
||||
bool HttpRequest::isExistingField(string index) {
|
||||
return header.isExistingField(std::move(index));
|
||||
}
|
||||
|
||||
string HttpRequest::cgiExport() {
|
||||
return header.cgiExport();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* HttpHeader.h - HttpHeader Class definition
|
||||
* Lorenz Stechauner, 2018-05-09
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_HTTP_REQUEST
|
||||
#define NECRONDA_HTTP_REQUEST
|
||||
|
||||
using namespace std;
|
||||
|
||||
class HttpRequest {
|
||||
private:
|
||||
HttpHeader header;
|
||||
string method;
|
||||
string path;
|
||||
string version;
|
||||
|
||||
public:
|
||||
HttpRequest();
|
||||
|
||||
explicit HttpRequest(Socket *socket);
|
||||
|
||||
HttpRequest(string method, string path, string version = "1.1");
|
||||
|
||||
void parseHeader(Socket *socket);
|
||||
|
||||
void sendHeader(Socket *socket);
|
||||
|
||||
string getField(string index);
|
||||
|
||||
void setField(string index, string data);
|
||||
|
||||
bool isExistingField(string index);
|
||||
|
||||
string getMethod();
|
||||
|
||||
string getPath();
|
||||
|
||||
string getVersion();
|
||||
|
||||
void setMethod(string method);
|
||||
|
||||
void setPath(string path);
|
||||
|
||||
void setVersion(string version);
|
||||
|
||||
string cgiExport();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -1,70 +0,0 @@
|
||||
//
|
||||
// Created by lorenz on 5/17/18.
|
||||
//
|
||||
|
||||
#include "HttpResponse.h"
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include "HttpStatusCode.h"
|
||||
|
||||
|
||||
HttpResponse::HttpResponse() {
|
||||
this->header = HttpHeader();
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse(Socket *socket) : HttpResponse::HttpResponse() {
|
||||
this->parseHeader(socket);
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse(int statuscode, string version) : HttpResponse::HttpResponse(::getStatusCode(statuscode), std::move(version)) {
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse(HttpStatusCode statuscode, string version) : HttpResponse::HttpResponse() {
|
||||
this->statuscode = statuscode;
|
||||
this->version = std::move(version);
|
||||
}
|
||||
|
||||
void HttpResponse::sendHeader(Socket *socket) {
|
||||
socket->send("HTTP/" + version + " " + to_string(statuscode.code) + " " + statuscode.message + "\r\n" +
|
||||
header.toString() + "\r\n");
|
||||
}
|
||||
|
||||
string HttpResponse::getField(string index) {
|
||||
return header.getField(std::move(index));
|
||||
}
|
||||
|
||||
void HttpResponse::setField(string index, string data) {
|
||||
header.setField(std::move(index), std::move(data));
|
||||
}
|
||||
|
||||
bool HttpResponse::isExistingField(string index) {
|
||||
return header.isExistingField(std::move(index));
|
||||
}
|
||||
|
||||
HttpStatusCode HttpResponse::getStatusCode() {
|
||||
return statuscode;
|
||||
}
|
||||
|
||||
string HttpResponse::getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
void HttpResponse::setStatusCode(HttpStatusCode statuscode) {
|
||||
this->statuscode = statuscode;
|
||||
}
|
||||
|
||||
void HttpResponse::setStatusCode(int statuscode) {
|
||||
this->statuscode = ::getStatusCode(statuscode);
|
||||
}
|
||||
|
||||
void HttpResponse::setVersion(string version) {
|
||||
this->version = std::move(version);
|
||||
}
|
||||
|
||||
void HttpResponse::parseHeader(Socket *socket) {
|
||||
|
||||
}
|
||||
|
||||
void HttpResponse::removeField(string index) {
|
||||
header.removeField(std::move(index));
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
|
||||
|
||||
#ifndef NECRONDA_HTTP_RESPONSE
|
||||
#define NECRONDA_HTTP_RESPONSE
|
||||
|
||||
|
||||
#include <string>
|
||||
#include "HttpHeader.h"
|
||||
#include "HttpStatusCode.h"
|
||||
#include "../Socket.h"
|
||||
|
||||
class HttpResponse {
|
||||
private:
|
||||
HttpHeader header;
|
||||
HttpStatusCode statuscode;
|
||||
string version;
|
||||
|
||||
public:
|
||||
HttpResponse();
|
||||
|
||||
explicit HttpResponse(Socket *socket);
|
||||
|
||||
explicit HttpResponse(int statuscode, string version = "1.1");
|
||||
|
||||
explicit HttpResponse(HttpStatusCode statuscode, string version = "1.1");
|
||||
|
||||
void parseHeader(Socket *socket);
|
||||
|
||||
void sendHeader(Socket *socket);
|
||||
|
||||
string getField(string index);
|
||||
|
||||
void setField(string index, string data);
|
||||
|
||||
bool isExistingField(string index);
|
||||
|
||||
HttpStatusCode getStatusCode();
|
||||
|
||||
string getVersion();
|
||||
|
||||
void setStatusCode(HttpStatusCode statuscode);
|
||||
|
||||
void setStatusCode(int statuscode);
|
||||
|
||||
void setVersion(string version);
|
||||
|
||||
void removeField(string index);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,66 +0,0 @@
|
||||
#include "HttpStatusCode.h"
|
||||
|
||||
/**
|
||||
* Necronda Web Server 3.0
|
||||
* HttpStatusCode.cpp - HTTP Status Code definition
|
||||
* Lorenz Stechauner, 2018-05-16
|
||||
* Reference: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
*/
|
||||
|
||||
|
||||
HttpStatusCode httpStatusCodes[] = {
|
||||
HttpStatusCode{100, "Informational", "Continue", ""},
|
||||
HttpStatusCode{101, "Informational", "Switching Protocols", ""},
|
||||
|
||||
HttpStatusCode{200, "Success", "OK", ""},
|
||||
HttpStatusCode{201, "Success", "Created", ""},
|
||||
HttpStatusCode{202, "Success", "Accepted", ""},
|
||||
HttpStatusCode{203, "Success", "Non-Authoritative Information", ""},
|
||||
HttpStatusCode{204, "Success", "No Content", ""},
|
||||
HttpStatusCode{205, "Success", "Reset Content", ""},
|
||||
HttpStatusCode{206, "Success", "Partial Content", ""},
|
||||
|
||||
HttpStatusCode{300, "Redirection", "Multiple Choices", ""},
|
||||
HttpStatusCode{301, "Redirection", "Moved Permanently", ""},
|
||||
HttpStatusCode{302, "Redirection", "Found", ""},
|
||||
HttpStatusCode{303, "Redirection", "See Other", ""},
|
||||
HttpStatusCode{304, "Redirection", "Not Modified", ""},
|
||||
HttpStatusCode{305, "Redirection", "Use Proxy", ""},
|
||||
HttpStatusCode{307, "Redirection", "Temporary Redirect", ""},
|
||||
HttpStatusCode{308, "Redirection", "Permanent Redirect", ""},
|
||||
|
||||
HttpStatusCode{400, "Client Error", "Bad Request", ""},
|
||||
HttpStatusCode{401, "Client Error", "Unauthorized", ""},
|
||||
HttpStatusCode{402, "Client Error", "Payment Required", ""},
|
||||
HttpStatusCode{403, "Client Error", "Forbidden", ""},
|
||||
HttpStatusCode{404, "Client Error", "Not Found", ""},
|
||||
HttpStatusCode{405, "Client Error", "Method Not Allowed", ""},
|
||||
HttpStatusCode{406, "Client Error", "Not Acceptable", ""},
|
||||
HttpStatusCode{407, "Client Error", "Proxy Authentication Required", ""},
|
||||
HttpStatusCode{408, "Client Error", "Request Timeout", ""},
|
||||
HttpStatusCode{409, "Client Error", "Conflict", ""},
|
||||
HttpStatusCode{410, "Client Error", "Gone", ""},
|
||||
HttpStatusCode{411, "Client Error", "Length Required", ""},
|
||||
HttpStatusCode{412, "Client Error", "Precondition Failed", ""},
|
||||
HttpStatusCode{413, "Client Error", "Request Entity Too Large", ""},
|
||||
HttpStatusCode{414, "Client Error", "Request-URI Too Long", ""},
|
||||
HttpStatusCode{415, "Client Error", "Unsupported Media Type", ""},
|
||||
HttpStatusCode{416, "Client Error", "Requested Range Not Satisfiable", ""},
|
||||
HttpStatusCode{417, "Client Error", "Expectation Failed", ""},
|
||||
|
||||
HttpStatusCode{500, "Server Error", "Internal Server Error", ""},
|
||||
HttpStatusCode{501, "Server Error", "Not Implemented", ""},
|
||||
HttpStatusCode{502, "Server Error", "Bad Gateway", ""},
|
||||
HttpStatusCode{503, "Server Error", "Service Unavailable", ""},
|
||||
HttpStatusCode{504, "Server Error", "Gateway Timeout", ""},
|
||||
HttpStatusCode{505, "Server Error", "HTTP Version Not Supported", ""},
|
||||
};
|
||||
|
||||
HttpStatusCode getStatusCode(int statuscode) {
|
||||
for (HttpStatusCode sc : httpStatusCodes) {
|
||||
if (sc.code == statuscode) {
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
throw (char *) "Invalid status code";
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
|
||||
|
||||
#ifndef NECRONDA_HTTP_STATUSCODE
|
||||
#define NECRONDA_HTTP_STATUSCODE
|
||||
|
||||
typedef struct {
|
||||
short code; // The status code (e.g. 200)
|
||||
const char *type; // The status type type (e.g Success)
|
||||
const char *message; // The status code message (e.g. OK)
|
||||
const char *description; // The status code description (currently not used)
|
||||
} HttpStatusCode;
|
||||
|
||||
HttpStatusCode getStatusCode(int statuscode);
|
||||
|
||||
#endif
|
@ -1,41 +0,0 @@
|
||||
//
|
||||
// Created by lorenz on 5/30/18.
|
||||
//
|
||||
|
||||
#include "procopen.h"
|
||||
|
||||
stds procopen(const char* command) {
|
||||
|
||||
int pipes[3][2];
|
||||
|
||||
pipe(pipes[PARENT_READ_PIPE]);
|
||||
pipe(pipes[PARENT_WRITE_PIPE]);
|
||||
pipe(pipes[PARENT_ERROR_PIPE]);
|
||||
|
||||
int pid = fork();
|
||||
|
||||
if(pid == 0) {
|
||||
dup2(CHILD_READ_FD, STDIN_FILENO);
|
||||
dup2(CHILD_WRITE_FD, STDOUT_FILENO);
|
||||
dup2(CHILD_ERROR_FD, STDERR_FILENO);
|
||||
|
||||
close(CHILD_READ_FD);
|
||||
close(CHILD_WRITE_FD);
|
||||
close(CHILD_ERROR_FD);
|
||||
|
||||
close(PARENT_READ_FD);
|
||||
close(PARENT_WRITE_FD);
|
||||
close(PARENT_ERROR_FD);
|
||||
|
||||
system(command);
|
||||
exit(0);
|
||||
} else {
|
||||
close(CHILD_READ_FD);
|
||||
close(CHILD_WRITE_FD);
|
||||
close(CHILD_ERROR_FD);
|
||||
|
||||
return stds{fdopen(PARENT_WRITE_FD, "w"), fdopen(PARENT_READ_FD, "r"), fdopen(PARENT_ERROR_FD, "r"), (pid_t) pid};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
//
|
||||
// Created by lorenz on 5/30/18.
|
||||
//
|
||||
|
||||
|
||||
#include <zconf.h>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
#ifndef NECRONDA_PROCOPEN
|
||||
#define NECRONDA_PROCOPEN
|
||||
|
||||
|
||||
#define PARENT_WRITE_PIPE 0
|
||||
#define PARENT_READ_PIPE 1
|
||||
#define PARENT_ERROR_PIPE 2
|
||||
|
||||
#define READ_FD 0
|
||||
#define WRITE_FD 1
|
||||
|
||||
#define PARENT_READ_FD ( pipes[PARENT_READ_PIPE][READ_FD] )
|
||||
#define PARENT_WRITE_FD ( pipes[PARENT_WRITE_PIPE][WRITE_FD] )
|
||||
#define PARENT_ERROR_FD ( pipes[PARENT_ERROR_PIPE][READ_FD] )
|
||||
|
||||
#define CHILD_READ_FD ( pipes[PARENT_WRITE_PIPE][READ_FD] )
|
||||
#define CHILD_WRITE_FD ( pipes[PARENT_READ_PIPE][WRITE_FD] )
|
||||
#define CHILD_ERROR_FD ( pipes[PARENT_ERROR_PIPE][WRITE_FD] )
|
||||
|
||||
|
||||
typedef struct {
|
||||
FILE* stdin;
|
||||
FILE* stdout;
|
||||
FILE* stderr;
|
||||
pid_t pid;
|
||||
} stds;
|
||||
|
||||
|
||||
stds procopen(const char* command);
|
||||
|
||||
|
||||
#endif
|
406
src/rev_proxy.c
Normal file
406
src/rev_proxy.c
Normal file
@ -0,0 +1,406 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Reverse proxy
|
||||
* src/rev_proxy.c
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#include "rev_proxy.h"
|
||||
|
||||
sock rev_proxy;
|
||||
char *rev_proxy_host = NULL;
|
||||
struct timeval server_timeout = {.tv_sec = SERVER_TIMEOUT, .tv_usec = 0};
|
||||
|
||||
int rev_proxy_request_header(http_req *req, int enc) {
|
||||
char buf1[256];
|
||||
char buf2[256];
|
||||
int p_len;
|
||||
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Connection", "keep-alive");
|
||||
|
||||
char *via = http_get_header_field(&req->hdr, "Via");
|
||||
sprintf(buf1, "HTTP/%s %s", req->version, DEFAULT_HOST);
|
||||
if (via == NULL) {
|
||||
http_add_header_field(&req->hdr, "Via", buf1);
|
||||
} else {
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
||||
print(ERR_STR "Header field 'Via' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
http_remove_header_field(&req->hdr, "Via", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Via", buf2);
|
||||
}
|
||||
|
||||
char *host = http_get_header_field(&req->hdr, "Host");
|
||||
char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
|
||||
int client_ipv6 = strchr(client_addr_str, ':') != NULL;
|
||||
int server_ipv6 = strchr(server_addr_str, ':') != NULL;
|
||||
|
||||
p_len = snprintf(buf1, sizeof(buf1), "by=%s%s%s;for=%s%s%s;host=%s;proto=%s",
|
||||
server_ipv6 ? "\"[" : "", server_addr_str, server_ipv6 ? "]\"" : "",
|
||||
client_ipv6 ? "\"[" : "", client_addr_str, client_ipv6 ? "]\"" : "",
|
||||
host, enc ? "https" : "http");
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||
print(ERR_STR "Appended part of header field 'Forwarded' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
if (forwarded == NULL) {
|
||||
http_add_header_field(&req->hdr, "Forwarded", buf1);
|
||||
} else {
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", forwarded, buf1);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
||||
print(ERR_STR "Header field 'Forwarded' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
http_remove_header_field(&req->hdr, "Forwarded", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Forwarded", buf2);
|
||||
}
|
||||
|
||||
char *xff = http_get_header_field(&req->hdr, "X-Forwarded-For");
|
||||
if (xff == NULL) {
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-For", client_addr_str);
|
||||
} else {
|
||||
sprintf(buf1, "%s, %s", xff, client_addr_str);
|
||||
http_remove_header_field(&req->hdr, "X-Forwarded-For", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-For", buf1);
|
||||
}
|
||||
|
||||
char *xfh = http_get_header_field(&req->hdr, "X-Forwarded-Host");
|
||||
if (xfh == NULL) {
|
||||
if (forwarded == NULL) {
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Host", host);
|
||||
} else {
|
||||
char *ptr = strchr(forwarded, ',');
|
||||
unsigned long len;
|
||||
if (ptr != NULL) len = ptr - forwarded;
|
||||
else len = strlen(forwarded);
|
||||
ptr = strstr(forwarded, "host=");
|
||||
if ((ptr - forwarded) < len) {
|
||||
char *end = strchr(ptr, ';');
|
||||
if (end == NULL) len -= (ptr - forwarded);
|
||||
else len = (end - ptr);
|
||||
len -= 5;
|
||||
sprintf(buf1, "%.*s", (int) len, ptr + 5);
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Host", buf1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *xfp = http_get_header_field(&req->hdr, "X-Forwarded-Proto");
|
||||
if (xfp == NULL) {
|
||||
if (forwarded == NULL) {
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Proto", enc ? "https" : "http");
|
||||
} else {
|
||||
char *ptr = strchr(forwarded, ',');
|
||||
unsigned long len;
|
||||
if (ptr != NULL) len = ptr - forwarded;
|
||||
else len = strlen(forwarded);
|
||||
ptr = strstr(forwarded, "proto=");
|
||||
if ((ptr - forwarded) < len) {
|
||||
char *end = strchr(ptr, ';');
|
||||
if (end == NULL) len -= (ptr - forwarded);
|
||||
else len = (end - ptr);
|
||||
len -= 6;
|
||||
sprintf(buf1, "%.*s", (int) len, ptr + 6);
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Proto", buf1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rev_proxy_response_header(http_req *req, http_res *res) {
|
||||
char buf1[256];
|
||||
char buf2[256];
|
||||
int p_len;
|
||||
|
||||
char *via = http_get_header_field(&res->hdr, "Via");
|
||||
p_len = snprintf(buf1, sizeof(buf1), "HTTP/%s %s", req->version, DEFAULT_HOST);
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||
print(ERR_STR "Appended part of header field 'Via' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
if (via == NULL) {
|
||||
http_add_header_field(&res->hdr, "Via", buf1);
|
||||
} else {
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
||||
print(ERR_STR "Header field 'Via' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
http_remove_header_field(&res->hdr, "Via", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&res->hdr, "Via", buf2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client, http_status *custom_status,
|
||||
char *err_msg) {
|
||||
char buffer[CHUNK_SIZE];
|
||||
long ret;
|
||||
int tries = 0;
|
||||
int retry = 0;
|
||||
|
||||
if (rev_proxy.socket != 0 && strcmp(rev_proxy_host, conf->name) == 0 && sock_check(&rev_proxy) == 0) {
|
||||
goto rev_proxy;
|
||||
}
|
||||
|
||||
retry:
|
||||
if (rev_proxy.socket != 0) {
|
||||
print(BLUE_STR "Closing proxy connection" CLR_STR);
|
||||
sock_close(&rev_proxy);
|
||||
}
|
||||
retry = 0;
|
||||
tries++;
|
||||
|
||||
rev_proxy.socket = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (rev_proxy.socket < 0) {
|
||||
print(ERR_STR "Unable to create socket: %s" CLR_STR, strerror(errno));
|
||||
res->status = http_get_status(500);
|
||||
return -1;
|
||||
}
|
||||
|
||||
server_timeout.tv_sec = SERVER_TIMEOUT;
|
||||
server_timeout.tv_usec = 0;
|
||||
if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
|
||||
goto rev_proxy_timeout_err;
|
||||
if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0) {
|
||||
rev_proxy_timeout_err:
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno));
|
||||
sprintf(err_msg, "Unable to set timeout for socket: %s", strerror(errno));
|
||||
goto proxy_err;
|
||||
}
|
||||
|
||||
struct hostent *host_ent = gethostbyname(conf->rev_proxy.hostname);
|
||||
if (host_ent == NULL) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to connect to server: Name or service not known" CLR_STR);
|
||||
sprintf(err_msg, "Unable to connect to server: Name or service not known.");
|
||||
goto proxy_err;
|
||||
}
|
||||
|
||||
struct sockaddr_in6 address = {.sin6_family = AF_INET6, .sin6_port = htons(conf->rev_proxy.port)};
|
||||
if (host_ent->h_addrtype == AF_INET6) {
|
||||
memcpy(&address.sin6_addr, host_ent->h_addr_list[0], host_ent->h_length);
|
||||
} else if (host_ent->h_addrtype == AF_INET) {
|
||||
unsigned char addr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0};
|
||||
memcpy(addr + 12, host_ent->h_addr_list[0], host_ent->h_length);
|
||||
memcpy(&address.sin6_addr, addr, 16);
|
||||
}
|
||||
|
||||
if (connect(rev_proxy.socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to connect to server: %s" CLR_STR, strerror(errno));
|
||||
sprintf(err_msg, "Unable to connect to server: %s.", strerror(errno));
|
||||
goto proxy_err;
|
||||
}
|
||||
|
||||
if (conf->rev_proxy.enc) {
|
||||
rev_proxy.ssl = SSL_new(rev_proxy.ctx);
|
||||
SSL_set_fd(rev_proxy.ssl, rev_proxy.socket);
|
||||
SSL_set_connect_state(rev_proxy.ssl);
|
||||
|
||||
ret = SSL_do_handshake(rev_proxy.ssl);
|
||||
rev_proxy._last_ret = ret;
|
||||
rev_proxy._errno = errno;
|
||||
rev_proxy._ssl_error = ERR_get_error();
|
||||
rev_proxy.enc = 1;
|
||||
if (ret < 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(&rev_proxy));
|
||||
sprintf(err_msg, "Unable to perform handshake: %s.", sock_strerror(&rev_proxy));
|
||||
goto proxy_err;
|
||||
}
|
||||
}
|
||||
|
||||
rev_proxy_host = conf->name;
|
||||
inet_ntop(address.sin6_family, (void *) &address.sin6_addr, buffer, sizeof(buffer));
|
||||
print(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buffer, conf->rev_proxy.port);
|
||||
|
||||
rev_proxy:
|
||||
ret = rev_proxy_request_header(req, (int) client->enc);
|
||||
if (ret != 0) {
|
||||
res->status = http_get_status(500);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = http_send_request(&rev_proxy, req);
|
||||
if (ret < 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to send request to server (1): %s" CLR_STR, sock_strerror(&rev_proxy));
|
||||
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
|
||||
retry = tries < 4;
|
||||
goto proxy_err;
|
||||
}
|
||||
|
||||
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
|
||||
if (content_length != NULL) {
|
||||
unsigned long content_len = strtoul(content_length, NULL, 10);
|
||||
if (client->buf_len - client->buf_off > 0) {
|
||||
unsigned long len = client->buf_len - client->buf_off;
|
||||
if (len > content_len) {
|
||||
len = content_len;
|
||||
}
|
||||
ret = sock_send(&rev_proxy, client->buf, len, 0);
|
||||
if (ret <= 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to send request to server (2): %s" CLR_STR, sock_strerror(&rev_proxy));
|
||||
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
|
||||
retry = tries < 4;
|
||||
goto proxy_err;
|
||||
}
|
||||
content_len -= len;
|
||||
}
|
||||
if (content_len > 0) {
|
||||
ret = sock_splice(&rev_proxy, client, buffer, sizeof(buffer), content_len);
|
||||
if (ret <= 0) {
|
||||
if (ret == -1) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to send request to server (3): %s" CLR_STR, sock_strerror(&rev_proxy));
|
||||
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
|
||||
goto proxy_err;
|
||||
} else if (ret == -2) {
|
||||
res->status = http_get_status(400);
|
||||
print(ERR_STR "Unable to receive request from client: %s" CLR_STR, sock_strerror(client));
|
||||
sprintf(err_msg, "Unable to receive request from client: %s.", sock_strerror(client));
|
||||
return -1;
|
||||
}
|
||||
res->status = http_get_status(500);
|
||||
print(ERR_STR "Unknown Error" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = sock_recv(&rev_proxy, buffer, sizeof(buffer), MSG_PEEK);
|
||||
if (ret <= 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to receive response from server: %s" CLR_STR, sock_strerror(&rev_proxy));
|
||||
sprintf(err_msg, "Unable to receive response from server: %s.", sock_strerror(&rev_proxy));
|
||||
goto proxy_err;
|
||||
}
|
||||
|
||||
char *buf = buffer;
|
||||
unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4);
|
||||
|
||||
if (header_len <= 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
|
||||
sprintf(err_msg, "Unable to parser header: End of header not found.");
|
||||
goto proxy_err;
|
||||
}
|
||||
|
||||
for (int i = 0; i < header_len; i++) {
|
||||
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
|
||||
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
|
||||
goto proxy_err;
|
||||
}
|
||||
}
|
||||
|
||||
char *ptr = buf;
|
||||
while (header_len != (ptr - buf)) {
|
||||
char *pos0 = strstr(ptr, "\r\n");
|
||||
if (pos0 == NULL) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
||||
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
||||
goto proxy_err;
|
||||
}
|
||||
if (ptr == buf) {
|
||||
if (strncmp(ptr, "HTTP/", 5) != 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
||||
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
||||
goto proxy_err;
|
||||
}
|
||||
int status_code = (int) strtol(ptr + 9, NULL, 10);
|
||||
res->status = http_get_status(status_code);
|
||||
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
|
||||
custom_status->code = status_code;
|
||||
strcpy(custom_status->type, "");
|
||||
strncpy(custom_status->msg, ptr + 13, strchr(ptr, '\r') - ptr - 13);
|
||||
res->status = custom_status;
|
||||
} else if (res->status == NULL) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to parse header: Invalid or unknown status code" CLR_STR);
|
||||
sprintf(err_msg, "Unable to parse header: Invalid or unknown status code.");
|
||||
goto proxy_err;
|
||||
}
|
||||
} else {
|
||||
ret = http_parse_header_field(&res->hdr, ptr, pos0);
|
||||
if (ret != 0) {
|
||||
res->status = http_get_status(502);
|
||||
print(ERR_STR "Unable to parse header" CLR_STR);
|
||||
sprintf(err_msg, "Unable to parse header.");
|
||||
goto proxy_err;
|
||||
}
|
||||
}
|
||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||
break;
|
||||
}
|
||||
ptr = pos0 + 2;
|
||||
}
|
||||
sock_recv(&rev_proxy, buffer, header_len, 0);
|
||||
|
||||
ret = rev_proxy_response_header(req, res);
|
||||
if (ret != 0) {
|
||||
res->status = http_get_status(500);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
proxy_err:
|
||||
if (retry) goto retry;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rev_proxy_send(sock *client, int chunked, unsigned long len_to_send) {
|
||||
long ret;
|
||||
char buffer[CHUNK_SIZE];
|
||||
long len, snd_len;
|
||||
// TODO handle websockets
|
||||
do {
|
||||
if (chunked) {
|
||||
ret = sock_recv(&rev_proxy, buffer, 16, MSG_PEEK);
|
||||
if (ret <= 0) {
|
||||
print("Unable to receive: %s", sock_strerror(&rev_proxy));
|
||||
break;
|
||||
}
|
||||
|
||||
len_to_send = strtol(buffer, NULL, 16);
|
||||
char *pos = strstr(buffer, "\r\n");
|
||||
len = pos - buffer + 2;
|
||||
ret = sock_send(client, buffer, len, 0);
|
||||
|
||||
sock_recv(&rev_proxy, buffer, len, 0);
|
||||
if (ret <= 0) break;
|
||||
}
|
||||
snd_len = 0;
|
||||
while (snd_len < len_to_send) {
|
||||
len = sock_recv(&rev_proxy, buffer, CHUNK_SIZE < (len_to_send - snd_len) ? CHUNK_SIZE : len_to_send - snd_len, 0);
|
||||
ret = sock_send(client, buffer, len, 0);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
||||
break;
|
||||
}
|
||||
snd_len += ret;
|
||||
}
|
||||
if (ret <= 0) break;
|
||||
if (chunked) {
|
||||
sock_recv(&rev_proxy, buffer, 2, 0);
|
||||
ret = sock_send(client, "\r\n", 2, 0);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (chunked && len_to_send > 0);
|
||||
return 0;
|
||||
}
|
20
src/rev_proxy.h
Normal file
20
src/rev_proxy.h
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Reverse proxy (header file)
|
||||
* src/rev_proxy.h
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_REV_PROXY_H
|
||||
#define NECRONDA_SERVER_REV_PROXY_H
|
||||
|
||||
int rev_proxy_request_header(http_req *req, int enc);
|
||||
|
||||
int rev_proxy_response_header(http_req *req, http_res *res);
|
||||
|
||||
int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client, http_status *custom_status,
|
||||
char *err_msg);
|
||||
|
||||
int rev_proxy_send(sock *client, int chunked, unsigned long len_to_send);
|
||||
|
||||
#endif //NECRONDA_SERVER_REV_PROXY_H
|
109
src/sock.c
Normal file
109
src/sock.c
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Basic TCP and TLS socket
|
||||
* src/sock.c
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#include "sock.h"
|
||||
|
||||
const char *sock_strerror(sock *s) {
|
||||
if (s->_last_ret == 0) {
|
||||
return "closed";
|
||||
} else if (s->enc) {
|
||||
if (s->_last_ret > 0) {
|
||||
return NULL;
|
||||
}
|
||||
const char *err1 = ERR_reason_error_string(s->_ssl_error);
|
||||
const char *err2 = strerror(errno);
|
||||
switch (SSL_get_error(s->ssl, (int) s->_last_ret)) {
|
||||
case SSL_ERROR_NONE:
|
||||
return NULL;
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
return "closed";
|
||||
case SSL_ERROR_WANT_READ:
|
||||
return "want read";
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
return "want write";
|
||||
case SSL_ERROR_WANT_CONNECT:
|
||||
return "want connect";
|
||||
case SSL_ERROR_WANT_ACCEPT:
|
||||
return "want accept";
|
||||
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||
return "want x509 lookup";
|
||||
case SSL_ERROR_SYSCALL:
|
||||
return ((s->_ssl_error == 0) ? ((s->_last_ret == 0) ? "protocol violation" : err2) : err1);
|
||||
case SSL_ERROR_SSL:
|
||||
return err1;
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
} else {
|
||||
return strerror(s->_errno);
|
||||
}
|
||||
}
|
||||
|
||||
long sock_send(sock *s, void *buf, unsigned long len, int flags) {
|
||||
long ret;
|
||||
if (s->enc) {
|
||||
ret = SSL_write(s->ssl, buf, (int) len);
|
||||
} else {
|
||||
ret = send(s->socket, buf, len, flags);
|
||||
}
|
||||
s->_last_ret = ret;
|
||||
s->_errno = errno;
|
||||
s->_ssl_error = ERR_get_error();
|
||||
return ret >= 0 ? ret : -1;
|
||||
}
|
||||
|
||||
long sock_recv(sock *s, void *buf, unsigned long len, int flags) {
|
||||
long ret;
|
||||
if (s->enc) {
|
||||
if (flags & MSG_PEEK) {
|
||||
ret = SSL_peek(s->ssl, buf, (int) len);
|
||||
} else {
|
||||
ret = SSL_read(s->ssl, buf, (int) len);
|
||||
}
|
||||
} else {
|
||||
ret = recv(s->socket, buf, len, flags);
|
||||
}
|
||||
s->_last_ret = ret;
|
||||
s->_errno = errno;
|
||||
s->_ssl_error = ERR_get_error();
|
||||
return ret >= 0 ? ret : -1;
|
||||
}
|
||||
|
||||
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len) {
|
||||
long ret;
|
||||
unsigned long send_len = 0;
|
||||
unsigned long next_len;
|
||||
while (send_len < len) {
|
||||
next_len = (buf_len < (len - send_len)) ? buf_len : (len - send_len);
|
||||
ret = sock_recv(src, buf, next_len, 0);
|
||||
if (ret < 0) return -2;
|
||||
next_len = ret;
|
||||
ret = sock_send(dst, buf, next_len, send_len + next_len < len ? MSG_MORE : 0);
|
||||
if (ret < 0) return -1;
|
||||
if (ret != next_len) return -3;
|
||||
send_len += next_len;
|
||||
}
|
||||
return (long) send_len;
|
||||
}
|
||||
|
||||
int sock_close(sock *s) {
|
||||
if ((int) s->enc && s->ssl != NULL) {
|
||||
SSL_shutdown(s->ssl);
|
||||
SSL_free(s->ssl);
|
||||
}
|
||||
shutdown(s->socket, SHUT_RDWR);
|
||||
close(s->socket);
|
||||
s->socket = 0;
|
||||
s->enc = 0;
|
||||
s->ssl = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sock_check(sock *s) {
|
||||
char buf;
|
||||
return recv(s->socket, &buf, 1, MSG_PEEK | MSG_DONTWAIT) == 1;
|
||||
}
|
43
src/sock.h
Normal file
43
src/sock.h
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Basic TCP and TLS socket (header file)
|
||||
* src/sock.h
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_SOCK_H
|
||||
#define NECRONDA_SERVER_SOCK_H
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/dh.h>
|
||||
|
||||
typedef struct {
|
||||
unsigned int enc:1;
|
||||
int socket;
|
||||
SSL_CTX *ctx;
|
||||
SSL *ssl;
|
||||
char *buf;
|
||||
unsigned long buf_len;
|
||||
unsigned long buf_off;
|
||||
long _last_ret;
|
||||
int _errno;
|
||||
unsigned long _ssl_error;
|
||||
} sock;
|
||||
|
||||
const char *sock_strerror(sock *s);
|
||||
|
||||
long sock_send(sock *s, void *buf, unsigned long len, int flags);
|
||||
|
||||
long sock_recv(sock *s, void *buf, unsigned long len, int flags);
|
||||
|
||||
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len);
|
||||
|
||||
int sock_close(sock *s);
|
||||
|
||||
int sock_check(sock *s);
|
||||
|
||||
#endif //NECRONDA_SERVER_SOCK_H
|
198
src/uri.c
Normal file
198
src/uri.c
Normal file
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* URI and path handlers
|
||||
* src/uri.c
|
||||
* Lorenz Stechauner, 2020-12-13
|
||||
*/
|
||||
|
||||
#include "uri.h"
|
||||
|
||||
|
||||
int path_is_directory(const char *path) {
|
||||
struct stat statbuf;
|
||||
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) != 0;
|
||||
}
|
||||
|
||||
int path_is_file(const char *path) {
|
||||
struct stat statbuf;
|
||||
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
|
||||
}
|
||||
|
||||
int path_exists(const char *path) {
|
||||
struct stat statbuf;
|
||||
return stat(path, &statbuf) == 0;
|
||||
}
|
||||
|
||||
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode) {
|
||||
char buf0[1024];
|
||||
char buf1[1024];
|
||||
char buf2[1024];
|
||||
char buf3[1024];
|
||||
int p_len;
|
||||
uri->webroot = NULL;
|
||||
uri->req_path = NULL;
|
||||
uri->path = NULL;
|
||||
uri->pathinfo = NULL;
|
||||
uri->query = NULL;
|
||||
uri->filename = NULL;
|
||||
uri->uri = NULL;
|
||||
uri->meta = NULL;
|
||||
uri->is_static = 1;
|
||||
uri->is_dir = 0;
|
||||
if (uri_str[0] != '/') {
|
||||
return 1;
|
||||
}
|
||||
uri->webroot = malloc(strlen(webroot) + 1);
|
||||
strcpy(uri->webroot, webroot);
|
||||
|
||||
char *query = strchr(uri_str, '?');
|
||||
if (query == NULL) {
|
||||
uri->query = NULL;
|
||||
} else {
|
||||
query[0] = 0;
|
||||
query++;
|
||||
ssize_t size = strlen(query) + 1;
|
||||
uri->query = malloc(size);
|
||||
strcpy(uri->query, query);
|
||||
}
|
||||
|
||||
ssize_t size = strlen(uri_str) + 1;
|
||||
uri->req_path = malloc(size);
|
||||
url_decode(uri_str, uri->req_path, &size);
|
||||
if (query != NULL) {
|
||||
query[-1] = '?';
|
||||
}
|
||||
if (strstr(uri->req_path, "/../") != NULL || strstr(uri->req_path, "/./") != NULL) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
size = strlen(uri->req_path) + 1;
|
||||
uri->path = malloc(size);
|
||||
uri->pathinfo = malloc(size);
|
||||
|
||||
char last = 0;
|
||||
for (int i = 0, j = 0; i < size - 1; i++) {
|
||||
char ch = uri->req_path[i];
|
||||
if (last != '/' || ch != '/') {
|
||||
uri->path[j++] = ch;
|
||||
uri->path[j] = 0;
|
||||
}
|
||||
last = ch;
|
||||
}
|
||||
|
||||
if (dir_mode == URI_DIR_MODE_NO_VALIDATION) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (uri->path[strlen(uri->path) - 1] == '/') {
|
||||
uri->path[strlen(uri->path) - 1] = 0;
|
||||
strcpy(uri->pathinfo, "/");
|
||||
} else {
|
||||
strcpy(uri->pathinfo, "");
|
||||
}
|
||||
|
||||
if (!path_exists(uri->webroot)) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sprintf(buf0, "%s%s", uri->webroot, uri->path);
|
||||
p_len = snprintf(buf1, sizeof(buf1), "%s.php", buf0);
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) return -1;
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s.html", buf0);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) return -1;
|
||||
|
||||
if (strlen(uri->path) <= 1 || path_exists(buf0) || path_is_file(buf1) || path_is_file(buf2)) {
|
||||
break;
|
||||
}
|
||||
|
||||
char *ptr;
|
||||
parent_dir:
|
||||
ptr = strrchr(uri->path, '/');
|
||||
size = strlen(ptr);
|
||||
sprintf(buf3, "%.*s%s", (int) size, ptr, uri->pathinfo);
|
||||
strcpy(uri->pathinfo, buf3);
|
||||
ptr[0] = 0;
|
||||
}
|
||||
if (uri->pathinfo[0] != 0) {
|
||||
sprintf(buf3, "%s", uri->pathinfo + 1);
|
||||
strcpy(uri->pathinfo, buf3);
|
||||
}
|
||||
|
||||
if (path_is_file(buf0)) {
|
||||
uri->filename = malloc(strlen(buf0) + 1);
|
||||
strcpy(uri->filename, buf0);
|
||||
ssize_t len = strlen(uri->path);
|
||||
if (strcmp(uri->path + len - 5, ".html") == 0) {
|
||||
uri->path[len - 5] = 0;
|
||||
} else if (strcmp(uri->path + len - 4, ".php") == 0) {
|
||||
uri->path[len - 4] = 0;
|
||||
uri->is_static = 0;
|
||||
}
|
||||
} else if (path_is_file(buf1)) {
|
||||
uri->is_static = 0;
|
||||
uri->filename = malloc(strlen(buf1) + 1);
|
||||
strcpy(uri->filename, buf1);
|
||||
} else if (path_is_file(buf2)) {
|
||||
uri->filename = malloc(strlen(buf2) + 1);
|
||||
strcpy(uri->filename, buf2);
|
||||
} else {
|
||||
uri->is_dir = 1;
|
||||
strcpy(uri->path + strlen(uri->path), "/");
|
||||
sprintf(buf1, "%s%sindex.php", uri->webroot, uri->path);
|
||||
sprintf(buf2, "%s%sindex.html", uri->webroot, uri->path);
|
||||
if (path_is_file(buf1)) {
|
||||
uri->filename = malloc(strlen(buf1) + 1);
|
||||
strcpy(uri->filename, buf1);
|
||||
uri->is_static = 0;
|
||||
} else if (path_is_file(buf2)) {
|
||||
uri->filename = malloc(strlen(buf2) + 1);
|
||||
strcpy(uri->filename, buf2);
|
||||
} else {
|
||||
if (dir_mode == URI_DIR_MODE_FORBIDDEN) {
|
||||
uri->is_static = 1;
|
||||
} else if (dir_mode == URI_DIR_MODE_LIST) {
|
||||
uri->is_static = 0;
|
||||
} else if (dir_mode == URI_DIR_MODE_INFO) {
|
||||
if (strlen(uri->path) > 1) {
|
||||
uri->path[strlen(uri->path) - 1] = 0;
|
||||
sprintf(buf0, "/%s", uri->pathinfo);
|
||||
strcpy(uri->pathinfo, buf0);
|
||||
goto parent_dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(uri->path + strlen(uri->path) - 5, "index") == 0) {
|
||||
uri->path[strlen(uri->path) - 5] = 0;
|
||||
}
|
||||
if (strcmp(uri->pathinfo, "index.php") == 0 || strcmp(uri->pathinfo, "index.html") == 0) {
|
||||
uri->pathinfo[0] = 0;
|
||||
}
|
||||
|
||||
sprintf(buf0, "%s%s%s%s%s", uri->path,
|
||||
(strlen(uri->pathinfo) == 0 || uri->path[strlen(uri->path) - 1] == '/') ? "" : "/", uri->pathinfo,
|
||||
uri->query != NULL ? "?" : "", uri->query != NULL ? uri->query : "");
|
||||
uri->uri = malloc(strlen(buf0) + 1);
|
||||
strcpy(uri->uri, buf0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uri_free(http_uri *uri) {
|
||||
if (uri->webroot != NULL) free(uri->webroot);
|
||||
if (uri->req_path != NULL) free(uri->req_path);
|
||||
if (uri->path != NULL) free(uri->path);
|
||||
if (uri->pathinfo != NULL) free(uri->pathinfo);
|
||||
if (uri->query != NULL) free(uri->query);
|
||||
if (uri->filename != NULL) free(uri->filename);
|
||||
if (uri->uri != NULL) free(uri->uri);
|
||||
uri->webroot = NULL;
|
||||
uri->req_path = NULL;
|
||||
uri->path = NULL;
|
||||
uri->pathinfo = NULL;
|
||||
uri->query = NULL;
|
||||
uri->filename = NULL;
|
||||
uri->uri = NULL;
|
||||
}
|
47
src/uri.h
Normal file
47
src/uri.h
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* URI and path handlers (header file)
|
||||
* src/uri.h
|
||||
* Lorenz Stechauner, 2020-12-13
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_URI_H
|
||||
#define NECRONDA_SERVER_URI_H
|
||||
|
||||
#define URI_DIR_MODE_NO_VALIDATION 0
|
||||
#define URI_DIR_MODE_FORBIDDEN 1
|
||||
#define URI_DIR_MODE_LIST 2
|
||||
#define URI_DIR_MODE_INFO 3
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
char etag[64];
|
||||
char type[24];
|
||||
char charset[16];
|
||||
char filename_comp[256];
|
||||
struct stat stat;
|
||||
} meta_data;
|
||||
|
||||
typedef struct {
|
||||
char *webroot; // "/srv/www/www.test.org"
|
||||
char *req_path; // "/account/login"
|
||||
char *path; // "/account/"
|
||||
char *pathinfo; // "login"
|
||||
char *query; // "username=test"
|
||||
char *filename; // "/account/index.php"
|
||||
char *uri; // "/account/login?username=test"
|
||||
meta_data *meta;
|
||||
unsigned char is_static:1;
|
||||
unsigned char is_dir:1;
|
||||
} http_uri;
|
||||
|
||||
|
||||
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode);
|
||||
|
||||
int uri_init_cache(http_uri *uri);
|
||||
|
||||
void uri_free(http_uri *uri);
|
||||
|
||||
#endif //NECRONDA_SERVER_URI_H
|
161
src/utils.c
Normal file
161
src/utils.c
Normal file
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Utilities
|
||||
* src/utils.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf) {
|
||||
if (micros < 10000) {
|
||||
sprintf(buf, "%.1f ms", (double) micros / 1000);
|
||||
} else if (micros < 1000000) {
|
||||
sprintf(buf, "%li ms", micros / 1000);
|
||||
} else if (micros < 60000000) {
|
||||
sprintf(buf, "%.1f s", (double) micros / 1000000);
|
||||
} else if (micros < 6000000000) {
|
||||
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
|
||||
} else {
|
||||
sprintf(buf, "%li min", micros / 1000000 / 60);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
int url_encode_component(const char *str, char *enc, ssize_t *size) {
|
||||
char *ptr = enc;
|
||||
char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@' || ch == '!' ||
|
||||
ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' ||
|
||||
ch == ';' || ch == '=' || ch < ' ' || ch > '~') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else if (ch == ' ') {
|
||||
ptr[0] = '+';
|
||||
} else {
|
||||
ptr[0] = ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int url_encode(const char *str, char *enc, ssize_t *size) {
|
||||
char *ptr = enc;
|
||||
unsigned char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch > 0x7F || ch == ' ') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else {
|
||||
ptr[0] = (char) ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int url_decode(const char *str, char *dec, ssize_t *size) {
|
||||
char *ptr = dec;
|
||||
char ch, buf[3];
|
||||
memset(dec, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - dec) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == '+') {
|
||||
ch = ' ';
|
||||
} else if (ch == '%') {
|
||||
memcpy(buf, str + i + 1, 2);
|
||||
buf[2] = 0;
|
||||
ch = (char) strtol(buf, NULL, 16);
|
||||
i += 2;
|
||||
} else if (ch == '?') {
|
||||
strcpy(ptr, str + i);
|
||||
break;
|
||||
}
|
||||
ptr[0] = ch;
|
||||
}
|
||||
*size = ptr - dec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
|
||||
switch (list->entry_data.type) {
|
||||
case MMDB_DATA_TYPE_MAP:
|
||||
*str_off += sprintf(str + *str_off, "{");
|
||||
break;
|
||||
case MMDB_DATA_TYPE_ARRAY:
|
||||
*str_off += sprintf(str + *str_off, "[");
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UTF8_STRING:
|
||||
*str_off += sprintf(str + *str_off, "\"%.*s\"", list->entry_data.data_size, list->entry_data.utf8_string);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT16:
|
||||
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint16);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT32:
|
||||
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint32);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT64:
|
||||
*str_off += sprintf(str + *str_off, "%lu", list->entry_data.uint64);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT128:
|
||||
*str_off += sprintf(str + *str_off, "%llu", (unsigned long long) list->entry_data.uint128);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_INT32:
|
||||
*str_off += sprintf(str + *str_off, "%i", list->entry_data.uint32);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_BOOLEAN:
|
||||
*str_off += sprintf(str + *str_off, "%s", list->entry_data.boolean ? "true" : "false");
|
||||
break;
|
||||
case MMDB_DATA_TYPE_FLOAT:
|
||||
*str_off += sprintf(str + *str_off, "%f", list->entry_data.float_value);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_DOUBLE:
|
||||
*str_off += sprintf(str + *str_off, "%f", list->entry_data.double_value);
|
||||
break;
|
||||
}
|
||||
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY) {
|
||||
return list->next;
|
||||
}
|
||||
MMDB_entry_data_list_s *next = list->next;
|
||||
int stat = 0;
|
||||
for (int i = 0; i < list->entry_data.data_size; i++) {
|
||||
next = mmdb_json(next, str, str_off, str_len);
|
||||
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
|
||||
stat = !stat;
|
||||
if (stat) {
|
||||
i--;
|
||||
*str_off += sprintf(str + *str_off, ":");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (i != list->entry_data.data_size - 1) *str_off += sprintf(str + *str_off, ",");
|
||||
}
|
||||
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
|
||||
*str_off += sprintf(str + *str_off, "}");
|
||||
} else {
|
||||
*str_off += sprintf(str + *str_off, "]");
|
||||
}
|
||||
return next;
|
||||
}
|
31
src/utils.h
Normal file
31
src/utils.h
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Utilities (header file)
|
||||
* src/utils.h
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_UTILS_H
|
||||
#define NECRONDA_SERVER_UTILS_H
|
||||
|
||||
char *log_prefix;
|
||||
|
||||
#define out_1(fmt) fprintf(stdout, "%s" fmt "\n", log_prefix)
|
||||
#define out_2(fmt, args...) fprintf(stdout, "%s" fmt "\n", log_prefix, args)
|
||||
|
||||
#define out_x(x, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, FUNC, ...) FUNC
|
||||
|
||||
#define print(...) out_x(, ##__VA_ARGS__, out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
|
||||
out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
|
||||
out_2(__VA_ARGS__), out_1(__VA_ARGS__))
|
||||
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf);
|
||||
|
||||
int url_encode_component(const char *str, char *enc, ssize_t *size);
|
||||
|
||||
int url_encode(const char *str, char *enc, ssize_t *size);
|
||||
|
||||
int url_decode(const char *str, char *dec, ssize_t *size);
|
||||
|
||||
#endif //NECRONDA_SERVER_UTILS_H
|
Reference in New Issue
Block a user