summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authortjp <tjp@ctrl-c.club>2023-11-13 07:25:39 -0700
committertjp <tjp@ctrl-c.club>2023-11-13 07:27:16 -0700
commit1e0f8e0aaeaf1bd2ee39c02e922238b641bcf88b (patch)
tree020e5de91f2343119fed10dede9d2c8262a3cd83 /internal
parenta808b4692656c10bb43e2d54a2f5ef2746d231d5 (diff)
refactor contribs to work with a Protocol interface
Diffstat (limited to 'internal')
-rw-r--r--internal/filetypes.go57
-rw-r--r--internal/types/protocol.go19
2 files changed, 76 insertions, 0 deletions
diff --git a/internal/filetypes.go b/internal/filetypes.go
new file mode 100644
index 0000000..6824ffc
--- /dev/null
+++ b/internal/filetypes.go
@@ -0,0 +1,57 @@
+package internal
+
+import (
+ "mime"
+ "os"
+ "strings"
+ "unicode/utf8"
+)
+
+func MediaType(fpath string) string {
+ if strings.HasSuffix(fpath, ".gmi") {
+ // This may not be present in the listings searched by mime.TypeByExtension,
+ // so provide a dedicated fast path for it here.
+ return "text/gemini"
+ }
+
+ slashIdx := strings.LastIndex(fpath, "/")
+ dotIdx := strings.LastIndex(fpath[slashIdx+1:], ".")
+ if dotIdx == -1 {
+ return "application/octet-stream"
+ }
+ ext := fpath[slashIdx+1+dotIdx:]
+
+ mtype := mime.TypeByExtension(ext)
+ if mtype == "" {
+ if ContentsAreText(fpath) {
+ return "text/plain"
+ }
+ return "application/octet-stream"
+ }
+ return mtype
+}
+
+func ContentsAreText(fpath string) bool {
+ f, err := os.Open(fpath)
+ if err != nil {
+ return false
+ }
+ defer func() { _ = f.Close() }()
+
+ var buf [1024]byte
+ n, err := f.Read(buf[:])
+ if err != nil {
+ return false
+ }
+
+ for i, c := range string(buf[:n]) {
+ if i+utf8.UTFMax > n {
+ // incomplete last char
+ break
+ }
+ if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
+ return false
+ }
+ }
+ return true
+}
diff --git a/internal/types/protocol.go b/internal/types/protocol.go
new file mode 100644
index 0000000..7166d8f
--- /dev/null
+++ b/internal/types/protocol.go
@@ -0,0 +1,19 @@
+package types
+
+import (
+ "io"
+ "net/url"
+)
+
+type ServerProtocol interface {
+ TemporaryRedirect(*url.URL) *Response
+ PermanentRedirect(*url.URL) *Response
+
+ TemporaryServerError(error) *Response
+ PermanentServerError(error) *Response
+ CGIFailure(error) *Response
+
+ Success(filename string, body io.Reader) *Response
+
+ ParseResponse(io.Reader) (*Response, error)
+}