add a manpage and README, and update the code to reflect the light changes - libgcgi - REST library for Gopher
(HTM) git clone git://bitreich.org/libgcgi git://hg6vgqziawt5s4dj.onion/libgcgi
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
(DIR) LICENSE
---
(DIR) commit f1f14c75ca3477d51d1a4e09b917a5e5f869e672
(DIR) parent 052f666afd7390d53ec4b3ad91882e7e76b7a49f
(HTM) Author: Josuah Demangeon <me@josuah.net>
Date: Tue, 2 Aug 2022 13:20:15 +0200
add a manpage and README, and update the code to reflect the light changes
Diffstat:
M Makefile | 3 +++
A README | 199 +++++++++++++++++++++++++++++++
M db/vars | 2 +-
D gph/404.gph | 1 -
A gph/page_not_found.gph | 1 +
M index.c | 8 ++++----
A libgcgi.3 | 339 +++++++++++++++++++++++++++++++
M libgcgi.c | 28 ++++++++++++++++++++++------
M libgcgi.h | 4 +---
9 files changed, 570 insertions(+), 15 deletions(-)
---
(DIR) diff --git a/Makefile b/Makefile
@@ -6,5 +6,8 @@ all: index.cgi
clean:
rm -f *.o index.cgi
+README: libgcgi.3
+ mandoc -Tutf8 libgcgi.3 | col -b | sed '1h; $$g' >$@
+
index.cgi: index.c libgcgi.c libgcgi.h
${CC} ${LDFLAGS} ${CFLAGS} -o $@ index.c libgcgi.c
(DIR) diff --git a/README b/README
@@ -0,0 +1,199 @@
+LIBGCGI(3) Library Functions Manual LIBGCGI(3)
+
+NAME
+ gcgi_handle_request, gcgi_fatal, gcgi_template, gcgi_set_var,
+ gcgi_get_var, gcgi_free_var_list, gcgi_read_var_list,
+ gcgi_write_var_list, gcgi_gopher_search, gcgi_gopher_path,
+ gcgi_gopher_query, gcgi_gopher_host, gcgi_gopher_port, REST library for
+ Gopher
+
+SYNOPSIS
+ #include <libgcgi.h>
+
+ void
+ gcgi_handle_request(struct gcgi_handler h[], char **argv, int argc);
+
+ void
+ gcgi_fatal(char *fmt, ...);
+
+ void
+ gcgi_template(char const *path, struct gcgi_var_list *vars);
+
+ void
+ gcgi_set_var(struct gcgi_var_list *vars, char *key, char *val);
+
+ char *
+ gcgi_get_var(struct gcgi_var_list *vars, char *key);
+
+ void
+ gcgi_free_var_list(struct gcgi_var_list *vars);
+
+ void
+ gcgi_read_var_list(struct gcgi_var_list *vars, char *path);
+
+ int
+ gcgi_write_var_list(struct gcgi_var_list *vars, char *path);
+
+ char *gcgi_gopher_search
+ char *gcgi_gopher_path
+ char *gcgi_gopher_host
+ char *gcgi_gopher_port
+ struct gcgi_var_list gcgi_gopher_query
+
+DESCRIPTION
+ Request Handling
+ The central element of the library is an array of structures, using
+ appropriate handler depending on the query path.
+
+ struct gcgi_handler {
+ char const *glob;
+ void (*fn)(char **matches);
+ };
+
+ The glob is a string against which the path (everything in the query
+ before the ?) will be matched against.
+
+ The fn function pointer will be called, with an array of matches passed
+ as argument. There are as many matches populated as there are * in
+ glob.
+
+ void gcgi_handle_request(struct gcgi_handler h[], int argc, char **argv)
+ Given an array of handlers h, call the first function pointer
+ that matches. argc and argv should be set to the program ones to
+ extract the arguments given by geomyidae(8). The h struct is an
+ array of struct gcgi_handler:
+
+ Content Generation
+ According to geomyidae(8) behavior, the output format will be:
+ a raw gophermap if the binary is index.cgi,
+ a geomyidae(8) gph format if the binary is index.dcgi.
+
+ void gcgi_fatal(char *fmt, ...)
+ Prints an error message formatted by fmt and exit(3) the program
+ with status 1.
+
+ void gcgi_template(char const *path, struct gcgi_var_list *vars)
+ Format the template at path replacing every occurence of
+ {{key}} by the matching value by searching in vars.
+
+ void gcgi_print_gophermap(char type, char *desc, char *path, char *host,
+ char *port)
+ Print a gophermap entry line with type, desc, path, host, port to
+ be set to the chosen value as described in RFC 1436. Both host
+ and port are NULL, default values will be used.
+
+
+ void gcgi_print_gph(char type, char *desc, char *path, char *host, char
+ *port)
+ Print a gph entry line with type, desc, path, host, port to be
+ set to the chosen value as described in geomyidae(8) manual page.
+ If host or port are NULL, default values will be used.
+
+ Variable List Handling
+ A common data format is used for handling lists of variables:
+ For parsing a simple text-based database format and writing it back.
+ For storing the parsed query string in gcgi_gopher_query.
+ For passing variables to expand in the templates.
+
+ void gcgi_set_var(struct gcgi_var_list *vars, char *key, char *val)
+ Overwrite with val the value of a variable matching key of vars.
+ The key and val buffers are not duplicated, and must remain valid
+ at all time they need to be accessible, such as through
+ gcgi_get_var().
+
+ char * gcgi_get_var(struct gcgi_var_list *vars, char *key)
+ Get the value of the variable of vars matching key or NULL if
+ none match.
+
+ void gcgi_free_var_list(struct gcgi_var_list *vars)
+ Free memory used by a list of variable. This only frees the
+ memory allocated by this library.
+
+ void gcgi_read_var_list(struct gcgi_var_list *vars, char *path)
+ Store all variables from path onto variables in vars. The file
+ format is similar to RFC822 messages or HTTP headers:
+ One line per variable, with a key=value format.
+ The key is everything at the beginning of the line until the
+ occurence of :.
+ The value is everything after : .
+ After the list of variables, an empty line declares the body
+ of the message, which continues until the end and is stored in
+ a text key.
+
+ int gcgi_write_var_list(struct gcgi_var_list *vars, char *path)
+ Encode the variable list vars into a new file at path. A
+ temporary file will be created in the meantime, and the
+ replacement will be atomic so that no partial write can occur.
+ The text special key will be turned into the body of the
+ message after an empty line instead of a variable on its own
+ line.
+
+ Global Variables
+ These variables are filled with the components of the query. They will
+ only be valid after handle_request() is called.
+
+ char *gcgi_gopher_search
+ From argv[1], this is the search string, passed after a tab in
+ the gopher protocol for item type 7.
+
+ char *gcgi_gopher_path
+ From argv[2], this is the query path. It is the full query
+ without the search string and with the query string removed.
+
+ struct gcgi_var_list gcgi_gopher_query
+ From argv[2], this is the query string stored as a key-value
+ gcgi_var_list. It is extracted from the part of the query after
+ the ?, usually formated as
+ ?key1=value1&key2=value2&key3=value3
+
+ char *gcgi_gopher_host
+ From argv[3], this is the current host name configured in
+ geomyidae(8). It is what to use as a host in links printed
+ out.
+
+ char *gcgi_gopher_port
+ From argv[4], this is the current port number configured in
+ geomyidae(8). It is what to use as a port in links printed
+ out.
+
+EXAMPLES
+ #include "libgcgi.h"
+
+ /* implementation of each handler here */
+
+ static struct gcgi_handler handlers[] = {
+ { "/", page_home },
+ { "/song", page_song_list },
+ { "/song/*", page_song_item },
+ { "*", page_not_found },
+ { NULL, NULL },
+ };
+
+ int
+ main(int argc, char **argv)
+ {
+ /* privilege dropping, chroot and/or syscall restriction here */
+
+ gcgi_handle_request(handlers, argv, argc);
+ return 0;
+ }
+
+ENVIRONMENT VARIABLES
+ libgcgi does not use environment variable, but the application code can
+ make use of them. The environment variables applied to geomyidae(8) will
+ be inherited and accessible.
+
+BUGS
+ To debug libgcgi, it is possible to call it on a command line, which will
+ show all logging and error messages displayed on stderr:
+
+ $ ./index.cgi "" "/song/never-bored-of-adventure?lyrics=1&comments=1" "" ""
+
+CAVEATS
+ The Gopher protocol is not designed for file upload. A dedicated file
+ upload protocol such as SFTP or FTP may be used instead.
+
+ The Gopher protocol is not designed for dynamic scripting. A dedicated
+ remote interface protocol such as SSH or telnet may be used instead.
+
+LIBGCGI(3) Library Functions Manual LIBGCGI(3)
(DIR) diff --git a/db/vars b/db/vars
@@ -1 +1 @@
-Variable-From-Db: Lucky 777 Hat
+name: world
(DIR) diff --git a/gph/404.gph b/gph/404.gph
@@ -1 +0,0 @@
-Hello world!
(DIR) diff --git a/gph/page_not_found.gph b/gph/page_not_found.gph
@@ -0,0 +1 @@
+Hello {{name}}!
(DIR) diff --git a/index.c b/index.c
@@ -10,7 +10,7 @@
#endif
static void
-error_404(char **matches)
+error_page_not_found(char **matches)
{
struct gcgi_var_list vars = {0};
char *var;
@@ -21,12 +21,12 @@ error_404(char **matches)
if ((var = gcgi_get_var(&gcgi_gopher_query, "var")) != NULL)
printf("I got the $var though! -> '%s'\n", var);
- gcgi_template("gph/404.gph", &vars);
+ gcgi_template("gph/error_page_not_found.gph", &vars);
}
static struct gcgi_handler handlers[] = {
- { "*", error_404 },
- { NULL, NULL },
+ { "*", error_page_not_found },
+ { NULL, NULL },
};
int
(DIR) diff --git a/libgcgi.3 b/libgcgi.3
@@ -0,0 +1,339 @@
+.Dd $Mdocdate: August 01 2022 $
+.Dt LIBGCGI 3
+.Os
+.
+.
+.Sh NAME
+.
+.Nm gcgi_handle_request ,
+.Nm gcgi_fatal ,
+.Nm gcgi_template ,
+.Nm gcgi_set_var ,
+.Nm gcgi_get_var ,
+.Nm gcgi_free_var_list ,
+.Nm gcgi_read_var_list ,
+.Nm gcgi_write_var_list ,
+.Nm gcgi_gopher_search ,
+.Nm gcgi_gopher_path ,
+.Nm gcgi_gopher_query ,
+.Nm gcgi_gopher_host ,
+.Nm gcgi_gopher_port ,
+.Nd REST library for Gopher
+.
+.
+.Sh SYNOPSIS
+.
+.In libgcgi.h
+.
+.Ft "void" Fn gcgi_handle_request "struct gcgi_handler h[]" "char **argv" "int argc"
+.Ft "void" Fn gcgi_fatal "char *fmt" "..."
+.Ft "void" Fn gcgi_template "char const *path" "struct gcgi_var_list *vars"
+.Ft "void" Fn gcgi_set_var "struct gcgi_var_list *vars" "char *key" "char *val"
+.Ft "char *" Fn gcgi_get_var "struct gcgi_var_list *vars" "char *key"
+.Ft "void" Fn gcgi_free_var_list "struct gcgi_var_list *vars"
+.Ft "void" Fn gcgi_read_var_list "struct gcgi_var_list *vars" "char *path"
+.Ft "int" Fn gcgi_write_var_list "struct gcgi_var_list *vars" "char *path"
+.Vt char *gcgi_gopher_search
+.Vt char *gcgi_gopher_path
+.Vt char *gcgi_gopher_host
+.Vt char *gcgi_gopher_port
+.Vt struct gcgi_var_list gcgi_gopher_query
+.
+.
+.Sh DESCRIPTION
+.
+.
+.Ss Request Handling
+.
+The central element of the library is an array of structures,
+using appropriate handler depending on the query path.
+.Pp
+.Bd -literal
+struct gcgi_handler {
+ char const *glob;
+ void (*fn)(char **matches);
+};
+.Ed
+.
+.Pp
+The
+.Vt glob
+is a string against which the path (everything in the query before the
+.Dq ? )
+will be matched against.
+.Pp
+The
+.Vt fn
+function pointer will be called, with an array of matches passed as argument.
+There are as many matches populated as there are
+.Dq "*"
+in
+.Vt glob .
+.
+.Pp
+.Bl -tag
+.
+.It Ft "void" Fn gcgi_handle_request "struct gcgi_handler h[]" "int argc" "char **argv"
+Given an array of handlers
+.Fa h ,
+call the first function pointer that matches.
+.Fa argc
+and
+.Fa argv
+should be set to the program ones to extract the arguments given by
+.Xr geomyidae 8 .
+The
+.Fa h
+struct is an array of
+.Vt struct gcgi_handler :
+.
+.El
+.
+.
+.Ss Content Generation
+.
+According to
+.Xr geomyidae 8
+behavior, the output format will be:
+.Bl -bullet -compact -width x
+.
+.It
+a raw gophermap if the binary is
+.Dq index.cgi ,
+.It
+a
+.Xr geomyidae 8
+.Sq gph
+format if the binary is
+.Dq index.dcgi .
+.El
+.
+.Pp
+.Bl -tag
+.
+.It Ft "void" Fn gcgi_fatal "char *fmt" "..."
+Prints an error message formatted by
+.Fa fmt
+and
+.Xr exit 3
+the program with status 1.
+.
+.It Ft "void" Fn gcgi_template "char const *path" "struct gcgi_var_list *vars"
+Format the template at
+.Fa path
+replacing every occurence of
+.Dq {{key}}
+by the matching value by searching in
+.Fa vars .
+.
+.It Vt void Fn gcgi_print_gophermap "char type" "char *desc" "char *path" "char *host" "char *port"
+Print a gophermap entry line with
+.Fa type ,
+.Fa desc ,
+.Fa path ,
+.Fa host ,
+.Fa port
+to be set to the chosen value as described in RFC 1436.
+Both
+.Fa host
+and
+.Fa port
+are NULL, default values will be used.
+
+.It Ft void Fn gcgi_print_gph "char type" "char *desc" "char *path" "char *host" "char *port"
+Print a gph entry line with
+.Fa type ,
+.Fa desc ,
+.Fa path ,
+.Fa host ,
+.Fa port
+to be set to the chosen value as described in
+.Xr geomyidae 8
+manual page.
+If
+.Fa host
+or
+.Fa port
+are NULL, default values will be used.
+.
+.El
+.
+.
+.Ss Variable List Handling
+.
+A common data format is used for handling lists of variables:
+.Bl -bullet -compact -width x
+.It
+For parsing a simple text-based database format and writing it back.
+.It
+For storing the parsed query string in
+.Vt gcgi_gopher_query .
+.It
+For passing variables to expand in the templates.
+.El
+.
+.Pp
+.Bl -tag
+.
+.It Ft "void" Fn gcgi_set_var "struct gcgi_var_list *vars" "char *key" "char *val"
+Overwrite with
+.Fa val
+the value of a variable matching
+.Fa key
+of
+.Fa vars .
+The
+.Fa key
+and
+.Fa val
+buffers are not duplicated, and must remain valid at all time they need to be
+accessible, such as through
+.Fn gcgi_get_var .
+.
+.It Ft "char *" Fn gcgi_get_var "struct gcgi_var_list *vars" "char *key"
+Get the value of the variable of
+.Fa vars
+matching
+.Fa key
+or NULL if none match.
+.
+.It Ft "void" Fn gcgi_free_var_list "struct gcgi_var_list *vars"
+Free memory used by a list of variable.
+This only frees the memory allocated by this library.
+.
+.It Ft "void" Fn gcgi_read_var_list "struct gcgi_var_list *vars" "char *path"
+Store all variables from
+.Fa path
+onto variables in
+.Fa vars .
+The file format is similar to RFC822 messages or HTTP headers:
+.Bl -bullet -compact -width x
+.It
+One line per variable, with a key=value format.
+.It
+The key is everything at the beginning of the line until the occurence of
+.Dq ":" .
+.It
+The value is everything after
+.Dq ": " .
+.It
+After the list of variables, an empty line declares the body of the message,
+which continues until the end and is stored in a
+.Dq text
+key.
+.El
+.
+.It Ft "int" Fn gcgi_write_var_list "struct gcgi_var_list *vars" "char *path"
+Encode the variable list
+.Fa vars
+into a new file at
+.Fa path .
+A temporary file will be created in the meantime,
+and the replacement will be atomic so that no partial write can occur.
+The
+.Dq text
+special key will be turned into the body of the message after an empty line
+instead of a variable on its own line.
+.
+.El
+.
+.
+.Ss Global Variables
+.
+These variables are filled with the components of the query.
+They will only be valid after
+.Fn handle_request
+is called.
+.
+.Pp
+.Bl -tag
+.
+.It Vt char *gcgi_gopher_search
+From argv[1], this is the search string, passed after a tab in
+the gopher protocol for item type
+.Dq 7 .
+.
+.It Vt char *gcgi_gopher_path
+From argv[2], this is the query path.
+It is the full query without the search string and with the query string removed.
+.
+.It Vt struct gcgi_var_list gcgi_gopher_query
+From argv[2], this is the query string stored as a key-value
+.Vt gcgi_var_list .
+It is extracted from the part of the query after the
+.Dq ? ,
+usually formated as
+.Dq ?key1=value1&key2=value2&key3=value3
+.
+.It Vt char *gcgi_gopher_host
+From argv[3], this is the current host name configured in
+.Xr geomyidae 8 .
+It is what to use as a
+.Sq host
+in links printed out.
+.
+.It Vt char *gcgi_gopher_port
+From argv[4], this is the current port number configured in
+.Xr geomyidae 8 .
+It is what to use as a
+.Sq port
+in links printed out.
+.
+.El
+.
+.
+.Sh EXAMPLES
+.
+.
+.Bd -literal
+#include "libgcgi.h"
+
+/* implementation of each handler here */
+
+static struct gcgi_handler handlers[] = {
+ { "/", page_home },
+ { "/song", page_song_list },
+ { "/song/*", page_song_item },
+ { "*", page_not_found },
+ { NULL, NULL },
+};
+
+int
+main(int argc, char **argv)
+{
+ /* privilege dropping, chroot and/or syscall restriction here */
+
+ gcgi_handle_request(handlers, argv, argc);
+ return 0;
+}
+.Ed
+.
+.
+.Sh ENVIRONMENT VARIABLES
+.
+.Nm libgcgi
+does not use environment variable, but the application code can make use of them.
+The environment variables applied to
+.Xr geomyidae 8
+will be inherited and accessible.
+.
+.
+.Sh BUGS
+.
+To debug
+.Nm libgcgi ,
+it is possible to call it on a command line, which will show all logging and error messages displayed on stderr:
+.
+.Bd -literal
+$ ./index.cgi "" "/song/never-bored-of-adventure?lyrics=1&comments=1" "" ""
+.Ed
+.
+.
+.Sh CAVEATS
+.
+The Gopher protocol is not designed for file upload.
+A dedicated file upload protocol such as SFTP or FTP may be used instead.
+.
+.Pp
+The Gopher protocol is not designed for dynamic scripting.
+A dedicated remote interface protocol such as SSH or telnet may be used instead.
(DIR) diff --git a/libgcgi.c b/libgcgi.c
@@ -70,7 +70,7 @@ gcgi_cmp_var(const void *v1, const void *v2)
return strcasecmp(((struct gcgi_var *)v1)->key, ((struct gcgi_var *)v2)->key);
}
-void
+static void
gcgi_add_var(struct gcgi_var_list *vars, char *key, char *val)
{
void *mem;
@@ -83,7 +83,7 @@ gcgi_add_var(struct gcgi_var_list *vars, char *key, char *val)
vars->list[vars->len-1].val = val;
}
-void
+static void
gcgi_sort_var_list(struct gcgi_var_list *vars)
{
qsort(vars->list, vars->len, sizeof *vars->list, gcgi_cmp_var);
@@ -138,8 +138,7 @@ gcgi_read_var_list(struct gcgi_var_list *vars, char *path)
void
gcgi_free_var_list(struct gcgi_var_list *vars)
{
- if (vars->buf != NULL)
- free(vars->buf);
+ free(vars->buf);
free(vars->list);
}
@@ -159,7 +158,7 @@ gcgi_write_var_list(struct gcgi_var_list *vars, char *dst)
gcgi_fatal("opening '%s' for writing", path);
for (v = vars->list, n = vars->len; n > 0; v++, n--) {
- if (strcasecmp(v->key, "Text") == 0) {
+ if (strcasecmp(v->key, "text") == 0) {
text = text ? text : v->val;
continue;
}
@@ -203,7 +202,6 @@ gcgi_decode_url(struct gcgi_var_list *vars, char *s)
char *tok, *eq;
while ((tok = strsep(&s, "&"))) {
- //gcgi_decode_hex(tok);
if ((eq = strchr(tok, '=')) == NULL)
continue;
*eq = '\0';
@@ -256,6 +254,24 @@ gcgi_next_var(char *head, char **tail)
}
void
+gcgi_print_gophermap(char type, char *desc, char *path, char *host, char *port)
+{
+ assert(type >= 0x30);
+ printf("%c%s\t%s\t%s\t%s\n", type, desc, path, host, port);
+}
+
+void
+gcgi_print_gph(char type, char *desc, char *path, char *host, char *port)
+{
+ assert(type >= 0x30);
+ if (host == NULL)
+ host = "server";
+ if (port == NULL)
+ port = "port";
+ printf("[%c|%s|%s|%s|%s]\n", type, desc, path, host, port);
+}
+
+void
gcgi_template(char const *path, struct gcgi_var_list *vars)
{
FILE *fp;
(DIR) diff --git a/libgcgi.h b/libgcgi.h
@@ -28,8 +28,6 @@ void gcgi_fatal(char *fmt, ...);
void gcgi_template(char const *path, struct gcgi_var_list *vars);
/* manage a `key`-`val` pair storage `vars`, as used with gcgi_template */
-void gcgi_add_var(struct gcgi_var_list *vars, char *key, char *val);
-void gcgi_sort_var_list(struct gcgi_var_list *vars);
void gcgi_set_var(struct gcgi_var_list *vars, char *key, char *val);
char *gcgi_get_var(struct gcgi_var_list *vars, char *key);
void gcgi_free_var_list(struct gcgi_var_list *vars);
@@ -41,8 +39,8 @@ int gcgi_write_var_list(struct gcgi_var_list *vars, char *path);
/* components of the gopher request */
extern char *gcgi_gopher_search;
extern char *gcgi_gopher_path;
+extern struct gcgi_var_list gcgi_gopher_query;
extern char *gcgi_gopher_host;
extern char *gcgi_gopher_port;
-extern struct gcgi_var_list gcgi_gopher_query;
#endif