stamail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit cd8d754cc036718b092b4aaf899852a8ee41e649
parent e46d8a4d383015f61d2b763986e24063b55d243d
Author: Nathaniel Chappelle <nathaniel@chappelle.dev>
Date:   Thu, 29 Jan 2026 10:27:10 -0800

Switched to s expressions; it works a lot better and creates threads. Now to render

Diffstat:
MMakefile | 10+++++-----
Mheaders.h | 1-
Djsmn.h | 473-------------------------------------------------------------------------------
Djson.c | 185-------------------------------------------------------------------------------
Djson.h | 32--------------------------------
Asexp.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asexp.h | 28++++++++++++++++++++++++++++
Mstamail.c | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mstamail.h | 41+++++++++++++++++++++++++++++++++--------
9 files changed, 253 insertions(+), 706 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ CC = cc CFLAGS = -std=c99 -Wall -Wextra -O2 -OBJ = stamail.o json.o thread.o headers.o +OBJ = stamail.o sexp.o thread.o headers.o BIN = stamail all: $(BIN) @@ -8,11 +8,11 @@ all: $(BIN) $(BIN): $(OBJ) $(CC) $(CFLAGS) -o $@ $(OBJ) -stamail.o: stamail.c json.h +stamail.o: stamail.c stamail.h $(CC) $(CFLAGS) -c stamail.c -json.o: json.c json.h jsmn.h - $(CC) $(CFLAGS) -c json.c +sexp.o: sexp.c stamail.h + $(CC) $(CFLAGS) -c sexp.c thread.o: thread.c thread.h $(CC) $(CFLAGS) -c thread.c @@ -24,7 +24,7 @@ clean: rm -rf $(OBJ) $(BIN) ./output/ messages: - cat ./test/test.json | ./stamail + cat ./test/test.sexp | ./stamail .PHONY: all clean diff --git a/headers.h b/headers.h @@ -1,7 +1,6 @@ #ifndef HEADERS_H #define HEADERS_H -static char *read_header(FILE *fp, const char *header_name, int *len); int extract_threading_headers(struct message_node *node); #endif diff --git a/jsmn.h b/jsmn.h @@ -1,473 +0,0 @@ -/* - * jsmn, a minimalistic JSON parser in C. - * - * MIT License - * - * Copyright (c) 2010 Serge Zaitsev - * - * 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. - */ -#ifndef JSMN_H -#define JSMN_H - -#include <stddef.h> - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef JSMN_STATIC -#define JSMN_API static -#else -#define JSMN_API extern -#endif - -/** - * JSON type identifier. Basic types are: - * o Object - * o Array - * o String - * o Other primitive: number, boolean (true/false) or null - */ -typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1 << 0, - JSMN_ARRAY = 1 << 1, - JSMN_STRING = 1 << 2, - JSMN_PRIMITIVE = 1 << 3 -} jsmntype_t; - -enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 -}; - -/** - * JSON token description. - * type type (object, array, string etc.) - * start start position in JSON data string - * end end position in JSON data string - */ -typedef struct jsmntok { - jsmntype_t type; - int start; - int end; - int size; -#ifdef JSMN_PARENT_LINKS - int parent; -#endif -} jsmntok_t; - -/** - * JSON parser. Contains an array of token blocks available. Also stores - * the string being parsed now and current position in that string. - */ -typedef struct jsmn_parser { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g. parent object or array */ -} jsmn_parser; - -/** - * Create JSON parser over an array of tokens - */ -JSMN_API void jsmn_init(jsmn_parser *parser); - -/** - * Run JSON parser. It parses a JSON data string into and array of tokens, each - * describing - * a single JSON object. - */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens); - -#ifndef JSMN_HEADER -/** - * Allocates a fresh unused token from the token pool. - */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, - const size_t num_tokens) { - jsmntok_t *tok; - if (parser->toknext >= num_tokens) { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; -} - -/** - * Fills token type and boundaries. - */ -static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, - const int start, const int end) { - token->type = type; - token->start = start; - token->end = end; - token->size = 0; -} - -/** - * Fills next available token with JSON primitive. - */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t': - case '\r': - case '\n': - case ' ': - case ',': - case ']': - case '}': - goto found; - default: - /* to quiet a warning from gcc*/ - break; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; -} - -/** - * Fills next token with JSON string. - */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { - jsmntok_t *token; - - int start = parser->pos; - - /* Skip starting quote */ - parser->pos++; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': - case '/': - case '\\': - case 'b': - case 'f': - case 'r': - case 'n': - case 't': - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; - i++) { - /* If it isn't a hex character we have an error */ - if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; -} - -/** - * Parse JSON string and fill tokens. - */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens) { - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) { - case '{': - case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - return JSMN_ERROR_NOMEM; - } - if (parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; -#ifdef JSMN_STRICT - /* In strict mode an object or array can't become a key */ - if (t->type == JSMN_OBJECT) { - return JSMN_ERROR_INVAL; - } -#endif - t->size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': - case ']': - if (tokens == NULL) { - break; - } - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if (token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) { - return JSMN_ERROR_INVAL; - } - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) { - tokens[parser->toksuper].size++; - } - break; - case '\t': - case '\r': - case '\n': - case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 't': - case 'f': - case 'n': - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - const jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) { - return r; - } - count++; - if (parser->toksuper != -1 && tokens != NULL) { - tokens[parser->toksuper].size++; - } - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } - - return count; -} - -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -JSMN_API void jsmn_init(jsmn_parser *parser) { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; -} - -#endif /* JSMN_HEADER */ - -#ifdef __cplusplus -} -#endif - -#endif /* JSMN_H */ diff --git a/json.c b/json.c @@ -1,185 +0,0 @@ -#include <stdio.h> -#include <string.h> -#include "json.h" -#include "jsmn.h" - -static int tok_streq(const char *js, jsmntok_t *t, const char *s) { - return (t->type == JSMN_STRING && - (int)strlen(s) == t->end - t->start && - strncmp(js + t->start, s, t->end - t->start) == 0); -} - -static void scan(const char *js, jsmntok_t *t, int *i, int ntok, - message_cb cb, void *ud) { - if (*i >= ntok) return; - - if (t[*i].type == JSMN_OBJECT) { - int n = t[*i].size; - (*i)++; - - const char *id = NULL, *from = NULL, *subject = NULL, *date = NULL, *body = NULL; - const char *in_reply_to = NULL, *references = NULL; - const char *filename = NULL; - int id_len=0, from_len=0, subject_len=0, date_len=0, body_len=0; - int in_reply_to_len=0, references_len=0; - int filename_len=0; - - for (int k = 0; k < n; k++) { - jsmntok_t *key = &t[*i]; (*i)++; - jsmntok_t *val = &t[*i]; - - /* id */ - if (tok_streq(js, key, "id")) { - id = js + val->start; - id_len = val->end - val->start; - (*i)++; - } - /* filename for irt and references */ - else if (tok_streq(js, key, "filename") && val->type == JSMN_ARRAY) { - /* filename is an array, get first element */ - if (val->size > 0) { - (*i)++; - jsmntok_t *fname = &t[*i]; - if (fname->type == JSMN_STRING) { - filename = js + fname->start; - filename_len = fname->end - fname->start; - } - /* Skip rest of array */ - for (int f = 1; f < val->size; f++) { - (*i)++; - } - } else { - (*i)++; - } - } - /* headers */ - else if (tok_streq(js, key, "headers") && val->type == JSMN_OBJECT) { - int hn = val->size; - (*i)++; - for (int h = 0; h < hn; h++) { - jsmntok_t *hkey = &t[*i]; (*i)++; - jsmntok_t *hval = &t[*i]; - - if (tok_streq(js, hkey, "From")) { - from = js + hval->start; - from_len = hval->end - hval->start; - } else if (tok_streq(js, hkey, "Subject")) { - subject = js + hval->start; - subject_len = hval->end - hval->start; - } else if (tok_streq(js, hkey, "Date")) { - date = js + hval->start; - date_len = hval->end - hval->start; - } - else if (tok_streq(js, hkey, "In-reply-to")) { - in_reply_to = js + hval->start; - in_reply_to_len = hval->end - hval->start; - /* Strip < and > brackets if present */ - if (in_reply_to_len > 2 && - in_reply_to[0] == '<' && - in_reply_to[in_reply_to_len - 1] == '>') { - in_reply_to++; - in_reply_to_len -= 2; - } - } else if (tok_streq(js, hkey, "References")) { - references = js + hval->start; - references_len = hval->end - hval->start; - } - (*i)++; - } - } - /* body */ - else if (tok_streq(js, key, "body") && val->type == JSMN_ARRAY) { - int bn = val->size; - (*i)++; - for (int b = 0; b < bn; b++) { - if (t[*i].type == JSMN_OBJECT) { - int on = t[*i].size; - (*i)++; - const char *ctype = NULL; - int ctype_len = 0; - - for (int x = 0; x < on; x++) { - jsmntok_t *bkey = &t[*i]; (*i)++; - jsmntok_t *bval = &t[*i]; - - if (tok_streq(js, bkey, "content-type")) { - ctype = js + bval->start; - ctype_len = bval->end - bval->start; - } else if (tok_streq(js, bkey, "content")) { - if (ctype && strncmp(ctype, "text/plain", ctype_len) == 0) { - body = js + bval->start; - body_len = bval->end - bval->start; - } - } - (*i)++; - } - } else { - (*i)++; - } - } - } - else { - /* skip value */ - if (val->type == JSMN_OBJECT || val->type == JSMN_ARRAY) { - int skip = val->size * 2; - (*i)++; - for (int s = 0; s < skip; s++) (*i)++; - } else { - (*i)++; - } - } - } - - /* If this object looks like a message, print it */ - if (id && subject && from && date) { - struct message m = { - .id = id, .id_len = id_len, - .from = from, .from_len = from_len, - .subject = subject, .subject_len = subject_len, - .date = date, .date_len = date_len, - .body = body, .body_len = body_len, - .filename = filename, .filename_len = filename_len, - .in_reply_to = NULL, .in_reply_to_len = 0, - .references = NULL, .references_len = 0 - }; - cb(&m, ud); - // printf("Message-ID: %.*s\n", id_len, id); - // printf("From: %.*s\n", from_len, from); - // printf("Subject: %.*s\n", subject_len, subject); - // printf("Date: %.*s\n", date_len, date); - // if (body) { - // printf("Body:\n%.*s\n", body_len, body); - // } - // printf("--------------------------------------------------\n"); - } - return; - } - - /* recurse arrays */ - if (t[*i].type == JSMN_ARRAY) { - int n = t[*i].size; - (*i)++; - for (int k = 0; k < n; k++) { - scan(js, t, i, ntok, cb, ud); - } - return; - } - - (*i)++; -} - -void parse_mail_json(const char *json, int len, message_cb cb, void* userdata) { - jsmn_parser p; - jsmn_init(&p); - - jsmntok_t tokens[4096]; - int ntok = jsmn_parse(&p, json, len, tokens, 4096); - if (ntok < 0) { - fprintf(stderr, "json: parse error\n"); - return; - } - - int i = 0; - scan(json, tokens, &i, ntok, cb, userdata); -} - diff --git a/json.h b/json.h @@ -1,32 +0,0 @@ -#ifndef JSON_H -#define JSON_H - -#include <time.h> - -struct message { - const char *id; int id_len; - const char *from; int from_len; - const char *subject;int subject_len; - const char *date; int date_len; - const char *body; int body_len; - - /* Threading fields */ - const char *in_reply_to; - int in_reply_to_len; - const char *references; - int references_len; - - const char *filename; - int filename_len; - - time_t timestamp; -}; - -/* Called for every message found */ -typedef void (*message_cb)(struct message *m, void *userdata); - -/* Parse JSON buffer and print extracted mail fields */ -void parse_mail_json(const char *json, int len, message_cb cb, void *userdata); - -#endif - diff --git a/sexp.c b/sexp.c @@ -0,0 +1,97 @@ +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include "sexp.h" + +static const char *p; +static const char *end; + +static void skip_ws(void) { + while (p < end && isspace((unsigned char)*p)) + p++; +} + +static struct sexp *parse_expr(void); + +static struct sexp *new_sexp(enum sexp_type t, const char *s, int len) { + struct sexp *x = calloc(1, sizeof(*x)); + x->type = t; + x->start = s; + x->len = len; + return x; +} + +static struct sexp *parse_list(void) { + p++; /* skip '(' */ + struct sexp *list = new_sexp(SEXP_LIST, NULL, 0); + struct sexp **tail = &list->child; + + for (;;) { + skip_ws(); + if (p >= end) + break; + if (*p == ')') { + p++; + break; + } + struct sexp *e = parse_expr(); + if (!e) break; + *tail = e; + tail = &e->next; + } + return list; +} + +static struct sexp *parse_string(void) { + const char *s = ++p; + while (p < end) { + if (*p == '"' && p[-1] != '\\') + break; + p++; + } + int len = p - s; + if (p < end) p++; + return new_sexp(SEXP_STRING, s, len); +} + +static struct sexp *parse_atom(void) { + const char *s = p; + while (p < end && !isspace((unsigned char)*p) && *p != '(' && *p != ')') + p++; + int len = p - s; + + int isnum = 1; + for (int i = 0; i < len; i++) { + if (!isdigit((unsigned char)s[i])) { + isnum = 0; + break; + } + } + + return new_sexp(isnum ? SEXP_INT : SEXP_SYMBOL, s, len); +} + +static struct sexp *parse_expr(void) { + skip_ws(); + if (p >= end) return NULL; + + if (*p == '(') + return parse_list(); + if (*p == '"') + return parse_string(); + return parse_atom(); +} + +struct sexp *sexp_parse(const char *buf, size_t len) { + p = buf; + end = buf + len; + return parse_expr(); +} + +void sexp_free(struct sexp *s) { + if (!s) return; + sexp_free(s->child); + sexp_free(s->next); + free(s); +} + diff --git a/sexp.h b/sexp.h @@ -0,0 +1,28 @@ +#ifndef SEXP_H +#define SEXP_H + +#include <stddef.h> + +enum sexp_type { + SEXP_LIST, + SEXP_SYMBOL, + SEXP_STRING, + SEXP_INT +}; + +struct sexp { + enum sexp_type type; + const char *start; + int len; + + struct sexp *child; /* for lists */ + struct sexp *next; /* sibling */ +}; + + +struct sexp *sexp_parse(const char *buf, size_t len); +void sexp_free(struct sexp *s); + + +#endif + diff --git a/stamail.c b/stamail.c @@ -6,8 +6,8 @@ #include <sys/types.h> #include <errno.h> -#include "json.h" #include "stamail.h" +#include "sexp.h" #include "thread.h" #include "headers.h" @@ -55,6 +55,94 @@ static void collect_message(struct message *m, void *ud) { message_count++; } +static struct sexp *plist_get(struct sexp *plist, const char *key) { + for (struct sexp *e = plist; e && e->next; e = e->next->next) { + if (e->type == SEXP_SYMBOL && + e->len == (int)strlen(key) && + memcmp(e->start, key, e->len) == 0) + return e->next; + } + return NULL; +} + +static void parse_message_plist(struct sexp *plist, + struct message *m) { + memset(m, 0, sizeof(*m)); + + struct sexp *v; + + if ((v = plist_get(plist, ":id"))) { + m->id = v->start; + m->id_len = v->len; + } + if ((v = plist_get(plist, ":filename"))) { + if (v->type == SEXP_LIST && v->child) { + m->filename = v->child->start; + m->filename_len = v->child->len; + } + } + if ((v = plist_get(plist, ":timestamp"))) { + m->timestamp = strtoul(v->start, NULL, 10); + } + + if ((v = plist_get(plist, ":headers")) && v->type == SEXP_LIST) { + struct sexp *h = v->child; + struct sexp *x; + + if ((x = plist_get(h, ":From"))) { + m->from = x->start; + m->from_len = x->len; + } + if ((x = plist_get(h, ":Subject"))) { + m->subject = x->start; + m->subject_len = x->len; + } + if ((x = plist_get(h, ":Date"))) { + m->date = x->start; + m->date_len = x->len; + } + } + + if ((v = plist_get(plist, ":body")) && v->type == SEXP_LIST) { + struct sexp *b = v->child; + if (b && (b = plist_get(b->child, ":content"))) { + m->body = b->start; + m->body_len = b->len; + } + } +} + +void parse_mail_sexp(const char *sexp, size_t len, + message_cb cb, void *ud) { + struct sexp *root = sexp_parse(sexp, len); + if (!root) return; + + struct message m; + + /* DFS */ + void walk(struct sexp *n) { + if (!n) return; + + if (n->type == SEXP_LIST && n->child && + n->child->type == SEXP_LIST) { + + struct sexp *plist = n->child->child; + if (plist_get(plist, ":id")) { + parse_message_plist(plist, &m); + cb(&m, ud); + } + } + + walk(n->child); + walk(n->next); + } + + walk(root); + sexp_free(root); +} + + + /* HTML Escape Function */ static void fprinthtml(FILE *fp, const char *s, int len) { if (!s || len <= 0) @@ -315,7 +403,7 @@ int main(int argc, char *argv[]) { return 1; } - parse_mail_json(buf, len, collect_message, NULL); + parse_mail_sexp(buf, len, collect_message, NULL); /* Debug: print what we got from JSON */ for (struct message_node *n = head; n; n = n->next) { diff --git a/stamail.h b/stamail.h @@ -1,7 +1,35 @@ #ifndef STAMAIL_H #define STAMAIL_H -#include "json.h" +#include <time.h> + + +/* Thread Root */ +struct thread { + struct thread_node *root; + char *subject; + time_t latest; + int message_count; +}; + +struct message { + const char *id; int id_len; + const char *from; int from_len; + const char *subject;int subject_len; + const char *date; int date_len; + const char *body; int body_len; + + /* Threading fields */ + const char *in_reply_to; + int in_reply_to_len; + const char *references; + int references_len; + + const char *filename; + int filename_len; + + time_t timestamp; +}; struct message_node { struct message m; @@ -20,12 +48,9 @@ struct thread_node { int num_children; }; -/* Thread Root */ -struct thread { - struct thread_node *root; - char *subject; - time_t latest; - int message_count; -}; + +/* Called for every message found */ +typedef void (*message_cb)(struct message *m, void *userdata); + #endif