all repos — flounder @ 97318d31e14710276d3c3e12565a91d22abb37b7

A small site builder for the Gemini protocol

utils.go (view raw)

  1package main
  2
  3import (
  4	"archive/zip"
  5	"fmt"
  6	"io"
  7	"mime"
  8	"os"
  9	"path"
 10	"path/filepath"
 11	"strings"
 12	"time"
 13	"unicode/utf8"
 14)
 15
 16// Check if it is a text file, first by checking mimetype, then by reading bytes
 17// Stolen from https://github.com/golang/tools/blob/master/godoc/util/util.go
 18func isTextFile(fullPath string) bool {
 19	isText := strings.HasPrefix(mime.TypeByExtension(path.Ext(fullPath)), "text")
 20	if isText {
 21		return true
 22	}
 23	const max = 1024 // at least utf8.UTFMax
 24	s := make([]byte, 1024)
 25	f, err := os.Open(fullPath)
 26	if os.IsNotExist(err) {
 27		return true // for the purposes of editing, we return true
 28	}
 29	n, err := f.Read(s)
 30	s = s[0:n]
 31	if err != nil {
 32		return false
 33	}
 34	f.Close()
 35
 36	for i, c := range string(s) {
 37		if i+utf8.UTFMax > len(s) {
 38			// last char may be incomplete - ignore
 39			break
 40		}
 41		if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
 42			// decoding error or control character - not a text file
 43			return false
 44		}
 45	}
 46	return true
 47}
 48
 49func isGemini(filename string) bool {
 50	extension := path.Ext(filename)
 51	return extension == ".gmi" || extension == ".gemini"
 52}
 53
 54func timeago(t *time.Time) string {
 55	d := time.Since(*t)
 56	if d.Seconds() < 60 {
 57		seconds := int(d.Seconds())
 58		if seconds == 1 {
 59			return "1 second ago"
 60		}
 61		return fmt.Sprintf("%d seconds ago", seconds)
 62	} else if d.Minutes() < 60 {
 63		minutes := int(d.Minutes())
 64		if minutes == 1 {
 65			return "1 minute ago"
 66		}
 67		return fmt.Sprintf("%d minutes ago", minutes)
 68	} else if d.Hours() < 24 {
 69		hours := int(d.Hours())
 70		if hours == 1 {
 71			return "1 hour ago"
 72		}
 73		return fmt.Sprintf("%d hours ago", hours)
 74	} else {
 75		days := int(d.Hours()) / 24
 76		if days == 1 {
 77			return "1 day ago"
 78		}
 79		return fmt.Sprintf("%d days ago", days)
 80	}
 81}
 82
 83// safe
 84func getUserDirectory(username string) string {
 85	// extra filepath.clean just to be safe
 86	userFolder := path.Join(c.FilesDirectory, filepath.Clean(username))
 87	return userFolder
 88}
 89
 90// ugh idk
 91func safeGetFilePath(username string, filename string) string {
 92	return path.Join(getUserDirectory(username), filepath.Clean(filename))
 93}
 94
 95// TODO move into checkIfValidFile. rename it
 96func userHasSpace(user string, newBytes int) bool {
 97	userPath := path.Join(c.FilesDirectory, user)
 98	size, err := dirSize(userPath)
 99	if err != nil || size+int64(newBytes) > c.MaxUserBytes {
100		return false
101	}
102	return true
103}
104
105func dirSize(path string) (int64, error) {
106	var size int64
107	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
108		if err != nil {
109			return err
110		}
111		if !info.IsDir() {
112			size += info.Size()
113		}
114		return err
115	})
116	return size, err
117}
118
119/// Perform some checks to make sure the file is OK
120func checkIfValidFile(filename string, fileBytes []byte) error {
121	if len(filename) == 0 {
122		return fmt.Errorf("Please enter a filename")
123	}
124	if len(filename) > 256 { // arbitrarily chosen
125		return fmt.Errorf("Filename is too long")
126	}
127	ext := strings.ToLower(path.Ext(filename))
128	found := false
129	for _, mimetype := range c.OkExtensions {
130		if ext == mimetype {
131			found = true
132		}
133	}
134	if !found {
135		return fmt.Errorf("Invalid file extension: %s", ext)
136	}
137	if len(fileBytes) > c.MaxFileBytes {
138		return fmt.Errorf("File too large. File was %d bytes, Max file size is %d", len(fileBytes), c.MaxFileBytes)
139	}
140	//
141	return nil
142}
143
144func zipit(source string, target io.Writer) error {
145	archive := zip.NewWriter(target)
146
147	info, err := os.Stat(source)
148	if err != nil {
149		return nil
150	}
151
152	var baseDir string
153	if info.IsDir() {
154		baseDir = filepath.Base(source)
155	}
156
157	filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
158		if err != nil {
159			return err
160		}
161
162		header, err := zip.FileInfoHeader(info)
163		if err != nil {
164			return err
165		}
166
167		if baseDir != "" {
168			header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
169		}
170
171		if info.IsDir() {
172			header.Name += "/"
173		} else {
174			header.Method = zip.Deflate
175		}
176
177		writer, err := archive.CreateHeader(header)
178		if err != nil {
179			return err
180		}
181
182		if info.IsDir() {
183			return nil
184		}
185
186		file, err := os.Open(path)
187		if err != nil {
188			return err
189		}
190		defer file.Close()
191		_, err = io.Copy(writer, file)
192		return err
193	})
194
195	archive.Close()
196
197	return err
198}