diff --git a/include/tofu.h b/include/tofu.h

index 29aa9bc21567868cafb25a09dbc25ea0685ab01c..a88167ba0fb6606b2b170e5005c55131f1861972 100644

--- a/include/tofu.h

+++ b/include/tofu.h

@@ -44,5 +44,6 @@ };

void gemini_tofu_init(struct gemini_tofu *tofu,

SSL_CTX *ssl_ctx, tofu_callback_t *cb, void *data);

+void gemini_tofu_finish(struct gemini_tofu *tofu);

#endif

diff --git a/src/client.c b/src/client.c

index 2d39f56cb8e45f7b708e5a5dea1ec6fa84e188c9..bb508e04c8d41fe12aa99ae42b8fa30d4f3a9a2e 100644

--- a/src/client.c

+++ b/src/client.c

@@ -118,11 +118,14 @@ goto cleanup;

} else {

if (strcmp(scheme, "gemini") != 0) {

res = GEMINI_ERR_NOT_GEMINI;

+ free(scheme);

goto cleanup;

}

+ free(scheme);

}

if (curl_url_get(uri, CURLUPART_HOST, &host, 0) != CURLUE_OK) {

res = GEMINI_ERR_INVALID_URL;

+ free(host);

goto cleanup;

}

@@ -139,6 +142,7 @@ int r;

BIO *sbio = BIO_new(BIO_f_ssl());

res = gemini_connect(uri, options, resp, &resp->fd);

if (res != GEMINI_OK) {

+ free(host);

goto cleanup;

}

@@ -146,11 +150,14 @@ resp->ssl = SSL_new(resp->ssl_ctx);

assert(resp->ssl);

SSL_set_connect_state(resp->ssl);

if ((r = SSL_set1_host(resp->ssl, host)) != 1) {

+ free(host);

goto ssl_error;

}

if ((r = SSL_set_tlsext_host_name(resp->ssl, host)) != 1) {

+ free(host);

goto ssl_error;

}

+ free(host);

if ((r = SSL_set_fd(resp->ssl, resp->fd)) != 1) {

goto ssl_error;

}

@@ -235,15 +242,16 @@ resp->fd = -1;

}

if (resp->bio) {

- BIO_free(BIO_pop(resp->bio)); // ssl bio

- BIO_free(resp->bio); // buffered bio

+ BIO_free_all(resp->bio);

resp->bio = NULL;

}

if (resp->ssl) {

SSL_free(resp->ssl);

}

- SSL_CTX_free(resp->ssl_ctx);

+ if (resp->ssl_ctx) {

+ SSL_CTX_free(resp->ssl_ctx);

+ }

free(resp->meta);

resp->ssl = NULL;

diff --git a/src/gmni.c b/src/gmni.c

index c13e0cd55623f557a8dc676d69aea54661b11faf..4af98fbbe70b09ce5a7fce46863438b0487d8738 100644

--- a/src/gmni.c

+++ b/src/gmni.c

@@ -336,6 +336,8 @@ next:

gemini_response_finish(&resp);

}

+ SSL_CTX_free(opts.ssl_ctx);

free(url);

+ gemini_tofu_finish(&cfg.tofu);

return ret;

}

diff --git a/src/gmnlm.c b/src/gmnlm.c

index 5d45ffe7372f71ab32461bf71a590cc494269e96..a2717fb84c76fd50a000ceda540af54c60396252 100644

--- a/src/gmnlm.c

+++ b/src/gmnlm.c

@@ -83,6 +83,7 @@ if (!history) {

return;

}

history_free(history->next);

+ free(history->url);

free(history);

}

