/* colla.h -- All colla libraries in a single header Do the following in *one* C file to create the implementation #define COLLA_IMPL Use the following in the same C file for options #define COLLA_NO_THREADS // don't include the threads module #define COLLA_NO_NET // don't include networking stuff */ #pragma once #include #include typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typedef uint8_t uint8; typedef uint16_t uint16; typedef uint32_t uint32; typedef uint64_t uint64; typedef int8_t int8; typedef int16_t int16; typedef int32_t int32; typedef int64_t int64; typedef size_t usize; typedef ptrdiff_t isize; #pragma once #ifdef __cplusplus extern "C" { #endif /* Define any of this to turn on the option * -> TLOG_NO_COLOURS: print without using colours * -> TLOG_VS: print to visual studio console, also turns on TLOG_NO_COLOURS * -> TLOG_DONT_EXIT_ON_FATAL: don't call 'exit(1)' when using LogFatal */ #include #include enum { LogAll, LogTrace, LogDebug, LogInfo, LogWarning, LogError, LogFatal }; void traceLog(int level, const char *fmt, ...); void traceLogVaList(int level, const char *fmt, va_list args); void traceUseNewline(bool use_newline); #define tall(...) traceLog(LogAll, __VA_ARGS__) #define trace(...) traceLog(LogTrace, __VA_ARGS__) #define debug(...) traceLog(LogDebug, __VA_ARGS__) #define info(...) traceLog(LogInfo, __VA_ARGS__) #define warn(...) traceLog(LogWarning, __VA_ARGS__) #define err(...) traceLog(LogError, __VA_ARGS__) #define fatal(...) traceLog(LogFatal, __VA_ARGS__) #ifdef __cplusplus } // extern "C" #endif #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include #include /* #include "collatypes.h" */ #define STRV_NOT_FOUND SIZE_MAX typedef struct str_t { char *buf; usize len; } str_t; typedef struct { const char *buf; usize len; } strview_t; // == STR_T ======================================================== str_t strInit(void); str_t strFromStr(const char *cstr); str_t strFromView(strview_t view); str_t strFromBuf(const char *buf, usize len); str_t strFromFmt(const char *fmt, ...); str_t strFromWCHAR(const wchar_t *src, usize len); wchar_t *strToWCHAR(str_t ctx); void strFree(str_t ctx); str_t strDup(str_t ctx); str_t strMove(str_t *ctx); strview_t strGetView(str_t ctx); char *strBegin(str_t ctx); char *strEnd(str_t ctx); char strBack(str_t ctx); bool strIsEmpty(str_t ctx); void strReplace(str_t *ctx, char from, char to); // if len == SIZE_MAX, copies until end str_t strSubstr(str_t ctx, usize from, usize to); // if len == SIZE_MAX, returns until end strview_t strSubview(str_t ctx, usize from, usize to); void strLower(str_t *ctx); str_t strToLower(str_t ctx); void strUpper(str_t *ctx); str_t strToUpper(str_t ctx); #ifdef STR_TESTING void strTest(void); #endif // == STRVIEW_T ==================================================== strview_t strvInit(const char *cstr); strview_t strvInitStr(str_t str); strview_t strvInitLen(const char *buf, usize size); char strvFront(strview_t ctx); char strvBack(strview_t ctx); const char *strvBegin(strview_t ctx); const char *strvEnd(strview_t ctx); // move view forward by n characters strview_t strvRemovePrefix(strview_t ctx, usize n); // move view backwards by n characters strview_t strvRemoveSuffix(strview_t ctx, usize n); // removes whitespace from the beginning and the end strview_t strvTrim(strview_t ctx); // removes whitespace from the beginning strview_t strvTrimLeft(strview_t ctx); // removes whitespace from the end strview_t strvTrimRight(strview_t ctx); bool strvIsEmpty(strview_t ctx); str_t strvCopy(strview_t ctx); str_t strvCopyN(strview_t ctx, usize count, usize from); usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from); strview_t strvSub(strview_t ctx, usize from, usize to); int strvCompare(strview_t ctx, strview_t other); int strvICompare(strview_t ctx, strview_t other); bool strvStartsWith(strview_t ctx, char c); bool strvStartsWithView(strview_t ctx, strview_t view); bool strvEndsWith(strview_t ctx, char c); bool strvEndsWithView(strview_t ctx, strview_t view); bool strvContains(strview_t ctx, char c); bool strvContainsView(strview_t ctx, strview_t view); usize strvFind(strview_t ctx, char c, usize from); usize strvFindView(strview_t ctx, strview_t view, usize from); usize strvRFind(strview_t ctx, char c, usize from); usize strvRFindView(strview_t ctx, strview_t view, usize from); // Finds the first occurrence of any of the characters of 'view' in this view usize strvFindFirstOf(strview_t ctx, strview_t view, usize from); usize strvFindLastOf(strview_t ctx, strview_t view, usize from); usize strvFindFirstNot(strview_t ctx, char c, usize from); usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from); usize strvFindLastNot(strview_t ctx, char c, usize from); usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from); #ifdef __cplusplus } // extern "C" #endif #pragma once #ifdef __cplusplus extern "C" { #endif #define vec(T) T * #define vecFree(vec) ((vec) ? free(_vecheader(vec)),NULL : NULL) #define vecCopy(src, dest) (vecFree(dest), vecAdd(dest, vecCount(src)), memcpy(dest, src, vecCount(src))) #define vecAppend(vec, val) (_vecmaygrow(vec, 1), (vec)[_veclen(vec)] = (val), _veclen(vec)++) #define vecLen(vec) ((vec) ? _veclen(vec) : 0) #define vecCap(vec) ((vec) ? _veccap(vec) : 0) #define vecBeg(vec) (vec) #define vecEnd(vec) ((vec) ? (vec) + _veclen(vec) : NULL) #define vecBack(vec) ((vec)[_veclen(vec) - 1]) #define vecAdd(vec, n) (_vecmaygrow(vec, (n)), _veclen(vec) += (size_type)(n), &(vec)[_veclen(vec)-(n)]) #define vecReserve(vec, n) (_vecmaygrow(vec, (n))) #define vecShrink(vec) (_vecshrink((void **)&(vec), _veclen(vec), sizeof(*(vec)))) #define vecClear(vec) ((vec) ? _veclen(vec) = 0 : 0) #define vecPop(vec) ((vec)[--_veclen(vec)]) // == IMPLEMENTATION ========================================================================================== #include #include #include #ifndef size_type #define size_type uint32_t #endif #define _vecheader(vec) ((size_type *)(vec) - 2) #define _veccap(vec) _vecheader(vec)[0] #define _veclen(vec) _vecheader(vec)[1] #define _vecneedgrow(vec, n) ((vec) == NULL || _veclen(vec) + n >= _veccap(vec)) #define _vecmaygrow(vec, n) (_vecneedgrow(vec, (n)) ? _vecgrow(vec, (size_type)(n)) : (void)0) #define _vecgrow(vec, n) _vecgrowimpl((void **)&(vec), (n), sizeof(*(vec))) inline static void _vecgrowimpl(void **arr, size_type increment, size_type itemsize) { int newcap = *arr ? 2 * _veccap(*arr) + increment : increment + 1; void *ptr = realloc(*arr ? _vecheader(*arr) : 0, itemsize * newcap + sizeof(size_type) * 2); assert(ptr); if (ptr) { if (!*arr) ((size_type *)ptr)[1] = 0; *arr = (void *) ((size_type *)ptr + 2); _veccap(*arr) = newcap; } } inline static void _vecshrink(void **arr, size_type newcap, size_t itemsize) { if (newcap == _veccap(*arr) || !*arr) return; void *ptr = realloc(_vecheader(*arr), itemsize * newcap + sizeof(size_type) * 2); assert(ptr); if (ptr) { *arr = (void *) ((size_type *)ptr + 2); if (_veclen(*arr) < newcap) _veclen(*arr) = newcap; _veccap(*arr) = newcap; } } #ifdef __cplusplus } // extern "C" #endif #pragma once /* #include "collatypes.h" */ /* #include "vec.h" */ /* #include "str.h" */ /* Example usage: hashSetSeed(time(NULL)); vec(const char *) strings = NULL; hashmap_t map = hmInit(32); // mapGet returns 0 in case it doesn't find anything, this way we don't need // to check its return value vecAppend(strings, "nil"); hmSet(&map, hashCStr("english"), vecAppend(strings, "hello")); hmSet(&map, hashCStr("french"), vecAppend(strings, "bonjour")); hmSet(&map, hashCStr("italian"), vecAppend(strings, "ciao")); printf("english: %s\n", strings[hmGet(map, hashCStr("english"))]); printf("french: %s\n", strings[hmGet(map, hashCStr("french"))]); printf("italian: %s\n", strings[hmGet(map, hashCStr("italian"))]); mapFree(map); vecFree(strings); */ typedef struct { uint64 hash; uint64 index; } hashnode_t; typedef struct { vec(hashnode_t) nodes; } hashmap_t; hashmap_t hmInit(usize initial_cap); void hmFree(hashmap_t map); void hmSet(hashmap_t *map, uint64 hash, uint64 index); uint64 hmGet(hashmap_t map, uint64 hash); void hmDelete(hashmap_t *map, uint64 hash); void hashSetSeed(uint64 new_seed); uint64 hash(const void *data, usize len); uint64 hashStr(str_t str); uint64 hashView(strview_t view); uint64 hashCStr(const char *cstr); #pragma once /* #include "collatypes.h" */ typedef uint32 rune; enum { UTF8_MAX_SIZE = 4, UTF8_INVALID = 0x80 }; // grabs the next UTF-8 codepoint and advances string ptr rune utf8Decode(const char **str); // encodes a codepoint as UTF-8 and returns the length usize utf8Encode(char *str, rune ch); // returns the size of the next UTF-8 codepoint int utf8Size(const char *str); // returns the size of a UTF-8 codepoint usize utf8CpSize(rune ch); #pragma once #include /* #include "str.h" */ /* #include "vec.h" */ /* #include "utf8.h" */ #ifdef __cplusplus extern "C" { #endif // == INI READER ======================================================================== typedef struct { strview_t key; strview_t value; } inivalue_t; typedef struct { strview_t name; vec(inivalue_t) values; } initable_t; typedef struct { str_t text; vec(initable_t) tables; } ini_t; typedef struct { bool merge_duplicate_tables; bool merge_duplicate_keys; } iniopts_t; ini_t iniParse(const char *filename, const iniopts_t *options); ini_t iniParseString(const char *inistr, const iniopts_t *options); void iniFree(ini_t ctx); initable_t *iniGetTable(ini_t *ctx, const char *name); inivalue_t *iniGet(initable_t *ctx, const char *key); vec(strview_t) iniAsArray(const inivalue_t *value, char delim); // delim is expected to be a single utf8 character vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim); uint64 iniAsUInt(const inivalue_t *value); int64 iniAsInt(const inivalue_t *value); double iniAsNum(const inivalue_t *value); // == INI WRITER ======================================================================== typedef struct { str_t key; str_t value; } winivalue_t; typedef struct { str_t key; vec(winivalue_t) values; } winitable_t; typedef struct { vec(winitable_t) tables; } iniwriter_t; typedef struct { bool no_discalimer; } winiopts_t; iniwriter_t winiInit(); void winiFree(iniwriter_t ctx); str_t winiToString(iniwriter_t ctx, const winiopts_t *options); void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options); winivalue_t *winiAddValEmpty(winitable_t *table); winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value); winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value); winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value); winitable_t *winiAddTablEmpty(iniwriter_t *ctx); winitable_t *winiAddTab(iniwriter_t *ctx, const char *name); winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name); winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name); #ifdef __cplusplus } // extern "C" #endif #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include /* #include "collatypes.h" */ /* #include "str.h" */ /* == INPUT STREAM ============================================ */ typedef struct { const char *start; const char *cur; usize size; } str_istream_t; // initialize with null-terminated string str_istream_t istrInit(const char *str); str_istream_t istrInitLen(const char *str, usize len); // get the current character and advance char istrGet(str_istream_t *ctx); // get the current character but don't advance char istrPeek(str_istream_t *ctx); // ignore characters until the delimiter void istrIgnore(str_istream_t *ctx, char delim); // skip n characters void istrSkip(str_istream_t *ctx, usize n); // skips whitespace (' ', '\n', '\t', '\r') void istrSkipWhitespace(str_istream_t *ctx); // read len bytes into buffer, the buffer will not be null terminated void istrRead(str_istream_t *ctx, char *buf, usize len); // read a maximum of len bytes into buffer, the buffer will not be null terminated // returns the number of bytes read usize istrReadMax(str_istream_t *ctx, char *buf, usize len); // returns to the beginning of the stream void istrRewind(str_istream_t *ctx); // returns the number of bytes read from beginning of stream usize istrTell(str_istream_t ctx); // returns the number of bytes left to read in the stream usize istrRemaining(str_istream_t ctx); // return true if the stream doesn't have any new bytes to read bool istrIsFinished(str_istream_t ctx); bool istrGetbool(str_istream_t *ctx, bool *val); bool istrGetu8(str_istream_t *ctx, uint8 *val); bool istrGetu16(str_istream_t *ctx, uint16 *val); bool istrGetu32(str_istream_t *ctx, uint32 *val); bool istrGetu64(str_istream_t *ctx, uint64 *val); bool istrGeti8(str_istream_t *ctx, int8 *val); bool istrGeti16(str_istream_t *ctx, int16 *val); bool istrGeti32(str_istream_t *ctx, int32 *val); bool istrGeti64(str_istream_t *ctx, int64 *val); bool istrGetfloat(str_istream_t *ctx, float *val); bool istrGetdouble(str_istream_t *ctx, double *val); // get a string until a delimiter, the string is allocated by the function and should be freed usize istrGetstring(str_istream_t *ctx, char **val, char delim); // get a string of maximum size len, the string is not allocated by the function and will be null terminated usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len); strview_t istrGetview(str_istream_t *ctx, char delim); strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to); /* == OUTPUT STREAM =========================================== */ typedef struct { char *buf; usize len; usize cap; } str_ostream_t; str_ostream_t ostrInit(void); str_ostream_t ostrInitLen(usize initial_alloc); str_ostream_t ostrInitStr(const char *buf, usize len); void ostrFree(str_ostream_t ctx); void ostrClear(str_ostream_t *ctx); char ostrBack(str_ostream_t ctx); str_t ostrAsStr(str_ostream_t ctx); strview_t ostrAsView(str_ostream_t ctx); void ostrReplace(str_ostream_t *ctx, char from, char to); void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...); void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args); void ostrPutc(str_ostream_t *ctx, char c); void ostrPuts(str_ostream_t *ctx, const char *str); void ostrAppendbool(str_ostream_t *ctx, bool val); void ostrAppendchar(str_ostream_t *ctx, char val); void ostrAppendu8(str_ostream_t *ctx, uint8 val); void ostrAppendu16(str_ostream_t *ctx, uint16 val); void ostrAppendu32(str_ostream_t *ctx, uint32 val); void ostrAppendu64(str_ostream_t *ctx, uint64 val); void ostrAppendi8(str_ostream_t *ctx, int8 val); void ostrAppendi16(str_ostream_t *ctx, int16 val); void ostrAppendi32(str_ostream_t *ctx, int32 val); void ostrAppendi64(str_ostream_t *ctx, int64 val); void ostrAppendfloat(str_ostream_t *ctx, float val); void ostrAppenddouble(str_ostream_t *ctx, double val); void ostrAppendview(str_ostream_t *ctx, strview_t view); #ifdef __cplusplus } // extern "C" #endif #pragma once #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef WIN32_EXTRA_LEAN #define WIN32_EXTRA_LEAN #endif #include #endif // _WIN32 #pragma once #ifdef __cplusplus extern "C" { #endif #include #include /* #include "str.h" */ /* #include "collatypes.h" */ #ifdef _WIN32 #include /* #include "win32_slim.h" */ isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp); isize getline(char **line_ptr, size_t *n, FILE *stream); #define stricmp _stricmp #else #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include int stricmp(const char *a, const char *b); #endif str_t getUserName(); #ifdef __cplusplus } // extern "C" #endif #pragma once #ifdef __cplusplus extern "C" { #endif /* #include "collatypes.h" */ /* #include "str.h" */ /* #include "vec.h" */ typedef enum { FILE_READ = 1 << 0, FILE_WRITE = 1 << 1, FILE_CLEAR = 1 << 2, FILE_BOTH = 1 << 3 } filemode_t; typedef uintptr_t file_t; typedef struct { const uint8 *buf; usize len; } filebuf_t; bool fileExists(const char *fname); file_t fileOpen(const char *fname, filemode_t mode); void fileClose(file_t ctx); bool fileIsValid(file_t ctx); bool filePutc(file_t ctx, char c); bool filePuts(file_t ctx, const char *str); bool filePutstr(file_t ctx, str_t str); bool filePutview(file_t ctx, strview_t view); usize fileRead(file_t ctx, void *buf, usize len); usize fileWrite(file_t ctx, const void *buf, usize len); bool fileSeekEnd(file_t ctx); void fileRewind(file_t ctx); uint64 fileTell(file_t ctx); vec(uint8) fileReadWhole(const char *fname); vec(uint8) fileReadWholeFP(file_t ctx); str_t fileReadWholeText(const char *fname); str_t fileReadWholeTextFP(file_t ctx); bool fileWriteWhole(const char *fname, filebuf_t data); bool fileWriteWholeFP(file_t ctx, filebuf_t data); bool fileWriteWholeText(const char *fname, strview_t string); bool fileWriteWholeTextFP(file_t ctx, strview_t string); #ifdef __cplusplus } // extern "C" #endif #pragma once #ifdef __cplusplus extern "C" { #endif /* #include "str.h" */ typedef void *dir_t; typedef enum { FS_TYPE_UNKNOWN, FS_TYPE_FILE, FS_TYPE_DIR, } fs_type_t; typedef struct { fs_type_t type; str_t name; } dir_entry_t; dir_t dirOpen(const char *path); void dirClose(dir_t ctx); bool dirValid(dir_t ctx); dir_entry_t *dirNext(dir_t ctx); void dirCreate(const char *path); #ifdef __cplusplus } // extern "C" #endif #ifndef COLLA_NO_NET #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #ifdef _WIN32 #define SOCK_WINDOWS 1 #else #define SOCK_POSIX 1 #endif #if SOCK_WINDOWS #pragma warning(disable:4996) // _WINSOCK_DEPRECATED_NO_WARNINGS /* #include "win32_slim.h" */ #include #include typedef SOCKET socket_t; typedef int sk_len_t; #elif SOCK_POSIX #include #include #include typedef int socket_t; typedef uint32_t sk_len_t; #define INVALID_SOCKET (-1) #define SOCKET_ERROR (-1) #endif typedef struct sockaddr sk_addr_t; typedef struct sockaddr_in sk_addrin_t; typedef enum { SOCK_TCP, SOCK_UDP, } sktype_t; // == RAW SOCKETS ========================================== // Initialize sockets, returns true on success bool skInit(void); // Terminates sockets, returns true on success bool skCleanup(void); // Opens a socket, check socket_t with skValid socket_t skOpen(sktype_t type); // Opens a socket using 'protocol', options are // ip, icmp, ggp, tcp, egp, pup, udp, hmp, xns-idp, rdp // check socket_t with skValid socket_t skOpenEx(const char *protocol); // Opens a socket, check socket_t with skValid socket_t skOpenPro(int af, int type, int protocol); // Fill out a sk_addrin_t structure with "ip" and "port" sk_addrin_t skAddrinInit(const char *ip, uint16_t port); // Closes a socket, returns true on success bool skClose(socket_t sock); // Associate a local address with a socket bool skBind(socket_t sock, const char *ip, uint16_t port); // Associate a local address with a socket bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen); // Place a socket in a state in which it is listening for an incoming connection bool skListen(socket_t sock); // Place a socket in a state in which it is listening for an incoming connection bool skListenPro(socket_t sock, int backlog); // Permits an incoming connection attempt on a socket socket_t skAccept(socket_t sock); // Permits an incoming connection attempt on a socket socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen); // Connects to a server (e.g. "127.0.0.1" or "google.com") with a port(e.g. 1234), returns true on success bool skConnect(socket_t sock, const char *server, unsigned short server_port); // Connects to a server, returns true on success bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen); // Sends data on a socket, returns true on success int skSend(socket_t sock, const void *buf, int len); // Sends data on a socket, returns true on success int skSendPro(socket_t sock, const void *buf, int len, int flags); // Sends data to a specific destination int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to); // Sends data to a specific destination int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen); // Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error int skReceive(socket_t sock, void *buf, int len); // Receives data from a socket, returns byte count on success, 0 on connection close or -1 on error int skReceivePro(socket_t sock, void *buf, int len, int flags); // Receives a datagram and stores the source address. int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from); // Receives a datagram and stores the source address. int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen); // Checks that a opened socket is valid, returns true on success bool skIsValid(socket_t sock); // Returns latest socket error, returns 0 if there is no error int skGetError(void); // Returns a human-readable string from a skGetError const char *skGetErrorString(void); // == UDP SOCKETS ========================================== typedef socket_t udpsock_t; #ifdef __cplusplus } // extern "C" #endif #pragma once #ifdef __cplusplus extern "C" { #endif #include #include /* #include "collatypes.h" */ /* #include "str.h" */ /* #include "strstream.h" */ /* #include "socket.h" */ typedef enum { REQ_GET, REQ_POST, REQ_HEAD, REQ_PUT, REQ_DELETE } reqtype_t; typedef enum { // 2xx: success STATUS_OK = 200, STATUS_CREATED = 201, STATUS_ACCEPTED = 202, STATUS_NO_CONTENT = 204, STATUS_RESET_CONTENT = 205, STATUS_PARTIAL_CONTENT = 206, // 3xx: redirection STATUS_MULTIPLE_CHOICES = 300, STATUS_MOVED_PERMANENTLY = 301, STATUS_MOVED_TEMPORARILY = 302, STATUS_NOT_MODIFIED = 304, // 4xx: client error STATUS_BAD_REQUEST = 400, STATUS_UNAUTHORIZED = 401, STATUS_FORBIDDEN = 403, STATUS_NOT_FOUND = 404, STATUS_RANGE_NOT_SATISFIABLE = 407, // 5xx: server error STATUS_INTERNAL_SERVER_ERROR = 500, STATUS_NOT_IMPLEMENTED = 501, STATUS_BAD_GATEWAY = 502, STATUS_SERVICE_NOT_AVAILABLE = 503, STATUS_GATEWAY_TIMEOUT = 504, STATUS_VERSION_NOT_SUPPORTED = 505, } resstatus_t; typedef struct { uint8 major; uint8 minor; } http_version_t; // translates a http_version_t to a single readable number (e.g. 1.1 -> 11, 1.0 -> 10, etc) int httpVerNumber(http_version_t ver); typedef struct { char *key; char *value; } http_field_t; /* #include "vec.h" */ // == HTTP REQUEST ============================================================ typedef struct { reqtype_t method; http_version_t version; vec(http_field_t) fields; char *uri; char *body; } http_request_t; http_request_t reqInit(void); void reqFree(http_request_t *ctx); bool reqHasField(http_request_t *ctx, const char *key); void reqSetField(http_request_t *ctx, const char *key, const char *value); void reqSetUri(http_request_t *ctx, strview_t uri); str_ostream_t reqPrepare(http_request_t *ctx); str_t reqString(http_request_t *ctx); // == HTTP RESPONSE =========================================================== typedef struct { resstatus_t status_code; vec(http_field_t) fields; http_version_t version; vec(uint8) body; } http_response_t; http_response_t resInit(void); void resFree(http_response_t *ctx); bool resHasField(http_response_t *ctx, const char *key); const char *resGetField(http_response_t *ctx, const char *field); void resParse(http_response_t *ctx, const char *data); void resParseFields(http_response_t *ctx, str_istream_t *in); // == HTTP CLIENT ============================================================= typedef struct { str_t host_name; uint16 port; socket_t socket; } http_client_t; http_client_t hcliInit(void); void hcliFree(http_client_t *ctx); void hcliSetHost(http_client_t *ctx, strview_t hostname); http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *request); // == HTTP ==================================================================== http_response_t httpGet(strview_t hostname, strview_t uri); // == URL ===================================================================== typedef struct { strview_t host; strview_t uri; } url_split_t; url_split_t urlSplit(strview_t uri); #ifdef __cplusplus } // extern "C" #endif #endif // COLLA_NO_NET #if !defined(__TINYC__) && !defined(COLLA_NO_THREADS) #pragma once #ifdef __cplusplus extern "C" { #endif #include #include // == THREAD =========================================== typedef uintptr_t cthread_t; typedef int (*cthread_func_t)(void *); cthread_t thrCreate(cthread_func_t func, void *arg); bool thrValid(cthread_t ctx); bool thrDetach(cthread_t ctx); cthread_t thrCurrent(void); int thrCurrentId(void); int thrGetId(cthread_t ctx); void thrExit(int code); bool thrJoin(cthread_t ctx, int *code); // == MUTEX ============================================ typedef uintptr_t cmutex_t; cmutex_t mtxInit(void); void mtxDestroy(cmutex_t ctx); bool mtxValid(cmutex_t ctx); bool mtxLock(cmutex_t ctx); bool mtxTryLock(cmutex_t ctx); bool mtxUnlock(cmutex_t ctx); #ifdef __cplusplus // small c++ class to make mutexes easier to use struct lock_t { inline lock_t(cmutex_t mutex) : mutex(mutex) { if (mtxValid(mutex)) { mtxLock(mutex); } } inline ~lock_t() { unlock(); } inline void unlock() { if (mtxValid(mutex)) { mtxUnlock(mutex); } mutex = 0; } cmutex_t mutex; }; #endif #ifdef __cplusplus } // extern "C" #endif #endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS) #ifdef COLLA_IMPL /* #include "tracelog.h" */ #include #include #include #ifdef _WIN32 #pragma warning(disable:4996) // _CRT_SECURE_NO_WARNINGS. /* #include "win32_slim.h" */ #ifndef TLOG_VS #define TLOG_WIN32_NO_VS #ifndef TLOG_NO_COLOURS #define TLOG_NO_COLOURS #endif #endif #endif #ifdef TLOG_VS #ifndef _WIN32 #error "can't use TLOG_VS if not on windows" #endif #endif #ifdef TLOG_NO_COLOURS #define BLACK "" #define RED "" #define GREEN "" #define YELLOW "" #define BLUE "" #define MAGENTA "" #define CYAN "" #define WHITE "" #define RESET "" #define BOLD "" #else #define BLACK "\033[30m" #define RED "\033[31m" #define GREEN "\033[32m" #define YELLOW "\033[33m" #define BLUE "\033[22;34m" #define MAGENTA "\033[35m" #define CYAN "\033[36m" #define WHITE "\033[37m" #define RESET "\033[0m" #define BOLD "\033[1m" #endif #define MAX_TRACELOG_MSG_LENGTH 1024 bool use_newline = true; #ifdef TLOG_WIN32_NO_VS static void setLevelColour(int level) { WORD attribute = 15; switch (level) { case LogDebug: attribute = 1; break; case LogInfo: attribute = 2; break; case LogWarning: attribute = 6; break; case LogError: attribute = 4; break; case LogFatal: attribute = 4; break; } HANDLE hc = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hc, attribute); } #endif void traceLog(int level, const char *fmt, ...) { va_list args; va_start(args, fmt); traceLogVaList(level, fmt, args); va_end(args); } void traceLogVaList(int level, const char *fmt, va_list args) { char buffer[MAX_TRACELOG_MSG_LENGTH]; memset(buffer, 0, sizeof(buffer)); const char *beg; switch (level) { case LogTrace: beg = BOLD WHITE "[TRACE]: " RESET; break; case LogDebug: beg = BOLD BLUE "[DEBUG]: " RESET; break; case LogInfo: beg = BOLD GREEN "[INFO]: " RESET; break; case LogWarning: beg = BOLD YELLOW "[WARNING]: " RESET; break; case LogError: beg = BOLD RED "[ERROR]: " RESET; break; case LogFatal: beg = BOLD RED "[FATAL]: " RESET; break; default: beg = ""; break; } size_t offset = 0; #ifndef TLOG_WIN32_NO_VS offset = strlen(beg); strncpy(buffer, beg, sizeof(buffer)); #endif vsnprintf(buffer + offset, sizeof(buffer) - offset, fmt, args); #if defined(TLOG_VS) OutputDebugStringA(buffer); if(use_newline) OutputDebugStringA("\n"); #elif defined(TLOG_WIN32_NO_VS) SetConsoleOutputCP(CP_UTF8); setLevelColour(level); printf("%s", beg); // set back to white setLevelColour(LogTrace); printf("%s", buffer); if(use_newline) puts(""); #else printf("%s", buffer); if(use_newline) puts(""); #endif #ifndef TLOG_DONT_EXIT_ON_FATAL if (level == LogFatal) exit(1); #endif } void traceUseNewline(bool newline) { use_newline = newline; } /* #include "strstream.h" */ #include #include #include #include #include #include #include // HUGE_VALF /* #include "tracelog.h" */ /* == INPUT STREAM ============================================ */ str_istream_t istrInit(const char *str) { return istrInitLen(str, strlen(str)); } str_istream_t istrInitLen(const char *str, usize len) { str_istream_t res; res.start = res.cur = str; res.size = len; return res; } char istrGet(str_istream_t *ctx) { return *ctx->cur++; } void istrIgnore(str_istream_t *ctx, char delim) { usize position = ctx->cur - ctx->start; usize i; for(i = position; i < ctx->size && *ctx->cur != delim; ++i, ++ctx->cur); } char istrPeek(str_istream_t *ctx) { return *ctx->cur; } void istrSkip(str_istream_t *ctx, usize n) { usize remaining = ctx->size - (ctx->cur - ctx->start); if(n > remaining) { warn("skipping more then remaining: %zu -> %zu", n, remaining); return; } ctx->cur += n; } void istrSkipWhitespace(str_istream_t *ctx) { while (*ctx->cur && isspace(*ctx->cur)) { ++ctx->cur; } } void istrRead(str_istream_t *ctx, char *buf, usize len) { usize remaining = ctx->size - (ctx->cur - ctx->start); if(len > remaining) { warn("istrRead: trying to read len %zu from remaining %zu", len, remaining); return; } memcpy(buf, ctx->cur, len); ctx->cur += len; } usize istrReadMax(str_istream_t *ctx, char *buf, usize len) { usize remaining = ctx->size - (ctx->cur - ctx->start); len = remaining < len ? remaining : len; memcpy(buf, ctx->cur, len); ctx->cur += len; return len; } void istrRewind(str_istream_t *ctx) { ctx->cur = ctx->start; } usize istrTell(str_istream_t ctx) { return ctx.cur - ctx.start; } usize istrRemaining(str_istream_t ctx) { return ctx.size - (ctx.cur - ctx.start); } bool istrIsFinished(str_istream_t ctx) { return (ctx.cur - ctx.start) >= ctx.size; } bool istrGetbool(str_istream_t *ctx, bool *val) { usize remaining = ctx->size - (ctx->cur - ctx->start); if(strncmp(ctx->cur, "true", remaining) == 0) { *val = true; return true; } if(strncmp(ctx->cur, "false", remaining) == 0) { *val = false; return true; } return false; } bool istrGetu8(str_istream_t *ctx, uint8 *val) { char *end = NULL; *val = (uint8) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGetu8: no valid conversion could be performed"); return false; } else if(*val == UINT8_MAX) { warn("istrGetu8: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGetu16(str_istream_t *ctx, uint16 *val) { char *end = NULL; *val = (uint16) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGetu16: no valid conversion could be performed"); return false; } else if(*val == UINT16_MAX) { warn("istrGetu16: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGetu32(str_istream_t *ctx, uint32 *val) { char *end = NULL; *val = (uint32) strtoul(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGetu32: no valid conversion could be performed"); return false; } else if(*val == UINT32_MAX) { warn("istrGetu32: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGetu64(str_istream_t *ctx, uint64 *val) { char *end = NULL; *val = strtoull(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGetu64: no valid conversion could be performed"); return false; } else if(*val == ULLONG_MAX) { warn("istrGetu64: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGeti8(str_istream_t *ctx, int8 *val) { char *end = NULL; *val = (int8) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGeti8: no valid conversion could be performed"); return false; } else if(*val == INT8_MAX || *val == INT8_MIN) { warn("istrGeti8: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGeti16(str_istream_t *ctx, int16 *val) { char *end = NULL; *val = (int16) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGeti16: no valid conversion could be performed"); return false; } else if(*val == INT16_MAX || *val == INT16_MIN) { warn("istrGeti16: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGeti32(str_istream_t *ctx, int32 *val) { char *end = NULL; *val = (int32) strtol(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGeti32: no valid conversion could be performed"); return false; } else if(*val == INT32_MAX || *val == INT32_MIN) { warn("istrGeti32: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGeti64(str_istream_t *ctx, int64 *val) { char *end = NULL; *val = strtoll(ctx->cur, &end, 0); if(ctx->cur == end) { warn("istrGeti64: no valid conversion could be performed"); return false; } else if(*val == INT64_MAX || *val == INT64_MIN) { warn("istrGeti64: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGetfloat(str_istream_t *ctx, float *val) { char *end = NULL; *val = strtof(ctx->cur, &end); if(ctx->cur == end) { warn("istrGetfloat: no valid conversion could be performed"); return false; } else if(*val == HUGE_VALF || *val == -HUGE_VALF) { warn("istrGetfloat: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } bool istrGetdouble(str_istream_t *ctx, double *val) { char *end = NULL; *val = strtod(ctx->cur, &end); if(ctx->cur == end) { warn("istrGetdouble: no valid conversion could be performed"); return false; } else if(*val == HUGE_VAL || *val == -HUGE_VAL) { warn("istrGetdouble: value read is out of the range of representable values"); return false; } ctx->cur = end; return true; } usize istrGetstring(str_istream_t *ctx, char **val, char delim) { const char *from = ctx->cur; istrIgnore(ctx, delim); // if it didn't actually find it, it just reached the end of the string if(*ctx->cur != delim) { *val = NULL; return 0; } usize len = ctx->cur - from; *val = (char *)malloc(len + 1); memcpy(*val, from, len); (*val)[len] = '\0'; return len; } usize istrGetstringBuf(str_istream_t *ctx, char *val, usize len) { usize remaining = ctx->size - (ctx->cur - ctx->start); len -= 1; len = remaining < len ? remaining : len; memcpy(val, ctx->cur, len); val[len] = '\0'; ctx->cur += len; return len; } strview_t istrGetview(str_istream_t *ctx, char delim) { const char *from = ctx->cur; istrIgnore(ctx, delim); usize len = ctx->cur - from; return strvInitLen(from, len); } strview_t istrGetviewLen(str_istream_t *ctx, usize from, usize to) { usize len = ctx->size - (ctx->cur - ctx->start) - from; if (to > len) to = len; if (from > to) from = to; return strvInitLen(ctx->cur + from, to - from); } /* == OUTPUT STREAM =========================================== */ static void _ostrRealloc(str_ostream_t *ctx, usize needed) { ctx->cap = (ctx->cap * 2) + needed; ctx->buf = (char *)realloc(ctx->buf, ctx->cap); } str_ostream_t ostrInit() { return ostrInitLen(1); } str_ostream_t ostrInitLen(usize initial_alloc) { str_ostream_t stream; stream.buf = (char *)calloc(initial_alloc, 1); stream.len = 0; stream.cap = initial_alloc; return stream; } str_ostream_t ostrInitStr(const char *cstr, usize len) { str_ostream_t stream; stream.buf = (char *)malloc(len + 1); memcpy(stream.buf, cstr, len); stream.len = len; stream.cap = len + 1; return stream; } void ostrFree(str_ostream_t ctx) { free(ctx.buf); } void ostrClear(str_ostream_t *ctx) { ctx->len = 0; } char ostrBack(str_ostream_t ctx) { if(ctx.len == 0) return '\0'; return ctx.buf[ctx.len - 1]; } str_t ostrAsStr(str_ostream_t ctx) { return (str_t) { .buf = ctx.buf, .len = ctx.len }; } strview_t ostrAsView(str_ostream_t ctx) { return (strview_t) { .buf = ctx.buf, .len = ctx.len }; } void ostrReplace(str_ostream_t *ctx, char from, char to) { for(usize i = 0; i < ctx->len; ++i) { if(ctx->buf[i] == from) { ctx->buf[i] = to; } } } void ostrPrintf(str_ostream_t *ctx, const char *fmt, ...) { va_list va; va_start(va, fmt); ostrPrintfV(ctx, fmt, va); va_end(va); } void ostrPrintfV(str_ostream_t *ctx, const char *fmt, va_list args) { va_list vtemp; int len; usize remaining; // vsnprintf returns the length of the formatted string, even if truncated // we use this to get the actual length of the formatted string va_copy(vtemp, args); len = vsnprintf(NULL, 0, fmt, vtemp); va_end(vtemp); if(len < 0) { err("couldn't format string \"%s\"", fmt); goto error; } remaining = ctx->cap - ctx->len; if(remaining < (usize)len) { _ostrRealloc(ctx, len + 1); remaining = ctx->cap - ctx->len; } // actual formatting here va_copy(vtemp, args); len = vsnprintf(ctx->buf + ctx->len, remaining, fmt, vtemp); va_end(vtemp); if(len < 0) { err("couldn't format stringh \"%s\"", fmt); goto error; } ctx->len += len; error: return; } #define APPEND_BUF_LEN 20 void ostrPutc(str_ostream_t *ctx, char c) { ostrAppendchar(ctx, c); } void ostrPuts(str_ostream_t *ctx, const char *str) { ostrAppendview(ctx, strvInit(str)); } void ostrAppendbool(str_ostream_t *ctx, bool val) { ostrAppendview(ctx, strvInit(val ? "true" : "false")); } void ostrAppendchar(str_ostream_t *ctx, char val) { if(ctx->len >= ctx->cap) { _ostrRealloc(ctx, 1); } ctx->buf[ctx->len++] = val; ctx->buf[ctx->len] = '\0'; } void ostrAppendu8(str_ostream_t *ctx, uint8 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%hhu", val); if(len <= 0) { err("ostrAppendu8: couldn't write %hhu", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendu16(str_ostream_t *ctx, uint16 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%hu", val); if(len <= 0) { err("ostrAppendu16: couldn't write %hu", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendu32(str_ostream_t *ctx, uint32 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%u", val); if(len <= 0) { err("ostrAppendu32: couldn't write %u", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendu64(str_ostream_t *ctx, uint64 val) { char buf[APPEND_BUF_LEN]; #if _WIN32 int len = snprintf(buf, sizeof(buf), "%llu", val); #else int len = snprintf(buf, sizeof(buf), "%lu", val); #endif if(len <= 0) { err("ostrAppendu64: couldn't write %lu", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendi8(str_ostream_t *ctx, int8 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%hhi", val); if(len <= 0) { err("ostrAppendi8: couldn't write %hhi", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendi16(str_ostream_t *ctx, int16 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%hi", val); if(len <= 0) { err("ostrAppendi16: couldn't write %hi", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendi32(str_ostream_t *ctx, int32 val) { char buf[APPEND_BUF_LEN]; int len = snprintf(buf, sizeof(buf), "%i", val); if(len <= 0) { err("ostrAppendi32: couldn't write %i", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendi64(str_ostream_t *ctx, int64 val) { char buf[APPEND_BUF_LEN]; #if _WIN32 int len = snprintf(buf, sizeof(buf), "%lli", val); #else int len = snprintf(buf, sizeof(buf), "%li", val); #endif if(len <= 0) { err("ostrAppendi64: couldn't write %li", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendfloat(str_ostream_t *ctx, float val) { char buf[APPEND_BUF_LEN * 3]; int len = snprintf(buf, sizeof(buf), "%g", (double)val); if(len <= 0) { err("ostrAppendfloat: couldn't write %g", (double)val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppenddouble(str_ostream_t *ctx, double val) { char buf[APPEND_BUF_LEN * 3]; int len = snprintf(buf, sizeof(buf), "%g", val); if(len <= 0) { err("ostrAppenddouble: couldn't write %g", val); return; } ostrAppendview(ctx, strvInitLen(buf, len)); } void ostrAppendview(str_ostream_t *ctx, strview_t view) { if((ctx->cap - ctx->len) <= view.len) { _ostrRealloc(ctx, view.len + 1); } memcpy(ctx->buf + ctx->len, view.buf, view.len); ctx->len += view.len; ctx->buf[ctx->len] = '\0'; } /* #include "str.h" */ #include #include #include #include #include #include /* #include "tracelog.h" */ /* #include "strstream.h" */ #ifdef _WIN32 /* #include "win32_slim.h" */ #else #include #endif #ifndef min #define min(a, b) ((a) < (b) ? (a) : (b)) #endif // == STR_T ======================================================== str_t strInit(void) { return (str_t) { .buf = NULL, .len = 0 }; } str_t strFromStr(const char *cstr) { return cstr ? strFromBuf(cstr, strlen(cstr)) : strInit(); } str_t strFromView(strview_t view) { return strFromBuf(view.buf, view.len); } str_t strFromBuf(const char *buf, usize len) { if (!buf) return strInit(); str_t str; str.len = len; str.buf = (char *)malloc(len + 1); memcpy(str.buf, buf, len); str.buf[len] = '\0'; return str; } str_t strFromFmt(const char *fmt, ...) { str_ostream_t out = ostrInit(); va_list va; va_start(va, fmt); ostrPrintfV(&out, fmt, va); va_end(va); return ostrAsStr(out); } void strFree(str_t ctx) { free(ctx.buf); } str_t strFromWCHAR(const wchar_t *src, usize len) { if(len == 0) len = wcslen(src); #ifdef _WIN32 // TODO CP_ACP should be CP_UTF8 but if i put CP_UTF8 it doesn't work?? int result_len = WideCharToMultiByte( CP_ACP, 0, src, (int)len, NULL, 0, NULL, NULL ); char *buf = (char *)malloc(result_len + 1); if(buf) { WideCharToMultiByte( CP_ACP, 0, src, (int)len, buf, result_len, NULL, NULL ); buf[result_len] = '\0'; } return (str_t) { .buf = buf, .len = result_len }; #else usize actual_len = len * sizeof(wchar_t); usize dest_len = len * 6; char *dest = (char *)malloc(dest_len); iconv_t cd = iconv_open("UTF-8", "WCHAR_T"); assert(cd); usize dest_left = dest_len; char *dest_temp = dest; char *src_temp = (char*)src; usize lost = iconv(cd, &src_temp, &actual_len, &dest_temp, &dest_left); assert(lost != ((usize)-1)); (void)lost; dest_len -= dest_left; dest = (char *)realloc(dest, dest_len + 1); dest[dest_len] = '\0'; iconv_close(cd); return (str_t){ .buf = dest, .len = dest_len }; #endif } wchar_t *strToWCHAR(str_t ctx) { #ifdef _WIN32 UINT codepage = CP_ACP; int len = MultiByteToWideChar( codepage, 0, ctx.buf, (int)ctx.len, NULL, 0 ); wchar_t *str = (wchar_t *)malloc(sizeof(wchar_t) * (len + 1)); if(!str) return NULL; len = MultiByteToWideChar( codepage, 0, ctx.buf, (int)ctx.len, str, len ); str[len] = '\0'; return str; #else usize dest_len = ctx.len * sizeof(wchar_t); char *dest = (char *)malloc(dest_len); iconv_t cd = iconv_open("WCHAR_T", "UTF-8"); assert(cd); usize dest_left = dest_len; char *dest_temp = dest; char *src_temp = ctx.buf; usize lost = iconv(cd, &src_temp, &ctx.len, &dest_temp, &dest_left); assert(lost != ((usize)-1)); (void)lost; dest_len -= dest_left; dest = (char *)realloc(dest, dest_len + 1); dest[dest_len] = '\0'; iconv_close(cd); return (wchar_t *)dest; #endif } str_t strDup(str_t ctx) { return strFromBuf(ctx.buf, ctx.len); } str_t strMove(str_t *ctx) { str_t out = *ctx; ctx->buf = NULL; ctx->len = 0; return out; } strview_t strGetView(str_t ctx) { return (strview_t) { .buf = ctx.buf, .len = ctx.len }; } char *strBegin(str_t ctx) { return ctx.buf; } char *strEnd(str_t ctx) { return ctx.buf ? ctx.buf + ctx.len : NULL; } char strBack(str_t ctx) { return ctx.buf ? ctx.buf[ctx.len - 1] : '\0'; } bool strIsEmpty(str_t ctx) { return ctx.len == 0; } void strSwap(str_t *ctx, str_t *other) { char *buf = other->buf; usize len = other->len; other->buf = ctx->buf; other->len = ctx->len; ctx->buf = buf; ctx->len = len; } void strReplace(str_t *ctx, char from, char to) { for(usize i = 0; i < ctx->len; ++i) { if(ctx->buf[i] == from) { ctx->buf[i] = to; } } } str_t strSubstr(str_t ctx, usize from, usize to) { if(strIsEmpty(ctx)) return strInit(); if (to > ctx.len) to = ctx.len; if (from > to) from = to; return strFromBuf(ctx.buf + from, to - from); } strview_t strSubview(str_t ctx, usize from, usize to) { if(strIsEmpty(ctx)) return strvInit(NULL); if (to > ctx.len) to = ctx.len; if (from > to) from = to; return strvInitLen(ctx.buf + from, to - from); } void strLower(str_t *ctx) { for(usize i = 0; i < ctx->len; ++i) { ctx->buf[i] = (char)tolower(ctx->buf[i]); } } str_t strToLower(str_t ctx) { str_t str = strDup(ctx); strLower(&str); return str; } void strUpper(str_t *ctx) { for(usize i = 0; i < ctx->len; ++i) { ctx->buf[i] = (char)toupper(ctx->buf[i]); } } str_t strToUpper(str_t ctx) { str_t str = strDup(ctx); strUpper(&str); return str; } // == STRVIEW_T ==================================================== strview_t strvInit(const char *cstr) { return strvInitLen(cstr, cstr ? strlen(cstr) : 0); } strview_t strvInitStr(str_t str) { return strvInitLen(str.buf, str.len); } strview_t strvInitLen(const char *buf, usize size) { return (strview_t) { .buf = buf, .len = size }; } char strvFront(strview_t ctx) { return ctx.buf[0]; } char strvBack(strview_t ctx) { return ctx.buf[ctx.len - 1]; } const char *strvBegin(strview_t ctx) { return ctx.buf; } const char *strvEnd(strview_t ctx) { return ctx.buf + ctx.len; } bool strvIsEmpty(strview_t ctx) { return ctx.len == 0; } strview_t strvRemovePrefix(strview_t ctx, usize n) { if (n > ctx.len) n = ctx.len; return (strview_t){ .buf = ctx.buf + n, .len = ctx.len - n }; } strview_t strvRemoveSuffix(strview_t ctx, usize n) { if (n > ctx.len) n = ctx.len; return (strview_t){ .buf = ctx.buf, .len = ctx.len - n }; } strview_t strvTrim(strview_t ctx) { return strvTrimLeft(strvTrimRight(ctx)); } strview_t strvTrimLeft(strview_t ctx) { strview_t out = ctx; for (usize i = 0; i < ctx.len && isspace(ctx.buf[i]); ++i) { ++out.buf; --out.len; } return out; } strview_t strvTrimRight(strview_t ctx) { strview_t out = ctx; for (isize i = ctx.len - 1; i >= 0 && isspace(ctx.buf[i]); --i) { --out.len; } return out; } str_t strvCopy(strview_t ctx) { return strFromView(ctx); } str_t strvCopyN(strview_t ctx, usize count, usize from) { usize sz = ctx.len + 1 - from; count = min(count, sz); return strFromBuf(ctx.buf + from, count); } usize strvCopyBuf(strview_t ctx, char *buf, usize len, usize from) { usize sz = ctx.len + 1 - from; len = min(len, sz); memcpy(buf, ctx.buf + from, len); buf[len - 1] = '\0'; return len - 1; } strview_t strvSub(strview_t ctx, usize from, usize to) { if (to > ctx.len) to = ctx.len; if (from > to) from = to; return strvInitLen(ctx.buf + from, to - from); } int strvCompare(strview_t ctx, strview_t other) { if(ctx.len < other.len) return -1; if(ctx.len > other.len) return 1; return memcmp(ctx.buf, other.buf, ctx.len); } int strvICompare(strview_t ctx, strview_t other) { if(ctx.len < other.len) return -1; if(ctx.len > other.len) return 1; for(usize i = 0; i < ctx.len; ++i) { int a = tolower(ctx.buf[i]); int b = tolower(other.buf[i]); if(a != b) return a - b; } return 0; } bool strvStartsWith(strview_t ctx, char c) { return strvFront(ctx) == c; } bool strvStartsWithView(strview_t ctx, strview_t view) { if(ctx.len < view.len) return false; return memcmp(ctx.buf, view.buf, view.len) == 0; } bool strvEndsWith(strview_t ctx, char c) { return strvBack(ctx) == c; } bool strvEndsWithView(strview_t ctx, strview_t view) { if(ctx.len < view.len) return false; return memcmp(ctx.buf + ctx.len - view.len, view.buf, view.len) == 0; } bool strvContains(strview_t ctx, char c) { for(usize i = 0; i < ctx.len; ++i) { if(ctx.buf[i] == c) return true; } return false; } bool strvContainsView(strview_t ctx, strview_t view) { if(ctx.len < view.len) return false; usize end = ctx.len - view.len; for(usize i = 0; i < end; ++i) { if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return true; } return false; } usize strvFind(strview_t ctx, char c, usize from) { for(usize i = from; i < ctx.len; ++i) { if(ctx.buf[i] == c) return i; } return SIZE_MAX; } usize strvFindView(strview_t ctx, strview_t view, usize from) { if(ctx.len < view.len) return SIZE_MAX; usize end = ctx.len - view.len; for(usize i = from; i < end; ++i) { if(memcmp(ctx.buf + i, view.buf, view.len) == 0) return i; } return SIZE_MAX; } usize strvRFind(strview_t ctx, char c, usize from) { if(from >= ctx.len) { from = ctx.len - 1; } const char *buf = ctx.buf + from; for(; buf >= ctx.buf; --buf) { if(*buf == c) return (buf - ctx.buf); } return SIZE_MAX; } usize strvRFindView(strview_t ctx, strview_t view, usize from) { from = min(from, ctx.len); if(view.len > ctx.len) { from -= view.len; } const char *buf = ctx.buf + from; for(; buf >= ctx.buf; --buf) { if(memcmp(buf, view.buf, view.len) == 0) return (buf - ctx.buf); } return SIZE_MAX; } usize strvFindFirstOf(strview_t ctx, strview_t view, usize from) { if(ctx.len < view.len) return SIZE_MAX; for(usize i = from; i < ctx.len; ++i) { for(usize j = 0; j < view.len; ++j) { if(ctx.buf[i] == view.buf[j]) return i; } } return SIZE_MAX; } usize strvFindLastOf(strview_t ctx, strview_t view, usize from) { if(from >= ctx.len) { from = ctx.len - 1; } const char *buf = ctx.buf + from; for(; buf >= ctx.buf; --buf) { for(usize j = 0; j < view.len; ++j) { if(*buf == view.buf[j]) return (buf - ctx.buf); } } return SIZE_MAX; } usize strvFindFirstNot(strview_t ctx, char c, usize from) { usize end = ctx.len - 1; for(usize i = from; i < end; ++i) { if(ctx.buf[i] != c) return i; } return SIZE_MAX; } usize strvFindFirstNotOf(strview_t ctx, strview_t view, usize from) { for(usize i = from; i < ctx.len; ++i) { if(!strvContains(view, ctx.buf[i])) { return i; } } return SIZE_MAX; } usize strvFindLastNot(strview_t ctx, char c, usize from) { if(from >= ctx.len) { from = ctx.len - 1; } const char *buf = ctx.buf + from; for(; buf >= ctx.buf; --buf) { if(*buf != c) { return buf - ctx.buf; } } return SIZE_MAX; } usize strvFindLastNotOf(strview_t ctx, strview_t view, usize from) { if(from >= ctx.len) { from = ctx.len - 1; } const char *buf = ctx.buf + from; for(; buf >= ctx.buf; --buf) { if(!strvContains(view, *buf)) { return buf - ctx.buf; } } return SIZE_MAX; } #ifdef STR_TESTING #include /* #include "tracelog.h" */ void strTest(void) { str_t s; debug("== testing init ================="); { s = strInit(); printf("%s %zu\n", s.buf, s.len); strFree(&s); s = strInitStr("hello world"); printf("\"%s\" %zu\n", s.buf, s.len); strFree(&s); uint8 buf[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; s = strFromBuf((char *)buf, sizeof(buf)); printf("\"%s\" %zu\n", s.buf, s.len); strFree(&s); } debug("== testing view ================="); { s = strInitStr("hello world"); strview_t view = strGetView(&s); printf("\"%.*s\" %zu\n", (int)view.len, view.buf, view.len); strFree(&s); } debug("== testing begin/end ============"); { s = strInitStr("hello world"); char *beg = strBegin(&s); char *end = strEnd(&s); printf("[ "); for(; beg < end; ++beg) { printf("%c ", *beg); } printf("]\n"); strFree(&s); } debug("== testing back/isempty ========="); { s = strInitStr("hello world"); printf("[ "); while(!strIsEmpty(&s)) { printf("%c ", strBack(&s)); strPop(&s); } printf("]\n"); strFree(&s); } debug("== testing append ==============="); { s = strInitStr("hello "); printf("\"%s\" %zu\n", s.buf, s.len); strAppend(&s, "world"); printf("\"%s\" %zu\n", s.buf, s.len); strAppendView(&s, strvInit(", how is it ")); printf("\"%s\" %zu\n", s.buf, s.len); uint8 buf[] = { 'g', 'o', 'i', 'n', 'g' }; strAppendBuf(&s, (char*)buf, sizeof(buf)); printf("\"%s\" %zu\n", s.buf, s.len); strAppendChars(&s, '?', 2); printf("\"%s\" %zu\n", s.buf, s.len); strFree(&s); } debug("== testing push/pop ============="); { s = strInit(); str_t s2 = strInitStr("hello world"); printf("%-14s %-14s\n", "s", "s2"); printf("----------------------------\n"); while(!strIsEmpty(&s2)) { printf("%-14s %-14s\n", s.buf, s2.buf); strPush(&s, strPop(&s2)); } printf("%-14s %-14s\n", s.buf, s2.buf); strFree(&s); strFree(&s2); } debug("== testing swap ================="); { s = strInitStr("hello"); str_t s2 = strInitStr("world"); printf("%-8s %-8s\n", "s", "s2"); printf("----------------\n"); printf("%-8s %-8s\n", s.buf, s2.buf); strSwap(&s, &s2); printf("%-8s %-8s\n", s.buf, s2.buf); strFree(&s); strFree(&s2); } debug("== testing substr ==============="); { s = strInitStr("hello world"); printf("s: %s\n", s.buf); printf("-- string\n"); str_t s2 = strSubstr(&s, 0, 5); printf("0..5: \"%s\"\n", s2.buf); strFree(&s2); s2 = strSubstr(&s, 5, SIZE_MAX); printf("6..SIZE_MAX: \"%s\"\n", s2.buf); strFree(&s2); printf("-- view\n"); strview_t v = strSubview(&s, 0, 5); printf("0..5: \"%.*s\"\n", (int)v.len, v.buf); v = strSubview(&s, 5, SIZE_MAX); printf("6..SIZE_MAX: \"%.*s\"\n", (int)v.len, v.buf); strFree(&s); } strFree(&s); } #endif /* #include "hashmap.h" */ static uint64 hash_seed = 0; hashmap_t hmInit(usize initial_cap) { hashmap_t map = {0}; if (!initial_cap) initial_cap = 512; vecReserve(map.nodes, initial_cap); memset(map.nodes, 0, sizeof(hashnode_t) * initial_cap); return map; } void hmFree(hashmap_t map) { vecFree(map.nodes); } void hmSet(hashmap_t *map, uint64 hash, uint64 index) { uint32 hm_index = hash % vecCap(map->nodes); while (map->nodes[hm_index].hash) { hashnode_t *node = &map->nodes[hm_index]; if (node->hash == hash) { node->index = index; return; } hm_index = (hm_index + 1) % vecCap(map->nodes); } map->nodes[hm_index].hash = hash; map->nodes[hm_index].index = index; _veclen(map->nodes)++; float load_factor = (float)vecLen(map->nodes) / (float)vecCap(map->nodes); if (load_factor > 0.75f) { uint32 old_cap = vecCap(map->nodes); vecReserve(map->nodes, old_cap); for (usize i = old_cap; i < vecCap(map->nodes); ++i) { map->nodes[i].hash = 0; map->nodes[i].index = 0; } } } uint64 hmGet(hashmap_t map, uint64 hash) { uint32 hm_index = hash % vecCap(map.nodes); do { hashnode_t *node = &map.nodes[hm_index]; if (node->hash == hash) { return node->index; } hm_index = (hm_index + 1) % vecCap(map.nodes); } while (map.nodes[hm_index].hash); return 0; } void hmDelete(hashmap_t *map, uint64 hash) { uint32 hm_index = hash % vecCap(map->nodes); do { hashnode_t *node = &map->nodes[hm_index]; if (node->hash == hash) { node->hash = 0; node->index = 0; break; } hm_index = (hm_index + 1) % vecCap(map->nodes); } while (map->nodes[hm_index].hash); if(vecLen(map->nodes)) _veclen(map->nodes)--; } void hashSetSeed(uint64 new_seed) { hash_seed = new_seed; } uint64 hash(const void *ptr, usize len) { const uint64 m = 0xC6A4A7935BD1E995LLU; const int r = 47; uint64 h = hash_seed ^ (len * m); const uint64 *data = (const uint64 *)ptr; const uint64 *end = (len >> 3) + data; while (data != end) { uint64 k = *data++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char * data2 = (const unsigned char *)data; switch(len & 7) { case 7: h ^= (uint64_t)(data2[6]) << 48; case 6: h ^= (uint64_t)(data2[5]) << 40; case 5: h ^= (uint64_t)(data2[4]) << 32; case 4: h ^= (uint64_t)(data2[3]) << 24; case 3: h ^= (uint64_t)(data2[2]) << 16; case 2: h ^= (uint64_t)(data2[1]) << 8; case 1: h ^= (uint64_t)(data2[0]); h *= m; }; h ^= h >> r; h *= m; h ^= h >> r; return h; } uint64 hashStr(str_t str) { return hash(str.buf, str.len); } uint64 hashView(strview_t view) { return hash(view.buf, view.len); } uint64 hashCStr(const char *cstr) { return hash(cstr, strlen(cstr)); } /* #include "utf8.h" */ static const uint8 masks[] = { 0x7f, // 0111-1111 0x1f, // 0001-1111 0x0f, // 0000-1111 0x07, // 0000-0111 0x03, // 0000-0011 0x01 // 0000-0001 }; struct { uint8 mask; uint8 result; int octets; } sizes[] = { { 0x80, 0x00, 1 }, // 1000-0000, 0000-0000 { 0xE0, 0xC0, 2 }, // 1110-0000, 1100-0000 { 0xF0, 0xE0, 3 }, // 1111-0000, 1110-0000 { 0xF8, 0xF0, 4 }, // 1111-1000, 1111-0000 { 0xFC, 0xF8, 5 }, // 1111-1100, 1111-1000 { 0xFE, 0xF8, 6 }, // 1111-1110, 1111-1000 { 0x80, 0x80, -1 }, // 1000-0000, 1000-0000 }; /* UTF-8 codepoints are encoded using the first bits of the first character byte 1 | byte 2 | byte 3 | byte 4 0xxx xxxx | | | 110x xxxx | 10xx xxxx | | 1110 xxxx | 10xx xxxx | 10xx xxxx | 1111 0xxx | 10xx xxxx | 10xx xxxx | 10xx xxxx so when we decode it we first find the size of the codepoint (from 1 to 4) then we apply the mask to the first byte to get the first character then we keep shifting the rune left 6 and applying the next byte to the mask until the codepoint is finished (size is 0) ## EXAMPLE utf8 string (€) = 1110-0010 1000-0010 1010-1100 cp = 0000-0000 0000-0000 0000-0000 0000-0000 size = 3 mask = 0x0f -> 0000-1111 cp = *s & mask = 1110-0010 & 0000-1111 = 0000-0000 0000-0000 0000-0000 0000-0010 ++s = 1000-0010 --size = 2 cp <<= 6 = 0000-0000 0000-0000 0000-0000 1000-0000 cp |= *s & 0x3f = 1000-0010 & 0011-1111 = 0000-0000 0000-0000 0000-0000 1000-0010 ++s = 1010-1100 --size = 1 cp <<= 6 = 0000-0000 0000-0000 0010-0000 1000-0000 cp |= *s & 0x3f = 1010-1100 & 0011-1111 = 0000-0000 0000-0000 0010-0000 1010-1100 ++s = ---------- final codepoint = 0010-0000 1010-1100 € codepoint = 0010-0000 1010-1100 */ rune utf8Decode(const char **char_str) { uint8 **s = (uint8 **)char_str; rune ch = 0; // if is ascii if (**s < 128) { ch = **s; ++*s; return ch; } int size = utf8Size((char *)*s); if (size == -1) { ++*s; return UTF8_INVALID; } uint8 mask = masks[size - 1]; ch = **s & mask; ++*s; while(--size) { ch <<= 6; ch |= **s & 0x3f; // 0011-1111 ++*s; } return ch; } /* to encode a codepoint in a utf8 string we first need to find the length of the codepoint then we start from the rightmost byte and loop for each byte of the codepoint using the length we got before until the first byte (which we skip) > and (&) with 0x3f so we ignore the first to bits of the codepoint > or (|) with 0x80 so we make sure that the first two bits are 10 > bitshift the codepoint right 6 finally, we apply the correct length-mask to the first byte ## EXAMPLE ch € = 0010-0000 1010-1100 ch < 0x10000 first = 0xe0 = 1110-0000 len = 3 str[2] = (ch & 0x3f) | 0x80 = 1010-1100 & 0011-1111 | 1000-0000 = 1010-1100 ch >>= 6 = 0010-0000 1010-1100 >> 6 = 1000-0010 str[1] = (ch & 0x3f) | 0x80 = 1000-0010 & 0011-1111 | 1000-000 = 1000-0010 ch >>= 6 = 1000-0010 >> 6 = 0000-0010 str[0] = ch | first_mask = 0000-0010 | 1111-0000 = 1111-0010 str = 1111-0010 1000-0010 1010-1100 utf8 € = 1110-0010 1000-0010 1010-1100 */ usize utf8Encode(char *str, rune codepoint) { usize len = 0; uint8 first; if (codepoint < 0x80) { // 0000-0000 0000-0000 0000-0000 1000-0000 first = 0; len = 1; } else if (codepoint < 0x800) { // 0000-0000 0000-0000 0000-1000 0000-0000 first = 0xc0; // 1100-0000 len = 2; } else if (codepoint < 0x10000) { // 0000-0000 0000-0001 0000-0000 0000-0000 first = 0xe0; // 1110-0000 len = 3; } else { first = 0xf0; // 1111-0000 len = 4; } for (usize i = len - 1; i > 0; --i) { // 0x3f -> 0011-1111 // 0x80 -> 1000-0000 str[i] = (codepoint & 0x3f) | 0x80; codepoint >>= 6; } str[0] = codepoint | first; return len; } int utf8Size(const char *str) { uint8 c = (uint8)*str; for(usize i = 0; i < (sizeof(sizes) / sizeof(*sizes)); ++i) { if ((c & sizes[i].mask) == sizes[i].result) { return sizes[i].octets; } } return -1; } usize utf8CpSize(rune ch) { if (ch < 0x80) return 1; else if (ch < 0x800) return 2; else if (ch < 0x10000) return 3; return 4; } /* #include "ini.h" */ /* #include "strstream.h" */ // == INI READER ======================================================================== static const iniopts_t default_opts = {0}; static initable_t *findTable(ini_t *ctx, strview_t name); static inivalue_t *findValue(vec(inivalue_t) values, strview_t key); static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options); static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options); void _iniParseInternal(ini_t *ini, const iniopts_t *options) { // add root table vecAppend(ini->tables, (initable_t){0}); str_istream_t in = istrInitLen(ini->text.buf, ini->text.len); istrSkipWhitespace(&in); while (!istrIsFinished(in)) { switch(*in.cur) { case '[': addTable(ini, &in, options); break; case '#': case ';': istrIgnore(&in, '\n'); break; default: addValue(&ini->tables[0], &in, options); break; } istrSkipWhitespace(&in); } } ini_t iniParse(const char *filename, const iniopts_t *options) { ini_t ini = { .text = fileReadWholeText(filename) }; if (strIsEmpty(ini.text)) return ini; if (!options) options = &default_opts; _iniParseInternal(&ini, options); return ini; } ini_t iniParseString(const char *inistr, const iniopts_t *options) { ini_t ini = { .text = strFromStr(inistr) }; if (!options) options = &default_opts; _iniParseInternal(&ini, options); return ini; } void iniFree(ini_t ctx) { strFree(ctx.text); for (uint32 i = 0; i < vecLen(ctx.tables); ++i) { vecFree(ctx.tables[i].values); } vecFree(ctx.tables); } initable_t *iniGetTable(ini_t *ctx, const char *name) { if (!name) { return &ctx->tables[0]; } else { return findTable(ctx, strvInit(name)); } } inivalue_t *iniGet(initable_t *ctx, const char *key) { return ctx ? findValue(ctx->values, strvInit(key)) : NULL; } vec(strview_t) iniAsArray(const inivalue_t *value, char delim) { if (!value) return NULL; if (!delim) delim = ' '; vec(strview_t) out = NULL; strview_t v = value->value; usize start = 0; for (usize i = 0; i < v.len; ++i) { if (v.buf[i] == delim) { strview_t arr_val = strvTrim(strvSub(v, start, i)); if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val); start = i + 1; } } strview_t last = strvTrim(strvSub(v, start, -1)); if (!strvIsEmpty(last)) vecAppend(out, last); return out; } vec(strview_t) iniAsArrayU8(const inivalue_t *value, const char *delim) { if (!value || !delim) return NULL; rune cpdelim = utf8Decode(&delim); vec(strview_t) out = NULL; strview_t v = value->value; const char *start = v.buf; const char *buf = v.buf; const char *prevbuf = buf; for(rune cp = utf8Decode(&buf); buf != (v.buf + v.len); cp = utf8Decode(&buf) ) { if (cp == cpdelim) { usize start_pos = start - v.buf; usize end_pos = prevbuf - v.buf; strview_t arr_val = strvTrim(strvSub(v, start_pos, end_pos)); if (!strvIsEmpty(arr_val)) vecAppend(out, arr_val); // buf has already gone to the next codepoint, skipping the delimiter start = buf; } prevbuf = buf; } strview_t last = strvTrim(strvSub(v, start - v.buf, -1)); if (!strvIsEmpty(last)) vecAppend(out, last); return out; } uint64 iniAsUInt(const inivalue_t *value) { if (!value) return 0; str_istream_t in = istrInitLen(value->value.buf, value->value.len); uint64 val = 0; if (!istrGetu64(&in, &val)) val = 0; return val; } int64 iniAsInt(const inivalue_t *value) { if (!value) return 0; str_istream_t in = istrInitLen(value->value.buf, value->value.len); int64 val = 0; if (!istrGeti64(&in, &val)) val = 0; return val; } double iniAsNum(const inivalue_t *value) { if (!value) return 0.f; str_istream_t in = istrInitLen(value->value.buf, value->value.len); double val = 0; if (!istrGetdouble(&in, &val)) val = 0; return val; } // == INI WRITER ======================================================================== /* #include "strstream.h" */ /* #include "file.h" */ static const winiopts_t default_wopts = {0}; iniwriter_t winiInit() { iniwriter_t out = {0}; vecAppend(out.tables, (winitable_t){0}); return out; } void winiFree(iniwriter_t ctx) { for (winitable_t *tab = ctx.tables; tab != vecEnd(ctx.tables); ++tab) { strFree(tab->key); for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) { strFree(val->key); strFree(val->value); } vecFree(tab->values); } vecFree(ctx.tables); } str_t winiToString(iniwriter_t ctx, const winiopts_t *options) { if (!options) options = &default_wopts; str_ostream_t out = ostrInitLen(1024 * 20); if (!options->no_discalimer) ostrPuts(&out, "# auto-generated by colla's ini.h, do not modify!\n"); // add root values winitable_t *root = &ctx.tables[0]; for (winivalue_t *val = root->values; val != vecEnd(root->values); ++val) { ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf); } if (root->values) ostrPuts(&out, "\n"); // add each table for (usize i = 1; i < vecLen(ctx.tables); ++i) { winitable_t *tab = &ctx.tables[i]; ostrPrintf(&out, "[%s]\n", tab->key.buf); for (winivalue_t *val = tab->values; val != vecEnd(tab->values); ++val) { ostrPrintf(&out, "%s = %s\n", val->key.buf, val->value.buf); } if ((i + 1) < vecLen(ctx.tables)) ostrPuts(&out, "\n"); } return ostrAsStr(out); } void winiToFile(iniwriter_t ctx, const char *filename, const winiopts_t *options) { if (!options) options = &default_wopts; file_t fp = fileOpen(filename, FILE_WRITE); if (!fileIsValid(fp)) { err("couldn't write ini to file %s", filename); return; } str_t string = winiToString(ctx, options); fileWriteWholeTextFP(fp, strvInitStr(string)); strFree(string); fileClose(fp); } winivalue_t *winiAddValEmpty(winitable_t *table) { if (!table) return NULL; vecAppend(table->values, (winivalue_t){0}); return &vecBack(table->values); } winivalue_t *winiAddVal(winitable_t *table, const char *key, const char *value) { if (!table) return NULL; winivalue_t val = { .key = strFromStr(key), .value = strFromStr(value) }; vecAppend(table->values, val); return &vecBack(table->values); } winivalue_t *winiAddValStr(winitable_t *table, str_t key, str_t value) { if (!table) return NULL; winivalue_t val = { .key = key, .value = value }; vecAppend(table->values, val); return &vecBack(table->values); } winivalue_t *winiAddValView(winitable_t *table, strview_t key, strview_t value) { if (!table) return NULL; winivalue_t val = { .key = strFromView(key), .value = strFromView(value) }; vecAppend(table->values, val); return &vecBack(table->values); } winitable_t *winiAddTablEmpty(iniwriter_t *ctx) { vecAppend(ctx->tables, (winitable_t){0}); return &vecBack(ctx->tables); } winitable_t *winiAddTab(iniwriter_t *ctx, const char *name) { winitable_t tab = { .key = strFromStr(name) }; vecAppend(ctx->tables, tab); return &vecBack(ctx->tables); } winitable_t *winiAddTabStr(iniwriter_t *ctx, str_t name) { winitable_t tab = { .key = name }; vecAppend(ctx->tables, tab); return &vecBack(ctx->tables); } winitable_t *winiAddTabView(iniwriter_t *ctx, strview_t name) { winitable_t tab = { .key = strFromView(name) }; vecAppend(ctx->tables, tab); return &vecBack(ctx->tables); } // == PRIVATE FUNCTIONS ======================================================== static initable_t *findTable(ini_t *ctx, strview_t name) { if (strvIsEmpty(name)) return NULL; for (uint32 i = 1; i < vecLen(ctx->tables); ++i) { if (strvCompare(ctx->tables[i].name, name) == 0) { return &ctx->tables[i]; } } return NULL; } static inivalue_t *findValue(vec(inivalue_t) values, strview_t key) { if (strvIsEmpty(key)) return NULL; for (uint32 i = 0; i < vecLen(values); ++i) { if (strvCompare(values[i].key, key) == 0) { return &values[i]; } } return NULL; } static void addTable(ini_t *ctx, str_istream_t *in, const iniopts_t *options) { istrSkip(in, 1); // skip [ strview_t name = istrGetview(in, ']'); istrSkip(in, 1); // skip ] initable_t *table = options->merge_duplicate_tables ? findTable(ctx, name) : NULL; if (!table) { vecAppend(ctx->tables, (initable_t){ name }); table = &vecBack(ctx->tables); } istrIgnore(in, '\n'); istrSkip(in, 1); while (!istrIsFinished(*in)) { switch (*in->cur) { case '\n': case '\r': return; case '#': case ';': istrIgnore(in, '\n'); break; default: addValue(table, in, options); break; } } } static void addValue(initable_t *table, str_istream_t *in, const iniopts_t *options) { if (!table) fatal("table is null"); strview_t key = strvTrim(istrGetview(in, '=')); istrSkip(in, 1); strview_t value = strvTrim(istrGetview(in, '\n')); // value might be until EOF, in that case no use in skipping if (!istrIsFinished(*in)) istrSkip(in, 1); // skip newline inivalue_t *new_value = options->merge_duplicate_keys ? findValue(table->values, key) : NULL; if (!new_value) { inivalue_t ini_val = (inivalue_t){ key, value }; vecAppend(table->values, ini_val); } else { new_value->value = value; } } /* #include "os.h" */ #include #include #include #ifdef _WIN32 #define _BUFSZ 128 #include // modified from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getdelim.c?only_with_tag=MAIN isize getdelim(char **buf, size_t *bufsz, int delimiter, FILE *fp) { char *ptr, *eptr; if(*buf == NULL || *bufsz == 0) { *bufsz = _BUFSZ; if((*buf = malloc(*bufsz)) == NULL) { return -1; } } isize result = -1; // usually fgetc locks every read, using windows-specific // _lock_file and _unlock_file will be faster _lock_file(fp); for(ptr = *buf, eptr = *buf + *bufsz;;) { int c = _getc_nolock(fp); if(c == -1) { if(feof(fp)) { isize diff = (isize)(ptr - *buf); if(diff != 0) { *ptr = '\0'; result = diff; break; } } break; } *ptr++ = (char)c; if(c == delimiter) { *ptr = '\0'; result = ptr - *buf; break; } if((ptr + 2) >= eptr) { char *nbuf; size_t nbufsz = *bufsz * 2; isize d = ptr - *buf; if((nbuf = realloc(*buf, nbufsz)) == NULL) { break; } *buf = nbuf; *bufsz = nbufsz; eptr = nbuf + nbufsz; ptr = nbuf + d; } } _unlock_file(fp); return result; } // taken from netbsd source http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/pkgtools/libnbcompat/files/getline.c?only_with_tag=MAIN isize getline(char **line_ptr, size_t *n, FILE *stream) { return getdelim(line_ptr, n, '\n', stream); } str_t getUserName() { char buf[UNLEN + 1]; DWORD sz = sizeof(buf); BOOL res = GetUserNameA(buf, &sz); if(!res) { return strInit(); } return strFromBuf(buf, sz); } #else #include #include #include #include int stricmp(const char *a, const char *b) { int result; if (a == b) { return 0; } while ((result = tolower(*a) - tolower(*b++)) == 0) { if (*a++ == '\0') { break; } } return result; } str_t getUserName() { return strFromStr(getlogin()); } #endif /* #include "file.h" */ /* #include "tracelog.h" */ #ifdef _WIN32 /* #include "win32_slim.h" */ #include static DWORD _toWin32Access(int mode) { if(mode & FILE_READ) return GENERIC_READ; if(mode & FILE_WRITE) return GENERIC_WRITE; if(mode & FILE_BOTH) return GENERIC_READ | GENERIC_WRITE; fatal("unrecognized access mode: %d", mode); return 0; } static DWORD _toWin32Creation(filemode_t mode) { if(mode & FILE_READ) return OPEN_EXISTING; if(mode == (FILE_WRITE | FILE_CLEAR)) return CREATE_ALWAYS; if(mode & FILE_WRITE) return OPEN_ALWAYS; if(mode & FILE_BOTH) return OPEN_ALWAYS; fatal("unrecognized creation mode: %d", mode); return 0; } bool fileExists(const char *fname) { DWORD dwAttrib = GetFileAttributesA(fname); return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); } file_t fileOpen(const char *fname, filemode_t mode) { return (file_t)CreateFileA( fname, _toWin32Access(mode), 0, NULL, _toWin32Creation(mode), FILE_ATTRIBUTE_NORMAL, NULL ); } void fileClose(file_t ctx) { if (ctx) { CloseHandle((HANDLE)ctx); } } bool fileIsValid(file_t ctx) { return (HANDLE)ctx != INVALID_HANDLE_VALUE; } bool filePutc(file_t ctx, char c) { return fileWrite(ctx, &c, 1) == 1; } bool filePuts(file_t ctx, const char *str) { usize len = strlen(str); return fileWrite(ctx, str, len) == len; } bool filePutstr(file_t ctx, str_t str) { return fileWrite(ctx, str.buf, str.len) == str.len; } bool filePutview(file_t ctx, strview_t view) { return fileWrite(ctx, view.buf, view.len) == view.len; } usize fileRead(file_t ctx, void *buf, usize len) { DWORD bytes_read = 0; BOOL result = ReadFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL); return result == TRUE ? (usize)bytes_read : 0; } usize fileWrite(file_t ctx, const void *buf, usize len) { DWORD bytes_read = 0; BOOL result = WriteFile((HANDLE)ctx, buf, (DWORD)len, &bytes_read, NULL); return result == TRUE ? (usize)bytes_read : 0; } bool fileSeekEnd(file_t ctx) { return SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_END) == TRUE; } void fileRewind(file_t ctx) { SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, NULL, FILE_BEGIN); } uint64 fileTell(file_t ctx) { LARGE_INTEGER tell; BOOL result = SetFilePointerEx((HANDLE)ctx, (LARGE_INTEGER){0}, &tell, FILE_CURRENT); return result == TRUE ? (uint64)tell.QuadPart : 0; } #else #include #include #include #include #include const char *_toStdioMode(filemode_t mode) { switch(mode) { case FILE_READ: return "rb"; case FILE_BOTH: return "r+b"; case FILE_WRITE: return "wb"; default: fatal("mode not recognized: %d", mode); return ""; } } bool fileExists(const char *fname) { return access(fname, F_OK) == 0; } file_t fileOpen(const char *fname, filemode_t mode) { return (file_t)(void*) fopen(fname, _toStdioMode(mode)); } void fileClose(file_t ctx) { if(ctx) { fclose((FILE*)ctx); } } bool fileIsValid(file_t ctx) { return (FILE *)ctx != NULL; } bool filePutc(file_t ctx, char c) { return fputc(c, (FILE*)ctx) == c; } bool filePuts(file_t ctx, const char *str) { return fputs(str, (FILE*)ctx) != EOF; } bool filePutstr(file_t ctx, str_t str) { return fileWrite(ctx, str.buf, str.len) == str.len; } bool filePutview(file_t ctx, strview_t view) { return fileWrite(ctx, view.buf, view.len) == view.len; } usize fileRead(file_t ctx, void *buf, usize len) { return fread(buf, 1, len, (FILE*)ctx); } usize fileWrite(file_t ctx, const void *buf, usize len) { return fwrite(buf, 1, len, (FILE*)ctx); } bool fileSeekEnd(file_t ctx) { return fseek((FILE*)ctx, 0, SEEK_END) == 0; } void fileRewind(file_t ctx) { rewind((FILE*)ctx); } uint64 fileTell(file_t ctx) { return (uint64)ftell((FILE*)ctx); } #endif static str_t _readWholeInternalStr(file_t ctx) { str_t contents = strInit(); uint64 fsize = 0; usize read = 0; if(!fileSeekEnd(ctx)) { err("file: couldn't read until end"); goto failed; } fsize = fileTell(ctx); fileRewind(ctx); contents.buf = (char *)malloc(fsize + 1); contents.len = fsize; if(!contents.buf) { err("file: couldn't allocate buffer"); goto failed; } read = fileRead(ctx, contents.buf, fsize); if(read != fsize) { err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize); goto failed_free; } contents.buf[contents.len] = '\0'; failed: return contents; failed_free: strFree(contents); return strInit(); } static vec(uint8) _readWholeInternalVec(file_t ctx) { vec(uint8) contents = NULL; uint64 fsize = 0; usize read = 0; if(!fileSeekEnd(ctx)) { err("file: couldn't read until end"); goto failed; } fsize = fileTell(ctx); fileRewind(ctx); vecReserve(contents, fsize); if(!contents) { err("file: couldn't allocate buffer"); goto failed; } read = fileRead(ctx, contents, fsize); if(read != fsize) { err("file: read wrong amount of bytes: %zu instead of %zu", read, fsize); goto failed_free; } failed: return contents; failed_free: vecFree(contents); return contents; } vec(uint8) fileReadWhole(const char *fname) { file_t fp = fileOpen(fname, FILE_READ); if (!fileIsValid(fp)) return NULL; vec(uint8) contents = fileReadWholeFP(fp); fileClose(fp); return contents; } vec(uint8) fileReadWholeFP(file_t ctx) { return _readWholeInternalVec(ctx); } str_t fileReadWholeText(const char *fname) { file_t fp = fileOpen(fname, FILE_READ); if(!fileIsValid(fp)) { err("couldn't open file %s", fname); return strInit(); } str_t contents = fileReadWholeTextFP(fp); fileClose(fp); return contents; } str_t fileReadWholeTextFP(file_t ctx) { return _readWholeInternalStr(ctx); } bool fileWriteWhole(const char *fname, filebuf_t data) { file_t fp = fileOpen(fname, FILE_WRITE); if (!fileIsValid(fp)) { err("couldn't open file %s", fname); return false; } bool res = fileWriteWholeFP(fp, data); fileClose(fp); return res; } bool fileWriteWholeFP(file_t ctx, filebuf_t data) { usize written = fileWrite(ctx, data.buf, data.len); return written == data.len; } bool fileWriteWholeText(const char *fname, strview_t string) { file_t fp = fileOpen(fname, FILE_WRITE); if (!fileIsValid(fp)) { err("couldn't open file %s", fname); return false; } bool res = fileWriteWholeTextFP(fp, string); fileClose(fp); return res; } bool fileWriteWholeTextFP(file_t ctx, strview_t string) { return fileWriteWholeFP(ctx, (filebuf_t){ (uint8 *)string.buf, string.len }); } /* #include "dir.h" */ /* #include "tracelog.h" */ #ifdef _WIN32 /* #include "win32_slim.h" */ #include #include /* #include "strstream.h" */ typedef struct { dir_entry_t cur; dir_entry_t next; HANDLE handle; } _dir_internal_t; static dir_entry_t _fillDirEntry(WIN32_FIND_DATAW *data) { return (dir_entry_t) { .type = data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? FS_TYPE_DIR : FS_TYPE_FILE, .name = strFromWCHAR(data->cFileName, 0) }; } static _dir_internal_t _getFirst(const wchar_t *path) { _dir_internal_t res = {0}; WIN32_FIND_DATAW data = {0}; res.handle = FindFirstFileW(path, &data); if(res.handle != INVALID_HANDLE_VALUE) { res.next = _fillDirEntry(&data); } return res; } static void _getNext(_dir_internal_t *ctx) { WIN32_FIND_DATAW data = {0}; BOOL result = FindNextFileW(ctx->handle, &data); if(!result) { if(GetLastError() == ERROR_NO_MORE_FILES) { FindClose(ctx->handle); ctx->handle = NULL; return; } } ctx->next = _fillDirEntry(&data); } dir_t dirOpen(const char *path) { DWORD n = GetFullPathName(path, 0, NULL, NULL); str_ostream_t out = ostrInitLen(n + 3); n = GetFullPathName(path, n, out.buf, NULL); assert(n > 0); out.len += n; switch(ostrBack(out)) { case '\\': case '/': case ':': // directory ends in path separator // NOP break; default: ostrPutc(&out, '\\'); } ostrPutc(&out, '*'); _dir_internal_t *dir = malloc(sizeof(_dir_internal_t)); if(dir) { wchar_t *wpath = strToWCHAR(ostrAsStr(out)); assert(wpath); *dir = _getFirst(wpath); free(wpath); } ostrFree(out); return dir; } void dirClose(dir_t ctx) { free(ctx); } bool dirValid(dir_t ctx) { _dir_internal_t *dir = (_dir_internal_t*)ctx; return dir->handle != INVALID_HANDLE_VALUE; } dir_entry_t *dirNext(dir_t ctx) { _dir_internal_t *dir = (_dir_internal_t*)ctx; strFree(dir->cur.name); if(!dir->handle) return NULL; dir->cur = dir->next; _getNext(dir); return &dir->cur; } void dirCreate(const char *path) { CreateDirectoryA(path, NULL); } #else #include #include #include #include // taken from https://sites.uclouvain.be/SystInfo/usr/include/dirent.h.html // hopefully shouldn't be needed #ifndef DT_DIR #define DT_DIR 4 #endif #ifndef DT_REG #define DT_REG 8 #endif typedef struct { DIR *dir; dir_entry_t next; } _dir_internal_t; dir_t dirOpen(const char *path) { _dir_internal_t *ctx = (_dir_internal_t *)calloc(1, sizeof(_dir_internal_t)); if(ctx) ctx->dir = opendir(path); return ctx; } void dirClose(dir_t ctx) { if(ctx) { _dir_internal_t *in = (_dir_internal_t *)ctx; closedir(in->dir); free(in); } } bool dirValid(dir_t ctx) { _dir_internal_t *dir = (_dir_internal_t*)ctx; return dir->dir != NULL; } dir_entry_t *dirNext(dir_t ctx) { if(!ctx) return NULL; _dir_internal_t *in = (_dir_internal_t *)ctx; strFree(in->next.name); struct dirent *dp = readdir(in->dir); if(!dp) return NULL; switch(dp->d_type) { case DT_DIR: in->next.type = FS_TYPE_DIR; break; case DT_REG: in->next.type = FS_TYPE_FILE; break; default: in->next.type = FS_TYPE_UNKNOWN; break; } in->next.name = strFromStr(dp->d_name); return &in->next; } void dirCreate(const char *path) { mkdir(path, 0700); } #endif #ifndef COLLA_NO_NET /* #include "socket.h" */ #include /* #include "tracelog.h" */ #ifndef NDEBUG // VERY MUCH NOT THREAD SAFE static int initialize_count = 0; #endif #if SOCK_WINDOWS static bool _win_skInit(); static bool _win_skCleanup(); static int _win_skGetError(); static const char *_win_skGetErrorString(); #define SOCK_CALL(fun) _win_##fun #elif SOCK_POSIX #include #include #include #include // strerror #define INVALID_SOCKET (-1) #define SOCKET_ERROR (-1) static bool _posix_skInit(); static bool _posix_skCleanup(); static int _posix_skGetError(); static const char *_posix_skGetErrorString(); #define SOCK_CALL(fun) _posix_##fun #endif bool skInit() { #ifndef NDEBUG ++initialize_count; #endif return SOCK_CALL(skInit()); } bool skCleanup() { #ifndef NDEBUG --initialize_count; #endif return SOCK_CALL(skCleanup()); } socket_t skOpen(sktype_t type) { int sock_type = 0; switch(type) { case SOCK_TCP: sock_type = SOCK_STREAM; break; case SOCK_UDP: sock_type = SOCK_DGRAM; break; default: fatal("skType not recognized: %d", type); break; } return skOpenPro(AF_INET, sock_type, 0); } socket_t skOpenEx(const char *protocol) { #ifndef NDEBUG if(initialize_count <= 0) { fatal("skInit has not been called"); } #endif struct protoent *proto = getprotobyname(protocol); if(!proto) { return INVALID_SOCKET; } return skOpenPro(AF_INET, SOCK_STREAM, proto->p_proto); } socket_t skOpenPro(int af, int type, int protocol) { #ifndef NDEBUG if(initialize_count <= 0) { fatal("skInit has not been called"); } #endif return socket(af, type, protocol); } sk_addrin_t skAddrinInit(const char *ip, uint16_t port) { sk_addrin_t addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(ip); return addr; } bool skClose(socket_t sock) { #if SOCK_WINDOWS int error = closesocket(sock); #elif SOCK_POSIX int error = close(sock); #endif sock = INVALID_SOCKET; return error != SOCKET_ERROR; } bool skBind(socket_t sock, const char *ip, uint16_t port) { sk_addrin_t addr; addr.sin_family = AF_INET; // TODO use inet_pton instead addr.sin_addr.s_addr = inet_addr(ip); addr.sin_port = htons(port); return skBindPro(sock, (sk_addr_t *) &addr, sizeof(addr)); } bool skBindPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) { return bind(sock, name, namelen) != SOCKET_ERROR; } bool skListen(socket_t sock) { return skListenPro(sock, 1); } bool skListenPro(socket_t sock, int backlog) { return listen(sock, backlog) != SOCKET_ERROR; } socket_t skAccept(socket_t sock) { sk_addrin_t addr; sk_len_t addr_size = (sk_len_t)sizeof(addr); return skAcceptPro(sock, (sk_addr_t *) &addr, &addr_size); } socket_t skAcceptPro(socket_t sock, sk_addr_t *addr, sk_len_t *addrlen) { return accept(sock, addr, addrlen); } bool skConnect(socket_t sock, const char *server, unsigned short server_port) { // TODO use getaddrinfo insetad struct hostent *host = gethostbyname(server); // if gethostbyname fails, inet_addr will also fail and return an easier to debug error const char *address = server; if(host) { address = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); } sk_addrin_t addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(address); addr.sin_port = htons(server_port); return skConnectPro(sock, (sk_addr_t *) &addr, sizeof(addr)); } bool skConnectPro(socket_t sock, const sk_addr_t *name, sk_len_t namelen) { return connect(sock, name, namelen) != SOCKET_ERROR; } int skSend(socket_t sock, const void *buf, int len) { return skSendPro(sock, buf, len, 0); } int skSendPro(socket_t sock, const void *buf, int len, int flags) { return send(sock, buf, len, flags); } int skSendTo(socket_t sock, const void *buf, int len, const sk_addrin_t *to) { return skSendToPro(sock, buf, len, 0, (sk_addr_t*) to, sizeof(sk_addrin_t)); } int skSendToPro(socket_t sock, const void *buf, int len, int flags, const sk_addr_t *to, int tolen) { return sendto(sock, buf, len, flags, to, tolen); } int skReceive(socket_t sock, void *buf, int len) { return skReceivePro(sock, buf, len, 0); } int skReceivePro(socket_t sock, void *buf, int len, int flags) { return recv(sock, buf, len, flags); } int skReceiveFrom(socket_t sock, void *buf, int len, sk_addrin_t *from) { sk_len_t fromlen = sizeof(sk_addr_t); return skReceiveFromPro(sock, buf, len, 0, (sk_addr_t*)from, &fromlen); } int skReceiveFromPro(socket_t sock, void *buf, int len, int flags, sk_addr_t *from, sk_len_t *fromlen) { return recvfrom(sock, buf, len, flags, from, fromlen); } bool skIsValid(socket_t sock) { return sock != INVALID_SOCKET; } int skGetError() { return SOCK_CALL(skGetError()); } const char *skGetErrorString() { return SOCK_CALL(skGetErrorString()); } #ifdef SOCK_WINDOWS static bool _win_skInit() { WSADATA w; int error = WSAStartup(0x0202, &w); return error == 0; } static bool _win_skCleanup() { return WSACleanup() == 0; } static int _win_skGetError() { return WSAGetLastError(); } static const char *_win_skGetErrorString() { switch(_win_skGetError()) { case WSA_INVALID_HANDLE: return "Specified event object handle is invalid."; case WSA_NOT_ENOUGH_MEMORY: return "Insufficient memory available."; case WSA_INVALID_PARAMETER: return "One or more parameters are invalid."; case WSA_OPERATION_ABORTED: return "Overlapped operation aborted."; case WSA_IO_INCOMPLETE: return "Overlapped I/O event object not in signaled state."; case WSA_IO_PENDING: return "Overlapped operations will complete later."; case WSAEINTR: return "Interrupted function call."; case WSAEBADF: return "File handle is not valid."; case WSAEACCES: return "Permission denied."; case WSAEFAULT: return "Bad address."; case WSAEINVAL: return "Invalid argument."; case WSAEMFILE: return "Too many open files."; case WSAEWOULDBLOCK: return "Resource temporarily unavailable."; case WSAEINPROGRESS: return "Operation now in progress."; case WSAEALREADY: return "Operation already in progress."; case WSAENOTSOCK: return "Socket operation on nonsocket."; case WSAEDESTADDRREQ: return "Destination address required."; case WSAEMSGSIZE: return "Message too long."; case WSAEPROTOTYPE: return "Protocol wrong type for socket."; case WSAENOPROTOOPT: return "Bad protocol option."; case WSAEPROTONOSUPPORT: return "Protocol not supported."; case WSAESOCKTNOSUPPORT: return "Socket type not supported."; case WSAEOPNOTSUPP: return "Operation not supported."; case WSAEPFNOSUPPORT: return "Protocol family not supported."; case WSAEAFNOSUPPORT: return "Address family not supported by protocol family."; case WSAEADDRINUSE: return "Address already in use."; case WSAEADDRNOTAVAIL: return "Cannot assign requested address."; case WSAENETDOWN: return "Network is down."; case WSAENETUNREACH: return "Network is unreachable."; case WSAENETRESET: return "Network dropped connection on reset."; case WSAECONNABORTED: return "Software caused connection abort."; case WSAECONNRESET: return "Connection reset by peer."; case WSAENOBUFS: return "No buffer space available."; case WSAEISCONN: return "Socket is already connected."; case WSAENOTCONN: return "Socket is not connected."; case WSAESHUTDOWN: return "Cannot send after socket shutdown."; case WSAETOOMANYREFS: return "Too many references."; case WSAETIMEDOUT: return "Connection timed out."; case WSAECONNREFUSED: return "Connection refused."; case WSAELOOP: return "Cannot translate name."; case WSAENAMETOOLONG: return "Name too long."; case WSAEHOSTDOWN: return "Host is down."; case WSAEHOSTUNREACH: return "No route to host."; case WSAENOTEMPTY: return "Directory not empty."; case WSAEPROCLIM: return "Too many processes."; case WSAEUSERS: return "User quota exceeded."; case WSAEDQUOT: return "Disk quota exceeded."; case WSAESTALE: return "Stale file handle reference."; case WSAEREMOTE: return "Item is remote."; case WSASYSNOTREADY: return "Network subsystem is unavailable."; case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range."; case WSANOTINITIALISED: return "Successful WSAStartup not yet performed."; case WSAEDISCON: return "Graceful shutdown in progress."; case WSAENOMORE: return "No more results."; case WSAECANCELLED: return "Call has been canceled."; case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid."; case WSAEINVALIDPROVIDER: return "Service provider is invalid."; case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize."; case WSASYSCALLFAILURE: return "System call failure."; case WSASERVICE_NOT_FOUND: return "Service not found."; case WSATYPE_NOT_FOUND: return "Class type not found."; case WSA_E_NO_MORE: return "No more results."; case WSA_E_CANCELLED: return "Call was canceled."; case WSAEREFUSED: return "Database query was refused."; case WSAHOST_NOT_FOUND: return "Host not found."; case WSATRY_AGAIN: return "Nonauthoritative host not found."; case WSANO_RECOVERY: return "This is a nonrecoverable error."; case WSANO_DATA: return "Valid name, no data record of requested type."; case WSA_QOS_RECEIVERS: return "QoS receivers."; case WSA_QOS_SENDERS: return "QoS senders."; case WSA_QOS_NO_SENDERS: return "No QoS senders."; case WSA_QOS_NO_RECEIVERS: return "QoS no receivers."; case WSA_QOS_REQUEST_CONFIRMED: return "QoS request confirmed."; case WSA_QOS_ADMISSION_FAILURE: return "QoS admission error."; case WSA_QOS_POLICY_FAILURE: return "QoS policy failure."; case WSA_QOS_BAD_STYLE: return "QoS bad style."; case WSA_QOS_BAD_OBJECT: return "QoS bad object."; case WSA_QOS_TRAFFIC_CTRL_ERROR: return "QoS traffic control error."; case WSA_QOS_GENERIC_ERROR: return "QoS generic error."; case WSA_QOS_ESERVICETYPE: return "QoS service type error."; case WSA_QOS_EFLOWSPEC: return "QoS flowspec error."; case WSA_QOS_EPROVSPECBUF: return "Invalid QoS provider buffer."; case WSA_QOS_EFILTERSTYLE: return "Invalid QoS filter style."; case WSA_QOS_EFILTERTYPE: return "Invalid QoS filter type."; case WSA_QOS_EFILTERCOUNT: return "Incorrect QoS filter count."; case WSA_QOS_EOBJLENGTH: return "Invalid QoS object length."; case WSA_QOS_EFLOWCOUNT: return "Incorrect QoS flow count."; case WSA_QOS_EUNKOWNPSOBJ: return "Unrecognized QoS object."; case WSA_QOS_EPOLICYOBJ: return "Invalid QoS policy object."; case WSA_QOS_EFLOWDESC: return "Invalid QoS flow descriptor."; case WSA_QOS_EPSFLOWSPEC: return "Invalid QoS provider-specific flowspec."; case WSA_QOS_EPSFILTERSPEC: return "Invalid QoS provider-specific filterspec."; case WSA_QOS_ESDMODEOBJ: return "Invalid QoS shape discard mode object."; case WSA_QOS_ESHAPERATEOBJ: return "Invalid QoS shaping rate object."; case WSA_QOS_RESERVED_PETYPE: return "Reserved policy QoS element type."; } return "(nothing)"; } #else static bool _posix_skInit() { return true; } static bool _posix_skCleanup() { return true; } static int _posix_skGetError() { return errno; } static const char *_posix_skGetErrorString() { return strerror(errno); } #endif /* #include "http.h" */ #include #include #include /* #include "os.h" */ /* #include "tracelog.h" */ /* #include "vec.h" */ // == INTERNAL ================================================================ static void _setField(vec(http_field_t) *fields_vec, const char *key, const char *value) { vec(http_field_t) fields = *fields_vec; for (uint32 i = 0; i < vecLen(fields); ++i) { if (stricmp(fields[i].key, key) == 0) { char **curval = &fields[i].value; usize curlen = strlen(*curval); usize newlen = strlen(value); if(newlen > curlen) { *curval = (char *)realloc(*curval, newlen + 1); } memcpy(*curval, value, newlen); (*curval)[newlen] = '\0'; return; } } // otherwise, add it to the list http_field_t field; usize klen = strlen(key); usize vlen = strlen(value); field.key = (char *)malloc(klen + 1); field.value = (char *)malloc(vlen + 1); memcpy(field.key, key, klen); memcpy(field.value, value, vlen); field.key[klen] = field.value[vlen] = '\0'; vecAppend(*fields_vec, field); } // == HTTP VERSION ============================================================ int httpVerNumber(http_version_t ver) { return (ver.major * 10) + ver.minor; } // == HTTP REQUEST ============================================================ http_request_t reqInit() { http_request_t req; memset(&req, 0, sizeof(req)); reqSetUri(&req, strvInit("/")); req.version = (http_version_t){1, 1}; return req; } void reqFree(http_request_t *ctx) { for (uint32 i = 0; i < vecLen(ctx->fields); ++i) { free(ctx->fields[i].key); free(ctx->fields[i].value); } vecFree(ctx->fields); free(ctx->uri); free(ctx->body); memset(ctx, 0, sizeof(http_request_t)); } bool reqHasField(http_request_t *ctx, const char *key) { for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { if(stricmp(ctx->fields[i].key, key) == 0) { return true; } } return false; } void reqSetField(http_request_t *ctx, const char *key, const char *value) { _setField(&ctx->fields, key, value); } void reqSetUri(http_request_t *ctx, strview_t uri) { if (strvIsEmpty(uri)) return; free(ctx->uri); if (uri.buf[0] != '/') { ctx->uri = (char *)realloc(ctx->uri, uri.len + 1); ctx->uri[0] = '/'; memcpy(ctx->uri + 1, uri.buf, uri.len); ctx->uri[uri.len] = '\0'; } else { ctx->uri = strvCopy(uri).buf; } } str_ostream_t reqPrepare(http_request_t *ctx) { str_ostream_t out = ostrInitLen(1024); const char *method = NULL; switch(ctx->method) { case REQ_GET: method = "GET"; break; case REQ_POST: method = "POST"; break; case REQ_HEAD: method = "HEAD"; break; case REQ_PUT: method = "PUT"; break; case REQ_DELETE: method = "DELETE"; break; default: err("unrecognized method: %d", method); goto error; } ostrPrintf(&out, "%s %s HTTP/%hhu.%hhu\r\n", method, ctx->uri, ctx->version.major, ctx->version.minor ); for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { ostrPrintf(&out, "%s: %s\r\n", ctx->fields[i].key, ctx->fields[i].value); } ostrAppendview(&out, strvInit("\r\n")); if(ctx->body) { ostrAppendview(&out, strvInit(ctx->body)); } error: return out; } str_t reqString(http_request_t *ctx) { str_ostream_t out = reqPrepare(ctx); return ostrAsStr(out); } // == HTTP RESPONSE =========================================================== http_response_t resInit() { return (http_response_t) {0}; } void resFree(http_response_t *ctx) { for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { free(ctx->fields[i].key); free(ctx->fields[i].value); } vecFree(ctx->fields); vecFree(ctx->body); memset(ctx, 0, sizeof(http_response_t)); } bool resHasField(http_response_t *ctx, const char *key) { for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { if(stricmp(ctx->fields[i].key, key) == 0) { return true; } } return false; } const char *resGetField(http_response_t *ctx, const char *field) { for(uint32 i = 0; i < vecLen(ctx->fields); ++i) { if(stricmp(ctx->fields[i].key, field) == 0) { return ctx->fields[i].value; } } return NULL; } void resParse(http_response_t *ctx, const char *data) { str_istream_t in = istrInit(data); char hp[5]; istrGetstringBuf(&in, hp, 5); if(stricmp(hp, "http") != 0) { err("response doesn't start with 'HTTP', instead with %c%c%c%c", hp[0], hp[1], hp[2], hp[3]); return; } istrSkip(&in, 1); // skip / istrGetu8(&in, &ctx->version.major); istrSkip(&in, 1); // skip . istrGetu8(&in, &ctx->version.minor); istrGeti32(&in, (int32*)&ctx->status_code); istrIgnore(&in, '\n'); istrSkip(&in, 1); // skip \n resParseFields(ctx, &in); const char *tran_encoding = resGetField(ctx, "transfer-encoding"); if(tran_encoding == NULL || stricmp(tran_encoding, "chunked") != 0) { strview_t body = istrGetviewLen(&in, 0, SIZE_MAX); vecClear(ctx->body); vecReserve(ctx->body, body.len); memcpy(ctx->body, body.buf, body.len); } else { fatal("chunked encoding not implemented yet"); } } void resParseFields(http_response_t *ctx, str_istream_t *in) { strview_t line; do { line = istrGetview(in, '\r'); usize pos = strvFind(line, ':', 0); if(pos != STRV_NOT_FOUND) { strview_t key = strvSub(line, 0, pos); strview_t value = strvSub(line, pos + 2, SIZE_MAX); char *key_str = NULL; char *value_str = NULL; key_str = strvCopy(key).buf; value_str = strvCopy(value).buf; _setField(&ctx->fields, key_str, value_str); free(key_str); free(value_str); } istrSkip(in, 2); // skip \r\n } while(line.len > 2); } // == HTTP CLIENT ============================================================= http_client_t hcliInit() { return (http_client_t) { .port = 80, }; } void hcliFree(http_client_t *ctx) { strFree(ctx->host_name); memset(ctx, 0, sizeof(http_client_t)); } void hcliSetHost(http_client_t *ctx, strview_t hostname) { // if the hostname starts with http:// (case insensitive) if(strvICompare(strvSub(hostname, 0, 7), strvInit("http://")) == 0) { ctx->host_name = strvCopy(strvSub(hostname, 7, SIZE_MAX)); } else if(strvICompare(strvSub(hostname, 0, 8), strvInit("https://")) == 0) { err("HTTPS protocol not yet supported"); return; } else { // undefined protocol, use HTTP ctx->host_name = strvCopy(hostname); } } http_response_t hcliSendRequest(http_client_t *ctx, http_request_t *req) { if (strBack(ctx->host_name) == '/') { ctx->host_name.buf[--ctx->host_name.len] = '\0'; } if(!reqHasField(req, "Host")) { reqSetField(req, "Host", ctx->host_name.buf); } if(!reqHasField(req, "Content-Length")) { if(req->body) { str_ostream_t out = ostrInitLen(20); ostrAppendu64(&out, strlen(req->body)); reqSetField(req, "Content-Length", out.buf); ostrFree(out); } else { reqSetField(req, "Content-Length", "0"); } } if(req->method == REQ_POST && !reqHasField(req, "Content-Type")) { reqSetField(req, "Content-Type", "application/x-www-form-urlencoded"); } if(httpVerNumber(req->version) >= 11 && !reqHasField(req, "Connection")) { reqSetField(req, "Connection", "close"); } http_response_t res = resInit(); str_t req_str = strInit(); str_ostream_t received = ostrInitLen(1024); if(!skInit()) { err("couldn't initialize sockets %s", skGetErrorString()); goto skopen_error; } ctx->socket = skOpen(SOCK_TCP); if(ctx->socket == INVALID_SOCKET) { err("couldn't open socket %s", skGetErrorString()); goto error; } if(skConnect(ctx->socket, ctx->host_name.buf, ctx->port)) { req_str = reqString(req); if(req_str.len == 0) { err("couldn't get string from request"); goto error; } if(skSend(ctx->socket, req_str.buf, (int)req_str.len) == SOCKET_ERROR) { err("couldn't send request to socket: %s", skGetErrorString()); goto error; } char buffer[1024]; int read = 0; do { read = skReceive(ctx->socket, buffer, sizeof(buffer)); if(read == -1) { err("couldn't get the data from the server: %s", skGetErrorString()); goto error; } ostrAppendview(&received, strvInitLen(buffer, read)); } while(read != 0); // if the data received is not null terminated if(*(received.buf + received.len) != '\0') { ostrPutc(&received, '\0'); received.len--; } resParse(&res, received.buf); } else { err("Couldn't connect to host %s -> %s", ctx->host_name, skGetErrorString()); } if(!skClose(ctx->socket)) { err("Couldn't close socket"); } error: if(!skCleanup()) { err("couldn't clean up sockets %s", skGetErrorString()); } skopen_error: strFree(req_str); ostrFree(received); return res; } http_response_t httpGet(strview_t hostname, strview_t uri) { http_request_t request = reqInit(); request.method = REQ_GET; reqSetUri(&request, uri); http_client_t client = hcliInit(); hcliSetHost(&client, hostname); http_response_t res = hcliSendRequest(&client, &request); reqFree(&request); hcliFree(&client); return res; } url_split_t urlSplit(strview_t uri) { url_split_t out = {0}; if (strvStartsWithView(uri, strvInit("https://"))) { uri = strvRemovePrefix(uri, 8); } else if (strvStartsWithView(uri, strvInit("http://"))) { uri = strvRemovePrefix(uri, 7); } out.host = strvSub(uri, 0, strvFind(uri, '/', 0)); out.uri = strvSub(uri, out.host.len, -1); return out; } #endif // COLLA_NO_NET #if !defined(__TINYC__) && !defined(COLLA_NO_THREADS) /* #include "cthreads.h" */ typedef struct { cthread_func_t func; void *arg; } _thr_internal_t; #ifdef _WIN32 /* #include "win32_slim.h" */ #include // == THREAD =========================================== static DWORD _thrFuncInternal(void *arg) { _thr_internal_t *params = (_thr_internal_t *)arg; cthread_func_t func = params->func; void *argument = params->arg; free(params); return (DWORD)func(argument); } cthread_t thrCreate(cthread_func_t func, void *arg) { HANDLE thread = INVALID_HANDLE_VALUE; _thr_internal_t *params = malloc(sizeof(_thr_internal_t)); if(params) { params->func = func; params->arg = arg; thread = CreateThread(NULL, 0, _thrFuncInternal, params, 0, NULL); } return (cthread_t)thread; } bool thrValid(cthread_t ctx) { return (HANDLE)ctx != INVALID_HANDLE_VALUE; } bool thrDetach(cthread_t ctx) { return CloseHandle((HANDLE)ctx); } cthread_t thrCurrent(void) { return (cthread_t)GetCurrentThread(); } int thrCurrentId(void) { return GetCurrentThreadId(); } int thrGetId(cthread_t ctx) { return GetThreadId((HANDLE)ctx); } void thrExit(int code) { ExitThread(code); } bool thrJoin(cthread_t ctx, int *code) { if(!ctx) return false; int return_code = WaitForSingleObject((HANDLE)ctx, INFINITE); if(code) *code = return_code; BOOL success = CloseHandle((HANDLE)ctx); return return_code != WAIT_FAILED && success; } // == MUTEX ============================================ cmutex_t mtxInit(void) { CRITICAL_SECTION *crit_sec = malloc(sizeof(CRITICAL_SECTION)); if(crit_sec) { InitializeCriticalSection(crit_sec); } return (cmutex_t)crit_sec; } void mtxDestroy(cmutex_t ctx) { DeleteCriticalSection((CRITICAL_SECTION *)ctx); } bool mtxValid(cmutex_t ctx) { return (void *)ctx != NULL; } bool mtxLock(cmutex_t ctx) { EnterCriticalSection((CRITICAL_SECTION *)ctx); return true; } bool mtxTryLock(cmutex_t ctx) { return TryEnterCriticalSection((CRITICAL_SECTION *)ctx); } bool mtxUnlock(cmutex_t ctx) { LeaveCriticalSection((CRITICAL_SECTION *)ctx); return true; } #else #include #include #include #include #include // == THREAD =========================================== #define INT_TO_VOIDP(a) ((void *)((uintptr_t)(a))) static void *_thrFuncInternal(void *arg) { _thr_internal_t *params = (_thr_internal_t *)arg; cthread_func_t func = params->func; void *argument = params->arg; free(params); return INT_TO_VOIDP(func(argument)); } cthread_t thrCreate(cthread_func_t func, void *arg) { pthread_t handle = (pthread_t)NULL; _thr_internal_t *params = malloc(sizeof(_thr_internal_t)); if(params) { params->func = func; params->arg = arg; int result = pthread_create(&handle, NULL, _thrFuncInternal, params); if(result) handle = (pthread_t)NULL; } return (cthread_t)handle; } bool thrValid(cthread_t ctx) { return (void *)ctx != NULL; } bool thrDetach(cthread_t ctx) { return pthread_detach((pthread_t)ctx) == 0; } cthread_t thrCurrent(void) { return (cthread_t)pthread_self(); } int thrCurrentId(void) { return (int)pthread_self(); } int thrGetId(cthread_t ctx) { return (int)ctx; } void thrExit(int code) { pthread_exit(INT_TO_VOIDP(code)); } bool thrJoin(cthread_t ctx, int *code) { void *result = code; return pthread_join((pthread_t)ctx, &result) != 0; } // == MUTEX ============================================ cmutex_t mtxInit(void) { pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t)); if(mutex) { int res = pthread_mutex_init(mutex, NULL); if(res != 0) mutex = NULL; } return (cmutex_t)mutex; } void mtxDestroy(cmutex_t ctx) { pthread_mutex_destroy((pthread_mutex_t *)ctx); } bool mtxValid(cmutex_t ctx) { return (void *)ctx != NULL; } bool mtxLock(cmutex_t ctx) { return pthread_mutex_lock((pthread_mutex_t *)ctx) == 0; } bool mtxTryLock(cmutex_t ctx) { return pthread_mutex_trylock((pthread_mutex_t *)ctx) == 0; } bool mtxUnlock(cmutex_t ctx) { return pthread_mutex_unlock((pthread_mutex_t *)ctx) == 0; } #endif #endif // !defined(__TINYC__) && !defined(COLLA_NO_THREADS) #endif /* COLLA_IMPL */ /* MIT License Copyright (c) 1994-2019 Lua.org, PUC-Rio. Copyright (c) 2020-2022 snarmph. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */