diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..95d63a5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,137 @@ +--- +Language: Cpp +# BasedOnStyle: Mozilla +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: TopLevel +AlwaysBreakAfterReturnType: TopLevel +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: false + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Mozilla +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeComma +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: false +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +... + diff --git a/.drone.yml b/.drone.yml index ee24c4a..174b1f6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,3 +7,16 @@ steps: image: gcc commands: - make + - mv quark quark-dirl + +- name: publish + image: appleboy/drone-scp + settings: + host: friedl.net + username: + from_secret: deploy_user + password: + from_secret: deploy_password + port: 22 + target: /var/services/dirlist/repo/bin/suckless/quark + source: quark-dirl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78eb9d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +.gdb_history +config.h +quark \ No newline at end of file diff --git a/Makefile b/Makefile index 0c0ebed..7573552 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,12 @@ include config.mk -COMPONENTS = data http sock util +COMPONENTS = data http sock util dirl all: quark -data.o: data.c data.h util.h http.h config.mk +data.o: data.c data.h util.h http.h dirl.h config.mk +dirl.o: dirl.c dirl.h util.h http.h config.mk http.o: http.c http.h util.h http.h data.h config.h config.mk main.o: main.c util.h sock.h http.h arg.h config.h config.mk sock.o: sock.c sock.h util.h config.mk diff --git a/README.md b/README.md index ebb6cfe..c2d1193 100644 --- a/README.md +++ b/README.md @@ -5,36 +5,94 @@ found at https://git.suckless.org/quark. Quark is a small http server. -# Feature Patches +# DIRL -## Dirl: Customizable directory listing -[dirl](https://git.friedl.net/playground/suckless-quark/src/branch/dirlist) lets -you serve a fully customizable directory listing. +dirl is a quark extension for customized directory listings. -You can compile `dirl` from the `dirlist` branch, download a pre-compiled [musl -binary](https://dirlist.friedl.net/bin/suckless/quark/quark-dirl) or even pull a -pre-made [docker -image](https://hub.docker.com/repository/docker/arminfriedl/quark). +Per default dirl generates html for a directory listing like this: -You can find an example deployment of [here](https://dirlist.friedl.net/). It -uses the default template just with a custom css. You can define your own -templates too for full customization. For details see the dirl -[README.md](https://git.friedl.net/playground/suckless-quark/src/branch/dirlist/README.md). +```html + + + + + + Index of {uri} + + +

Index of {uri}

+

↵ Parent Directory

+
+ + + -# Issues + + + + + + + -## fork: Resource temporarily unavailable -When running [quark](http://tools.suckless.org/quark/) (#6606994) on my system -with `sudo ./quark -p 9763 -u -g ` it dies with `./quark: fork: -Resource temporarily unavailable` at `fork()`. + +
NameModifiedSize
{entry}{suffix} + {modified}{size}
+
+

+ Served by quark and dirl +

