diff --git a/.gitignore b/.gitignore

index 6ec8d5188d32b997f3a91f8a11f64f4e79b43770..4a40b62507e740b789fdb6d2d4a63c41e3b102c8 100644

--- a/.gitignore

+++ b/.gitignore

@@ -4,3 +4,5 @@ gmni

gmnlm

*.1

*.o

+*.a

+*.pc

diff --git a/Makefile b/Makefile

index 5ac44dbda9d629823f99124f67ace8125e9bc697..22fed5d19ec4f989ead26d22d3d1887614e4927c 100644

--- a/Makefile

+++ b/Makefile

@@ -1,6 +1,7 @@

.POSIX:

.SUFFIXES:

OUTDIR=.build

+VERSION=0.0.0

include $(OUTDIR)/config.mk

include $(OUTDIR)/cppcache

@@ -12,9 +13,26 @@ gmnlm: $(gmnlm_objects)

@printf 'CCLD\t$@\n'

@$(CC) $(LDFLAGS) -o $@ $(gmnlm_objects) $(LIBS)

+libgmni.a: $(libgmni.a_objects)

+ @printf 'AR\t$@\n'

+ @$(AR) -rcs $@ $(libgmni.a_objects)

+

doc/gmni.1: doc/gmni.scd

doc/gmnlm.1: doc/gmnlm.scd

+libgmni.pc:

+ @printf 'GEN\t$@\n'

+ @printf 'prefix=%s\n' "$(PREFIX)" > $@

+ @printf 'exec_prefix=${prefix}\n' >> $@

+ @printf 'includedir=${prefix}/include\n' >> $@

+ @printf 'libdir=${prefix}/lib\n' >> $@

+ @printf 'Name: libgmni\n' >> $@

+ @printf 'Version: %s\n' "$(VERSION)" >> $@

+ @printf 'Description: The gmni client library\n' >> $@

+ @printf 'Requires: libssl libcrypto\n' >> $@

+ @printf 'Cflags: -I${includedir}/gmni\n' >> $@

+ @printf 'Libs: -L${libdir} -lgmni\n' >> $@

+

.SUFFIXES: .c .o .scd .1

.c.o:

@@ -22,7 +40,7 @@ @printf 'CC\t$@\n'

@touch $(OUTDIR)/cppcache

@grep {body}lt; $(OUTDIR)/cppcache >/dev/null || \

$(CPP) $(CFLAGS) -MM -MT $@ {body}lt; >> $(OUTDIR)/cppcache

- @$(CC) -c $(CFLAGS) -o $@ {body}lt;

+ @$(CC) -c -fPIC $(CFLAGS) -o $@ {body}lt;

.scd.1:

@printf 'SCDOC\t$@\n'

@@ -31,7 +49,7 @@

docs: doc/gmni.1 doc/gmnlm.1

clean:

- @rm -f gmni gmnlm doc/gmni.1 doc/gmnlm.1 $(gmnlm_objects) $(gmni_objects)

+ @rm -f gmni gmnlm libgmni.a libgmni.pc doc/gmni.1 doc/gmnlm.1 $(gmnlm_objects) $(gmni_objects)

distclean: clean

@rm -rf "$(OUTDIR)"

@@ -41,6 +59,11 @@ mkdir -p $(BINDIR)

mkdir -p $(MANDIR)/man1

install -Dm755 gmni $(BINDIR)/gmni

install -Dm755 gmnlm $(BINDIR)/gmnlm

+ install -Dm755 libgmni.a $(LIBDIR)/libgmni.a

+ install -Dm644 include/gmni/gmni.h $(INCLUDEDIR)/gmni/gmni.h

+ install -Dm644 include/gmni/tofu.h $(INCLUDEDIR)/gmni/tofu.h

+ install -Dm644 include/gmni/url.h $(INCLUDEDIR)/gmni/url.h

+ install -Dm644 libgmni.pc $(LIBDIR)/pkgconfig

install -Dm644 doc/gmni.1 $(MANDIR)/man1/gmni.1

install -Dm644 doc/gmnlm.1 $(MANDIR)/man1/gmnlm.1

diff --git a/config.sh b/config.sh

index b03dfffbc5976e69d2d878dbf3b6c545ebcad377..a6963622d4b41d6e25bb4fe230c2513849c077d3 100644

--- a/config.sh

+++ b/config.sh

@@ -134,6 +134,7 @@ OUTDIR=${outdir}

_INSTDIR=\$(DESTDIR)\$(PREFIX)

BINDIR?=${BINDIR:-\$(_INSTDIR)/bin}

LIBDIR?=${LIBDIR:-\$(_INSTDIR)/lib}

+ INCLUDEDIR?=${INCLUDEDIR:-\$(_INSTDIR)/include}

MANDIR?=${MANDIR:-\$(_INSTDIR)/share/man}

CACHE=\$(OUTDIR)/cache

CFLAGS=${CFLAGS}

@@ -146,7 +147,7 @@ EOF

for target in $all

do

- $target >>"$outdir"/config.mk

+ ${target//./_} >>"$outdir"/config.mk

done

echo done

diff --git a/configure b/configure

index 44db11c4765077677a1f506f034c846cc736ab8c..e82a0e27e27c9873c8dac92fd3385e65bf511648 100755

--- a/configure

+++ b/configure

@@ -23,6 +23,21 @@ src/url.c \

src/util.c

}

-all="gmni gmnlm"

+libgmni_a() {

+ genrules libgmni.a \

+ src/client.c \

+ src/escape.c \

+ src/tofu.c \

+ src/url.c \

+ src/util.c \

+ src/parser.c

+}

+

+libgmni_pc() {

+ :

+}

+

+all="gmni gmnlm libgmni.a libgmni.pc"

+

run_configure

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

deleted file mode 100644

index 7e27b489d71fd3a43ca60292b17d56cab3caa5f8..0000000000000000000000000000000000000000

--- a/include/gmni.h

+++ /dev/null

@@ -1,164 +0,0 @@

-#ifndef GEMINI_CLIENT_H

-#define GEMINI_CLIENT_H

-#include <netdb.h>

-#include <openssl/ssl.h>

-#include <stdbool.h>

-#include <sys/socket.h>

-

-enum gemini_result {

- GEMINI_OK,

- GEMINI_ERR_OOM,

- GEMINI_ERR_INVALID_URL,

- GEMINI_ERR_NOT_GEMINI,

- GEMINI_ERR_RESOLVE,

- GEMINI_ERR_CONNECT,

- GEMINI_ERR_SSL,

- GEMINI_ERR_SSL_VERIFY,

- GEMINI_ERR_IO,

- GEMINI_ERR_PROTOCOL,

-};

-

-enum gemini_status {

- GEMINI_STATUS_INPUT = 10,

- GEMINI_STATUS_SENSITIVE_INPUT = 11,

- GEMINI_STATUS_SUCCESS = 20,

- GEMINI_STATUS_REDIRECT_TEMPORARY = 30,

- GEMINI_STATUS_REDIRECT_PERMANENT = 31,

- GEMINI_STATUS_TEMPORARY_FAILURE = 40,

- GEMINI_STATUS_SERVER_UNAVAILABLE = 41,

- GEMINI_STATUS_CGI_ERROR = 42,

- GEMINI_STATUS_PROXY_ERROR = 43,

- GEMINI_STATUS_SLOW_DOWN = 44,

- GEMINI_STATUS_PERMANENT_FAILURE = 50,

- GEMINI_STATUS_NOT_FOUND = 51,

- GEMINI_STATUS_GONE = 52,

- GEMINI_STATUS_PROXY_REQUEST_REFUSED = 53,

- GEMINI_STATUS_BAD_REQUEST = 59,

- GEMINI_STATUS_CLIENT_CERTIFICATE_REQUIRED = 60,

- GEMINI_STATUS_CERTIFICATE_NOT_AUTHORIZED = 61,

- GEMINI_STATUS_CERTIFICATE_NOT_VALID = 62,

-};

-

-enum gemini_status_class {

- GEMINI_STATUS_CLASS_INPUT = 10,

- GEMINI_STATUS_CLASS_SUCCESS = 20,

- GEMINI_STATUS_CLASS_REDIRECT = 30,

- GEMINI_STATUS_CLASS_TEMPORARY_FAILURE = 40,

- GEMINI_STATUS_CLASS_PERMANENT_FAILURE = 50,

- GEMINI_STATUS_CLASS_CLIENT_CERTIFICATE_REQUIRED = 60,

-};

