I have made a start

I have done some work on opening a socket and waiting for a connection.
This can be read line by line and I have started a request struct that
it will accept.

Also started on some docs. Not much is yet working. I am going to start
learning µnit for unit tests:

https://nemequ.github.io/munit/
This commit is contained in:
Jonathan Hodgson 2021-12-27 21:43:11 +00:00
parent 4e17e706fa
commit f48a110429
15 changed files with 599 additions and 1 deletions

64
src/config.c Normal file
View file

@ -0,0 +1,64 @@
#include "config.h"
/*
* Checks if the given path exists by calling stat().
*
*/
static bool path_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}
/*
* This function resolves ~ in pathnames.
*/
static char* resolveTilde(const char *path) {
static glob_t globbuf;
int res = glob(path, GLOB_TILDE, NULL, &globbuf);
/* no match, or many wildcard matches are bad */
if (res == GLOB_NOMATCH)
return strdup(path);
else if (res != 0) {
printf("glob() failed\n");
return NULL;
} else {
return strdup(globbuf.gl_pathv[0]);
}
globfree(&globbuf);
}
/*
* Returns a pointer to a config object containing defaults
*/
Config* configDefaults(){
Config *conf = malloc( sizeof( Config ) );
conf->database = "proxy.sqlite";
conf->port = 8080;
conf->localConfig = "proxy.conf";
conf->userConfig = getDefaultUserConfigLoc();
return conf;
}
char* getConfigDir(){
char *xdg_config_home;
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
xdg_config_home = resolveTilde("~/.config");
return xdg_config_home;
}
char* getDefaultUserConfigLoc(){
char *configDir = getConfigDir();
char *configFile[strlen(configDir) + 11];
memset(configFile, '\0', strlen(configDir) + 11);
strcpy( configFile, configDir );
strcat( configFile, configDir );
return configFile;
}

24
src/config.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <glob.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
typedef struct {
char *database;
char *localConfig; // Project Specific
char *userConfig; // User Specific
unsigned int port;
} Config;
static bool path_exists(const char *path);
static char* resolveTilde(const char *path);
Config* configDefaults();
char* getConfigDir();
char* getDefaultUserConfigLoc();
#endif /* ifndef CONFIG_H */

46
src/database.c Normal file
View file

@ -0,0 +1,46 @@
#include "database.h"
sqlite3 *db_open(char *file){
sqlite3 *db;
/* Open database */
int rc = sqlite3_open(file, &db);
if( rc ) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
return NULL;
} else {
fprintf(stderr, "Opened database successfully\n");
return db;
}
}
bool db_file_exists(char *file){
FILE *fp;
if ( (fp = fopen(file, "r")) != NULL) {
fclose(fp);
return true;
} else {
return false;
}
}
bool db_create(char *file){
FILE *fp = fopen(file, "a");
if(fp == NULL) {
printf("file can't be opened\n");
return false;
}
fclose(fp);
sqlite3 *db = db_open(file);
// Do stuff here to create the tables.
// Will probably need Requests and Responses - maybe others
sqlite3_close(db);
return true;
}

13
src/database.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef DATABASE_H
#define DATABASE_H
#include <stdio.h>
#include <sqlite3.h>
#include <stdbool.h>
sqlite3 *db_open(char *file);
bool db_file_exists(char *file);
//Returns true if the file was successfully created, false otherwise
bool db_create(char *file);
#endif /* ifndef LOG_H */

76
src/main.c Normal file
View file

@ -0,0 +1,76 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "proxy.h"
#include "config.h"
#define PACKAGE_NAME "WICTP"
#define DEFAULT_DATABASE "data.sqlite"
#define DEFAULT_CONFIG "config.json"
#define DEFAULT_PORT 8080
unsigned int validatePortNumber(unsigned int portNumber){
// Given that we are dealing with unsined ints, we don't need to worry about
// checking for values less than 0
if ( portNumber > 65535 ){
printf( "Port %i is invalid, using default of %i", portNumber, DEFAULT_PORT );
return DEFAULT_PORT;
}
return portNumber;
}
void printHelp(){
printf("Usage: %s [options]\n", PACKAGE_NAME);
printf( "Options are:\n" );
printf( "\t --database DATABASE A sqlite3 database file (Defatult: %s)\n", DEFAULT_DATABASE );
printf( "\t --config CONFIG A config file (Default: %s)\n", DEFAULT_CONFIG );
printf( "\t --port PORT The listening port (Default: %i)\n", DEFAULT_PORT );
printf( "\t --help Display version information.\n");
}
int main(int argc, char**argv){
char *database = DEFAULT_DATABASE;
char *config = DEFAULT_CONFIG;
unsigned int port = DEFAULT_PORT;
Config *defaultconfig = configDefaults();
for ( unsigned int i = 1; i < argc; i++ ){
if ( strcmp( argv[i], "--database" ) == 0 ){
if ( i + 1 < argc ){
database = argv[++i];
} else {
printf("--database requires a positional argument\n");
}
} else if ( strcmp( argv[i], "--config" ) == 0 ){
if ( i + 1 < argc ){
config = argv[++i];
} else {
printf("--config requires a positional argument\n");
}
} else if ( strcmp( argv[i], "--port" ) == 0 ){
if ( i + 1 < argc ){
port = validatePortNumber( atoi(argv[++i]) );
} else {
printf("--port requires a positional argument\n");
}
} else if ( strcmp( argv[i], "--help" ) == 0 ){
printHelp();
return 0;
} else {
printf("Unknown option %s\n", argv[i]);
}
}
if ( !db_file_exists(database) ){
printf("Creating DB");
db_create(database);
}
proxy_startListener(port);
return 0;
}

