Generator
This site is rendered from md files using a filter I developed.
main.c
#include "main.h"
const char *HEADER_UserAgent = "X-User-Agent";
const char *HEADER_BaseURL = "X-Base-URL";
const char *HEADER_RealIP = "X-Real-IP";
const char *HEADER_DocumentRoot = "X-Document-Root";
uint16_t _TCP_PORT = 8888;
unsigned int _CONNECTION_TOT_SEC = 10;
unsigned int _THREAD_POOL_SIZE = 4;
unsigned int _CONNECTION_LIMIT = 10000;
int is_horizontal_rule(const char* line) {
int count = 0;
char prev = '\0';
while (*line) {
if (*line != ' ' && *line != '-' && *line != '*' && *line != '_') {
return 0;
}
if (*line == '-' || *line == '*' || *line == '_') {
if (prev != ' ' && prev != '\0' && prev != *line) {
return 0;
}
count++;
}
prev = *line;
line++;
}
return count >= 3;
}
int ends_with(const char* str, const char* suffix) {
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
return (str_len >= suffix_len) &&
(strcasecmp(str + str_len - suffix_len, suffix) == 0);
}
void render_link_or_media(DynamicBuffer *h, const char* text, const char* url) {
if (ends_with(url, ".mp4") || ends_with(url, ".webm") || ends_with(url, ".ogg")) {
hprintf(h, "<video width=\"320\" height=\"240\" controls><source src=\"%s\" type=\"video/mp4\">"
"Your browser does not support the video tag.</video>\n", url);
} else if (ends_with(url, ".jpg") || ends_with(url, ".jpeg") || ends_with(url, ".png") || ends_with(url, ".gif")) {
hprintf(h, "<img src=\"%s\" alt=\"%s\">", url, text);
} else {
hprintf(h, "<a href=\"%s\">%s</a>", url, text);
}
}
enum TokenType {
KEYWORD,
IDENTIFIER,
STRING,
NUMBER,
COMMENT,
OPERATOR,
PUNCTUATION,
WHITESPACE
};
struct Token {
enum TokenType type;
char text[MAX_TOKEN_LENGTH];
int indent;
};
static const char* keywords[] = {
"auto", "break", "case", "char", "const", "continue", "default", "do",
"double", "else", "enum", "extern", "float", "for", "goto", "if",
"int", "long", "register", "return", "short", "signed", "sizeof", "static",
"struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while"
};
static const int num_keywords = sizeof(keywords) / sizeof(keywords[0]);
int is_keyword(const char* word) {
for (int i = 0; i < num_keywords; i++) {
if (strcmp(word, keywords[i]) == 0) {
return 1;
}
}
return 0;
}
void escape_html(DynamicBuffer *h, const char* src) {
while (*src) {
switch (*src) {
case '&': hprintf(h, "&"); break;
case '<': hprintf(h, "<"); break;
case '>': hprintf(h, ">"); break;
case '"': hprintf(h, """); break;
case '\'': hprintf(h, "'"); break;
default: hprintf(h, "%c", *src);
}
src++;
}
}
const char* get_token_class_name(enum TokenType type) {
switch (type) {
case KEYWORD: return "keyword";
case IDENTIFIER: return "identifier";
case STRING: return "string";
case NUMBER: return "number";
case COMMENT: return "comment";
case OPERATOR: return "operator";
case PUNCTUATION: return "punctuation";
default: return "";
}
}
void print_highlighted_token(DynamicBuffer *h, struct Token token) {
const char* class_name;
switch (token.type) {
case KEYWORD: class_name = "keyword"; break;
case IDENTIFIER: class_name = "identifier"; break;
case STRING: class_name = "string"; break;
case NUMBER: class_name = "number"; break;
case COMMENT: class_name = "comment"; break;
case OPERATOR: class_name = "operator"; break;
case PUNCTUATION: class_name = "punctuation"; break;
case WHITESPACE:
hprintf(h, "%s", token.text);
return;
default: class_name = ""; break;
}
hprintf(h, "<span class=\"%s\">%s</span>", class_name, token.text);
}
void escape_html_stream(DynamicBuffer *h, const char* src) {
switch (*src) {
case '&': hprintf(h, "&"); break;
case '<': hprintf(h, "<"); break;
case '>': hprintf(h, ">"); break;
case '"': hprintf(h, """); break;
case '\'': hprintf(h, "'"); break;
default: hprintf(h, "%c", *src);
}
}
void process_inline_markdown(DynamicBuffer *h, const char* line) {
int in_bold = 0;
int in_italic = 0;
const char* ptr = line;
while (*ptr) {
if (*ptr == '*' || *ptr == '_') {
if (*(ptr + 1) == *ptr) { // Bold
hprintf(h, in_bold ? "</strong>" : "<strong>");
in_bold = !in_bold;
ptr += 2;
} else { // Italic
hprintf(h, in_italic ? "</em>" : "<em>");
in_italic = !in_italic;
ptr++;
}
} else if (*ptr == '!' && *(ptr + 1) == '[') { // Image
const char* alt_start = ptr + 2;
const char* alt_end = strchr(alt_start, ']');
if (alt_end && alt_end[1] == '(' && strchr(alt_end + 2, ')')) {
const char* url_start = alt_end + 2;
const char* url_end = strchr(url_start, ')');
hprintf(h, "<img src=\"");
while (url_start < url_end) {
escape_html_stream(h, url_start);
url_start++;
}
hprintf(h, "\" alt=\"");
while (alt_start < alt_end) {
escape_html_stream(h, alt_start);
alt_start++;
}
hprintf(h, "\">");
ptr = url_end + 1;
} else {
escape_html_stream(h, ptr);
ptr++;
}
} else if (*ptr == '[') { // Link
const char* text_start = ptr + 1;
const char* text_end = strchr(text_start, ']');
if (text_end && text_end[1] == '(' && strchr(text_end + 2, ')')) {
const char* url_start = text_end + 2;
const char* url_end = strchr(url_start, ')');
hprintf(h, "<a href=\"");
while (url_start < url_end) {
escape_html_stream(h, url_start);
url_start++;
}
hprintf(h, "\">");
while (text_start < text_end) {
escape_html_stream(h, text_start);
text_start++;
}
hprintf(h, "</a>");
ptr = url_end + 1;
} else {
escape_html_stream(h, ptr);
ptr++;
}
} else {
escape_html_stream(h, ptr);
ptr++;
}
}
if (in_bold) { hprintf(h, "</strong>"); }
if (in_italic) { hprintf(h, "</em>"); }
}
struct Token get_next_token_from_string(const char** code, int tab_size) {
struct Token token;
const char* ptr = *code;
token.indent = 0;
if (isspace(*ptr)) {
token.type = WHITESPACE;
int i = 0;
while (isspace(*ptr) && i < MAX_TOKEN_LENGTH - 1) {
if (*ptr == '\t') {
token.indent += tab_size;
} else {
token.indent++;
}
token.text[i++] = *ptr++;
}
token.text[i] = '\0';
*code = ptr;
return token;
}
if (*ptr == '\0') {
token.type = WHITESPACE;
token.text[0] = '\0';
*code = ptr;
return token;
}
if (isalpha(*ptr) || *ptr == '_') {
token.type = IDENTIFIER;
int i = 0;
do {
token.text[i++] = *ptr++;
} while ((isalnum(*ptr) || *ptr == '_') && i < MAX_TOKEN_LENGTH - 1);
token.text[i] = '\0';
if (is_keyword(token.text)) {
token.type = KEYWORD;
}
*code = ptr;
return token;
}
if (isdigit(*ptr)) {
token.type = NUMBER;
int i = 0;
do {
token.text[i++] = *ptr++;
} while ((isdigit(*ptr) || *ptr == '.') && i < MAX_TOKEN_LENGTH - 1);
token.text[i] = '\0';
*code = ptr;
return token;
}
if (*ptr == '"') {
token.type = STRING;
int i = 0;
token.text[i++] = *ptr++;
while (*ptr != '\0' && *ptr != '"' && *ptr != '\n' && i < MAX_TOKEN_LENGTH - 2) {
if (*ptr == '\\' && *(ptr + 1) != '\0') {
token.text[i++] = *ptr++;
token.text[i++] = *ptr++;
} else {
token.text[i++] = *ptr++;
}
}
if (*ptr == '"') {
token.text[i++] = *ptr++;
}
token.text[i] = '\0';
*code = ptr;
return token;
}
if (*ptr == '/') {
if (*(ptr + 1) == '/') {
token.type = COMMENT;
int i = 0;
while (*ptr != '\0' && *ptr != '\n' && i < MAX_TOKEN_LENGTH - 1) {
token.text[i++] = *ptr++;
}
token.text[i] = '\0';
*code = ptr;
return token;
}
}
token.type = (strchr("+-*/%=<>!&|^~", *ptr) ? OPERATOR : PUNCTUATION);
token.text[0] = *ptr++;
token.text[1] = '\0';
*code = ptr;
return token;
}
int is_header(const char* line, int* level) {
*level = 0;
while (*line == '#') { (*level)++; line++; }
return *level > 0 && *level <= 6 && *line == ' ';
}
void highlight_code(DynamicBuffer *h, const char* code, int tab_size) {
struct Token token;
const char* ptr = code;
int newline = 1;
while (*ptr) {
token = get_next_token_from_string(&ptr, tab_size);
if (newline && token.type != WHITESPACE) {
hprintf(h, "<span class=\"line\">");
newline = 0;
}
if (token.type == WHITESPACE) {
if (strchr(token.text, '\n')) {
if (!newline) {
hprintf(h, "</span>");
}
escape_html(h, token.text);
newline = 1;
} else {
escape_html(h, token.text);
}
} else {
hprintf(h, "<span class=\"%s\">", get_token_class_name(token.type));
escape_html(h, token.text);
hprintf(h, "</span>");
}
if (*ptr == '\0' && !newline) {
hprintf(h, "</span>");
}
}
}
void render_code_block(DynamicBuffer *h, FILE *fd) {
DynamicBuffer code_buffer = {0};
char line[1024]; // reasonable line buffer size
int in_code_block = 1;
int is_empty_block = 1; // Flag to check if the block is empty
// TODO add code detector
hprintf(h, "<style>"
".keyword { color: blue; }"
".identifier { color: black; }"
".string { color: green; }"
".number { color: orange; }"
".comment { color: gray; }"
".operator { color: red; }"
".punctuation { color: black; }"
"</style>\n");
hprintf(h, "<pre><code>");
while (fgets(line, sizeof(line), fd) && in_code_block) {
if (strncmp(line, "```", 3) == 0) {
in_code_block = 0;
break;
}
is_empty_block = 0;
hprintf(&code_buffer, "%s", line);
}
if (!is_empty_block) {
highlight_code(h, code_buffer.data, 4); // Assume tab size of 4
}
hprintf(h, "</code></pre>\n");
free(code_buffer.data);
}
void render_table_row(DynamicBuffer *h, const char* line, int is_header) {
hprintf(h, "<tr>\n");
char* token = strtok((char*)line, "|");
while (token != NULL) {
while (*token == ' ') token++; // Trim leading spaces
char* end = token + strlen(token) - 1;
while (end > token && *end == ' ') end--; // Trim trailing spaces
*(end + 1) = '\0';
if (is_header) {
hprintf(h, " <th>%s</th>\n", token);
} else {
hprintf(h, " <td>%s</td>\n", token);
}
token = strtok(NULL, "|");
}
hprintf(h, "</tr>\n");
}
void render_line(DynamicBuffer *h, char* line, FILE* fd) {
int header_level;
if (is_header(line, &header_level)) {
hprintf(h, "<h%d>", header_level);
process_inline_markdown(h, line + header_level + 1);
hprintf(h, "</h%d>\n", header_level);
} else if (is_horizontal_rule(line)) {
hprintf(h, "<hr>\n");
} else if (strncmp(line, ">", 1) == 0) { // Blockquote
hprintf(h, "<blockquote><p>");
process_inline_markdown(h, line + 1);
hprintf(h, "</p></blockquote>\n");
} else if (strncmp(line, "```", 3) == 0) { // Code block
render_code_block(h, fd);
} else if (strncmp(line, "- ", 2) == 0 || strncmp(line, "* ", 2) == 0 || strncmp(line, "+ ", 2) == 0) { // Unordered list
static int in_list = 0;
if (!in_list) {
hprintf(h, "<ul>\n");
in_list = 1;
}
hprintf(h, "<li>");
process_inline_markdown(h, line + 2);
hprintf(h, "</li>\n");
// Check next line to see if we need to close the list
char next_line[MAX_LINE_LENGTH];
long pos = ftell(fd);
if (fgets(next_line, sizeof(next_line), fd)) {
if (!(strncmp(next_line, "- ", 2) == 0 || strncmp(next_line, "* ", 2) == 0 || strncmp(next_line, "+ ", 2) == 0)) {
hprintf(h, "</ul>\n");
in_list = 0;
}
fseek(fd, pos, SEEK_SET); // Go back to previous position
}
} else if (isdigit(line[0]) && strncmp(line + 1, ". ", 2) == 0) { // Ordered list
static int in_ordered_list = 0;
if (!in_ordered_list) {
hprintf(h, "<ol>\n");
in_ordered_list = 1;
}
hprintf(h, "<li>");
process_inline_markdown(h, line + 3);
hprintf(h, "</li>\n");
// Check next line to see if we need to close the list
char next_line[MAX_LINE_LENGTH];
long pos = ftell(fd);
if (fgets(next_line, sizeof(next_line), fd)) {
if (!(isdigit(next_line[0]) && strncmp(next_line + 1, ". ", 2) == 0)) {
hprintf(h, "</ol>\n");
in_ordered_list = 0;
}
fseek(fd, pos, SEEK_SET); // Go back to previous position
}
} else if (line[0] == '|') { // Table
render_table_row(h, line, 1); // Assuming this is the header row
// Handle table body rows
char next_line[MAX_LINE_LENGTH];
while (fgets(next_line, sizeof(next_line), fd) && next_line[0] == '|') {
if (strstr(next_line, "---")) {
continue; // Skip separator row
}
render_table_row(h, next_line, 0);
}
fseek(fd, -strlen(next_line), SEEK_CUR); // Go back one line
} else if (strlen(line) == 0) { // Empty line
hprintf(h, "\n");
} else { // Regular paragraph
hprintf(h, "<p>");
process_inline_markdown(h, line);
hprintf(h, "</p>\n");
}
}
void md_content(DynamicBuffer* h, FILE* fd) {
char line[MAX_CODE_BLOCK_SIZE];
int in_paragraph = 0;
int in_table = 0;
while (fgets(line, sizeof(line), fd)) {
line[strcspn(line, "\n")] = 0; // Remove newline character
if (strlen(line) == 0) {
if (in_paragraph) {
hprintf(h, "</p>\n");
in_paragraph = 0;
}
if (in_table) {
hprintf(h, "</table>\n");
in_table = 0;
}
} else if (line[0] == '|') { // Table row
if (!in_table) {
hprintf(h, "<table>\n");
in_table = 1;
render_table_row(h, line, 1); // Render header row
} else if (strstr(line, "---")) {
// Skip the separator row
} else {
render_table_row(h, line, 0); // Render regular row
}
} else {
if (!in_paragraph && !is_header(line, &(int){0}) && !is_horizontal_rule(line)) {
hprintf(h, "<p>");
in_paragraph = 1;
}
render_line(h, line, fd);
}
}
if (in_paragraph) { hprintf(h, "</p>\n"); }
if (in_table) { hprintf(h, "</table>\n"); }
}
char* find_format_hashtags(FILE* fd) {
char line[MAX_LINE_LENGTH];
char* formatted_tags = NULL;
long file_size;
fseek(fd, 0, SEEK_END);
file_size = ftell(fd);
if (file_size == 0) {
return NULL;
}
long pos = -2;
while (pos >= -file_size && fgetc(fd) != '\n') {
fseek(fd, pos, SEEK_END);
pos--;
}
if (fgets(line, sizeof(line), fd) == NULL) {
return NULL; // Failed to read the last line
}
line[strcspn(line, "\n")] = 0;
formatted_tags = malloc(strlen(line) + 1);
if (formatted_tags == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return NULL;
}
formatted_tags[0] = '\0';
char* token = strtok(line, " ");
int first_tag = 1;
while (token != NULL) {
if (token[0] == '#') {
// Remove '#' symbol
char* tag = token + 1;
// Replace underscore with space
for (char* ch = tag; *ch; ch++) {
if (*ch == '_') *ch = ' ';
}
if (!first_tag) {
strcat(formatted_tags, ", ");
}
strcat(formatted_tags, tag);
first_tag = 0;
}
token = strtok(NULL, " ");
}
// If no tags found, free memory and return NULL
if (first_tag) {
free(formatted_tags);
return NULL;
}
return formatted_tags;
}
int bufferVprintf(DynamicBuffer* buffer, const char* format, va_list args) {
va_list args_copy;
va_copy(args_copy, args);
int i = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
if (i < 0) {
++buffer->corrupts;
return 0;
}
size_t reqsz = buffer->size + i + 1;
if (reqsz > buffer->capacity) {
size_t new_capacity = ((reqsz - 1) / EXPANDBUFFER_CHUNK_SIZE + 1) * EXPANDBUFFER_CHUNK_SIZE;
char* new_data = realloc(buffer->data, new_capacity);
if (new_data) {
buffer->data = new_data;
buffer->capacity = new_capacity;
} else {
++buffer->corrupts;
return 0;
}
}
i = vsnprintf(buffer->data + buffer->size, buffer->capacity - buffer->size, format, args);
if (i < 0) {
++buffer->corrupts;
return 0;
}
buffer->size += i;
return i;
}
int hprintf(DynamicBuffer* buffer, const char* format, ...) {
va_list args;
va_start(args, format);
int result = bufferVprintf(buffer, format, args);
va_end(args);
return result;
}
char* sanitize_url(const char* url) {
if (url == NULL) return NULL;
size_t len = strlen(url);
char* sanitized = malloc(len + 1);
if (sanitized == NULL) return NULL;
size_t i = 0, j = 0;
bool previous_slash = false;
// Skip leading '.' or '..'
while (url[i] == '.' || url[i] == '/') i++;
for (; i < len; i++) {
if (url[i] == '/' && previous_slash) {
continue; // Skip consecutive slashes
}
if (isprint(url[i]) && url[i] != '\\') {
sanitized[j++] = url[i];
previous_slash = (url[i] == '/');
}
}
sanitized[j] = '\0';
// Check if the sanitized URL is empty or only contains '/'
if (j == 0 || (j == 1 && sanitized[0] == '/')) {
free(sanitized);
return NULL;
}
return sanitized;
}
enum MHD_Result serve_file(struct MHD_Connection *connection, const char *url) {
enum MHD_Result result = MHD_NO;
char *real_path = NULL;
DynamicBuffer *buffer = NULL;
ReqData req = {
.Bot = 0,
.UserAgent = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, HEADER_UserAgent),
.BaseURL = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, HEADER_BaseURL),
.RealIP = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, HEADER_RealIP),
.DocRoot = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, HEADER_DocumentRoot)
};
char* sanitized_url = sanitize_url(url);
if (!sanitized_url) {
goto cleanup; // URL sanitization failed
}
req.URL = sanitized_url;
char full_path[PATH_MAX + 1]; // Use PATH_MAX from limits.h
if (req.DocRoot) {
int written = snprintf(full_path, sizeof(full_path), "%s/%s", req.DocRoot, sanitized_url);
if (written < 0 || (size_t)written >= sizeof(full_path) - 1) {
goto cleanup; // Path too long or encoding error
}
} else {
int written = snprintf(full_path, sizeof(full_path), "./%s", sanitized_url);
if (written < 0 || (size_t)written >= sizeof(full_path) - 1) {
goto cleanup; // Path too long or encoding error
}
}
full_path[sizeof(full_path) - 1] = '\0'; // Ensure null-termination
errno = 0;
char resolved_path[PATH_MAX + 1];
if (realpath(full_path, resolved_path) == NULL) {
goto cleanup;
}
if (req.DocRoot) {
char resolved_docroot[PATH_MAX + 1];
if (realpath(req.DocRoot, resolved_docroot) == NULL) {
goto cleanup;
}
if (strncmp(resolved_path, resolved_docroot, strlen(resolved_docroot)) != 0) {
goto cleanup; // Path is outside of allowed directory
}
}
int written = snprintf(req.filename, sizeof(req.filename), "%s", resolved_path);
if (written < 0 || (size_t)written >= sizeof(req.filename) - 1) {
goto cleanup; // Path too long for req.filename or encoding error
}
req.filename[sizeof(req.filename) - 1] = '\0'; // Ensure null-termination
buffer = malloc(sizeof(DynamicBuffer));
if (!buffer) {
goto cleanup;
}
buffer->data = malloc(EXPANDBUFFER_CHUNK_SIZE);
if (!buffer->data) {
free(buffer);
buffer = NULL;
goto cleanup;
}
buffer->capacity = EXPANDBUFFER_CHUNK_SIZE;
buffer->size = 0;
buffer->corrupts = 0;
req.h = buffer;
memset(&req.timeinfo, 0, sizeof(struct tm));
if (stat(req.filename, &req.attr) != 0) {
goto cleanup;
}
if (localtime_r(&req.attr.st_mtime, &req.timeinfo) == NULL) {
goto cleanup;
}
generateHtml(&req);
if ((buffer->corrupts == 0) && (buffer->size > 0)) {
struct MHD_Response *response = MHD_create_response_from_buffer(
buffer->size,
buffer->data,
MHD_RESPMEM_MUST_FREE
);
if (!response) {
goto cleanup;
}
result = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
} else {
struct MHD_Response *response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
if (!response) {
goto cleanup;
}
result = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
MHD_destroy_response(response);
}
cleanup:
if (sanitized_url) { free(sanitized_url); }
if (real_path) { free(real_path); }
if (result != MHD_YES && buffer) {
if (buffer->data) {
free(buffer->data);
}
free(buffer);
}
return result;
}
static enum MHD_Result RequestProcess(void *cls,
struct MHD_Connection *connection,
const char *url,
const char *method,
const char *version,
const char *upload_data,
size_t *upload_data_size,
void **req_cls
) {
enum MHD_Result result = MHD_NO;
if (strlen(url) >= MAX_URL_LENGTH) { return MHD_NO; }
if (0 == strcmp(method, "GET")) {
const char *str_redirect = "/redirect";
if (strncmp(url, str_redirect, strlen(str_redirect)) == 0) {
result = serve_static_redirect(connection);
} else {
result = serve_file(connection, url);
}
} else {
fprintf(stderr, "Method! [%s]\n", method);
}
(void) cls; (void) url; (void) version; (void) upload_data;
(void) upload_data_size; (void) req_cls; // mute warning
return result;
}
int main (int argc, char *const *argv) {
(void)argc; (void)argv;
struct MHD_Daemon *d;
struct sockaddr_in loopback_addr;
memset(&loopback_addr, 0, sizeof(loopback_addr));
loopback_addr.sin_family = AF_INET;
loopback_addr.sin_port = htons(_TCP_PORT);
loopback_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
d = MHD_start_daemon(
MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_EPOLL | MHD_USE_TURBO,
_TCP_PORT,
NULL, NULL, &RequestProcess, NULL,
MHD_OPTION_SOCK_ADDR, (struct sockaddr *)(&loopback_addr),
MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) _CONNECTION_TOT_SEC,
MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) _THREAD_POOL_SIZE,
MHD_OPTION_CONNECTION_LIMIT, (unsigned int) _CONNECTION_LIMIT,
MHD_OPTION_END
);
if (d == NULL) {
fprintf(stderr, "Failed to create MHDaemon\n");
return 1;
}
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &loopback_addr.sin_addr, ip_str, sizeof(ip_str));
printf("Listen on %s:%d Press ENTER to stop\n", ip_str, _TCP_PORT);
int c;
while ((c = getchar()) != '\n' && c != EOF) {
// Consume any extra input
}
MHD_stop_daemon(d);
return 0;
}
main.h
#ifndef MAIN_H_INCLUDED
#define MAIN_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <libgen.h>
#include <sys/stat.h>
#include <dirent.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include <microhttpd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#define MAX_TOKEN_LENGTH 2048
#define MAX_CODE_BLOCK_SIZE 2048
#define MAX_LINE_LENGTH 2048
#define EXPANDBUFFER_CHUNK_SIZE 2048
#define MAX_PATH 256
#define MAX_URL_LENGTH 2048
typedef struct {
char* data;
size_t size;
size_t capacity;
size_t corrupts;
} DynamicBuffer;
typedef struct {
uint64_t Bot;
const char* DocRoot;
const char* UserAgent;
const char* BaseURL;
const char* RealIP;
char filename[1024];
struct tm timeinfo;
const char* URL;
FILE* file;
struct stat attr;
DynamicBuffer *h;
} ReqData;
void md_content(DynamicBuffer* h, FILE* fd);
void render_link_or_media(DynamicBuffer *h, const char* text, const char* url);
void render_table_row(DynamicBuffer *h, const char* line, int is_header);
// void extract_md_metadata(FILE* fd, char** h1_title, char** description, char** formatted_tags);
// void print_time_datetime(DynamicBuffer* h, const struct tm *date);
// char* find_h1_title(FILE* fd);
char* find_format_hashtags(FILE* fd);
int hprintf(DynamicBuffer* buffer, const char* format, ...);
void generateHtml(ReqData* Req);
#define HTAG_BLOCK(fd, tag) \
for(int _i = ({ hprintf(fd, "<%s>", tag); 0;}); \
!_i; \
_i = ({hprintf(fd, "</%s>\n", tag); 1;}))
#define HTAG_BLOCKA(fd, tag, attr) \
for(int _i = ({hprintf(fd, "<%s %s>", tag, attr); 0;}); \
!_i; \
_i = ({hprintf(fd, "</%s>\n", tag); 1;}))
enum MHD_Result serve_static_access_denied(struct MHD_Connection *connection);
enum MHD_Result serve_static_jpeg(struct MHD_Connection *connection, const char *file_path);
enum MHD_Result serve_static_redirect(struct MHD_Connection *connection);
extern const char *HEADER_UserAgent;
extern const char *HEADER_BaseURL;
extern const char *HEADER_RealIP;
extern const char *HEADER_DocumentRoot;
#endif // MAIN_H_INCLUDED
site.c
#include "main.h"
static const char *Access_denied = "Access denied";
// Example:
// #define FPATH_TEMPLETE "./store/%ld/%ld.jpeg"
// static const char *redir_to_HomePage = "/";
// static const char *redir_to_whatsapp = "https://wa.me/00000000";
// static const char *redir_to_telegram = "https://t.me/telegram";
// static const char *redir_to_viber = "viber://chat?number=00000000";
// static const char *redir_to_email = "mailto:[email protected]?subject=HELLO";
#include "../secret.h"
void render_menu_links(DynamicBuffer *h) {
render_link_or_media(h, "Telegram", "/redirect?messenger=telegram");
render_link_or_media(h, "WhatsApp", "/redirect?messenger=whatsapp");
render_link_or_media(h, "Viber", "/redirect?messenger=viber");
render_link_or_media(h, "Email", "/redirect?messenger=email");
render_link_or_media(h, "LinkedIn", redir_to_LinkedIn);
render_link_or_media(h, "GitHub", redir_to_GitHub);
render_link_or_media(h, "Facebook", redir_to_Facebook);
render_link_or_media(h, "Instagram", redir_to_Instagram);
render_link_or_media(h, "About", "/about");
}
#define MAX_BOTS 14
const char *bots[MAX_BOTS] = {
"googlebot", "telegrambot", "twitterbot",
"bingbot", "slurp", "duckduckbot",
"baiduspider", "yandex", "sogou",
"exabot", "facebot", "ia_archiver",
"curl", "wget"
};
typedef enum {
BOT_NONE = 0,
BOT_GOOGLE = 1 << 0,
BOT_TELEGRAM = 1 << 1,
BOT_TWITTER = 1 << 2,
BOT_BING = 1 << 3,
BOT_YAHOO = 1 << 4,
BOT_DUCKDUCKGO = 1 << 5,
BOT_BAIDU = 1 << 6,
BOT_YANDEX = 1 << 7,
BOT_SOGOU = 1 << 8,
BOT_EXABOT = 1 << 9,
BOT_FACEBOOK = 1 << 10,
BOT_ARCHIVE = 1 << 11,
BOT_CURL = 1 << 12,
BOT_WGET = 1 << 13
} BotType;
uint64_t detect_bot_UA(const char *user_agent) {
uint64_t result = 0;
char *lUA = strdup(user_agent);
for (int i = 0; lUA[i]; i++) { lUA[i] = tolower(lUA[i]); }
for (int i = 0; i < MAX_BOTS; i++) {
if (strstr(lUA, bots[i]) != NULL) {
result |= (1ULL << i);
}
}
free(lUA);
return result;
}
const char *days_of_week_en[] = {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
};
const char *months_en[] = {
"January", "February", "March", "April",
"May", "June", "July", "August", "September",
"October", "November", "December"
};
void print_time_datetime(DynamicBuffer* h, const struct tm *date) {
const char** days_of_week = days_of_week_en;
const char** months = months_en;
if (!h || !date || !days_of_week || !months) return;
hprintf(h, "<time datetime=\"%d-%02d-%02d %02d:%02d\">%s %s %02d, %d</time>\n",
date->tm_year + 1900, date->tm_mon + 1, date->tm_mday,
date->tm_hour, date->tm_min,
days_of_week[date->tm_wday],
months[date->tm_mon],
date->tm_mday, date->tm_year + 1900);
}
int file_access_with_extension(const char *filename, const char *extension) {
if (filename == NULL || extension == NULL) { return 1; }
char *new_path = (char *)malloc(strlen(filename) + strlen(extension) + 1);
if (new_path == NULL) { return 1; }
strcpy(new_path, filename);
strcat(new_path, extension);
int ret = access(new_path, F_OK);
free(new_path);
return ret; // 0 - OK
}
char* file_mkpath_with_extension(const char *filename, const char *extension) {
if (filename == NULL || extension == NULL) { return NULL; }
char *new_path = (char *)malloc(strlen(filename) + strlen(extension) + 1);
if (new_path == NULL) { return NULL; }
strcpy(new_path, filename);
strcat(new_path, extension);
if (access(new_path, F_OK) == 0) {
return new_path;
} else {
free(new_path);
return NULL;
}
}
FILE* file_open_with_extension(const char *filename, const char *extension) {
if (filename == NULL || extension == NULL) { return NULL; }
char *new_path = (char *)malloc(strlen(filename) + strlen(extension) + 1);
if (new_path == NULL) { return NULL; }
strcpy(new_path, filename);
strcat(new_path, extension);
FILE *file = fopen(new_path, "r");
free(new_path);
return file;
}
void print_l_r_m_h(ReqData* Req, const char* rel, const char* size, const char* href) {
if (!Req || !rel || !href) return;
hprintf(Req->h, "<link rel=\"%s\" ", rel);
if (size) {
if (size[0]) {
hprintf(Req->h, "media=\"%s\" ", size);
}
}
if (href[0] == '\1') {
hprintf(Req->h, "href=\"%s/%s\">\n", Req->BaseURL, href + 1);
} else {
hprintf(Req->h, "href=\"%s\">\n", href);
}
}
void esprint(DynamicBuffer *h, const char* src) {
size_t len = strlen(src);
for (size_t i = 0; i < len; i++) {
switch (src[i]) {
case '&': hprintf(h, "&"); break;
case '<': hprintf(h, "<"); break;
case '>': hprintf(h, ">"); break;
case '"': hprintf(h, """); break;
case '\'': hprintf(h, "'"); break;
default: hprintf(h, "%c", src[i]);
}
}
}
void printMetaTags(ReqData* Req,
const char* title,
const char* description,
const char* keywords_custom,
// const char* canonical_url,
const char* author,
const char* twitter_channel,
const char* telegram_channel,
const char* locale)
{
hprintf(Req->h, "\n<meta charset=\"UTF-8\">\n");
if (!Req->Bot) {
hprintf(Req->h, "<meta name=\"viewport\" content=\"width=device-width, "
"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n");
}
if (title && title[0]) {
hprintf(Req->h, "<title>");
esprint(Req->h, title);
hprintf(Req->h, "</title>\n");
hprintf(Req->h, "<meta name=\"title\" content=\"");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
hprintf(Req->h, "<meta name=\"twitter:title\" content=\"");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
}
if (description && description[0]) {
hprintf(Req->h, "<meta name=\"description\" content=\"");
esprint(Req->h, description);
hprintf(Req->h, "\">\n");
hprintf(Req->h, "<meta name=\"twitter:description\" content=\"");
esprint(Req->h, description);
hprintf(Req->h, "\">\n");
hprintf(Req->h, "<meta property=\"og:description\" content=\"");
esprint(Req->h, description);
hprintf(Req->h, "\">\n");
}
hprintf(Req->h, "<meta name=\"keywords\" content=\"");
esprint(Req->h, def_keywords_a);
if (keywords_custom) {
hprintf(Req->h, ", ");
esprint(Req->h, keywords_custom);
} else {
if (Req->Bot) {
esprint(Req->h, def_keywords_b);
}
}
hprintf(Req->h, "\">\n");
// <meta name="generator" content="generator.com" />
// <meta name="application-name" content="generator.com" />
// <meta property="fb:app_id" content="000000000" />
// <meta name="apple-itunes-app" content="app-id=111111111111" />
// <meta name="apple-mobile-web-app-title" content="XXXXX.com" />
if (author && author[0]) {
hprintf(Req->h, "<meta name=\"author\" content=\"");
esprint(Req->h, author);
hprintf(Req->h, "\">\n");
hprintf(Req->h, "<meta property=\"article:author\" content=\"");
esprint(Req->h, author);
hprintf(Req->h, "\">\n");
}
// CSS files
print_l_r_m_h(Req, "stylesheet", NULL, "\1css/default.css");
print_l_r_m_h(Req, "stylesheet", "only screen and (max-width: 550px)", "\1css/mobile.css");
print_l_r_m_h(Req, "stylesheet", "only screen and (min-width: 551px)", "\1css/desktop.css");
// Favicon
hprintf(Req->h, "<link rel=\"icon\" type=\"image/png\" href=\"%s\">\n", "/favicon.png");
// Twitter Card
hprintf(Req->h, "<meta name=\"twitter:card\" content=\"summary_large_image\">\n");
if (twitter_channel && twitter_channel[0]) {
hprintf(Req->h, "<meta name=\"twitter:site\" content=\"%s\">\n", twitter_channel);
}
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<meta name=\"twitter:image\" content=\"%s/%s%s\">\n", Req->BaseURL, Req->URL, ".jpeg");
hprintf(Req->h, "<meta property=\"og:image\" content=\"%s/%s%s\">\n", Req->BaseURL, Req->URL, ".jpeg");
}
// Open Graph
hprintf(Req->h, "<meta property=\"og:type\" content=\"website\">\n");
hprintf(Req->h, "<meta property=\"og:title\" content=\"");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
hprintf(Req->h, "<meta property=\"og:site_name\" content=\"");
esprint(Req->h, def_title);
hprintf(Req->h, "\">\n");
// Req->BaseURL, Req->URL, ".jpeg");
hprintf(Req->h, "<meta property=\"og:url\" content=\"%s/%s\">\n", Req->BaseURL, Req->URL);
hprintf(Req->h, "<meta property=\"og:image:type\" content=\"image/jpeg\">\n");
hprintf(Req->h, "<meta property=\"og:image:alt\" content=\"");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
if (Req->Bot & BOT_TELEGRAM) {
if (telegram_channel && telegram_channel[0]) {
hprintf(Req->h, "<meta property=\"telegram:channel\" content=\"%s\">\n", telegram_channel);
}
hprintf(Req->h, "<meta property=\"tg:site_verification\" content=\"g7j8/rPFXfhyrq5q0QQV7EsYWv4=\">\n");
}
if (Req->timeinfo.tm_year) {
char time_str[74];
snprintf(time_str, sizeof(time_str), "%d-%02d-%02dT%02d:%02d:%02dZ", Req->timeinfo.tm_year + 1900,
Req->timeinfo.tm_mon + 1, Req->timeinfo.tm_mday, Req->timeinfo.tm_hour, Req->timeinfo.tm_min, Req->timeinfo.tm_sec);
hprintf(Req->h, "<meta name=\"date\" content=\"%s\">\n"
"<meta name=\"last-modified\" content=\"%s\">\n"
"<meta property=\"article:published_time\" content=\"%s\">\n",
time_str, time_str, time_str);
}
// Canonical
hprintf(Req->h, "<link rel=\"canonical\" href=\"%s/%s\">\n", Req->BaseURL, Req->URL);
if (locale && locale[0]) {
hprintf(Req->h, "<meta property=\"og:locale\" content=\"%s\">\n", locale);
hprintf(Req->h, "<link rel=\"alternate\" hreflang=\"%s\" href=\"%s/%s\">\n",
locale, Req->BaseURL, Req->URL);
}
// Google hreflang tag
hprintf(Req->h, "<link rel=\"alternate\" hreflang=\"x-default\" href=\"%s/%s\">\n", Req->BaseURL, Req->URL);
// <meta name="google-site-verification" content="XXXXXXXXXXXXXXXXXXXXXX" />
}
void extract_md_metadata(FILE* fd, char** h1_title, char** description, char** formatted_tags) {
char line[MAX_LINE_LENGTH];
long file_size;
int h1_found = 0;
size_t desc_len = 0;
*h1_title = NULL;
*description = NULL;
*formatted_tags = NULL;
// Allocate memory for description
*description = malloc(MAX_DESC_LEN + 1);
if (*description == NULL) {
fprintf(stderr, "Memory allocation failed for description\n");
return;
}
(*description)[0] = '\0';
// Get file size
fseek(fd, 0, SEEK_END);
file_size = ftell(fd);
rewind(fd);
if (file_size == 0) {
return; // File is empty
}
// Read the file line by line
while (fgets(line, sizeof(line), fd)) {
// Remove newline character
line[strcspn(line, "\n")] = 0;
// Check for h1 title if not found yet
if (!h1_found && line[0] == '#' && line[1] == ' ') {
char* start = line + 2;
while (*start == ' ') start++;
char* end = start + strlen(start) - 1;
while (end > start && *end == ' ') end--;
*(end + 1) = '\0';
*h1_title = malloc(end - start + 2);
if (*h1_title == NULL) {
fprintf(stderr, "Memory allocation failed for h1_title\n");
return;
}
// Safely copy and escape the h1 title
char* dst = *h1_title;
for (char* src = start; *src && dst - *h1_title < end - start + 1; src++) {
switch (*src) {
case '&': strcpy(dst, "&"); dst += 5; break;
case '<': strcpy(dst, "<"); dst += 4; break;
case '>': strcpy(dst, ">"); dst += 4; break;
case '"': strcpy(dst, """); dst += 6; break;
case '\'': strcpy(dst, "'"); dst += 5; break;
default: *dst++ = *src;
}
}
*dst = '\0';
h1_found = 1;
}
// Process description
else if (desc_len < MAX_DESC_LEN && line[0] != '#') {
for (char* c = line; *c && desc_len < MAX_DESC_LEN; c++) {
if (isprint(*c)) {
switch (*c) {
case '&':
if (desc_len + 5 <= MAX_DESC_LEN) {
memcpy(*description + desc_len, "&", 5);
desc_len += 5;
}
break;
case '<':
if (desc_len + 4 <= MAX_DESC_LEN) {
memcpy(*description + desc_len, "<", 4);
desc_len += 4;
}
break;
case '>':
if (desc_len + 4 <= MAX_DESC_LEN) {
memcpy(*description + desc_len, ">", 4);
desc_len += 4;
}
break;
default:
(*description)[desc_len++] = *c;
}
}
}
if (desc_len < MAX_DESC_LEN) {
(*description)[desc_len++] = ' ';
}
}
}
(*description)[desc_len] = '\0';
char* end = *description + strlen(*description) - 1;
while (end > *description && *end == ' ') {
*end = '\0';
end--;
}
if (line[0] != '\0') {
*formatted_tags = malloc(strlen(line) * 6 + 1);
if (*formatted_tags == NULL) {
fprintf(stderr, "Memory allocation failed for formatted_tags\n");
return;
}
(*formatted_tags)[0] = '\0';
char* token = strtok(line, " ");
int first_tag = 1;
while (token != NULL) {
if (token[0] == '#') {
char* tag = token + 1;
if (!first_tag) {
strcat(*formatted_tags, ", ");
}
char* dst = *formatted_tags + strlen(*formatted_tags);
for (char* src = tag; *src; src++) {
switch (*src) {
case '&': strcpy(dst, "&"); dst += 5; break;
case '<': strcpy(dst, "<"); dst += 4; break;
case '>': strcpy(dst, ">"); dst += 4; break;
case '"': strcpy(dst, """); dst += 6; break;
case '\'': strcpy(dst, "'"); dst += 5; break;
case '_': *dst++ = ' '; break;
default: *dst++ = *src;
}
}
*dst = '\0';
first_tag = 0;
}
token = strtok(NULL, " ");
}
if (first_tag) {
free(*formatted_tags);
*formatted_tags = NULL;
}
}
}
// Helper function to remove leading and trailing spaces
char* trim(const char* str) {
if (str == NULL) return NULL;
while (isspace((unsigned char)*str)) str++;
if (*str == 0) return strdup("");
const char* end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) end--;
end++;
size_t len = end - str;
char* result = malloc(len + 1);
if (result) {
memcpy(result, str, len);
result[len] = '\0';
}
return result;
}
char* concatenateStrings(const char* default_str, const char* section_name, const char* site_name) {
char* result = NULL;
size_t len = 0;
char* trimmed_default = trim(default_str);
char* trimmed_section = trim(section_name);
char* trimmed_site = trim(site_name);
int has_site_name = (trimmed_site != NULL && trimmed_site[0] != '\0');
int has_section_name = (trimmed_section != NULL && trimmed_section[0] != '\0');
if (has_site_name) {
if (has_section_name) {
len = snprintf(NULL, 0, FORMAT_TEMPLATE, trimmed_section, trimmed_site) + 1;
result = (char*)malloc(len);
if (result) {
snprintf(result, len, FORMAT_TEMPLATE, trimmed_section, trimmed_site);
}
} else {
len = strlen(trimmed_site) + 1;
result = strdup(trimmed_site);
}
} else if (has_section_name) {
len = snprintf(NULL, 0, FORMAT_TEMPLATE, trimmed_section, trimmed_default) + 1;
result = (char*)malloc(len);
if (result) {
snprintf(result, len, FORMAT_TEMPLATE, trimmed_section, trimmed_default);
}
} else {
result = strdup(trimmed_default);
}
free(trimmed_default);
free(trimmed_section);
free(trimmed_site);
return result;
}
void generateHtml(ReqData *Req) {
Req->file = fopen(Req->filename, "r");
if (!Req->file) { return; }
Req->Bot = detect_bot_UA(Req->UserAgent);
char *locale = "en";
char *author = "LocalAdmin";
char *twitter_channel = NULL;
char *telegram_channel = NULL;
if (def_author && def_author[0]) author = (char *)def_author;
if (def_twitter_channel && def_twitter_channel[0]) twitter_channel = (char *)def_twitter_channel;
if (def_telegram_channel && def_telegram_channel[0]) telegram_channel = (char *)def_telegram_channel;
char *tags_a = NULL;
char *title_a = NULL;
char *description_a = NULL;
extract_md_metadata(Req->file, &title_a, &description_a, &tags_a);
// if (tags_a != NULL) { printf(" Tags: %s\n", tags_a); }
// if (title_a != NULL) { printf(" Title (h1): %s\n", title_a); }
// if (description_a != NULL) { printf("Description: %s\n", description_a); }
char *title = concatenateStrings("Home", title_a, def_title);
char *description = concatenateStrings("Description", description_a, def_abstract);
hprintf(Req->h, "<!DOCTYPE html>\n");
if (Req->Bot) {
hprintf(Req->h, "<html>\n");
HTAG_BLOCK(Req->h, "head") {
printMetaTags(Req, title, description, tags_a,
author, twitter_channel, telegram_channel, locale);
}
} else {
hprintf(Req->h, "<html lang=\"%s\" dir=\"ltr\">\n", locale);
HTAG_BLOCKA(Req->h, "head", "class=\"page\"") {
printMetaTags(Req, title, description, tags_a,
author, twitter_channel, telegram_channel, locale);
}
}
HTAG_BLOCK(Req->h, "body") {
if (Req->Bot & BOT_TELEGRAM) {
HTAG_BLOCKA(Req->h, "div", "class=\"article\"") {
HTAG_BLOCK(Req->h, "article class=\"article__content\"") {
hprintf(Req->h, "<figure>");
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<img src=\"%s/%s%s\" />", Req->BaseURL, Req->URL, ".jpeg");
}
hprintf(Req->h, "<figcaption>");
esprint(Req->h, title);
hprintf(Req->h, "</figcaption></figure><p>");
esprint(Req->h, description);
hprintf(Req->h, "</p>\n");
}
}
} else if (Req->Bot & BOT_FACEBOOK) {
hprintf(Req->h, "<h1>");
esprint(Req->h, title);
hprintf(Req->h, "</h1>\n<p>");
esprint(Req->h, description);
hprintf(Req->h, "</p>");
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<img src=\"%s/%s%s\" alt=\"", Req->BaseURL, Req->URL, ".jpeg");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
}
} else if (Req->Bot & BOT_GOOGLE) {
HTAG_BLOCKA(Req->h, "div", "itemscope itemtype=\"http://schema.org/Article\"") {
hprintf(Req->h, "<h1 itemprop=\"headline\">");
esprint(Req->h, title);
hprintf(Req->h, "</h1>\n");
hprintf(Req->h, "<meta itemprop=\"description\" content=\"");
esprint(Req->h, description);
hprintf(Req->h, "\">\n");
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<img src=\"%s/%s%s\" alt=\"", Req->BaseURL, Req->URL, ".jpeg");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
}
hprintf(Req->h, "<div itemprop=\"articleBody\">");
esprint(Req->h, description);
hprintf(Req->h, "</div>\n");
}
} else if (Req->Bot & BOT_TWITTER) {
HTAG_BLOCKA(Req->h, "div", "class=\"tweet\"") {
hprintf(Req->h, "<h1>");
esprint(Req->h, title);
hprintf(Req->h, "</h1>\n<p>");
esprint(Req->h, description);
hprintf(Req->h, "</p>");
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<img src=\"%s/%s%s\" alt=\"", Req->BaseURL, Req->URL, ".jpeg");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
}
}
} else if (Req->Bot & BOT_BING) {
HTAG_BLOCKA(Req->h, "div", "class=\"bing-snippet\"") {
hprintf(Req->h, "<h1>");
esprint(Req->h, title);
hprintf(Req->h, "</h1>\n<p>");
esprint(Req->h, description);
hprintf(Req->h, "</p>");
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<img src=\"%s/%s%s\" alt=\"", Req->BaseURL, Req->URL, ".jpeg");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
}
}
} else if (Req->Bot != 0) {
// Common case for other bots
HTAG_BLOCK(Req->h, "article") {
hprintf(Req->h, "<h1>"); esprint(Req->h, title); hprintf(Req->h, "</h1>\n");
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<img src=\"%s/%s%s\" alt=\"", Req->BaseURL, Req->URL, ".jpeg");
esprint(Req->h, title);
hprintf(Req->h, "\">\n");
}
hprintf(Req->h, "<p>"); esprint(Req->h, description); hprintf(Req->h, "</p>\n");
}
} else {
HTAG_BLOCKA(Req->h, "header", "id=\"site-header\"") {
HTAG_BLOCKA(Req->h, "section", "id=\"top\"") {
hprintf(Req->h, "<a href=\"%s\"><div id=\"logo\" style=\"background-image: url('%s%s');\"></div></a>\n",
Req->BaseURL, Req->BaseURL, def_logofile );
if (title_a && title_a[0]) {
hprintf(Req->h, "<h1><a href=\"%s/%s\">%s</a></h1>\n", Req->BaseURL, Req->URL, title_a);
} else {
hprintf(Req->h, "<h1><a href=\"%s\">%s</a></h1>\n", Req->BaseURL, title);
}
// if (description_a && description_a[0]) {
// hprintf(Req->h, "<p id=\"site-abstract\">%s</p>\n", description_a);
// } else {
hprintf(Req->h, "<p id=\"site-abstract\">%s</p>\n", def_abstract);
// }
}
HTAG_BLOCKA(Req->h, "div", "class=\"menu\"") {
HTAG_BLOCKA(Req->h, "div", "id=\"menu-section\"") {
hprintf(Req->h,
"<label for=\"show-menu\" class=\"show-menu\">%s</label>\n"
"<input type=\"checkbox\" id=\"show-menu\" role=\"button\">\n",
"Show Menu"); // TODO "Show Menu"
HTAG_BLOCKA(Req->h, "nav", "id=\"topmenu\"") {
render_menu_links(Req->h);
}
}
}
} // header END
HTAG_BLOCK(Req->h, "content") {
Req->file = fopen(Req->filename, "r");
if (!Req->file) {
HTAG_BLOCK(Req->h, "blockquote") { hprintf(Req->h, "<p>Filter can't open the specified file</p>"); }
} else {
HTAG_BLOCK(Req->h, "header") {
HTAG_BLOCKA(Req->h, "div", "id=\"banner\"") {
if (file_access_with_extension(Req->filename, ".jpeg") == 0) {
hprintf(Req->h, "<picture class=\"banner\">\n"
"<img src=\"%s/%s%s\" alt=\"Banner\"></picture>\n", Req->BaseURL, Req->URL, ".jpeg");
hprintf(Req->h, "<p class=\"timestamp\"><span class=\"published\">Published: ");
print_time_datetime(Req->h, &Req->timeinfo/*, days_of_week, months*/);
hprintf(Req->h, "</span></p>\n");
}
}
}
hprintf(Req->h, "<p class=\"floatstop\"></p>");
HTAG_BLOCKA(Req->h, "div", "id=\"content\"") {
HTAG_BLOCKA(Req->h, "div", "id=\"main\"") {
HTAG_BLOCK(Req->h, "main") {
HTAG_BLOCKA(Req->h, "section", "id=\"intro\"") {
md_content(Req->h, Req->file);
}
// HTAG_BLOCKA(Req->h, "section", "id=\"article-list\"") {
// hprintf(Req->h,"<h2>Articles</h2>\n");
// list_articles(Req->h);
// }
}
}
HTAG_BLOCKA(Req->h, "div", "id=\"sidebar\"") {
FILE* sbf = file_open_with_extension(Req->filename, ".sb");
if (sbf) { // open OK
char line[256];
while (fgets(line, sizeof(line), sbf)) {
hprintf(Req->h, "%s", line);
}
} else { // not open
if (file_access_with_extension(Req->filename, ".sb") == 0) {
hprintf(Req->h, "<p>Filter can't open the specified file</p>");
}
}
if (sbf) { fclose(sbf); }
// hprintf(Req->h,"<h2>Tags</h2>");
// HTAG_BLOCK(Req->h, "nav") {
// hprintf(Req->h, "<a class=\"tag\" href=\"%s\">%s</a>\n", "url", "name");
// hprintf(Req->h, "<a class=\"tag\" href=\"%s\">%s</a>\n", "url", "name");
// hprintf(Req->h, "<a class=\"tag\" href=\"%s\">%s</a>\n", "url", "name");
// }
// hprintf(Req->h,"<p class=\"rss\">\n<a class=\"rss\" href=\"%s\">", "TXT");
// hprintf(Req->h,"<img class=\"rss-logo\" src=\"%s/css/rss.svg\" alt=\"RSS logo\">", Req->BaseURL);
// hprintf(Req->h,"<span class=\"rss-label\">%s</span></a>\n", "TXT");
}
}
hprintf(Req->h,"<p class=\"floatstop\"></p>\n");
fclose(Req->file); Req->file = NULL;
}
} // content END
HTAG_BLOCK(Req->h, "footer") {
print_l_r_m_h(Req, "stylesheet", NULL, "\1css/float.css");
hprintf(Req->h, def_footer);
if (Req->RealIP && Req->UserAgent) {
hprintf(Req->h,"<p><strong>IP: </strong>%s<strong> UA: </strong>%s</p>\n", Req->RealIP, Req->UserAgent);
}
} // footer END
}
} // </body>
hprintf(Req->h, "</html>");
if (tags_a) free(tags_a); // free mem
if (title_a) free(title_a); // free mem
if (description_a) free(description_a); // free mem
}
enum MHD_Result serve_static_access_denied(struct MHD_Connection *connection) {
struct MHD_Response *response =
MHD_create_response_from_buffer(
strlen(Access_denied),
(void*)Access_denied,
MHD_RESPMEM_PERSISTENT);
enum MHD_Result ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, response);
MHD_destroy_response(response);
return ret;
}
enum MHD_Result serve_static_jpeg(struct MHD_Connection *connection, const char *file_path) {
if (!connection || !file_path) { return MHD_NO; }
FILE *file = fopen(file_path, "rb");
if (!file) {
return serve_static_access_denied(connection);
}
fseek(file, 0, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0, SEEK_SET);
char *buffer = malloc(file_size);
if (!buffer) {
fclose(file);
return MHD_NO;
}
size_t bytes_read = fread(buffer, 1, file_size, file);
fclose(file);
if (bytes_read != file_size) {
free(buffer);
return MHD_NO;
}
struct MHD_Response *response = MHD_create_response_from_buffer(file_size, buffer, MHD_RESPMEM_MUST_FREE);
if (!response) {
free(buffer);
return MHD_NO;
}
MHD_add_response_header(response, "Content-Type", "image/jpeg");
enum MHD_Result ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
}
enum MHD_Result serve_static_redirect(struct MHD_Connection *connection) {
const char *UA = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, HEADER_UserAgent);
if (UA && (detect_bot_UA(UA) != 0)) {
return serve_static_access_denied(connection);
}
const char *dir_id_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "dir");
const char *file_id_str = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "file");
if (dir_id_str && file_id_str) {
char *endptr;
long dir_id = strtol(dir_id_str, &endptr, 10);
if (*endptr != '\0' || dir_id < 0) { return MHD_NO; }
long file_id = strtol(file_id_str, &endptr, 10);
if (*endptr != '\0' || file_id < 0) { return MHD_NO; }
char file_path[MAX_PATH];
snprintf(file_path, sizeof(file_path), FPATH_TEMPLETE, dir_id, file_id);
return serve_static_jpeg(connection, file_path);
}
const char *messenger = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "messenger");
const char *redirect_url = redir_to_HomePage;
if (messenger) {
if (strcmp(messenger, "whatsapp") == 0) { redirect_url = redir_to_whatsapp; } else
if (strcmp(messenger, "telegram") == 0) { redirect_url = redir_to_telegram; } else
if (strcmp(messenger, "viber") == 0) { redirect_url = redir_to_viber; } else
if (strcmp(messenger, "email") == 0) { redirect_url = redir_to_email; }
}
struct MHD_Response *response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
MHD_add_response_header(response, "Location", redirect_url);
int ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
MHD_destroy_response(response);
return ret;
}
#if 0
const char* get_bot_name(uint64_t bot_flags) {
for (int i = 0; i < MAX_BOTS; i++) {
if (bot_flags & (1ULL << i)) {
return bots[i];
}
}
return "Unknown";
}
typedef struct {
char *class_art_type;
char *url;
char *title;
struct tm* date;
char* description;
char* read_txt;
} article_next;
article_next articles[] = { {
.class_art_type = "article_type",
.url = "url1",
.title = "title1",
.date = NULL,
.description = "description1",
.read_txt = "read_txt1",
}, {
.class_art_type = "article_type",
.url = "url2",
.title = "title2",
.date = NULL,
.description = "description2",
.read_txt = "read_txt2",
}, {
NULL
}};
void list_articles(DynamicBuffer* h) {
for (int i = 0; articles[i].class_art_type != NULL; i++) {
HTAG_BLOCKA(h, "article", "class=\"ainlist\"") {
hprintf(h, "<h3 class=\"%s\"><a href=\"%s\">%s</a></h3>", articles[i].class_art_type, articles[i].url, articles[i].title);
if (articles[i].date == NULL) {
time_t now;
time(&now);
articles[i].date = localtime(&now);
}
print_time_datetime(h, articles[i].date);
hprintf(h, "<p class=\"abstract\">%s</p>", articles[i].description);
// hprintf(h, "<div class=\"tags\"><img class=\"tag-icon\" src=\"%scss/tag.svg\" alt=\"Tags\"/>", "./");
hprintf(h, "<p><a class=\"link-to-article\" href=\"%s\">%s</a></p>", articles[i].url, articles[i].read_txt);
}
}
}
#endif
nginx server section
index index.md index.html index.htm;
location ~ \.md$ {
proxy_set_header X-User-Agent $http_user_agent;
proxy_set_header X-Document-Root $document_root;
proxy_set_header X-Base-URL $scheme://$host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8888;
}
location = /redirect {
proxy_set_header X-User-Agent $http_user_agent;
proxy_set_header X-Document-Root $document_root;
proxy_set_header X-Base-URL $scheme://$host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8888;
}
location ~ /\. { deny all; }
#Generator #rendered #mdfilter #Markdown #Cprogramminglanguage #nginx #server