-

-struct gemini_response {

- enum gemini_status status;

- char *meta;

-

- // Response body may be read from here if appropriate:

- BIO *bio;

-

- // Connection state

- SSL_CTX *ssl_ctx;

- SSL *ssl;

- int fd;

-};

-

-struct gemini_options {

- // If NULL, an SSL context will be created. If unset, the ssl field

- // must also be NULL.

- SSL_CTX *ssl_ctx;

-

- // If ai_family != AF_UNSPEC (the default value on most systems), the

- // client will connect to this address and skip name resolution.

- struct addrinfo *addr;

-

- // If non-NULL, these hints are provided to getaddrinfo. Useful, for

- // example, to force IPv4/IPv6.

- struct addrinfo *hints;

-};

-

-// Requests the specified URL via the gemini protocol. If options is non-NULL,

-// it may specify some additional configuration to adjust client behavior.

-//

-// Returns a value indicating the success of the request.

-//

-// Caller must call gemini_response_finish afterwards to clean up resources

-// before exiting or re-using it for another request.

-enum gemini_result gemini_request(const char *url,

- struct gemini_options *options,

- struct gemini_response *resp);

-

-// Must be called after gemini_request in order to free up the resources

-// allocated during the request.

-void gemini_response_finish(struct gemini_response *resp);

-

-// Returns a user-friendly string describing an error.

-const char *gemini_strerr(enum gemini_result r, struct gemini_response *resp);

-

-// Returns the given URL with the input response set to the specified value.

-// The caller must free the string.

-char *gemini_input_url(const char *url, const char *input);

-

-// Returns the general response class (i.e. with the second digit set to zero)

-// of the given Gemini status code.

-enum gemini_status_class gemini_response_class(enum gemini_status status);

-

-enum gemini_tok {

- GEMINI_TEXT,

- GEMINI_LINK,

- GEMINI_PREFORMATTED_BEGIN,

- GEMINI_PREFORMATTED_END,

- GEMINI_PREFORMATTED_TEXT,

- GEMINI_HEADING,

- GEMINI_LIST_ITEM,

- GEMINI_QUOTE,

-};

-

-struct gemini_token {

- enum gemini_tok token;

-

- // The token field determines which of the union members is valid.

- union {

- char *text;

-

- struct {

- char *text;

- char *url; // May be NULL

- } link;

-

- char *preformatted;

-

- struct {

- char *title;

- int level; // 1, 2, or 3

- } heading;

-

- char *list_item;

- char *quote_text;

- };

-};

-

-struct gemini_parser {

- BIO *f;

- char *buf;

- size_t bufsz;

- size_t bufln;

- bool preformatted;

-};

-

-// Initializes a text/gemini parser which reads from the specified BIO.

-void gemini_parser_init(struct gemini_parser *p, BIO *f);

-

-// Finishes this text/gemini parser and frees up its resources.

-void gemini_parser_finish(struct gemini_parser *p);

-

-// Reads the next token from a text/gemini file.

-//

-// Returns 0 on success, 1 on EOF, and -1 on failure.

-//

-// Caller must call gemini_token_finish before exiting or re-using the token

-// parameter.

-int gemini_parser_next(struct gemini_parser *p, struct gemini_token *token);

-

-// Must be called after gemini_next to free up resources for the next token.

-void gemini_token_finish(struct gemini_token *token);

-

-#endif

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

deleted file mode 100644

index a88167ba0fb6606b2b170e5005c55131f1861972..0000000000000000000000000000000000000000

--- a/include/tofu.h

+++ /dev/null

@@ -1,49 +0,0 @@

-#ifndef GEMINI_TOFU_H

-#define GEMINI_TOFU_H

-#include <limits.h>

-#include <openssl/ssl.h>

-#include <openssl/x509.h>

-#include <time.h>

-

-enum tofu_error {

- TOFU_VALID,

- // Expired, wrong CN, etc.

- TOFU_INVALID_CERT,

- // Cert is valid but we haven't seen it before

- TOFU_UNTRUSTED_CERT,

- // Cert is valid but we already trust another cert for this host

- TOFU_FINGERPRINT_MISMATCH,

-};

-

-enum tofu_action {

- TOFU_ASK,

- TOFU_FAIL,

- TOFU_TRUST_ONCE,

- TOFU_TRUST_ALWAYS,

-};

-

-struct known_host {

- char *host, *fingerprint;

- time_t expires;

- int lineno;

- struct known_host *next;

-};

-

-// Called when the user needs to be prompted to agree to trust an unknown

-// certificate. Return true to trust this certificate.

-typedef enum tofu_action (tofu_callback_t)(enum tofu_error error,

- const char *fingerprint, struct known_host *host, void *data);

-

-struct gemini_tofu {

- char known_hosts_path[PATH_MAX+1];

- struct known_host *known_hosts;

- int lineno;

- tofu_callback_t *callback;

- void *cb_data;

-};

-

-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/include/url.h b/include/url.h

deleted file mode 100644

index 155fd55740dbe47a498062713aca217ab259734f..0000000000000000000000000000000000000000

--- a/include/url.h

+++ /dev/null

@@ -1,103 +0,0 @@

-#ifndef URLAPI_H

-#define URLAPI_H

-/***************************************************************************

- * _ _ ____ _

- * Project ___| | | | _ \| |

- * / __| | | | |_) | |

- * | (__| |_| | _ <| |___

- * \___|\___/|_| \_\_____|

- *

- * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.

- *

- * This software is licensed as described in the file COPYING, which

- * you should have received as part of this distribution. The terms

- * are also available at https://curl.haxx.se/docs/copyright.html.

- *

- * You may opt to use, copy, modify, merge, publish, distribute and/or sell

- * copies of the Software, and permit persons to whom the Software is

- * furnished to do so, under the terms of the COPYING file.

- *

- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY

- * KIND, either express or implied.

- *

- ***************************************************************************/

-

-/* the error codes for the URL API */

-typedef enum {

- CURLUE_OK,

- CURLUE_BAD_HANDLE, /* 1 */

- CURLUE_BAD_PARTPOINTER, /* 2 */

- CURLUE_MALFORMED_INPUT, /* 3 */

- CURLUE_BAD_PORT_NUMBER, /* 4 */

- CURLUE_UNSUPPORTED_SCHEME, /* 5 */

- CURLUE_URLDECODE, /* 6 */

- CURLUE_OUT_OF_MEMORY, /* 7 */

- CURLUE_USER_NOT_ALLOWED, /* 8 */

- CURLUE_UNKNOWN_PART, /* 9 */

- CURLUE_NO_SCHEME, /* 10 */

- CURLUE_NO_USER, /* 11 */

- CURLUE_NO_PASSWORD, /* 12 */

- CURLUE_NO_OPTIONS, /* 13 */

- CURLUE_NO_HOST, /* 14 */

- CURLUE_NO_PORT, /* 15 */

- CURLUE_NO_QUERY, /* 16 */

- CURLUE_NO_FRAGMENT /* 17 */

-} CURLUcode;

-

-typedef enum {

- CURLUPART_URL,

- CURLUPART_SCHEME,

- CURLUPART_USER,

- CURLUPART_PASSWORD,

- CURLUPART_OPTIONS,

- CURLUPART_HOST,

- CURLUPART_PORT,

- CURLUPART_PATH,

- CURLUPART_QUERY,

- CURLUPART_FRAGMENT

-} CURLUPart;

-

-#define CURLU_PATH_AS_IS (1<<4) /* leave dot sequences */

-#define CURLU_DISALLOW_USER (1<<5) /* no user+password allowed */

-#define CURLU_URLDECODE (1<<6) /* URL decode on get */

-#define CURLU_URLENCODE (1<<7) /* URL encode on set */

-#define CURLU_APPENDQUERY (1<<8) /* append a form style part */

-

-typedef struct Curl_URL CURLU;

-

-/*

- * curl_url() creates a new CURLU handle and returns a pointer to it.

- * Must be freed with curl_url_cleanup().

- */

-struct Curl_URL *curl_url(void);

-

-/*

- * curl_url_cleanup() frees the CURLU handle and related resources used for

- * the URL parsing. It will not free strings previously returned with the URL

- * API.

- */

-void curl_url_cleanup(struct Curl_URL *handle);

-

-/*

- * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new

- * handle must also be freed with curl_url_cleanup().

- */

-struct Curl_URL *curl_url_dup(struct Curl_URL *in);

-

-/*

- * curl_url_get() extracts a specific part of the URL from a CURLU

- * handle. Returns error code. The returned pointer MUST be freed with

- * free() afterwards.

- */

-CURLUcode curl_url_get(struct Curl_URL *handle, CURLUPart what,

- char **part, unsigned int flags);

-

-/*

- * curl_url_set() sets a specific part of the URL in a CURLU handle. Returns

- * error code. The passed in string will be copied. Passing a NULL instead of

- * a part string, clears that part.

- */

-CURLUcode curl_url_set(struct Curl_URL *handle, CURLUPart what,

- const char *part, unsigned int flags);

-

-#endif

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

index 398e133b182af7f2fbe907be83cd5ecdc1b3a671..8b6b9e7a3c09656a5d59ed86977cb8b5c39541d4 100644

--- a/src/client.c

+++ b/src/client.c

@@ -9,8 +9,8 @@ #include <string.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <unistd.h>

-#include "gmni.h"

-#include "url.h"

+#include <gmni/gmni.h>

+#include <gmni/url.h>

static enum gemini_result

gemini_get_addrinfo(struct Curl_URL *uri, struct gemini_options *options,

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

index 61f41e29c324d980bd3a1f270e78649cdcec88e0..e8b25f9060c021bda099aa97fc2ec5657aeb2672 100644

--- a/src/gmni.c

+++ b/src/gmni.c

@@ -12,8 +12,8 @@ #include <sys/socket.h>

#include <sys/types.h>

#include <termios.h>

#include <unistd.h>

-#include "gmni.h"

-#include "tofu.h"

+#include <gmni/gmni.h>

+#include <gmni/tofu.h>

#include "util.h"

static void

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

index 245ec85fa59fbcabde7e49c7d4d6e978aefa5f9a..2fba84e1f3c9e312ac53417d1b873158ceaf8c73 100644

--- a/src/gmnlm.c

+++ b/src/gmnlm.c

@@ -14,9 +14,9 @@ #include <sys/stat.h>

#include <sys/wait.h>

#include <termios.h>

#include <unistd.h>

-#include "gmni.h"

-#include "tofu.h"

-#include "url.h"

+#include <gmni/gmni.h>

+#include <gmni/tofu.h>

+#include <gmni/url.h>

#include "util.h"

struct link {

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

index 579415150f842557b6faf4097a63aac9324928fb..ad2c0e6306872f8dde450063ba8815e2ee7e314a 100644

--- a/src/parser.c

+++ b/src/parser.c

@@ -5,7 +5,7 @@ #include <stdbool.h>

#include <stddef.h>

#include <stdlib.h>

#include <string.h>

-#include "gmni.h"

+#include <gmni/gmni.h>

void

gemini_parser_init(struct gemini_parser *p, BIO *f)

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

index 48a627fe131996070d991bcc54bb7bcb40fddce4..863efc644a691370ee58ef0fb92291e9471adeb9 100644

--- a/src/tofu.c

+++ b/src/tofu.c

@@ -10,8 +10,8 @@ #include <openssl/x509v3.h>

#include <stdio.h>

#include <string.h>

#include <time.h>

-#include "gmni.h"

-#include "tofu.h"

+#include <gmni/gmni.h>

+#include <gmni/tofu.h>

#include "util.h"

static int

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

index dabf45f234e1e0957007b94ef12ef1125250dfea..47741e4ab1f91d336e5aba8117bde01094e370c3 100644

--- a/src/url.c

+++ b/src/url.c

@@ -31,7 +31,7 @@ #include <stdlib.h>

#include <string.h>

#include <strings.h>

#include "escape.h"

-#include "url.h"

+#include <gmni/url.h>

/* Provided by gmni */

static char *

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

index 360c99ac95de9dcfce860610aeef85e8986e99c2..0a479af3ea734ce25d0806aa086e61bef6b8953b 100644

--- a/src/util.c

+++ b/src/util.c

@@ -7,7 +7,7 @@ #include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/stat.h>

-#include "gmni.h"

+#include <gmni/gmni.h>

#include "util.h"

static void