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:
| M | Makefile | | | 10 | +++++----- |
| M | headers.h | | | 1 | - |
| D | jsmn.h | | | 473 | ------------------------------------------------------------------------------- |
| D | json.c | | | 185 | ------------------------------------------------------------------------------- |
| D | json.h | | | 32 | -------------------------------- |
| A | sexp.c | | | 97 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | sexp.h | | | 28 | ++++++++++++++++++++++++++++ |
| M | stamail.c | | | 92 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| M | stamail.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