ir33k / gmi100

Gemini CLI protocol client written in 100 lines of ANSI C
65 stars 1 forks source link

License? #4

Closed 8bitprodigy closed 5 months ago

8bitprodigy commented 5 months ago

Would be nice to know. If it's a permissive license, this could be used as a client library on the Dreamcast so developers can embed online news feeds in their games.

8bitprodigy commented 5 months ago

Nevermind, just saw it was in the code as public domain, just inlined with the header includes. I thought those were comments about the includes and overlooked them.

ir33k commented 5 months ago

Yes, it's public domain.

I don't see how this project can be used as library. I'd be interested to see what you are up to.

8bitprodigy commented 5 months ago

I meant as like a base to work off of, though I found another project that may be better suited, but thank you!

ir33k commented 5 months ago

Yea, gmi100 is not a good base as tricks used to reduce number of lines made it difficult to read.

But if you need the most besic of "fetch" like or "wget" like source code then you can use this:

// main.c
#include <assert.h>
#include <err.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>

#define PORT "1965"             // Default Gemini port

// Print program (with ARG0 program name) usage message to stderr.
static void
usage(char *arg0)
{
    fprintf(stderr,
        "%s [-h] HOST [PATH] [PORT]\n\n"
        "   -h  Print help (this message).\n"
        "   HOST    Gemini server host name.\n"
        "   PATH    Host resource path (\"/\").\n"
        "   PORT    Overwrite default Gemini port ("PORT").\n"
        , arg0);
}

// Establish AF_INET internet SOCK_STREAM connection to HOST of PORT.
// Return socket file descriptor or 0 on error.
static int
net_tcp(char *host, int port)
{
    int i, sfd;
    struct hostent *he;
    struct sockaddr_in addr;
    assert(host);
    assert(port > 0);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if ((sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        return 0;
    }
    if ((he = gethostbyname(host)) == 0) {
        return 0;
    }
    for (i=0; he->h_addr_list[i]; i++) {
        memcpy(&addr.sin_addr.s_addr, he->h_addr_list[i], sizeof(in_addr_t));
        if (connect(sfd, (struct sockaddr*)&addr, sizeof(addr))) {
            continue;
        }
        return sfd; // Success
    }
    return 0;
}

int
main(int argc, char **argv)
{
    char *host;             // Server hostname
    char *path;             // Host resource path
    char *port;             // Server port
    char uri[1024];         // Whole URI
    int opt;                // Program option character
    int sfd;                // TCP connection socket file descriptor
    SSL *ssl;               // SSL instance
    SSL_CTX *ctx;           // SSL connection context
    int sz;                 // Size of received data
    char buf[4096];         // Buffer for reading data

    while ((opt = getopt(argc, argv, "h")) != -1) {
        switch (opt) {
        case 'h':
        default:
            usage(argv[0]);
            return 0;
        }
    }
    host = argc > 1 ? argv[1] : 0;
    path = argc > 2 ? argv[2] : "/";
    port = argc > 3 ? argv[3] : PORT;
    if (!host) {
        usage(argv[0]);
        errx(1, "Missing HOST");
    }

    snprintf(uri, sizeof(uri), "gemini://%s:%s%s", host, port, path);
    fprintf(stderr, "%s\n", uri);

    if (!(sfd = net_tcp(host, atoi(port))))        errx(1, "net_tcp");
    if (!(ctx = SSL_CTX_new(TLS_client_method()))) errx(1, "SSL_CTX_new");
    if (!(ssl = SSL_new(ctx)))                     errx(1, "SSL_new");
    if (!SSL_set_tlsext_host_name(ssl, host))      errx(1, "SSL_set_tlsext");
    if (!SSL_set_fd(ssl, sfd))                     errx(1, "SSL_set_fd");
    if (SSL_connect(ssl) < 1)                      errx(1, "SSL_connect");
    if (SSL_write(ssl, uri, strlen(uri)) < 1)      errx(1, "SSL_write URI");
    if (SSL_write(ssl, "\r\n", 2) < 1)             errx(1, "SSL_write CRNL");

    while ((sz = SSL_read(ssl, buf, sizeof(buf))) > 0) {
        // Here you can parse output.
        fwrite(buf, 1, sz, stdout);
    }

    SSL_CTX_free(ctx);
        SSL_free(ssl);
    return 0;
}

Compile and run:

$ cc \
    -std=c99 \
    -Wall -Wextra -Wshadow -Wmissing-declarations -Wswitch-enum -Wno-deprecated-declarations -pedantic \
    -o gmif main.c \
    -lssl -lcrypto

$ ./gmif -h
./gmif [-h] HOST [PATH] [PORT]

    -h  Print help (this message).
    HOST    Gemini server host name.
    PATH    Host resource path ("/").
    PORT    Overwrite default Gemini port (1965).
$ ./gmif geminiprotocol.net
$ ./gmif geminiprotocol.net /software/
$ ./gmif tilde.pink /~irek/

But this program is so basic that it does nothing to parse URI or the text/gemini format. For basic URI parsing you might look into uri.h lib from my yupa project (uri.h, uri.c, and see tests uri.t.c for usage examples). Also mind that this program does nothing to interpret the response from server. It only ask for resource and prints whole response back. So redirections and other behaviors are not included. But as I said, this is the most basic for of gemini protocol fetch that you can have written in normal way, not like the gmi100.

There are probably other projects better for what you need. Regardless, good luck and have fun :wink: