// // damus.c // damus // // Created by William Casarin on 2022-10-17. // #include "damus.h" #include "bolt11.h" #include #include typedef unsigned char u8; struct cursor { const u8 *p; const u8 *start; const u8 *end; }; static inline int is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; } static void make_cursor(struct cursor *c, const u8 *content, size_t len) { c->start = content; c->end = content + len; c->p = content; } static int consume_until_whitespace(struct cursor *cur, int or_end) { char c; while (cur->p < cur->end) { c = *cur->p; if (is_whitespace(c)) return 1; cur->p++; } return or_end; } static int parse_char(struct cursor *cur, char c) { if (cur->p >= cur->end) return 0; if (*cur->p == c) { cur->p++; return 1; } return 0; } static inline int peek_char(struct cursor *cur, int ind) { if ((cur->p + ind < cur->start) || (cur->p + ind >= cur->end)) return -1; return *(cur->p + ind); } static int parse_digit(struct cursor *cur, int *digit) { int c; if ((c = peek_char(cur, 0)) == -1) return 0; c -= '0'; if (c >= 0 && c <= 9) { *digit = c; cur->p++; return 1; } return 0; } static int parse_str(struct cursor *cur, const char *str) { unsigned long len = strlen(str); if (cur->p + len >= cur->end) return 0; if (!memcmp(cur->p, str, len)) { cur->p += len; return 1; } return 0; } static int parse_mention(struct cursor *cur, struct block *block) { int d1, d2, d3, ind; const u8 *start = cur->p; if (!parse_str(cur, "#[")) return 0; if (!parse_digit(cur, &d1)) { cur->p = start; return 0; } ind = d1; if (parse_digit(cur, &d2)) ind = (d1 * 10) + d2; if (parse_digit(cur, &d3)) ind = (d1 * 100) + (d2 * 10) + d3; if (!parse_char(cur, ']')) { cur->p = start; return 0; } block->type = BLOCK_MENTION; block->block.mention = ind; return 1; } static int parse_hashtag(struct cursor *cur, struct block *block) { int c; const u8 *start = cur->p; if (!parse_char(cur, '#')) return 0; c = peek_char(cur, 0); if (c == -1 || is_whitespace(c) || c == '#') { cur->p = start; return 0; } consume_until_whitespace(cur, 1); block->type = BLOCK_HASHTAG; block->block.str.start = (const char*)(start + 1); block->block.str.end = (const char*)cur->p; return 1; } static int add_block(struct blocks *blocks, struct block block) { if (blocks->num_blocks + 1 >= MAX_BLOCKS) return 0; blocks->blocks[blocks->num_blocks++] = block; return 1; } static int add_text_block(struct blocks *blocks, const u8 *start, const u8 *end) { struct block b; b.type = BLOCK_TEXT; b.block.str.start = (const char*)start; b.block.str.end = (const char*)end; return add_block(blocks, b); } static int parse_url(struct cursor *cur, struct block *block) { const u8 *start = cur->p; if (!parse_str(cur, "http")) return 0; if (parse_char(cur, 's')) { if (!parse_str(cur, "://")) { cur->p = start; return 0; } } else { if (!parse_str(cur, "://")) { cur->p = start; return 0; } } if (!consume_until_whitespace(cur, 1)) { cur->p = start; return 0; } block->type = BLOCK_URL; block->block.str.start = (const char *)start; block->block.str.end = (const char *)cur->p; return 1; } static int parse_invoice(struct cursor *cur, struct block *block) { const u8 *start, *end; char *fail; struct bolt11 *bolt11; start = cur->p; if (!parse_str(cur, "lnbc")) return 0; if (!consume_until_whitespace(cur, 1)) { cur->p = start; return 0; } end = cur->p; char str[end - start + 1]; str[end - start] = 0; memcpy(str, start, end - start); if (!(bolt11 = bolt11_decode(NULL, str, &fail))) { cur->p = start; return 0; } block->type = BLOCK_INVOICE; block->block.invoice.invstr.start = (const char*)start; block->block.invoice.invstr.end = (const char*)end; block->block.invoice.bolt11 = bolt11; cur->p += end - start; return 1; } static int add_text_then_block(struct cursor *cur, struct blocks *blocks, struct block block, u8 **start, u8 *pre_mention) { if (!add_text_block(blocks, *start, pre_mention)) return 0; *start = (u8*)cur->p; if (!add_block(blocks, block)) return 0; return 1; } int damus_parse_content(struct blocks *blocks, const char *content) { int cp, c; struct cursor cur; struct block block; u8 *start, *pre_mention; blocks->num_blocks = 0; make_cursor(&cur, (const u8*)content, strlen(content)); start = cur.p; while (cur.p < cur.end && blocks->num_blocks < MAX_BLOCKS) { cp = peek_char(&cur, -1); c = peek_char(&cur, 0); pre_mention = cur.p; if (cp == -1 || is_whitespace(cp)) { if (c == '#' && (parse_mention(&cur, &block) || parse_hashtag(&cur, &block))) { if (!add_text_then_block(&cur, blocks, block, &start, pre_mention)) return 0; continue; } else if (c == 'h' && parse_url(&cur, &block)) { if (!add_text_then_block(&cur, blocks, block, &start, pre_mention)) return 0; continue; } else if (c == 'l' && parse_invoice(&cur, &block)) { if (!add_text_then_block(&cur, blocks, block, &start, pre_mention)) return 0; continue; } } cur.p++; } if (cur.p - start > 0) { if (!add_text_block(blocks, start, cur.p)) return 0; } return 1; } void blocks_init(struct blocks *blocks) { blocks->blocks = malloc(sizeof(struct block) * MAX_BLOCKS); blocks->num_blocks = 0; } void blocks_free(struct blocks *blocks) { if (blocks->blocks) { free(blocks->blocks); blocks->num_blocks = 0; } }