URI: 
       add a manpage and README, and update the code to reflect the light changes - libgcgi - REST library for Gopher
  HTML 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
  HTML 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