@@ -92,6 +93,9 @@ {

if (curl_url_set(browser->url, CURLUPART_URL, new_url, 0) != CURLUE_OK) {

fprintf(stderr, "Error: invalid URL\n");

return false;

+ }

+ if (browser->plain_url != NULL) {

+ free(browser->plain_url);

}

curl_url_get(browser->url, CURLUPART_URL, &browser->plain_url, 0);

if (history) {

@@ -130,17 +134,19 @@

static void

save_bookmark(struct browser *browser)

{

- const char *path_fmt = get_data_pathfmt();

+ char *path_fmt = get_data_pathfmt();

static char path[PATH_MAX+1];

snprintf(path, sizeof(path), path_fmt, "bookmarks.gmi");

if (mkdirs(dirname(path), 0755) != 0) {

snprintf(path, sizeof(path), path_fmt, "bookmarks.gmi");

+ free(path_fmt);

fprintf(stderr, "Error creating directory %s: %s\n",

dirname(path), strerror(errno));

return;

}

snprintf(path, sizeof(path), path_fmt, "bookmarks.gmi");

+ free(path_fmt);

FILE *f = fopen(path, "a");

if (!f) {

fprintf(stderr, "Error opening %s for writing: %s\n",

@@ -150,7 +156,7 @@ }

char *title = browser->page_title;

if (title) {

- title = trim_ws(browser->page_title);

+ title = trim_ws(strdup(browser->page_title));

}

fprintf(f, "=> %s%s%s\n", browser->plain_url,

@@ -159,6 +165,9 @@ fclose(f);

fprintf(browser->tty, "Bookmark saved: %s\n",

title ? title : browser->plain_url);

+ if (title != NULL) {

+ free(title);

+ }

}

static void

@@ -411,12 +420,14 @@ col += fprintf(out, " ");

}

break;

case GEMINI_PREFORMATTED_BEGIN:

+ gemini_token_finish(&tok);

+ /* fallthrough */

case GEMINI_PREFORMATTED_END:

continue; // Not used

case GEMINI_PREFORMATTED_TEXT:

col += fprintf(out, "` ");

if (text == NULL) {

- text = tok.text;

+ text = tok.preformatted;

}

break;

case GEMINI_HEADING:

@@ -484,6 +495,9 @@ if (!text[0]) {

text = NULL;

}

}

+ if (text == NULL) {

+ gemini_token_finish(&tok);

+ }

while (col >= ws.ws_col) {

col -= ws.ws_col;

@@ -510,8 +524,16 @@ case PROMPT_MORE:

break;

case PROMPT_QUIT:

browser->running = false;

+ if (text != NULL) {

+ gemini_token_finish(&tok);

+ }

+ gemini_parser_finish(&p);

return true;

case PROMPT_ANSWERED:

+ if (text != NULL) {

+ gemini_token_finish(&tok);

+ }

+ gemini_parser_finish(&p);

return true;

case PROMPT_NEXT:

searching = true;

@@ -523,6 +545,7 @@ row = col = 0;

}

}

+ gemini_token_finish(&tok);

gemini_parser_finish(&p);

return false;

}

@@ -617,6 +640,7 @@ CURLUcode uc = curl_url_get(browser->url,

CURLUPART_SCHEME, &scheme, 0);

assert(uc == CURLUE_OK); // Invariant

if (strcmp(scheme, "file") == 0) {

+ free(scheme);

requesting = false;

char *path;

@@ -630,6 +654,7 @@

FILE *fp = fopen(path, "r");

if (!fp) {

resp->status = GEMINI_STATUS_NOT_FOUND;

+ free(path);

break;

}

@@ -643,9 +668,14 @@ resp->meta = strdup("text/plain");

} else {

resp->meta = strdup("application/x-octet-stream");

}

+ free(path);

resp->status = GEMINI_STATUS_SUCCESS;

+ resp->fd = -1;

+ resp->ssl = NULL;

+ resp->ssl_ctx = NULL;

return display_response(browser, resp);

}

+ free(scheme);

enum gemini_result res = gemini_request(browser->plain_url,

&browser->opts, resp);

@@ -672,7 +702,7 @@ break;

case GEMINI_STATUS_CLASS_REDIRECT:

if (++nredir >= 5) {

requesting = false;

- fprintf(stderr, "Error: maximum redirects (5) exceeded");

+ fprintf(stderr, "Error: maximum redirects (5) exceeded\n");

break;

}

fprintf(stderr, "Following redirect to %s\n", resp->meta);

@@ -816,6 +846,7 @@ browser.unicode = false;

break;

default:

fprintf(stderr, "fatal: unknown flag %c\n", c);

+ curl_url_cleanup(browser.url);

return 1;

}

}

