Compare commits

...

7 Commits

Author SHA1 Message Date
Trevor Slocum a23ad13fd8 Fix connection issue affecting some sites 2021-07-10 11:30:00 -07:00
Trevor Slocum f933e9dd1b Update README 2021-07-10 11:19:49 -07:00
Trevor Slocum 6c7b8ec1d3 Document ConvertImages option 2021-07-09 20:16:11 -07:00
tslocum 8e8eef57e2 Merge pull request 'Convert images to images instead of links' (#5) from f/gmitohtml:image-output into master
Reviewed-on: https://code.rocketnine.space/tslocum/gmitohtml/pulls/5
2021-07-09 20:03:19 -07:00
Aaron Fischer 2fdec61b41 Move config to gmitohtml namespace 2021-07-09 23:19:30 +02:00
Aaron Fischer 0bec3c3eef Fix wrong package name and clean up tag-building
DRY the tag building for links and images.
2021-07-09 22:45:52 +02:00
Aaron Fischer 586b293bef Convert images to images instead of links
Image links will result in a `img` tag instead of a `a` tag. This
behaviour is disabled by default and can be enabled by adding the
following line to to config file:

convertimages: true

To get access to the config in the convert file, I had to move the
config file inside the gmitohtml namespace. This is specially handy
later on if the config file contains other settings which are useful for
the rest of the codebase.
2021-06-09 01:19:26 +02:00
9 changed files with 117 additions and 155 deletions

View File

@ -1,21 +0,0 @@
image: golang:latest
stages:
- validate
- build
fmt:
stage: validate
script:
- gofmt -l -s -e .
- exit $(gofmt -l -s -e . | wc -l)
vet:
stage: validate
script:
- go vet -composites=false ./...
test:
stage: validate
script:
- go test -race -v ./...

View File

@ -1,4 +1,8 @@
1.0.3:
1.0.5:
- Add option ConvertImages (thanks to @f)
- Fix connection issue affecting some sites
1.0.4:
- Migrate to code.rocketnine.space
1.0.3:

View File

@ -1,5 +1,5 @@
# gmitohtml
[![GoDoc](https://code.rocketnine.space/tslocum/godoc-static/-/raw/master/badge.svg)](https://docs.rocketnine.space/code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml)
[![GoDoc](https://code.rocketnine.space/tslocum/godoc-static/raw/branch/master/badge.svg)](https://docs.rocketnine.space/code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml)
[![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
[Gemini](https://gemini.circumlunar.space) to [HTML](https://en.wikipedia.org/wiki/HTML)
@ -7,9 +7,13 @@ conversion tool and daemon
## Download
### PC
[**Download gmitohtml**](https://gmitohtml.rocketnine.space/download/?sort=name&order=desc)
gmitohtml is available on Android as [Xenia](https://code.rocketnine.space/tslocum/xenia).
### Android
See [Xenia](https://code.rocketnine.space/tslocum/xenia).
## Compile

View File

@ -1,77 +0,0 @@
package main
import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml"
"gopkg.in/yaml.v3"
)
type certConfig struct {
Cert string
Key string
cert tls.Certificate
}
type appConfig struct {
Bookmarks map[string]string
Certs map[string]*certConfig
}
var config = &appConfig{
Bookmarks: make(map[string]string),
Certs: make(map[string]*certConfig),
}
func defaultConfigPath() string {
homedir, err := os.UserHomeDir()
if err == nil && homedir != "" {
return path.Join(homedir, ".config", "gmitohtml", "config.yaml")
}
return ""
}
func readconfig(configPath string) error {
if configPath == "" {
return errors.New("file unspecified")
}
configData, err := ioutil.ReadFile(configPath)
if err != nil {
return err
}
var newConfig *appConfig
err = yaml.Unmarshal(configData, &newConfig)
if err != nil {
return err
}
config = newConfig
return nil
}
func saveConfig(configPath string) error {
config.Bookmarks = gmitohtml.GetBookmarks()
out, err := yaml.Marshal(config)
if err != nil {
return fmt.Errorf("failed to marshal configuration: %s", err)
}
os.MkdirAll(path.Dir(configPath), 0755) // Ignore error
err = ioutil.WriteFile(configPath, out, 0644)
if err != nil {
return fmt.Errorf("failed to save configuration to %s: %s", configPath, err)
}
return nil
}

2
go.mod
View File

@ -2,4 +2,4 @@ module code.rocketnine.space/tslocum/gmitohtml
go 1.15
require gopkg.in/yaml.v3 v3.0.0-20210105161348-2e78108cf5f8
require code.rocketnine.space/tslocum/ez v0.0.0-20210506054357-569018bd037a

6
go.sum
View File

@ -1,4 +1,6 @@
code.rocketnine.space/tslocum/ez v0.0.0-20210506054357-569018bd037a h1:Ug5hgK5sM7bdK1gEl/pNLYTtBpFUxCvSCGYkEbUqdmw=
code.rocketnine.space/tslocum/ez v0.0.0-20210506054357-569018bd037a/go.mod h1:SQrM+bQ4eZdyAVTxuF2BNnyAnojHP6Kcmm2vMszoFWw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20210105161348-2e78108cf5f8 h1:tH9C0MON9YI3/KuD+u5+tQrQQ8px0MrcJ/avzeALw7o=
gopkg.in/yaml.v3 v3.0.0-20210105161348-2e78108cf5f8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

17
main.go
View File

@ -10,6 +10,7 @@ import (
"os/exec"
"runtime"
"code.rocketnine.space/tslocum/ez"
"code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml"
)
@ -46,8 +47,10 @@ func main() {
// TODO option to include response header in page
flag.Parse()
defaultConfig := defaultConfigPath()
if configFile == "" {
defaultConfig, err := ez.DefaultConfigPath("gmitohtml")
if err != nil {
log.Fatal(err)
} else if configFile == "" {
configFile = defaultConfig
}
@ -58,18 +61,18 @@ func main() {
}
if configExists || configFile != defaultConfig {
err := readconfig(configFile)
err := ez.Deserialize(gmitohtml.Config, configFile)
if err != nil {
log.Fatalf("failed to read configuration file at %s: %v\nSee CONFIGURATION.md for information on configuring gmitohtml", configFile, err)
}
for u, label := range config.Bookmarks {
for u, label := range gmitohtml.Config.Bookmarks {
gmitohtml.AddBookmark(u, label)
}
}
}
for domain, cc := range config.Certs {
for domain, cc := range gmitohtml.Config.Certs {
certData, err := ioutil.ReadFile(cc.Cert)
if err != nil {
log.Fatalf("failed to load client certificate for domain %s: %s", domain, err)
@ -88,9 +91,9 @@ func main() {
if daemon != "" {
gmitohtml.SetOnBookmarksChanged(func() {
config.Bookmarks = gmitohtml.GetBookmarks()
gmitohtml.Config.Bookmarks = gmitohtml.GetBookmarks()
err := saveConfig(configFile)
err := ez.Serialize(gmitohtml.Config, configFile)
if err != nil {
log.Fatal(err)
}

27
pkg/gmitohtml/config.go Normal file
View File

@ -0,0 +1,27 @@
package gmitohtml
import (
"crypto/tls"
)
type CertConfig struct {
Cert string
Key string
cert tls.Certificate
}
type AppConfig struct {
Bookmarks map[string]string
// Convert image links to images instead of normal links
ConvertImages bool
Certs map[string]*CertConfig
}
var Config = &AppConfig{
Bookmarks: make(map[string]string),
Certs: make(map[string]*CertConfig),
}

View File

@ -19,46 +19,53 @@ var daemonAddress string
var assetLock sync.Mutex
var imageExtensions = []string{"png", "jpg", "jpeg", "gif", "svg", "webp"}
func rewriteURL(u string, loc *url.URL) string {
if daemonAddress != "" {
scheme := "gemini"
if strings.HasPrefix(loc.Path, "/file/") {
scheme = "file"
}
if strings.HasPrefix(u, "file://") {
if !allowFileAccess {
return "http://" + daemonAddress + "/?FileAccessNotAllowed"
}
return "http://" + daemonAddress + "/file/" + u[7:]
}
offset := 0
if strings.HasPrefix(u, "gemini://") {
offset = 9
}
firstSlash := strings.IndexRune(u[offset:], '/')
if firstSlash != -1 {
u = strings.ToLower(u[:firstSlash+offset]) + u[firstSlash+offset:]
}
if strings.HasPrefix(u, "gemini://") {
return "http://" + daemonAddress + "/gemini/" + u[9:]
} else if strings.Contains(u, "://") {
return u
} else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") {
if u[0] != '/' {
if loc.Path[len(loc.Path)-1] == '/' {
u = path.Join("/", loc.Path, u)
} else {
u = path.Join("/", path.Dir(loc.Path), u)
}
}
return "http://" + daemonAddress + "/" + scheme + "/" + strings.ToLower(loc.Host) + u
}
return "http://" + daemonAddress + "/" + scheme + "/" + u
if daemonAddress == "" {
return u
}
return u
if loc.Path == "" {
loc.Path = "/"
}
scheme := "gemini"
if strings.HasPrefix(loc.Path, "/file/") {
scheme = "file"
}
if strings.HasPrefix(u, "file://") {
if !allowFileAccess {
return "http://" + daemonAddress + "/?FileAccessNotAllowed"
}
return "http://" + daemonAddress + "/file/" + u[7:]
}
offset := 0
if strings.HasPrefix(u, "gemini://") {
offset = 9
}
firstSlash := strings.IndexRune(u[offset:], '/')
if firstSlash != -1 {
u = strings.ToLower(u[:firstSlash+offset]) + u[firstSlash+offset:]
}
if strings.HasPrefix(u, "gemini://") {
return "http://" + daemonAddress + "/gemini/" + u[9:]
} else if strings.Contains(u, "://") {
return u
} else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") {
if u[0] != '/' {
if loc.Path[len(loc.Path)-1] == '/' {
u = path.Join("/", loc.Path, u)
} else {
u = path.Join("/", path.Dir(loc.Path), u)
}
}
return "http://" + daemonAddress + "/" + scheme + "/" + strings.ToLower(loc.Host) + u
}
return "http://" + daemonAddress + "/" + scheme + "/" + u
}
func newPage() []byte {
@ -126,12 +133,25 @@ func Convert(page []byte, u string) []byte {
linkLabel = line[splitStart:]
}
link := append([]byte(`<a href="`), html.EscapeString(rewriteURL(string(linkURL), parsedURL))...)
link = append(link, []byte(`">`)...)
link = append(link, html.EscapeString(string(linkLabel))...)
link = append(link, []byte(`</a>`)...)
result = append(result, link...)
result = append(result, []byte("<br>")...)
parts := strings.Split(string(linkURL), ".")
extension := parts[len(parts)-1]
isImage := false
for _, ext := range imageExtensions {
if extension == ext {
isImage = true
}
}
uri := html.EscapeString(rewriteURL(string(linkURL), parsedURL))
title := html.EscapeString(string(linkLabel))
// If link ends with gif/png/jpg, add a image instead of a link
if isImage && Config.ConvertImages {
result = append(result, []byte("<img src=\""+uri+"\" alt=\""+title+"\">")...)
} else {
result = append(result, []byte("<a href=\""+uri+"\">"+title+"</a><br>")...)
}
continue
}