aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSyndamia <kamen@syndamia.com>2024-01-06 11:15:57 +0200
committerSyndamia <kamen@syndamia.com>2024-01-06 11:15:57 +0200
commitcb86d3213519727a7fecd05b18b6a8adff230976 (patch)
tree6eb60c98731014da7df5d05212ab94b14e6db29a /src
parent56c2acfdbf1e4ee5d3fd44814a31ae64f57b3f49 (diff)
downloadpico-web-cb86d3213519727a7fecd05b18b6a8adff230976.tar
pico-web-cb86d3213519727a7fecd05b18b6a8adff230976.tar.gz
pico-web-cb86d3213519727a7fecd05b18b6a8adff230976.zip
(src) Moved source files to a src folder
Diffstat (limited to 'src')
-rw-r--r--src/browser-cli.c183
-rw-r--r--src/browser-cli.h12
-rw-r--r--src/browser.c99
-rw-r--r--src/server-cli.c46
-rw-r--r--src/server-cli.h8
-rw-r--r--src/server-connection.c133
-rw-r--r--src/server-connection.h12
-rw-r--r--src/server.c185
-rw-r--r--src/util.c180
-rw-r--r--src/util.h29
10 files changed, 887 insertions, 0 deletions
diff --git a/src/browser-cli.c b/src/browser-cli.c
new file mode 100644
index 0000000..3bea4ba
--- /dev/null
+++ b/src/browser-cli.c
@@ -0,0 +1,183 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string.h>
+#include <sds/sds.h>
+#include <regex.h>
+#include <util.h>
+
+struct md_syntax {
+ regex_t anchor;
+};
+
+struct md_syntax syntax = {
+ .anchor = NULL,
+};
+
+int* anchorsIndecies;
+int anchorsCount = 0;
+
+void initRendering() {
+ /*
+ * Compile regexes used in rendering
+ */
+
+ herr(regcomp(&syntax.anchor, "\\[\\([^]]*\\)\\](\\([^)]*\\))", 0), "regcomp");
+}
+
+void freeRendering() {
+ regfree(&syntax.anchor);
+
+ free(anchorsIndecies);
+}
+
+void renderPage(const sds page) {
+ if (sdslen(page) == 0) {
+ printf("Server didn't return page!\n");
+ return;
+ }
+
+ sds toPrint = sdsdup(page);
+
+ /*
+ * Parse Markdown constructs
+ */
+
+ /* Substitute and store anchorsIndecies */
+ if (anchorsIndecies != NULL) {
+ free(anchorsIndecies);
+ anchorsCount = 0;
+ anchorsIndecies = NULL;
+ }
+ toPrint = gsub_getm(toPrint, &syntax.anchor, "\16\033[4m\1\033[0m", &anchorsIndecies, &anchorsCount);
+
+ sds newPrint;
+ for (int i = 0, anchorInd = 0; i < anchorsCount; i++) {
+ anchorInd = strchr(toPrint, '\16') - toPrint;
+
+ /* In toPrint, replace '\16' with "\033[30;46m%d\033[0m", where %d is the variable i */
+ toPrint[anchorInd] = '\0';
+ newPrint = sdsgrowzero(sdsempty(), sdslen(toPrint) + digits(i) + 8 + 4);
+ sprintf(newPrint, "%s\033[30;46m%d\033[0m%s", toPrint, i, toPrint + anchorInd + 1);
+
+ sdsfree(toPrint);
+ toPrint = newPrint;
+ }
+
+ /*
+ * Print page on stdout
+ */
+ write(1, toPrint, sdslen(toPrint));
+
+ sdsfree(toPrint);
+}
+
+#define MAX_LEN_COMMAND 16
+#define COMMAND_FORMAT ": %16s"
+
+int portLen(char* start) {
+ int count = 0;
+ while ('0' <= *start && *start <= '9') {
+ count++;
+ start++;
+ }
+ return count;
+}
+
+int hostLen(char* start) {
+ int count = 0;
+ while (*start == '.' || ('0' <= *start && *start <= '9')) {
+ count++;
+ start++;
+ }
+ return count;
+}
+
+char* findBeginningOfPath(char* uri) {
+ char* startPath = strchr(uri, '/');
+ while (startPath != uri && startPath != NULL) {
+ if (*(startPath - 1) == '.') startPath--;
+ else break;
+ }
+ return startPath;
+}
+
+int handleCLI(sds *host, sds *port, sds *uri, const sds page) {
+ // Get a line
+ char line[1024];
+ fgets(line, 1024, stdin);
+
+ // Nothing
+ if (line[0] == '\0') {
+ printf("Please enter a valid command!\n");
+ return 0;
+ }
+
+ // Number or URL
+ if (line[0] != ':') {
+ sds newURI;
+
+ // Index of anchor
+ if (isNumber(line)) {
+ int gotoIndex = 0;
+ sscanf(line, "%d", &gotoIndex);
+
+ if (gotoIndex < 0 || gotoIndex >= anchorsCount) {
+ printf("Invalid anchor index!\n");
+ return 0;
+ }
+
+ char* start = strchr(page + anchorsIndecies[gotoIndex], '(') + 1;
+ newURI = sdsnewlen(start, strchr(start, ')') - start);
+ }
+ // New address
+ else {
+ newURI = sdsnewlen(line, strlen(line)-1); // skip newline
+ }
+
+ char* startPath = findBeginningOfPath(newURI);
+
+ // Handle relative URLs
+ if (startPath == newURI) {
+ sds beforePath = sdscatsds(sdsnewlen(*uri, findBeginningOfPath(*uri) - *uri), newURI);
+ sdsfree(newURI);
+ newURI = beforePath;
+ startPath = findBeginningOfPath(newURI);
+ }
+
+ if (*uri != NULL) sdsfree(*uri);
+ *uri = newURI;
+
+ char* startHost = strchr(newURI, '@');
+ char* startPort = strchr(newURI, ':');
+
+ // Update host
+ if (startHost != NULL && startHost < startPath) {
+ if (host != NULL) sdsfree(*host);
+ *host = sdsnewlen(startHost + 1, hostLen(startHost + 1));
+ }
+
+ // Update port
+ if (startPort != NULL && startPort < startPath) {
+ if (port != NULL) sdsfree(*port);
+ *port = sdsnewlen(startPort + 1, portLen(startPort + 1));
+ }
+
+ return 0;
+ }
+
+ // Special command
+
+ // Get command name and it's arguments
+ // Currently no command takes arguments
+ char name[MAX_LEN_COMMAND+1] = { '\0' };
+ int argsAssigned = sscanf(line, COMMAND_FORMAT, name);
+
+ if (streq(name, "q") || streq(name, "e") || streq(name, "quit") || streq(name, "exit")) {
+ return 1;
+ }
+
+ printf("Invalid command %s!\n", name);
+ return 0;
+}
diff --git a/src/browser-cli.h b/src/browser-cli.h
new file mode 100644
index 0000000..1837c6a
--- /dev/null
+++ b/src/browser-cli.h
@@ -0,0 +1,12 @@
+#ifndef BROWSER_CLI
+#define BROWSER_CLI
+
+#include <sds/sds.h>
+
+void initRendering();
+void freeRendering();
+
+void renderPage(const sds page);
+int handleCLI(sds *host, sds *port, sds *uri, const sds page);
+
+#endif
diff --git a/src/browser.c b/src/browser.c
new file mode 100644
index 0000000..7268831
--- /dev/null
+++ b/src/browser.c
@@ -0,0 +1,99 @@
+/* Receives a markdown file and "renders" it
+ */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string.h>
+#include <sds/sds.h>
+#include <util.h>
+#include <browser-cli.h>
+
+#define READ_BUFFER_SIZE 512
+
+sds get_page(const char* ip, const char* port, const char* URL) {
+ if (streq(URL, "blank")) return sdsnew("\n");
+
+ /*
+ * Create socket for connecting with server
+ */
+ int fd_socket = socket(AF_INET, SOCK_STREAM, 0);
+ herrc(fd_socket, "socket");
+
+ int aton_status = 0;
+ struct sockaddr_in sa_server = {
+ .sin_family = AF_INET,
+ .sin_port = atop(port),
+ .sin_addr = aton(ip, &aton_status),
+ };
+ herrc(aton_status, "inet_aton");
+
+ /*
+ * Request page
+ */
+
+ int connectStatus = connect(fd_socket, (struct sockaddr*)&sa_server, sizeof(struct sockaddr_in));
+ herrc(connectStatus, "connect");
+ if (connectStatus < 0) return sdsnew("Couldn't connect to server!\n");
+
+ write(fd_socket, URL, strlen(URL));
+
+ /*
+ * Receive page
+ */
+
+ sds page = sdsempty();
+
+ char buff[READ_BUFFER_SIZE];
+ clear_arr(buff);
+ while (read(fd_socket, buff, READ_BUFFER_SIZE)) {
+ page = sdscat(page, buff);
+ clear_arr(buff);
+ }
+
+ /*
+ * Final
+ */
+
+ close(fd_socket);
+ return page;
+}
+
+int main(int argc, char* argv[]) {
+ initRendering();
+
+ /*
+ * Server-client communication
+ */
+
+ sds page;
+ sds host = sdsnew("127.0.0.1");
+ sds port = sdsnew("8080");
+ sds uri = sdsnew("blank");
+
+ int stopProgram = 0;
+ while (!stopProgram) {
+ /*
+ * Get the page
+ */
+
+ printf("\033[30;107m%s\033[0m\n", uri);
+ page = get_page(host, port, uri);
+ renderPage(page);
+
+ /*
+ * Handle user input
+ */
+ stopProgram = handleCLI(&host, &port, &uri, page);
+ sdsfree(page);
+ }
+
+ freeRendering();
+ sdsfree(host);
+ sdsfree(port);
+ sdsfree(uri);
+}
diff --git a/src/server-cli.c b/src/server-cli.c
new file mode 100644
index 0000000..58e5ea4
--- /dev/null
+++ b/src/server-cli.c
@@ -0,0 +1,46 @@
+#include <util.h>
+#include <server-connection.h>
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+
+#define MAX_LEN_COMMAND 16
+#define COMMAND_FORMAT ": %16s"
+
+void handleCLI(sds **vhosts, int vhostsc) {
+ // Get a line
+ char line[256];
+ fgets(line, 256, stdin);
+
+ // Get command name and it's arguments
+ // Currently no command takes arguments
+ char name[MAX_LEN_COMMAND+1];
+ int argsAssigned = sscanf(line, COMMAND_FORMAT, name);
+
+ while (!streq(name, "q") && !streq(name, "e") && !streq(name, "quit") && !streq(name, "exit")) {
+ if (argsAssigned < 1) {
+ printf("Bad command syntax!\n");
+ }
+ else if (streq(name, "vhosts")) {
+ for (int i = 0; i < vhostsc; i++) {
+ printf("Name: \"%s\" Root dir: \"%s\" Error file: \"%s\"\n",
+ vhosts[i][vh_user],
+ vhosts[i][vh_path],
+ vhosts[i][vh_error]);
+ }
+ }
+ else if (streq(name, "help") || streq(name, "h") || streq(name, "?")) {
+ printf("help,h,?\tPrints this message\nvhosts\t\tPrints all registered virtual hosts\nquit,exit,q,e\tExits the program\n");
+ }
+ else {
+ printf("Unknown command %s!\n", name);
+ }
+
+ // Get line and divided it into command name and arguments
+ fgets(line, 256, stdin);
+ argsAssigned = sscanf(line, COMMAND_FORMAT, name);
+ }
+
+ printf("Exiting...\n");
+ kill(getppid(), SIGTERM);
+}
diff --git a/src/server-cli.h b/src/server-cli.h
new file mode 100644
index 0000000..b5b5875
--- /dev/null
+++ b/src/server-cli.h
@@ -0,0 +1,8 @@
+#ifndef H_SERVER_CLI
+#define H_SERVER_CLI
+
+#include <sds/sds.h>
+
+void handleCLI(sds **vhosts, int vhostsc);
+
+#endif
diff --git a/src/server-connection.c b/src/server-connection.c
new file mode 100644
index 0000000..285cc1d
--- /dev/null
+++ b/src/server-connection.c
@@ -0,0 +1,133 @@
+#include <server-connection.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <string.h>
+#include <util.h>
+
+sds constructFilePath(const sds root, const char* file);
+void sanitizeAddress(char* address);
+sds* findVhost(char* address, sds** vhosts, const int vhostsc);
+int openError(sds* filePath, int* fd, const sds* vhost, const char* client, const int fd_client);
+
+void on_connection(const char* client, const int fd_client, sds **vhosts, const int vhostsc) {
+ printf("[%s@%d] Connected successfully!\n", client, fd_client);
+
+ /* Get address request */
+ char address[256];
+ memset(address, 0, 256);
+
+ read(fd_client, address, 256);
+ sanitizeAddress(address);
+ printf("[%s@%d] Requested %s\n", client, fd_client, address);
+
+ /* Is the username connected with any file path? */
+ const sds *vhost = findVhost(address, vhosts, vhostsc);
+ if (vhost == NULL) {
+ fprintf(stderr, "[%s@%d] Unknown username in address %s\n", client, fd_client, address);
+ return;
+ }
+
+ /* Try to open the requested file or the error file */
+ sds filePath = constructFilePath(vhost[vh_path], strchr(address, '@') + 1);
+
+ int fd = 0;
+
+ /* Check if file is directory */
+ struct stat buf;
+ if (stat(filePath, &buf) == 0) {
+ if (S_ISDIR(buf.st_mode)) {
+ filePath = sdscat(filePath, "/index.md");
+ }
+ else if (!(S_ISREG(buf.st_mode))) {
+ fprintf(stderr, "[%s@%d] %s is not a regular file!\n", client, fd_client, filePath);
+
+ if (openError(&filePath, &fd, vhost, client, fd_client))
+ return;
+ }
+ }
+
+ if (fd <= 0)
+ fd = open(filePath, O_RDONLY);
+
+ if (fd < 0) {
+ fprintf(stderr, "[%s@%d] Error opening %s\n", client, fd_client, filePath);
+
+ if (openError(&filePath, &fd, vhost, client, fd_client))
+ return;
+ }
+
+ /* Send the file to the client */
+ printf("[%s@%d] Serving %s\n", client, fd_client, filePath);
+ sdsfree(filePath);
+
+ char buff[256];
+ memset(buff, 0, sizeof(buff));
+ while (read(fd, buff, 256)) {
+ write(fd_client, buff, strlen(buff));
+ memset(buff, 0, sizeof(buff));
+ }
+
+ /* Finalize */
+ close(fd);
+ printf("[%s@%d] Served!\n", client, fd_client);
+}
+
+sds constructFilePath(const sds root, const char* file) {
+ sds path = sdsdup(root);
+ if (root[sdslen(root)-1] != '/' && file[0] != '/')
+ path = sdscat(path, "/");
+ path = sdscat(path, file);
+ if (file[strlen(file)-1] == '/')
+ path = sdscat(path, "index.md");
+ return path;
+}
+
+void sanitizeAddress(char* address) {
+ /* Remove host and port */
+ char* startPath = strchr(address, '/');
+ if (startPath == NULL)
+ startPath = strchr(address, '\0');
+
+ char* startHost = strchr(address, '@');
+ shiftLeft(startHost + 1, address - startHost, startPath - startHost - 1);
+
+ /* Remove ../ */
+ for (char* prev = startHost+1, *i = startHost+1; i != NULL && *i != '\0';) {
+ if (i[1] == '.' && i[2] == '.' && i[3] == '/') {
+ shiftLeft(prev, strlen(prev), i - prev + 3);
+ i = prev;
+ }
+ else {
+ prev = i;
+ i = strchr(i+1, '/');
+ }
+ }
+}
+
+sds* findVhost(char* address, sds** vhosts, const int vhostsc) {
+ sds* vhost = NULL;
+ int usernameLen = strchr(address, '@') - address;
+ for (int i = 0; i < vhostsc; i++) {
+ if (strncmp(vhosts[i][vh_user], address, usernameLen) == 0) {
+ vhost = *vhosts + i;
+ break;
+ }
+ }
+ return vhost;
+}
+
+int openError(sds* filePath, int* fd, const sds* vhost, const char* client, const int fd_client) {
+ sdsfree(*filePath);
+ *filePath = constructFilePath(vhost[vh_path], vhost[vh_error]);
+ *fd = open(*filePath, O_RDONLY);
+ if (*fd < 0) {
+ fprintf(stderr, "[%s@%d] Error opening %s\n", client, fd_client, *filePath);
+ sdsfree(*filePath);
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/server-connection.h b/src/server-connection.h
new file mode 100644
index 0000000..71447ff
--- /dev/null
+++ b/src/server-connection.h
@@ -0,0 +1,12 @@
+#ifndef H_SERVER_CONNECTION
+#define H_SERVER_CONNECTION
+
+#include <sds/sds.h>
+
+#define vh_user 0
+#define vh_path 1
+#define vh_error 2
+
+void on_connection(const char* client, const int fd_client, sds **vhosts, const int vhostsc);
+
+#endif
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 0000000..aeff800
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,185 @@
+/* The server recieves connections and passes files to the clients
+ */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/select.h>
+
+#include <string.h>
+#include <sds/sds.h>
+#include <util.h>
+
+#include <server-connection.h>
+#include <server-cli.h>
+
+int createCommunicationSocket(const char* ip, const char* port) {
+ int fd_socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ herr(fd_socket, "socket");
+
+ int aton_status = 0;
+ struct sockaddr_in sa_socket = {
+ .sin_family = AF_INET,
+ .sin_port = atop(port),
+ .sin_addr = aton(ip, &aton_status),
+ };
+ herr(aton_status, "inet_aton");
+
+ // Reuse address when in TIME_WAIT state, after listening socket was closed
+ // https://stackoverflow.com/a/10651048/12036073
+ // https://superuser.com/questions/173535/what-are-close-wait-and-time-wait-states#comment951880_173543
+ int true = 1;
+ herr(setsockopt(fd_socket, SOL_SOCKET, SO_REUSEADDR, &true, sizeof(int)), "setsockopt");
+
+ herr(bind(fd_socket, (struct sockaddr*)&sa_socket, sizeof(struct sockaddr_in)), "bind");
+
+ herr(listen(fd_socket, 50), "listen");
+
+ return fd_socket;
+}
+
+void freeVhosts(sds **vhosts, int vhostsc) {
+ for (int i = 0; i < vhostsc; i++) {
+ sdsfreesplitres(vhosts[i], 3);
+ }
+ free(vhosts);
+}
+
+int acceptConnections = 1;
+
+void handler_refuseConnections(int signum) {
+ acceptConnections = 0;
+}
+
+int main(int argc, char* argv[]) {
+ /*
+ * Get server parameters
+ */
+
+ sds host = sdsnew("127.0.0.1");
+ sds port = sdsnew("8080");
+
+ int argvOffset = 1;
+ if (argc > 1 && charCount(argv[1], ',') == 1) {
+ argvOffset++;
+ char* sep = strchr(argv[1], ',');
+ sdsfree(host);
+ host = sdsnewlen(argv[1], sep - argv[1]);
+ sdsfree(port);
+ port = sdsnew(sep + 1);
+ }
+
+ /*
+ * Get hosts
+ */
+
+ int vhostsc = argc - argvOffset;
+ sds **vhosts = malloc(vhostsc * sizeof(sds*));
+ for (int i = 0, temp = 0; i < vhostsc; i++) {
+ vhosts[i] = sdssplitlen(argv[i+argvOffset], strlen(argv[i+argvOffset]), ",", 1, &temp);
+ }
+
+ /*
+ * Create socket for accepting connections
+ */
+
+ int fd_socket = createCommunicationSocket(host, port);
+ printf("Listening on %s:%s\n", host, port);
+
+ /*
+ * Server command-line interface
+ */
+
+ int pid_cli = fork();
+ if (pid_cli == 0) {
+ close(fd_socket);
+ handleCLI(vhosts, vhostsc);
+ freeVhosts(vhosts, vhostsc);
+ sdsfree(host);
+ sdsfree(port);
+ return 0;
+ }
+
+ /*
+ * Define variables
+ */
+
+ // Client address
+ struct sockaddr_in sa_client = {
+ .sin_family = AF_INET,
+ .sin_port = 0,
+ .sin_addr.s_addr = 0,
+ };
+ socklen_t sa_client_size = sizeof(struct sockaddr_in);
+
+ int fd_client;
+ int count = 0;
+ int pselectStat = 0;
+
+ /*
+ * Handle connection requests
+ */
+
+ // Data for pselect
+ fd_set rfds;
+ struct timespec tv = {
+ /* How long should we block (wait) for fd_socket to have a request */
+ .tv_sec = 0,
+ .tv_nsec = 100000,
+ };
+
+ // Stop accepting connections on SIGTERM
+ signal(SIGTERM, handler_refuseConnections);
+
+ while (acceptConnections) {
+ /*
+ * Check if fd_socket has something we can read
+ */
+
+ FD_ZERO(&rfds);
+ FD_SET(fd_socket, &rfds);
+
+ herrc(pselect(fd_socket + 1, &rfds, NULL, NULL, &tv, NULL), "pselect");
+ if (!FD_ISSET(fd_socket, &rfds)) continue;
+
+ /*
+ * Accept the connection
+ */
+
+ fd_client = accept(fd_socket, (struct sockaddr*)&sa_client, &sa_client_size);
+ if (acceptConnections) herrc(fd_client, "accept");
+ if (fd_client < 0) continue;
+
+ char* strAddr = inet_ntoa(sa_client.sin_addr);
+ count++;
+
+ /*
+ * Logic when connected with client
+ */
+
+ int fp = fork();
+ if (fp == 0) {
+ close(fd_socket);
+ on_connection(strAddr, fd_client, vhosts, argc - 1);
+ close(fd_client);
+ freeVhosts(vhosts, vhostsc);
+ sdsfree(host);
+ sdsfree(port);
+ return 0;
+ }
+ close(fd_client);
+ }
+
+ while(wait(NULL) > 0);
+ freeVhosts(vhosts, vhostsc);
+ close(fd_socket);
+ sdsfree(host);
+ sdsfree(port);
+}
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..822a4d2
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,180 @@
+#include <util.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <regex.h>
+
+/*
+ * Networking
+ */
+
+uint16_t atop(const char *port) {
+ return htons(atoi(port));
+}
+
+struct in_addr aton(const char* cp, int* output) {
+ struct in_addr inp;
+ *output = inet_aton(cp, &inp);
+ return inp;
+}
+
+/*
+ * Error handling
+ */
+
+void herrc(int output, const char* funcName) {
+ if (output < 0 && errno != EINTR) {
+ perror(funcName);
+ }
+}
+
+void herr(int output, const char* funcName) {
+ if (output < 0) {
+ perror(funcName);
+ exit(errno);
+ }
+}
+
+/*
+ * sds string substitution
+ */
+
+sds getMatch(const sds str, const regmatch_t match) {
+ return sdsnewlen(str + match.rm_so, match.rm_eo - match.rm_so);
+}
+int validMatch(const regmatch_t match) {
+ return match.rm_so > -1;
+}
+
+void resizeMatches(int* *matches, int* size) {
+ if (*size == 0) {
+ *matches = malloc((*size = 8) * sizeof(int*));
+ return;
+ }
+
+ int* *biggerArr = malloc((*size * 2) * sizeof(int*));
+ for (size_t i = 0; i < *size; i++) {
+ biggerArr[i] = matches[i];
+ }
+ *size *= 2;
+ free(matches);
+ *matches = *biggerArr;
+}
+void pushBackMatch(int* *matches, int *matchesCount, int *matchesSize, int matchStart) {
+ if (*matchesCount >= *matchesSize)
+ resizeMatches(matches, matchesSize);
+
+ (*matches)[*matchesCount] = matchStart;
+ *matchesCount += 1;
+}
+
+#define MATCHSTART str + strInd
+sds gsub_getm(sds str, const regex_t *regex, const char* repl, int* *matches, int *matchesCount) {
+ regmatch_t pmatch[10] = {
+ { .rm_so = 0, .rm_eo = 0, }, { .rm_so = 0, .rm_eo = 0, }, { .rm_so = 0, .rm_eo = 0, },
+ { .rm_so = 0, .rm_eo = 0, }, { .rm_so = 0, .rm_eo = 0, }, { .rm_so = 0, .rm_eo = 0, },
+ { .rm_so = 0, .rm_eo = 0, }, { .rm_so = 0, .rm_eo = 0, }, { .rm_so = 0, .rm_eo = 0, },
+ { .rm_so = 0, .rm_eo = 0, },
+ };
+
+ int strInd = 0;
+ int matchesSize = (matchesCount != NULL) ? *matchesCount : 0;
+ size_t replLen = strlen(repl);
+
+ sds ret = sdsempty();
+ /*
+ * Substitute all occurences of regex with repl in str
+ */
+ // sdslen is in O(1) time
+ while (strInd < sdslen(str)) {
+ /* Run regex */
+ if (regexec(regex, MATCHSTART, 10, pmatch, 0) != 0) {
+ /* If there are no matches, return the rest of the string as-is */
+ ret = sdscat(ret, MATCHSTART);
+ break;
+ }
+
+ /* Store everything before the match as-is */
+ ret = sdscatlen(ret, MATCHSTART, pmatch[0].rm_so);
+
+ /* Replace match with repl
+ * repl can include matched subexpressions */
+ for(size_t i = 0; i < replLen; i++) {
+ if (repl[i] <= '\10') {
+ if (pmatch[repl[i] % 10].rm_so > -1) {
+ sds match = getMatch(MATCHSTART, pmatch[repl[i] % 10]);
+ ret = sdscatsds(ret, match);
+ sdsfree(match);
+ }
+ }
+ else
+ ret = sdscatlen(ret, &repl[i], 1);
+ }
+
+ /* Add index of current match to matches */
+ if (matchesCount != NULL) {
+ pushBackMatch(matches, matchesCount, &matchesSize, strInd + pmatch[0].rm_so);
+ }
+
+ /* Continute after the current match */
+ strInd += pmatch[0].rm_eo;
+ }
+
+ sdsfree(str);
+ return ret;
+}
+
+sds gsub(sds str, const regex_t* regex, const char* repl) {
+ return gsub_getm(str, regex, repl, NULL, NULL);
+}
+
+/*
+ * other
+ */
+
+int digits(int num) {
+ if (num < 0) num *= -1;
+ // This is the fastest way to get the number of digits
+ if (num < 10) return 1;
+ if (num < 100) return 2;
+ if (num < 1000) return 3;
+ if (num < 10000) return 4;
+ if (num < 100000) return 5;
+ if (num < 1000000) return 6;
+ if (num < 10000000) return 7;
+ if (num < 100000000) return 8;
+ if (num < 1000000000) return 9;
+ // 2147483647 (2^31-1) is max value of int
+ return 10;
+}
+
+int streq(const char* first, const char* second) {
+ return strcmp(first, second) == 0;
+}
+
+void shiftLeft(char* str, size_t size, size_t shift) {
+ while (*(str + shift - 1) != '\0') {
+ *str = *(str + shift);
+ str++;
+ }
+}
+
+int isNumber(char* str) {
+ while (*str >= ' ') {
+ if (*str < '0' || *str > '9') return 0;
+ str++;
+ }
+ return 1;
+}
+
+int charCount(char* str, char cmp) {
+ int count = 0;
+ while (*str != '\0') {
+ count += *str == cmp;
+ str++;
+ }
+ return count;
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..b2836eb
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,29 @@
+#ifndef H_UTIL
+#define H_UTIL
+
+#include <inttypes.h>
+#include <sds/sds.h>
+#include <arpa/inet.h>
+#include <regex.h>
+
+/* Networking */
+uint16_t atop(const char *port);
+struct in_addr aton(const char* cp, int* output);
+
+/* Error handling */
+void herr(int output, const char* funcName);
+void herrc(int output, const char* funcName);
+
+/* sds string substition */
+sds gsub(sds str, const regex_t* regex, const char* repl);
+sds gsub_getm(sds str, const regex_t *regex, const char* repl, int* *matches, int *matchesCount);
+
+/* Other */
+#define clear_arr(arr) memset(arr, 0, sizeof(arr)/sizeof(*arr))
+int digits(int num);
+int streq(const char* first, const char* second);
+void shiftLeft(char* str, size_t size, size_t shift);
+int isNumber(char* str);
+int charCount(char* str, char cmp);
+
+#endif