+ + + +``` -Reason being that by default quark sets the RLIMIT_NPROC to 512 processes. When running as a non-exclusive user this limit is easily reached before even starting quark. +## Customize -`resource-depletion-fix` contains a small forkbomb (`minibomb.c`) to simulate a user with > 512 processes. Compile it with `make minibomb`. When running the minibomb and quark with the same user quark fails. +The default listing can be styled by a `style.css` in the root directory. -The `resource-depletion-fix` branch contains a fix by setting the RLIMIT_NPROC only if the current system limit is lower than what would be set by quark. You can [download the patch](https://dirlist.friedl.net/suckless/quark/), or compile from the `resource-depletion-fix` branch. +You can also use your fully customized template by creating one or all the +template files for each section. Per default the section templates are named: +- .header.tpl +- .entry.tpl (repeated for each directory entry) +- .footer.tpl -Note that quark also has a `-n` parameter with which the max number of processes can be set as an alternative to this patch. +Note that if you only provide some of the template files, they have to be +compatible with the generated default for the other sections. + +For each of these templates you can use placeholders that are replaced by their respective values: +- header + * `{uri}`: Replaced by the current path +- entry + * `{entry}`: Name of the entry + * `{suffix}`: A suffix for the entry, mostly useful to distinguish directories (suffix '/') from files + * `{modified}`: Date the entry was last modified + * `{size}`: Size of the entry (if available) + +### Subdirectory styling + +dirl tries to the closest template for the currently visited path. This gives +you the opportunity to override templates in subdirectories. dirl walks the +directory hierarchy upwards from the currently visited path. As soon as it finds +one of the template files in a directory, it stops searching and uses the +templates in that directory. + +In case no templates are found up until and including root, the default +templates are used. + +### Customize names + +The files defined as templates and style are ignored in the directory listing +itself. In case you need to list one of these directories, or have any other +reason to choose different names, the filenames can be configured in `dirl.h`. +Note that you need to compile your own quark version then. + +# Download +You can also download CI builds for [quark-dirl](https://dirlist.friedl.net/bin/suckless/quark/). + +There are no official releases. Quark has no dependencies and you can easily +build it from source. Don't forget to read up on the [suckless +philosophy](http://suckless.org/philosophy/). # Github Users If you are visiting this repository on GitHub, you are on a mirror of diff --git a/data.c b/data.c index 3b6b2e5..ae06a0c 100644 --- a/data.c +++ b/data.c @@ -10,6 +10,7 @@ #include "http.h" #include "data.h" #include "util.h" +#include "dirl.h" static int compareent(const struct dirent **d1, const struct dirent **d2) @@ -25,64 +26,6 @@ compareent(const struct dirent **d1, const struct dirent **d2) return strcmp((*d1)->d_name, (*d2)->d_name); } -static char * -suffix(int t) -{ - switch (t) { - case DT_FIFO: return "|"; - case DT_DIR: return "/"; - case DT_LNK: return "@"; - case DT_SOCK: return "="; - } - - return ""; -} - -static void -html_escape(const char *src, char *dst, size_t dst_siz) -{ - const struct { - char c; - char *s; - } escape[] = { - { '&', "&" }, - { '<', "<" }, - { '>', ">" }, - { '"', """ }, - { '\'', "'" }, - }; - size_t i, j, k, esclen; - - for (i = 0, j = 0; src[i] != '\0'; i++) { - for (k = 0; k < LEN(escape); k++) { - if (src[i] == escape[k].c) { - break; - } - } - if (k == LEN(escape)) { - /* no escape char at src[i] */ - if (j == dst_siz - 1) { - /* silent truncation */ - break; - } else { - dst[j++] = src[i]; - } - } else { - /* escape char at src[i] */ - esclen = strlen(escape[k].s); - - if (j >= dst_siz - esclen) { - /* silent truncation */ - break; - } else { - memcpy(&dst[j], escape[k].s, esclen); - j += esclen; - } - } - } - dst[j] = '\0'; -} - enum status data_send_dirlisting(int fd, const struct response *res) { @@ -90,48 +33,38 @@ data_send_dirlisting(int fd, const struct response *res) struct dirent **e; size_t i; int dirlen; - char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */ /* read directory */ if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) { return S_FORBIDDEN; } - /* listing header (we use esc because sizeof(esc) >= PATH_MAX) */ - html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc))); - if (dprintf(fd, - "\n\n\t" - "Index of %s\n" - "\t\n\t\t..", - esc) < 0) { - ret = S_REQUEST_TIMEOUT; - goto cleanup; - } + /* read templates */ + struct dirl_templ templates = dirl_read_templ(res->uri); - /* listing */ + /* listing header */ + if ((ret = dirl_header(fd, res, &templates))) { + return ret; + } + + /* entries */ for (i = 0; i < (size_t)dirlen; i++) { - /* skip hidden files, "." and ".." */ - if (e[i]->d_name[0] == '.') { - continue; - } + /* skip dirl special files */ + if(dirl_skip(e[i]->d_name)) { + continue; + } + + /* entry line */ + if ((ret = dirl_entry(fd, e[i], res, &templates))) { + goto cleanup; + } - /* entry line */ - html_escape(e[i]->d_name, esc, sizeof(esc)); - if (dprintf(fd, "
\n\t\t%s%s", - esc, - (e[i]->d_type == DT_DIR) ? "/" : "", - esc, - suffix(e[i]->d_type)) < 0) { - ret = S_REQUEST_TIMEOUT; - goto cleanup; - } } - /* listing footer */ - if (dprintf(fd, "\n\t\n\n") < 0) { - ret = S_REQUEST_TIMEOUT; - goto cleanup; - } + /* listing footer */ + if ((ret = dirl_footer(fd, &templates))) { + goto cleanup; + } cleanup: while (dirlen--) { diff --git a/dirl.c b/dirl.c new file mode 100644 index 0000000..67ec21f --- /dev/null +++ b/dirl.c @@ -0,0 +1,250 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "dirl.h" +#include "http.h" +#include "util.h" + +static char* +suffix(int t) +{ + switch (t) { + case DT_FIFO: + return "|"; + case DT_DIR: + return "/"; + case DT_LNK: + return "@"; + case DT_SOCK: + return "="; + } + + return ""; +} + +static void +html_escape(const char* src, char* dst, size_t dst_siz) +{ + const struct + { + char c; + char* s; + } escape[] = { + { '&', "&" }, { '<', "<" }, { '>', ">" }, + { '"', """ }, { '\'', "'" }, + }; + size_t i, j, k, esclen; + + for (i = 0, j = 0; src[i] != '\0'; i++) { + for (k = 0; k < LEN(escape); k++) { + if (src[i] == escape[k].c) { + break; + } + } + if (k == LEN(escape)) { + /* no escape char at src[i] */ + if (j == dst_siz - 1) { + /* silent truncation */ + break; + } else { + dst[j++] = src[i]; + } + } else { + /* escape char at src[i] */ + esclen = strlen(escape[k].s); + + if (j >= dst_siz - esclen) { + /* silent truncation */ + break; + } else { + memcpy(&dst[j], escape[k].s, esclen); + j += esclen; + } + } + } + dst[j] = '\0'; +} + +/* Try to find templates up until root + * + * Iterates the directory hierarchy upwards. Returns the closest path containing + * one of the template files or NULL if none could be found. + * + * Note that we are chrooted. + */ +static char* +dirl_find_templ_dir(const char* start_path) +{ + char* path_buf = calloc(sizeof(char), strlen(start_path)+1); + strcat(path_buf, start_path); + + if (strlen(path_buf) > 1 && path_buf[strlen(path_buf) - 1] == '/') { + // don't read last dir twice, except at root + path_buf[strlen(path_buf) - 1] = '\0'; + } + + while (strlen(path_buf) != 0) { + DIR* cur = opendir(path_buf); + struct dirent* de; + errno = 0; + while ((de = readdir(cur))) { + if (de->d_type == DT_REG) { + if (!strcmp(DIRL_HEADER, de->d_name) || !strcmp(DIRL_ENTRY, de->d_name) || + !strcmp(DIRL_FOOTER, de->d_name)) { + closedir(cur); + return path_buf; + } + } + } + + if (strlen(path_buf) > 1) { + char* parent = strrchr(path_buf, '/'); + (*parent) = '\0'; // strip tail from path_buf + + if (strlen(path_buf) == 0) { // we stripped root, let it loop once more + path_buf[0] = '/'; + path_buf[1] = '\0'; + } + } else { + path_buf[0] = '\0'; // we checked root, now terminate loop + } + } + + free(path_buf); + return NULL; +} + +/* Helper function to fill template from base+name if file exists */ +static void +dirl_fill_templ(char** templ, char* base, char* name, char* def) +{ + if (!base || !name) { + *templ = def; + return; + } + + char* path = calloc(sizeof(char), strlen(base) + strlen(name)); + strcpy(path, base); + strcat(path, name); + + char* file_buf = read_file(path); + free(path); + + if (file_buf) { + *templ = file_buf; + } else { + *templ = def; + } +} + +struct dirl_templ +dirl_read_templ(const char* path) +{ + struct dirl_templ templ; + + char* templ_dir = dirl_find_templ_dir(path); + + dirl_fill_templ(&templ.header, templ_dir, DIRL_HEADER, DIRL_HEADER_DEFAULT); + dirl_fill_templ(&templ.entry, templ_dir, DIRL_ENTRY, DIRL_ENTRY_DEFAULT); + dirl_fill_templ(&templ.footer, templ_dir, DIRL_FOOTER, DIRL_FOOTER_DEFAULT); + + free(templ_dir); + + return templ; +} + +enum status +dirl_header(int fd, const struct response* res, const struct dirl_templ* templ) +{ + /* Replace placeholder */ + char* nhead = calloc(sizeof(char), strlen(templ->header) + 1); + memcpy(nhead, templ->header, strlen(templ->header) + 1); + replace(&nhead, "{uri}", res->uri); + + /* Write header */ + write(fd, nhead, strlen(nhead)); + + free(nhead); + + /* listing header */ + return 0; +} + +enum status +dirl_entry(int fd, + const struct dirent* entry, + const struct response* res, + const struct dirl_templ* templ) +{ + struct stat stat_buf; + char* path_buf = calloc(sizeof(char), strlen(res->path)+strlen(entry->d_name)); + strcat(path_buf, res->uri); + strcat(path_buf, entry->d_name); + lstat(path_buf, &stat_buf); + + char* nentry = calloc(sizeof(char), strlen(templ->entry) + 1); + memcpy(nentry, templ->entry, strlen(templ->entry) + 1); + + /* Replace placeholder */ + char esc[PATH_MAX * 6]; + html_escape(entry->d_name, esc, PATH_MAX * 6); + replace(&nentry, "{entry}", entry->d_name); + + replace(&nentry, "{suffix}", suffix(entry->d_type)); + + char size_buf[1024]; + if (entry->d_type == DT_REG) { + snprintf(size_buf, 1024, "%ld", stat_buf.st_size); + } else { + sprintf(size_buf, "-"); + } + replace(&nentry, "{size}", size_buf); + + char time_buf[1024]; + struct tm tm; + gmtime_r(&stat_buf.st_mtim.tv_sec, &tm); + strftime(time_buf, 1024, "%F %H:%m", &tm); + replace(&nentry, "{modified}", time_buf); + + /* Write entry */ + write(fd, nentry, strlen(nentry)); + + free(nentry); + + return 0; +} + +enum status +dirl_footer(int fd, const struct dirl_templ* templ) +{ + /* Replace placeholder */ + char* nfoot = calloc(sizeof(char), strlen(templ->footer) + 1); + memcpy(nfoot, templ->footer, strlen(templ->footer) + 1); + + /* Write footer */ + write(fd, nfoot, strlen(nfoot)); + + free(nfoot); + return 0; +} + +int +dirl_skip(const char* name) +{ + return name[0] == '.' // + || !strcmp(name, DIRL_HEADER) // + || !strcmp(name, DIRL_ENTRY) // + || !strcmp(name, DIRL_FOOTER) // + || !strcmp(name, DIRL_STYLE); // +} diff --git a/dirl.h b/dirl.h new file mode 100644 index 0000000..b6505c7 --- /dev/null +++ b/dirl.h @@ -0,0 +1,91 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef DIRL_H +#define DIRL_H + +#include +#include +#include + +#include "http.h" + +#define DIRL_HEADER ".header.tpl" +#define DIRL_ENTRY ".entry.tpl" +#define DIRL_FOOTER ".footer.tpl" +#define DIRL_STYLE "style.css" + +/* Default template definitions + * + * Used if no template files can be found + */ +#define DIRL_HEADER_DEFAULT \ + "\n" \ + "\n" \ + " \n" \ + " \n" \ + " Index of {uri}\n" \ + " \n" \ + " \n" \ + "

Index of {uri}

\n" \ + "

↵ Parent Directory

\n" \ + "
\n" \ + " \n" \ + " " + +#define DIRL_ENTRY_DEFAULT \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define DIRL_FOOTER_DEFAULT \ + "
NameModifiedSize
{entry}{suffix}\n" \ + " {modified}{size}
\n" \ + "
\n" \ + "

" \ + "Served by" \ + " quark" \ + " and" \ + " " \ + "dirl" \ + "" \ + "

\n" \ + "\n" \ + "" + +struct dirl_templ +{ + char* header; + char* entry; + char* footer; +}; + +struct dirl_templ +dirl_read_templ(const char* path); + +/* Determine if an dirlist entry should be skipped + * + * Skips: + * - hidden files and directories + * - special directory entries (., ..) + * - header template: DIRL_HEADER + * - entry template: DIRL_ENTRY + * - footer template: DIRL_FOOTER + * - dirlist style: DRIL_STYLE + */ +int +dirl_skip(const char*); + +/* Print header into the response */ +enum status +dirl_header(int, const struct response*, const struct dirl_templ*); + +/* Print entry into the response */ +enum status +dirl_entry(int, const struct dirent*, const struct response*, const struct dirl_templ*); + +/* Print footer into the response */ +enum status +dirl_footer(int, const struct dirl_templ*); + +#endif /* DIRL_H */ diff --git a/util.c b/util.c index b281613..9127ae7 100644 --- a/util.c +++ b/util.c @@ -20,107 +20,194 @@ char *argv0; static void verr(const char *fmt, va_list ap) { - if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) { - fprintf(stderr, "%s: ", argv0); - } + if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) { + fprintf(stderr, "%s: ", argv0); + } - vfprintf(stderr, fmt, ap); + vfprintf(stderr, fmt, ap); - if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { - fputc(' ', stderr); - perror(NULL); - } else { - fputc('\n', stderr); - } + if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } } void warn(const char *fmt, ...) { - va_list ap; + va_list ap; - va_start(ap, fmt); - verr(fmt, ap); - va_end(ap); + va_start(ap, fmt); + verr(fmt, ap); + va_end(ap); } void die(const char *fmt, ...) { - va_list ap; + va_list ap; - va_start(ap, fmt); - verr(fmt, ap); - va_end(ap); + va_start(ap, fmt); + verr(fmt, ap); + va_end(ap); - exit(1); + exit(1); } void epledge(const char *promises, const char *execpromises) { - (void)promises; - (void)execpromises; + (void)promises; + (void)execpromises; #ifdef __OpenBSD__ - if (pledge(promises, execpromises) == -1) { - die("pledge:"); - } + if (pledge(promises, execpromises) == -1) { + die("pledge:"); + } #endif /* __OpenBSD__ */ } void eunveil(const char *path, const char *permissions) { - (void)path; - (void)permissions; + (void)path; + (void)permissions; #ifdef __OpenBSD__ - if (unveil(path, permissions) == -1) { - die("unveil:"); - } + if (unveil(path, permissions) == -1) { + die("unveil:"); + } #endif /* __OpenBSD__ */ } int timestamp(char *buf, size_t len, time_t t) { - struct tm tm; + struct tm tm; - if (gmtime_r(&t, &tm) == NULL || - strftime(buf, len, "%a, %d %b %Y %T GMT", &tm) == 0) { - return 1; - } + if (gmtime_r(&t, &tm) == NULL || + strftime(buf, len, "%a, %d %b %Y %T GMT", &tm) == 0) { + return 1; + } - return 0; + return 0; } int esnprintf(char *str, size_t size, const char *fmt, ...) { - va_list ap; - int ret; + va_list ap; + int ret; - va_start(ap, fmt); - ret = vsnprintf(str, size, fmt, ap); - va_end(ap); + va_start(ap, fmt); + ret = vsnprintf(str, size, fmt, ap); + va_end(ap); - return (ret < 0 || (size_t)ret >= size); + return (ret < 0 || (size_t)ret >= size); } int prepend(char *str, size_t size, const char *prefix) { - size_t len = strlen(str), prefixlen = strlen(prefix); + size_t len = strlen(str), prefixlen = strlen(prefix); - if (len + prefixlen + 1 > size) { - return 1; - } + if (len + prefixlen + 1 > size) { + return 1; + } - memmove(str + prefixlen, str, len + 1); - memcpy(str, prefix, prefixlen); + memmove(str + prefixlen, str, len + 1); + memcpy(str, prefix, prefixlen); - return 0; + return 0; +} + +void +replace(char **src, const char *old, const char* new) { + int old_len = strlen(old); + int new_len = strlen(new); + int src_len = strlen(*src); + + /* Count needed replacements */ + const char* tmp = *src; + int replc=0; + while ((tmp=strstr(tmp, old))) { + replc++; + tmp += old_len; + } + + /* Allocate enough space for the new string */ + size_t buf_size = src_len + replc * (new_len - old_len) + 1; + char* buf = calloc(sizeof(char), buf_size); + + /* Now start replacing */ + const char *srcidx = *src; + const char *srcidx_old = *src; + char *bufidx = buf; + + while (replc--) { + srcidx_old = strstr(srcidx, old); + + long repl_len = labs(srcidx_old - srcidx); + bufidx = strncpy(bufidx, srcidx, repl_len) + repl_len; + bufidx = strcpy(bufidx, new) + new_len; + + srcidx = srcidx_old+old_len; + } + + strncpy(bufidx, srcidx, strlen(srcidx)); // copy tail + + free(*src); + *src = buf; +} + +char* +read_file(const char* path){ + FILE* tpl_fp; + + if (!(tpl_fp = fopen(path, "r"))) { + return NULL; + } + + /* Get size of template */ + if (fseek(tpl_fp, 0L, SEEK_END) < 0) { + fclose(tpl_fp); + return NULL; + } + + long tpl_size; + if ((tpl_size = ftell(tpl_fp)) < 0) { + fclose(tpl_fp); + return NULL; + } + + rewind(tpl_fp); + + /* Read template into tpl_buf */ + char* tpl_buf = (char*)calloc(sizeof(char), tpl_size); + + if (tpl_buf == NULL) { + fclose(tpl_fp); + free(tpl_buf); + return NULL; + } + + if (fread(tpl_buf, 1, tpl_size, tpl_fp) < (size_t) tpl_size) { + if (feof(tpl_fp)) { + warn("Reached end of file %s prematurely", path); + } else if (ferror(tpl_fp)) { + warn("Error while reading file %s", path); + } + + fclose(tpl_fp); + free(tpl_buf); + clearerr(tpl_fp); + + return NULL; + } + + return tpl_buf; } #define INVALID 1 @@ -131,39 +218,39 @@ long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) { - long long ll = 0; - int error = 0; - char *ep; - struct errval { - const char *errstr; - int err; - } ev[4] = { - { NULL, 0 }, - { "invalid", EINVAL }, - { "too small", ERANGE }, - { "too large", ERANGE }, - }; + long long ll = 0; + int error = 0; + char *ep; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; - ev[0].err = errno; - errno = 0; - if (minval > maxval) { - error = INVALID; - } else { - ll = strtoll(numstr, &ep, 10); - if (numstr == ep || *ep != '\0') - error = INVALID; - else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) - error = TOOSMALL; - else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) - error = TOOLARGE; - } - if (errstrp != NULL) - *errstrp = ev[error].errstr; - errno = ev[error].err; - if (error) - ll = 0; + ev[0].err = errno; + errno = 0; + if (minval > maxval) { + error = INVALID; + } else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = TOOSMALL; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; - return ll; + return ll; } /* @@ -175,10 +262,10 @@ strtonum(const char *numstr, long long minval, long long maxval, void * reallocarray(void *optr, size_t nmemb, size_t size) { - if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && - nmemb > 0 && SIZE_MAX / nmemb < size) { - errno = ENOMEM; - return NULL; - } - return realloc(optr, size * nmemb); + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); } diff --git a/util.h b/util.h index bc7f8ec..be152bd 100644 --- a/util.h +++ b/util.h @@ -52,6 +52,8 @@ void eunveil(const char *, const char *); int timestamp(char *, size_t, time_t); int esnprintf(char *, size_t, const char *, ...); int prepend(char *, size_t, const char *); +void replace(char **, const char *, const char *); +char *read_file(const char* path); void *reallocarray(void *, size_t, size_t); long long strtonum(const char *, long long, long long, const char **);