#include "backend_file.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
//FIXME allocated this statically because i dont want to do it properly right now tbh
#define BACKEND_FILE_MAX_PATH 8192
static char* backend_path = NULL;
size_t expressions = 0;
static char** expression = NULL;
uint64_t init(){
//initialize the backend path to empty to be able to use it without problems later
backend_path = strdup("");
if(!backend_path){
fprintf(stderr, "Failed to allocate memory\n");
return 0;
}
return WEBSOCKSY_API_VERSION;
}
uint64_t configure(char* key, char* value){
if(!strcmp(key, "path")){
free(backend_path);
backend_path = strdup(value);
if(!backend_path){
fprintf(stderr, "Failed to allocate memory\n");
return 1;
}
if(strlen(backend_path) && backend_path[strlen(backend_path) - 1] == '/'){
backend_path[strlen(backend_path) - 1] = 0;
}
return 0;
}
else if(!strcmp(key, "expression")){
expression = realloc(expression, (expressions + 1) * sizeof(char*));
if(!expression){
fprintf(stderr, "Failed to allocate memory\n");
return 1;
}
expression[expressions] = strdup(value);
if(!expression[expressions]){
fprintf(stderr, "Failed to allocate memory\n");
return 1;
}
expressions++;
return 0;
}
fprintf(stderr, "Unknown backend configuration option %s\n", key);
return 1;
}
static int expression_replace(char* buffer, size_t buffer_length, size_t variable_length, char* content, size_t content_length){
size_t u;
//check whether the replacement fits
if(variable_length < content_length && strlen(buffer) + (content_length - variable_length) >= buffer_length){
fprintf(stderr, "Expression replacement buffer overrun: replacing %lu bytes with %lu bytes, current buffer used %lu, max %lu\n", variable_length, content_length, strlen(buffer), buffer_length);
return 1;
}
//move data after the replacement
memmove(buffer + content_length, buffer + variable_length, strlen(buffer) - variable_length + 1);
//insert replacement
memcpy(buffer, content, content_length);
//sanitize replacement
for(u = 0; u < content_length; u++){
if(buffer[u] == '/'){
buffer[u] = '_';
}
}
return 0;
}
static int expression_resolve(char* template, size_t length, char* endpoint, size_t headers, ws_http_header* header){
size_t u, index_len, p, value_len, variable_len;
char* index, *value;
for(u = 0; template[u]; u++){
index_len = 0;
variable_len = 0;
value = NULL;
if(template[u] == '%'){
if(!strncmp(template + u, "%endpoint%", 10)){
if(strlen(endpoint) < 1){
return 1;
}
//rtrim slash so test and test/ are the same file
if(strlen(endpoint) >= 2 && endpoint[strlen(endpoint) - 1] == '/'){
endpoint[strlen(endpoint) - 1] = 0;
}
value = endpoint + 1;
value_len = strlen(value);
variable_len = 10;
}
else if(!strncmp(template + u, "%cookie:", 8)){
//scan cookie values
index = template + u + 8;
for(; index[index_len] && index[index_len] != '%'; index_len++){
}
if(!index[index_len]){
fprintf(stderr, "Unterminated expression variable: %s\n", index);
return 1;
}
//find the cookie header
for(p = 0; p < headers; p++){
if(!strcmp(header[p].tag, "Cookie")){
value = header[p].value;
break;
}
}
//no cookie header, fail the expression
if(p == headers){
return 1;
}
do{
//ensure min length of cookie name
if(strlen(value) < index_len + 1){
return 1;
}
//check if cookie found
if(value[index_len] == '='
&& !strncmp(value, index, index_len)){
value += index_len + 1;
for(value_len = 0; value[value_len] && value[value_len] != ';'; value_len++){
}
break;
}
//skip to next cookie
for(; *value && strncmp(value, "; ", 2); value++){
}
if(*value){
value += 2;
}
} while(*value);
if(!*value){
return 1;
}
variable_len = 8 + index_len + 1;
}
else if(!strncmp(template + u, "%header:", 8)){
//scan headers
index = template + u + 8;
for(; index[index_len] && index[index_len] != '%'; index_len++){
}
if(!index[index_len]){
fprintf(stderr, "Unterminated expression variable: %s\n", index);
return 1;
}
//find the correct header
for(p = 0; p < headers; p++){
if(strlen(header[p].tag) == index_len
&& !strncmp(header[p].tag, index, index_len)){
break;
}
}
//no such header -> fail the expression
if(p == headers){
return 1;
}
value = header[p].value;
value_len = strlen(value);
variable_len = 8 + index_len + 1;
}
//perform replacement
if(value && expression_replace(template + u, length - u, variable_len, value, value_len)){
fprintf(stderr, "Expression replacement failed\n");
return 1;
}
if(value){
//skip the inserted value
u += value_len - 1;
}
}
}
return 0;
}
ws_peer_info query(char* endpoint, size_t protocols, char** protocol, size_t headers, ws_http_header* header, websocket* ws){
size_t u, p, line_alloc = 0;
ssize_t line_length = 0;
char* line = NULL, *components[3];
FILE* input = NULL;
char target_path[BACKEND_FILE_MAX_PATH];
ws_peer_info peer = {
.transport = peer_transport_detect,
.protocol = protocols
};
for(u = 0; u < expressions; u++){
//evaluate the current expression to find a path
snprintf(target_path, sizeof(target_path), "%s/%s", backend_path, expression[u]);
if(expression_resolve(target_path + strlen(backend_path) + 1, sizeof(target_path) - strlen(backend_path) - 1, endpoint, headers, header)){
continue;
}
//check whether the file exists
input = fopen(target_path, "r");
if(!input){
continue;
}
//read it
for(line_length = getline(&line, &line_alloc, input); line_length >= 0; line_length = getline(&line, &line_alloc, input)){
memset(components, 0, sizeof(components));
//rtrim line
p = strlen(line);
for(; p > 0 && !isprint(line[p]); p--){
line[p] = 0;
}
//read lines of host subproto framing framing-config
components[0] = strchr(line, ' ');
if(protocols && !components[0]){
continue;
}
//terminate host
components[0][0] = 0;
components[0]++;
//find framing, terminate subprotocol
components[1] = strchr(components[0], ' ');
if(components[1]){
components[1][0] = 0;
components[1]++;
//find find framing config, terminate framing
components[2] = strchr(components[1], ' ');
if(components[2]){
components[2][0] = 0;
components[2]++;
}
}
//find a match for any indicated protocol
for(p = 0; p < protocols; p++){
if(!strcmp(components[0], protocol[p])){
peer.protocol = p;
break;
}
}
//for '*' use the first available protocol
if(components[0] && !strcmp(components[0], "*")){
peer.protocol = 0;
}
//the current line does not match any indicated protocols
if(protocols && peer.protocol == protocols){
continue;
}
//copy data to peer info structure
peer.host = strdup(line);
peer.framing = core_framing(components[1]);
peer.framing_config = components[2] ? strdup(components[2]) : NULL;
break;
}
fclose(input);
//if peer found, break
if(peer.host){
break;
}
}
free(line);
return peer;
}
void cleanup(){
size_t u;
for(u = 0; u < expressions; u++){
free(expression[u]);
}
free(expression);
expression = NULL;
expressions = 0;
free(backend_path);
backend_path = NULL;
}