gm

an extremely simple gemini browser/formatter
git clone git://bvnf.space/gm.git
Log | Files | Refs | LICENSE

gm.go (2465B)


      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
package main

import (
    "bufio"
    "crypto/tls"
    "flag"
    "fmt"
    "io/ioutil"
    golog "log"
    "net/url"
    "os"
    "strings"
)

var (
    v     *bool
    plain *bool
)

func gemget(u string, port int) error {
    if !strings.HasPrefix(u, "gemini://") {
        u = "gemini://" + u
    }

    parsed, err := url.Parse(u)
    if err != nil {
        die(fmt.Sprintf("error parsing url (%s): %s", u, err.Error()))
    }

    conn, err := tls.Dial("tcp", parsed.Host+fmt.Sprintf(":%d", port), &tls.Config{InsecureSkipVerify: true})
    if err != nil {
        die("tls error: ", err)
    }
    defer conn.Close()
    conn.Write([]byte(u + "\r\n"))

    // just dump the response if we want
    if *plain {
        resp, err := ioutil.ReadAll(bufio.NewReader(conn))
        if err != nil {
            die(err)
        }
        fmt.Println(string(resp))
        return nil
    }

    var mimetype string
    scanner := bufio.NewScanner(conn)

    // read first line (status)
    scanner.Scan()
    statusline := scanner.Text()
    switch statusline[0] {
    case '1':
        // INPUT
    case '2':
        // SUCCESS
        mimetype = statusline[3:]
    case '3':
        // REDIRECT
        log("redirecting to", statusline[3:])
        return gemget(parsed.Host+"/"+statusline[3:], port)
    case '6':
        // CLIENT CERTIFICATE REQUIRED
    default:
        // reponse 4x or 5x
        // FAILURE
        die("error: ", statusline)
    }

    // get body
    var s string
    for scanner.Scan() {
        s = s + scanner.Text() + "\n"
    }

    // remove final \n
    s = s[:len(s)-1]

    if strings.HasPrefix(mimetype, "text/gemini") {
        s = *Prettify(&s)
    }
    fmt.Println(s)
    return nil
}

// log prints timestamped diagnosis messages to stderr if verbose output is turned on.
func log(msg ...interface{}) {
    if *v {
        fmt.Fprint(os.Stderr, "\033[32m")
        golog.Println(msg...)
        fmt.Fprint(os.Stderr, "\033[m")
    }
}

func die(msg ...interface{}) {
    fmt.Fprintf(os.Stderr, "\033[31m%s\033[m\n", fmt.Sprint(msg...))
    os.Exit(1)
}

func main() {
    port := flag.Int("p", 1965, "port number")
    plain = flag.Bool("plain", false, "print raw header and response (no formatting)")
    v = flag.Bool("v", false, "print more information to stderr")
    flag.Parse()
    // only accept one url
    if flag.NArg() != 1 {
        die("give one url")
    }

    u := flag.Args()[0]

    gemget(u, *port)
}