@@ -841,6 +872,7 @@ while (browser.running) {

static char prompt[4096];

if (do_requests(&browser, &resp)) {

// Skip prompts

+ gemini_response_finish(&resp);

goto next;

}

@@ -880,8 +912,16 @@ }

browser.links = NULL;

}

- history_free(browser.history);

+ gemini_tofu_finish(&browser.tofu);

+ struct history *hist = browser.history;

+ while (hist && hist->prev) {

+ hist = hist->prev;

+ }

+ history_free(hist);

SSL_CTX_free(browser.opts.ssl_ctx);

curl_url_cleanup(browser.url);

+ free(browser.page_title);

+ free(browser.plain_url);

+ fclose(browser.tty);

return 0;

}

diff --git a/src/parser.c b/src/parser.c

index 5b0f01399d934f593cdf987e096dd2cfb134d114..2f78e4641c08b86f5dc50cc9776ea80ec7f9aead 100644

--- a/src/parser.c

+++ b/src/parser.c

@@ -35,15 +35,14 @@ {

memset(tok, 0, sizeof(*tok));

int eof = 0;

- while (!strstr(p->buf, "\n")) {

- if (p->bufln == p->bufsz) {

+ while (!strchr(p->buf, '\n')) {

+ while (p->bufln >= p->bufsz - 1) {

p->bufsz *= 2;

- char *buf = realloc(p->buf, p->bufsz);

- assert(buf);

- p->buf = buf;

+ p->buf = realloc(p->buf, p->bufsz);

+ assert(p->buf);

}

- int n = BIO_read(p->f, &p->buf[p->bufln], p->bufsz - p->bufln);

+ ssize_t n = BIO_read(p->f, &p->buf[p->bufln], p->bufsz - p->bufln - 1);

if (n == -1) {

return -1;

} else if (n == 0) {

@@ -55,7 +54,7 @@ p->buf[p->bufln] = 0;

}

char *end;

- if ((end = strstr(p->buf, "\n")) != NULL) {

+ if ((end = strchr(p->buf, '\n')) != NULL) {

*end = 0;

}

diff --git a/src/tofu.c b/src/tofu.c

index 354e211c856451bdea120bbb1aed5917099eb285..45e1275d568cfcb4aacfe7f86788b76fff97916b 100644

--- a/src/tofu.c

+++ b/src/tofu.c

@@ -150,7 +150,7 @@ {.var = "GMNIDATA", .path = "/%s"},

{.var = "XDG_DATA_HOME", .path = "/gmni/%s"},

{.var = "HOME", .path = "/.local/share/gmni/%s"}

};

- const char *path_fmt = getpath(paths, sizeof(paths) / sizeof(paths[0]));

+ char *path_fmt = getpath(paths, sizeof(paths) / sizeof(paths[0]));

snprintf(tofu->known_hosts_path, sizeof(tofu->known_hosts_path),

path_fmt, "known_hosts");

@@ -164,6 +164,7 @@ }

snprintf(tofu->known_hosts_path, sizeof(tofu->known_hosts_path),

path_fmt, "known_hosts");

+ free(path_fmt);

tofu->callback = cb;

tofu->cb_data = cb_data;

@@ -175,6 +176,7 @@ return;

}

size_t n = 0;

char *line = NULL;

+ tofu->known_hosts = NULL;

while (getline(&line, &n, f) != -1) {

struct known_host *host = calloc(1, sizeof(struct known_host));

char *tok = strtok(line, " ");

@@ -184,6 +186,7 @@

tok = strtok(NULL, " ");

assert(tok);

if (strcmp(tok, "SHA-512") != 0) {

+ free(host->host);

free(host);

continue;

}

@@ -198,5 +201,20 @@ host->expires = strtoul(tok, NULL, 10);

host->next = tofu->known_hosts;

tofu->known_hosts = host;

+ }

+ free(line);

+ fclose(f);

+}

+

+void

+gemini_tofu_finish(struct gemini_tofu *tofu)

+{

+ struct known_host *host = tofu->known_hosts;

+ while (host) {

+ struct known_host *tmp = host;

+ host = host->next;

+ free(tmp->host);

+ free(tmp->fingerprint);

+ free(tmp);

}

}