summaryrefslogtreecommitdiff
path: root/src/inlines.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/inlines.c')
-rw-r--r--src/inlines.c272
1 files changed, 144 insertions, 128 deletions
diff --git a/src/inlines.c b/src/inlines.c
index 9216979..810230c 100644
--- a/src/inlines.c
+++ b/src/inlines.c
@@ -9,6 +9,7 @@
#include "utf8.h"
#include "scanners.h"
#include "inlines.h"
+#include "debug.h"
typedef struct InlineStack {
struct InlineStack *previous;
@@ -41,9 +42,9 @@ static unsigned char *bufdup(const unsigned char *buf)
if (buf) {
int len = strlen((char *)buf);
new = calloc(len + 1, sizeof(*new));
- if(new != NULL) {
- memcpy(new, buf, len + 1);
- }
+ if(new != NULL) {
+ memcpy(new, buf, len + 1);
+ }
}
return new;
@@ -52,13 +53,13 @@ static unsigned char *bufdup(const unsigned char *buf)
static inline node_inl *make_link_(node_inl *label, unsigned char *url, unsigned char *title)
{
node_inl* e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = INL_LINK;
- e->content.linkable.label = label;
- e->content.linkable.url = url;
- e->content.linkable.title = title;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = INL_LINK;
+ e->content.linkable.label = label;
+ e->content.linkable.url = url;
+ e->content.linkable.title = title;
+ e->next = NULL;
+ }
return e;
}
@@ -81,11 +82,11 @@ inline static node_inl* make_link(node_inl* label, chunk url, chunk title)
inline static node_inl* make_inlines(int t, node_inl* contents)
{
node_inl * e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = t;
- e->content.inlines = contents;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = t;
+ e->content.inlines = contents;
+ e->next = NULL;
+ }
return e;
}
@@ -93,11 +94,11 @@ inline static node_inl* make_inlines(int t, node_inl* contents)
inline static node_inl* make_literal(int t, chunk s)
{
node_inl * e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = t;
- e->content.literal = s;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = t;
+ e->content.literal = s;
+ e->next = NULL;
+ }
return e;
}
@@ -105,10 +106,10 @@ inline static node_inl* make_literal(int t, chunk s)
inline static node_inl* make_simple(int t)
{
node_inl* e = calloc(1, sizeof(*e));
- if(e != NULL) {
- e->tag = t;
- e->next = NULL;
- }
+ if(e != NULL) {
+ e->tag = t;
+ e->next = NULL;
+ }
return e;
}
@@ -121,10 +122,28 @@ inline static node_inl* make_simple(int t)
#define make_emph(contents) make_inlines(INL_EMPH, contents)
#define make_strong(contents) make_inlines(INL_STRONG, contents)
-// Free an inline list.
+// Utility function used by free_inlines
+void splice_into_list(node_inl* e, node_inl* children) {
+ node_inl * tmp;
+ if (children) {
+ tmp = children;
+ // Find last child
+ while (tmp->next) {
+ tmp = tmp->next;
+ }
+ // Splice children into list
+ tmp->next = e->next;
+ e->next = children;
+ }
+ return ;
+}
+
+// Free an inline list. Avoid recursion to prevent stack overflows
+// on deeply nested structures.
extern void free_inlines(node_inl* e)
{
node_inl * next;
+
while (e != NULL) {
switch (e->tag){
case INL_STRING:
@@ -139,13 +158,14 @@ extern void free_inlines(node_inl* e)
case INL_IMAGE:
free(e->content.linkable.url);
free(e->content.linkable.title);
- free_inlines(e->content.linkable.label);
+ splice_into_list(e, e->content.linkable.label);
break;
case INL_EMPH:
case INL_STRONG:
- free_inlines(e->content.inlines);
+ splice_into_list(e, e->content.inlines);
break;
default:
+ log_warn("Unknown inline tag %d", e->tag);
break;
}
next = e->next;
@@ -297,8 +317,8 @@ static int scan_delims(subject* subj, unsigned char c, bool * can_open, bool * c
advance(subj);
}
char_after = peek_char(subj);
- *can_open = numdelims > 0 && numdelims <= 3 && !isspace(char_after);
- *can_close = numdelims > 0 && numdelims <= 3 && !isspace(char_before);
+ *can_open = numdelims > 0 && !isspace(char_after);
+ *can_close = numdelims > 0 && !isspace(char_before);
if (c == '_') {
*can_open = *can_open && !isalnum(char_before);
*can_close = *can_close && !isalnum(char_after);
@@ -308,13 +328,13 @@ static int scan_delims(subject* subj, unsigned char c, bool * can_open, bool * c
static void free_openers(subject* subj, inline_stack* istack)
{
- inline_stack * tempstack;
- while (subj->emphasis_openers != istack) {
- tempstack = subj->emphasis_openers;
- subj->emphasis_openers = subj->emphasis_openers->previous;
- subj->emphasis_nestlevel--;
- free(tempstack);
- }
+ inline_stack * tempstack;
+ while (subj->emphasis_openers != istack) {
+ tempstack = subj->emphasis_openers;
+ subj->emphasis_openers = subj->emphasis_openers->previous;
+ subj->emphasis_nestlevel--;
+ free(tempstack);
+ }
}
// Parse strong/emph or a fallback.
@@ -324,6 +344,7 @@ static node_inl* handle_strong_emph(subject* subj, unsigned char c, node_inl **l
bool can_open, can_close;
int numdelims;
int useDelims;
+ int openerDelims;
inline_stack * istack;
node_inl * inl;
node_inl * emph;
@@ -332,81 +353,84 @@ static node_inl* handle_strong_emph(subject* subj, unsigned char c, node_inl **l
numdelims = scan_delims(subj, c, &can_open, &can_close);
if (can_close)
- {
- // walk the stack and find a matching opener, if there is one
- istack = subj->emphasis_openers;
- while (true)
{
- if (istack == NULL)
- goto cannotClose;
-
- if (istack->delim_char == c)
- break;
+ // walk the stack and find a matching opener, if there is one
+ istack = subj->emphasis_openers;
+ while (true)
+ {
+ if (istack == NULL)
+ goto cannotClose;
+
+ if (istack->delim_char == c)
+ break;
+
+ istack = istack->previous;
+ }
+
+ // calculate the actual number of delimeters used from this closer
+ openerDelims = istack->delim_count;
+ if (numdelims < 3 || openerDelims < 3) {
+ useDelims = numdelims <= openerDelims ? numdelims : openerDelims;
+ } else { // (numdelims >= 3 && openerDelims >= 3)
+ useDelims = numdelims % 2 == 0 ? 2 : 1;
+ }
- istack = istack->previous;
+ if (istack->delim_count == useDelims)
+ {
+ // the opener is completely used up - remove the stack entry and reuse the inline element
+ inl = istack->first_inline;
+ inl->tag = useDelims == 1 ? INL_EMPH : INL_STRONG;
+ chunk_free(&inl->content.literal);
+ inl->content.inlines = inl->next;
+ inl->next = NULL;
+
+ // remove this opener and all later ones from stack:
+ free_openers(subj, istack->previous);
+ *last = inl;
+ }
+ else
+ {
+ // the opener will only partially be used - stack entry remains (truncated) and a new inline is added.
+ inl = istack->first_inline;
+ istack->delim_count -= useDelims;
+ inl->content.literal.len = istack->delim_count;
+
+ emph = useDelims == 1 ? make_emph(inl->next) : make_strong(inl->next);
+ inl->next = emph;
+
+ // remove all later openers from stack:
+ free_openers(subj, istack);
+
+ *last = emph;
+ }
+
+ // if the closer was not fully used, move back a char or two and try again.
+ if (useDelims < numdelims)
+ {
+ subj->pos = subj->pos - numdelims + useDelims;
+ return NULL;
+ }
+
+ return NULL; // make_str(chunk_literal(""));
}
- // calculate the actual number of delimeters used from this closer
- useDelims = istack->delim_count;
- if (useDelims == 3) useDelims = numdelims == 3 ? 1 : numdelims;
- else if (useDelims > numdelims) useDelims = 1;
-
- if (istack->delim_count == useDelims)
- {
- // the opener is completely used up - remove the stack entry and reuse the inline element
- inl = istack->first_inline;
- inl->tag = useDelims == 1 ? INL_EMPH : INL_STRONG;
- chunk_free(&inl->content.literal);
- inl->content.inlines = inl->next;
- inl->next = NULL;
-
- // remove this opener and all later ones from stack:
- free_openers(subj, istack->previous);
- *last = inl;
- }
- else
- {
- // the opener will only partially be used - stack entry remains (truncated) and a new inline is added.
- inl = istack->first_inline;
- istack->delim_count -= useDelims;
- inl->content.literal.len = istack->delim_count;
-
- emph = useDelims == 1 ? make_emph(inl->next) : make_strong(inl->next);
- inl->next = emph;
-
- // remove all later openers from stack:
- free_openers(subj, istack);
-
- *last = emph;
- }
+ cannotClose:
+ inl_text = make_str(chunk_dup(&subj->input, subj->pos - numdelims, numdelims));
- // if the closer was not fully used, move back a char or two and try again.
- if (useDelims < numdelims)
+ if (can_open)
{
- subj->pos = subj->pos - numdelims + useDelims;
- return handle_strong_emph(subj, c, last);
+ istack = (inline_stack*)malloc(sizeof(inline_stack));
+ if (istack == NULL) {
+ return NULL;
+ }
+ istack->delim_count = numdelims;
+ istack->delim_char = c;
+ istack->first_inline = inl_text;
+ istack->previous = subj->emphasis_openers;
+ subj->emphasis_openers = istack;
+ subj->emphasis_nestlevel++;
}
- return NULL; // make_str(chunk_literal(""));
- }
-
-cannotClose:
- inl_text = make_str(chunk_dup(&subj->input, subj->pos - numdelims, numdelims));
-
- if (can_open && subj->emphasis_nestlevel < STACK_LIMIT)
- {
- istack = (inline_stack*)malloc(sizeof(inline_stack));
- if (istack == NULL) {
- return NULL;
- }
- istack->delim_count = numdelims;
- istack->delim_char = c;
- istack->first_inline = inl_text;
- istack->previous = subj->emphasis_openers;
- subj->emphasis_openers = istack;
- subj->emphasis_nestlevel++;
- }
-
return inl_text;
}
@@ -438,7 +462,7 @@ static node_inl* handle_entity(subject* subj)
len = houdini_unescape_ent(&ent,
subj->input.data + subj->pos,
subj->input.len - subj->pos
- );
+ );
if (len == 0)
return make_str(chunk_literal("&"));
@@ -538,9 +562,9 @@ static node_inl* handle_pointy_brace(subject* subj)
subj->pos += matchlen;
return make_autolink(
- make_str_with_entities(&contents),
- contents, 0
- );
+ make_str_with_entities(&contents),
+ contents, 0
+ );
}
// next try to match an email autolink
@@ -550,9 +574,9 @@ static node_inl* handle_pointy_brace(subject* subj)
subj->pos += matchlen;
return make_autolink(
- make_str_with_entities(&contents),
- contents, 1
- );
+ make_str_with_entities(&contents),
+ contents, 1
+ );
}
// finally, try to match an html tag
@@ -594,8 +618,7 @@ static int link_label(subject* subj, chunk *raw_label)
advance(subj); // advance past [
unsigned char c;
- while ((c = peek_char(subj)) &&
- (c != ']' || (nestlevel > 0 && nestlevel < STACK_LIMIT))) {
+ while ((c = peek_char(subj)) && (c != ']' || nestlevel > 0)) {
switch (c) {
case '`':
tmp = handle_backticks(subj);
@@ -646,11 +669,12 @@ static node_inl* handle_left_bracket(subject* subj)
int n;
int sps;
int found_label;
- int endlabel, starturl, endurl, starttitle, endtitle, endall;
+ int endlabel, startpos, starturl, endurl, starttitle, endtitle, endall;
chunk rawlabel;
chunk url, title;
+ startpos = subj->pos;
found_label = link_label(subj, &rawlabel);
endlabel = subj->pos;
@@ -679,13 +703,7 @@ static node_inl* handle_left_bracket(subject* subj)
return make_link(lab, url, title);
} else {
- // if we get here, we matched a label but didn't get further:
- subj->pos = endlabel;
- lab = parse_chunk_inlines(&rawlabel, subj->refmap);
- result = append_inlines(make_str(chunk_literal("[")),
- append_inlines(lab,
- make_str(chunk_literal("]"))));
- return result;
+ goto noMatch;
}
} else {
chunk rawlabel_tmp;
@@ -710,16 +728,14 @@ static node_inl* handle_left_bracket(subject* subj)
lab = parse_chunk_inlines(&rawlabel, NULL);
result = make_ref_link(lab, ref);
} else {
- subj->pos = endlabel;
- lab = parse_chunk_inlines(&rawlabel, subj->refmap);
- result = append_inlines(make_str(chunk_literal("[")),
- append_inlines(lab, make_str(chunk_literal("]"))));
+ goto noMatch;
}
return result;
}
}
+noMatch:
// If we fall through to here, it means we didn't match a link:
- advance(subj); // advance past [
+ subj->pos = startpos + 1; // advance past [
return make_str(chunk_literal("["));
}
@@ -755,9 +771,9 @@ extern node_inl* parse_inlines_while(subject* subj, int (*f)(subject*))
node_inl** last = &result;
node_inl* first = NULL;
while ((*f)(subj) && parse_inline(subj, last)) {
- if (!first) {
- first = *last;
- }
+ if (!first) {
+ first = *last;
+ }
}
inline_stack* istack = subj->emphasis_openers;