anntp

a nntp implementation in pure C99
Log | Files | Refs | README | LICENSE

commit 79b9d2183507ad8e668083eebb289219cd60cbd3
parent 4bb66d279b0ece9db166f953c5da09f04067815f
Author: Mario Rosell R. Martinez <mario@mariorosell.es>
Date:   Sat, 21 Mar 2026 20:19:09 +0100

tests: Add testing suite

Diffstat:
M.gitignore | 2++
Manntp.h | 239+++++--------------------------------------------------------------------------
Mexamples/nntpsh.c | 2+-
Atests/Makefile | 23+++++++++++++++++++++++
Atests/test.c | 46++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 86 insertions(+), 226 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -2,5 +2,7 @@ *.swo a.out thumbs.db +tests +*.o .DS_Store diff --git a/anntp.h b/anntp.h @@ -1,7 +1,7 @@ /** - ** anntp v0.1.0dev - public-domain nntp client implementation in C - by Mario Rosell an contributors - ** THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. - ** use at your own risk. + ** anntp v0.1.0 - public-domain nntp client implementation in C - by Mario Rosell an contributors + ** THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. + ** use at your own risk. ** #include ... ** ** Add: @@ -95,9 +95,9 @@ # if (defined(__GNUC__) && defined(_GNU_SOURCE)) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) # define ANNTP_STRDUP strdup # else -# define ANNTP_STRDUP ______anntp__NC__strdup______ +# define ANNTP_STRDUP anntp__NC__strdup char* -______anntp__NC__strdup______(const char* s) { +anntp__NC__strdup(const char* s) { size_t len = strlen(s) + 1; char* p = (char*)malloc(len); if (p) memcpy(p, s, len); @@ -157,7 +157,6 @@ struct anntp_group { char* name; AnntpArticleNumber first; AnntpArticleNumber last; - long count; char mode; /* y for posting allowed, n for no posting allowed, m for moderated */ }; @@ -167,11 +166,16 @@ struct anntp_article { char* from; char* date; char* body; - char* subject; +}; + +struct anntp_overview { + char** lines; + size_t count; }; typedef struct anntp_connection AnntpConnection; typedef struct anntp_article AnntpArticle; +typedef struct anntp_overview AnntpOverview; typedef struct anntp_group AnntpGroup; typedef int (*AnntpLineCb)(const char* line, void* extra); @@ -184,13 +188,8 @@ ANNTP_API void anntp_freeconn(AnntpConnection* conn); ANNTP_API ssize_t anntp_read(AnntpConnection* conn, uchar_t* buf, size_t bufsize); ANNTP_API ssize_t anntp_write(AnntpConnection* conn, const uchar_t* buf, size_t bufsize); ANNTP_API ssize_t anntp_write_all(AnntpConnection* conn, const uchar_t* buf, size_t bufsize); -ANNTP_API int anntp_group(AnntpConnection* conn, const char* groupname, AnntpGroup* out); -ANNTP_API ssize_t anntp_list(AnntpConnection* conn, AnntpGroup** out_groups); -ANNTP_API ssize_t anntp_article(AnntpConnection* conn, AnntpArticleNumber n, AnntpArticle* out_art); ANNTP_API ssize_t anntp_writeline(AnntpConnection* conn, const char* buf); ANNTP_API ssize_t anntp_readline(AnntpConnection* conn, char* buf, size_t maxlen); -ANNTP_API void anntp_free_article(AnntpArticle* a); -ANNTP_API void anntp_free_groups(AnntpGroup* gs, size_t count); ANNTP_API ssize_t anntp_readdot(AnntpConnection* conn, char* buf, size_t maxlen); ANNTP_API int anntp_readdot_cb(AnntpConnection* conn, AnntpLineCb cb, void* extra); ANNTP_API int anntp_auth(AnntpConnection* conn, const char* login, const char* password); @@ -325,210 +324,27 @@ anntp_group(AnntpConnection* cv, const char* group, AnntpGroup* out) if (anntp_readline(cv, line, sizeof(line)) <= 0) return ANE_IO; - /* expect a 221 for OK */ if (strncmp(line, "211", 3) != 0) return ANE_PROTO; int count; int first, last; - char mode; + char mode = '?'; char name[128]; /* validate */ - if (sscanf(line, "211 %d %d %d %127s", &count, &first, &last, name) != 4) - return ANE_PROTO; /* malformed line */ - + sscanf(line, "211 %d %d %d %127s", &count, &first, &last, name); strncpy(name, group, sizeof(name)-1); out->name = ANNTP_STRDUP(name); - if (!out->name) - return ANE_IO; - out->first = (AnntpArticleNumber)first; out->last = (AnntpArticleNumber)last; - out->count = (long)count; - - out->mode = '?'; /* we can't really know the status of a group without LIST ACTIVE */ + out->mode = mode; return ANE_OK; } ssize_t -anntp_list(AnntpConnection* cv, AnntpGroup** out_groups) -{ - if (!cv || !out_groups) return ANNTPE(ANE_PARAMS); - - size_t count = 0; - *out_groups = NULL; - - if (anntp_writeline(cv, "LIST ACTIVE") <= 0) - return ANNTPE(ANE_IO); - - char line[ANNTP_BUFSIZE]; - - if (anntp_readline(cv, line, sizeof(line)) <= 0) - return ANNTPE(ANE_IO); - - size_t cap = 16; - AnntpGroup* groups = (AnntpGroup*)ANNTP_MALLOC(cap * sizeof(AnntpGroup)); - if (!groups) - return ANNTPE(ANE_IO); - - for (;;) { - ssize_t n = anntp_readline(cv, line, sizeof(line)); - if (n <= 0) - goto fail; - - if (strncmp(line, ".\r\n", 3) == 0) - break; - - char name[256] = {0}; - long last = 0, first = 0; - char mode = '?'; - - if (sscanf(line, "%255s %ld %ld %c", name, &last, &first, &mode) != 4) - continue; - - if (count >= cap) { - cap *= 2; - AnntpGroup* tmp = (AnntpGroup*)realloc(groups, cap * sizeof(AnntpGroup)); - if (!tmp) - goto fail; - groups = tmp; - } - - groups[count].name = ANNTP_STRDUP(name); - if (!groups[count].name) - goto fail; - - groups[count].first = (AnntpArticleNumber)first; - groups[count].last = (AnntpArticleNumber)last; - groups[count].mode = mode; - - count++; - } - - *out_groups = groups; - return (ssize_t)count; - -fail: - if (groups) { - for (size_t i = 0; i < count; i++) { - if (groups[i].name) - ANNTP_FREE(groups[i].name); - } - ANNTP_FREE(groups); - } - return ANNTPE(ANE_IO); -} - -struct __ArticleStream { - AnntpArticle* out; - char* body; - size_t len; - size_t cap; - Bool headers_done; -}; - -static int -_article_line_cb(const char* line, void* extra) -{ - struct __ArticleStream* s = (struct __ArticleStream*)extra; - - if (!s->headers_done) { - if (line[0] == '\0') { /* blank line is end of headers */ - s->headers_done = true; - return 0; - } - - if (strncmp(line, "From:", 5) == 0 && !s->out->from) - s->out->from = ANNTP_STRDUP(line + 5); - else if (strncmp(line, "Date:", 5) == 0 && !s->out->date) - s->out->date = ANNTP_STRDUP(line + 5); - else if (strncmp(line, "Subject:", 8) == 0 && !s->out->subject) - s->out->subject = ANNTP_STRDUP(line + 8); - return 0; - } - - size_t line_len = strlen(line); - if (s->len + line_len + 2 >= s->cap) { - s->cap *= 2; - char* tmp = (char*)realloc(s->body, s->cap); - if (!tmp) return 1; /* stop on OOM */ - s->body = tmp; - } - - memcpy(s->body + s->len, line, line_len); - s->len += line_len; - s->body[s->len++] = '\r'; - s->body[s->len++] = '\n'; - - return 0; -} - -ssize_t -anntp_article(AnntpConnection* cv, AnntpArticleNumber n, AnntpArticle* out) -{ - if (!cv || !out) - return ANNTPE(ANE_PARAMS); - - char cmd[128]; - char line[ANNTP_BUFSIZE]; - - snprintf(cmd, sizeof(cmd), "ARTICLE %ld", (long)n); - - if (anntp_writeline(cv, cmd) <= 0) - return ANNTPE(ANE_IO); - - if (anntp_readline(cv, line, sizeof(line)) <= 0) - return ANNTPE(ANE_IO); - - if (strncmp(line, "220", 3) != 0) - return ANNTPE(ANE_PROTO); - - long num = 0; - char msgid[256] = {0}; - sscanf(line, "220 %ld %255s", &num, msgid); - - /* init out */ - out->n = (AnntpArticleNumber)num; - out->id = ANNTP_STRDUP(msgid); - out->from = NULL; - out->date = NULL; - out->body = NULL; - - if (!out->id) - return ANNTPE(ANE_IO); - - /* setup streamer */ - struct __ArticleStream stream; - stream.out = out; - stream.cap = ANNTP_BUFSIZE; - stream.len = 0; - stream.headers_done = false; - stream.body = (char*)ANNTP_MALLOC(stream.cap); - if (!stream.body) - return ANNTPE(ANE_IO); - - ssize_t ret = anntp_readdot_cb(cv, _article_line_cb, &stream); - if (ret < 0) { - ANNTP_FREE(stream.body); - return ret; - } - - /* finalize body */ - if (stream.len > 0) { - stream.body[stream.len] = '\0'; - out->body = stream.body; - } else { - ANNTP_FREE(stream.body); - out->body = NULL; - } - - return stream.len; -} - -ssize_t anntp_read(AnntpConnection* cv, uchar_t* buf, size_t len) { if (!cv || !buf) return ANNTPE(ANE_PARAMS); @@ -803,32 +619,5 @@ anntp_strerror(AnntpErrCode c) } } -void -anntp_free_article(AnntpArticle* a) -{ - if (!a) return; - if (a->id) { ANNTP_FREE(a->id); a->id = NULL; } - if (a->from) { ANNTP_FREE(a->from); a->from = NULL; } - if (a->date) { ANNTP_FREE(a->date); a->date = NULL; } - if (a->body) { ANNTP_FREE(a->body); a->body = NULL; } - if (a->subject) { ANNTP_FREE(a->subject); a->subject = NULL; } -} - -void -anntp_free_groups(AnntpGroup* groups, size_t count) -{ - if (!groups) return; - - for (size_t i = 0; i < count; i++) { - if (groups[i].name) { - ANNTP_FREE(groups[i].name); - groups[i].name = NULL; - } - } - - ANNTP_FREE(groups); -} - - #endif /* ANNTP_IMPLEMENTATION */ diff --git a/examples/nntpsh.c b/examples/nntpsh.c @@ -1,5 +1,5 @@ /* - * anntp v0.1: nntpsh.c - example 1 - nntp shell + * anntp v0.1DEV: nntpsh.c - example 1 - nntp shell * * This program basically connects to a host and allows you to run commands. * diff --git a/tests/Makefile b/tests/Makefile @@ -0,0 +1,23 @@ +CFLAGS= -Wall -Wextra -O0 -std=c99 -Wall -Werror -pipe +CPPFLAGS= -I../ -DANNTP_TLS +PROG= tests +CFILES= test.c +OFILES= ${CFILES:.c=.o} +LIBS!= pkgconf --libs openssl +LDFLAGS= ${LIBS} + +.SUFFIXES: .c .o + +.c.o: + ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +${PROG}: ${OFILES} + ${CC} ${OFILES} -o $@ ${LDFLAGS} + +.PHONY: all clean + +all: ${PROG} + +clean: + rm -f ${PROG} ${OFILES} + diff --git a/tests/test.c b/tests/test.c @@ -0,0 +1,46 @@ +#undef _NDEBUG_ +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#define ANNTP_IMPLEMENTATION +#include <anntp.h> + +static unsigned int nfailed = 0; +static unsigned int ntests = 0; + +void +assert(bool cond, char* desc) +{ + ntests++; + + if (!(cond)) { + fprintf(stderr, "> testing `%s'... FAIL!\n", desc); + nfailed++; + } else { + fprintf(stderr, "> testing `%s'... ok\n", desc); + } +} + +/*********************************************************************************************************************/ + +void +tests(void) +{ + + AnntpConnection* tc = anntp_mkconn("news.eternal-september.org", "119", true); + assert(tc != NULL, "make connection"); +} + +/*********************************************************************************************************************/ + +int +main(int argc, char** argv) +{ + anntp_init(); + (void)argc; (void)argv; + tests(); + + printf("> INFO: %d out of %d tests failed (%d succeeded).\n", nfailed, ntests, nfailed - ntests); + return nfailed > 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} +