42
src/proxy.c Normal file
View file

@ -0,0 +1,42 @@
#include "proxy.h"
void proxy_startListener(unsigned int port){
// https://www.geeksforgeeks.org/socket-programming-cc/
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char line[1024] = {0};
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
return ;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons( port );
// Forcefully attaching socket to the port 8080
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
perror("bind failed");
return ;
}
if (listen(server_fd, 3) < 0) {
perror("listen");
return ;
}
while ( true ){
printf("Listening\n");
if ((new_socket = accept(server_fd, (struct sockaddr *)&address,
(socklen_t*)&addrlen))<0) {
perror("accept");
return ;
}
Request *request = newRequestFromSocket(new_socket);
}
}

16
src/proxy.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef PROXY_H
#define PROXY_H
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <stdbool.h>
#include "request.h"
void proxy_startListener( unsigned int port );
#endif /* ifndef PROXY_H */

50
src/readline.c Normal file
View file

@ -0,0 +1,50 @@
// https://man7.org/tlpi/code/online/dist/sockets/read_line.c.html
#include "readline.h"
/* Read characters from 'fd' until a newline is encountered. If a newline
character is not encountered in the first (n - 1) bytes, then the excess
characters are discarded. The returned string placed in 'buf' is
null-terminated and includes the newline character if it was read in the
first (n - 1) bytes. The function return value is the number of bytes
placed in buffer (which includes the newline character if encountered,
but excludes the terminating null byte). */
ssize_t fdReadLine(int fd, void *buffer, size_t n) {
ssize_t numRead; /* # of bytes fetched by last read() */
size_t totRead; /* Total bytes read so far */
char *buf;
char ch;
if (n <= 0 || buffer == NULL) {
errno = EINVAL;
return -1;
}
buf = buffer; /* No pointer arithmetic on "void *" */
totRead = 0;
for (;;) {
numRead = read(fd, &ch, 1);
if (numRead == -1) {
if (errno == EINTR) /* Interrupted --> restart read() */
continue;
else
return -1; /* Some other error */
} else if (numRead == 0) { /* EOF */
if (totRead == 0) /* No bytes read; return 0 */
return 0;
else /* Some bytes read; add '\0' */
break;
} else { /* 'numRead' must be 1 if we get here */
if (totRead < n - 1) { /* Discard > (n - 1) bytes */
totRead++;
*buf++ = ch;
}
if (ch == '\n')
break;
}
}
*buf = '\0';
return totRead;
}

9
src/readline.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef READLINE_H
#define READLINE_H
#include <unistd.h>
#include <errno.h>
ssize_t fdReadLine(int fd, void *buffer, size_t n);
#endif /* ifndef READLINE_H */

69
src/request.c Normal file
View file

@ -0,0 +1,69 @@
#include "request.h"
Header* newHeader(char *str){
Header *header = malloc(sizeof(Header));
memset(header, 0, sizeof(Header));
int position = -1;
for ( unsigned int i = 0; i < strlen(str); i++ ){
if ( str[i] == ':' ){
position = i;
break;
}
}
if ( position == -1 ){
printf("Header without colon. Not sure what to do\n");
return NULL;
}
// We want to allocate 1 more than the length so we can have a \0 at the end
header->name = malloc( sizeof(char) * ( position + 1 ) );
memset(header->name, '\0', position+1);
strncpy( header->name, str, position );
for ( unsigned int i = position+1; i < strlen(str); i++ ){
if ( str[i] == '\t' || str[i] == ' ' ) continue;
position = i;
break;
}
//Anything left is the value
header->value = malloc( sizeof(char) * ( strlen(str) - position ) );
memset(header->value, '\0', ( strlen(str) - position ) );
strcpy( header->value, str + position );
return header;
}
Request* newRequest(){
Request *request = malloc(sizeof(Request));
memset(request, 0, sizeof(Request));
return request;
}
Request* newRequestFromSocket(int socket){
Request *req = newRequest();
int valread;
char line[1024] = {0};
valread = fdReadLine( socket , line, 1024);
char hello[] = "this is a test";
// The first line will give us some important information
//a length of 2 will indicate an empty line which will split the headers
//from the body (if there is a body)
while ( valread > 2 ){
printf("%s",line );
valread = fdReadLine( socket , line, 1024);
}
send(socket , hello , strlen(hello) , 0 );
printf("Hello message sent\n");
return req;
}

53
src/request.h Normal file
View file

@ -0,0 +1,53 @@
#ifndef REQUEST_H
#define REQUEST_H
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "readline.h"
#include <netinet/in.h>
typedef enum {HTTP,HTTPS} HTTPProtocol;
typedef enum {GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH} HTTPMethod;
/*
* A struct reperesenting an http header
*/
typedef struct {
char *name;
// The spec doesn't specify a max size for headers, but nginx doesn't accept headers bigger than 4096 so that's probably ok for us
char *value;
} Header;
/*
* A linked list wrapper around the headers
*/
typedef struct HeaderList HeaderList;
struct HeaderList {
Header header;
HeaderList *next;
};
/*
* A struct reperesenting an http request
*/
typedef struct {
// Common versions are: 0.9, 1.0, 1.1, 2.0
double version;
HTTPMethod method;
HTTPProtocol protocol;
char *host;
char *path;
HeaderList *headers;
char *queryString;
char *body;
} Request;
Header* newHeader();
Request* newRequest();
Request* newRequestFromSocket(int socket);
void* addHeader(Request *req);
#endif /* ifndef REQUEST_H */