mirror of
https://github.com/makew0rld/amfora.git
synced 2024-12-04 14:46:29 -05:00
90654cd2cf
This commit introduced flashing issues in the tab row when reformatting, and even can panic in certain reformatting situations. These bugs have been logged in the inital comment of the PR.
142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
package renderer
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"mime"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/makeworld-the-better-one/amfora/structs"
|
|
"github.com/makeworld-the-better-one/go-gemini"
|
|
"github.com/spf13/viper"
|
|
"golang.org/x/text/encoding/ianaindex"
|
|
)
|
|
|
|
var ErrTooLarge = errors.New("page content would be too large")
|
|
var ErrTimedOut = errors.New("page download timed out")
|
|
var ErrCantDisplay = errors.New("invalid content for a page")
|
|
var ErrBadEncoding = errors.New("unsupported encoding")
|
|
var ErrBadMediatype = errors.New("displayable mediatype is not handled in the code, implementation error")
|
|
|
|
// isUTF8 returns true for charsets that are compatible with UTF-8 and don't need to be decoded.
|
|
func isUTF8(charset string) bool {
|
|
utfCharsets := []string{"", "utf-8", "us-ascii"}
|
|
for i := range utfCharsets {
|
|
if strings.ToLower(charset) == utfCharsets[i] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CanDisplay returns true if the response is supported by Amfora
|
|
// for displaying on the screen.
|
|
// It also doubles as a function to detect whether something can be stored in a Page struct.
|
|
func CanDisplay(res *gemini.Response) bool {
|
|
if gemini.SimplifyStatus(res.Status) != 20 {
|
|
// No content
|
|
return false
|
|
}
|
|
mediatype, params, err := mime.ParseMediaType(res.Meta)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if !strings.HasPrefix(mediatype, "text/") {
|
|
// Amfora doesn't support other filetypes
|
|
return false
|
|
}
|
|
if isUTF8(params["charset"]) {
|
|
return true
|
|
}
|
|
enc, err := ianaindex.MIME.Encoding(params["charset"]) // Lowercasing is done inside
|
|
// Encoding sometimes returns nil, see #3 on this repo and golang/go#19421
|
|
return err == nil && enc != nil
|
|
}
|
|
|
|
// MakePage creates a formatted, rendered Page from the given network response and params.
|
|
// You must set the Page.Width value yourself.
|
|
func MakePage(url string, res *gemini.Response, width int, proxied bool) (*structs.Page, error) {
|
|
if !CanDisplay(res) {
|
|
return nil, ErrCantDisplay
|
|
}
|
|
|
|
buf := new(bytes.Buffer)
|
|
_, err := io.CopyN(buf, res.Body, viper.GetInt64("a-general.page_max_size")+1)
|
|
|
|
if err == nil {
|
|
// Content was larger than max size
|
|
return nil, ErrTooLarge
|
|
} else if err != io.EOF {
|
|
if os.IsTimeout(err) {
|
|
// I would use
|
|
// errors.Is(err, os.ErrDeadlineExceeded)
|
|
// but that isn't supported before Go 1.15.
|
|
|
|
return nil, ErrTimedOut
|
|
}
|
|
// Some other error
|
|
return nil, err
|
|
}
|
|
// Otherwise, the error is EOF, which is what we want.
|
|
|
|
mediatype, params, _ := mime.ParseMediaType(res.Meta)
|
|
|
|
// Convert content first
|
|
var utfText string
|
|
if isUTF8(params["charset"]) {
|
|
utfText = buf.String()
|
|
} else {
|
|
encoding, err := ianaindex.MIME.Encoding(params["charset"])
|
|
if encoding == nil || err != nil {
|
|
// Some encoding doesn't exist and wasn't caught in CanDisplay()
|
|
return nil, ErrBadEncoding
|
|
}
|
|
utfText, err = encoding.NewDecoder().String(buf.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if mediatype == "text/gemini" {
|
|
rendered, links := RenderGemini(utfText, width, proxied)
|
|
return &structs.Page{
|
|
Mediatype: structs.TextGemini,
|
|
RawMediatype: mediatype,
|
|
URL: url,
|
|
Raw: utfText,
|
|
Content: rendered,
|
|
Links: links,
|
|
MadeAt: time.Now(),
|
|
}, nil
|
|
} else if strings.HasPrefix(mediatype, "text/") {
|
|
if mediatype == "text/x-ansi" || strings.HasSuffix(url, ".ans") || strings.HasSuffix(url, ".ansi") {
|
|
// ANSI
|
|
return &structs.Page{
|
|
Mediatype: structs.TextAnsi,
|
|
RawMediatype: mediatype,
|
|
URL: url,
|
|
Raw: utfText,
|
|
Content: RenderANSI(utfText),
|
|
Links: []string{},
|
|
MadeAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// Treated as plaintext
|
|
return &structs.Page{
|
|
Mediatype: structs.TextPlain,
|
|
RawMediatype: mediatype,
|
|
URL: url,
|
|
Raw: utfText,
|
|
Content: RenderPlainText(utfText),
|
|
Links: []string{},
|
|
MadeAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
return nil, ErrBadMediatype
|
|
}
|