all repos — flounder @ f39fba2d03282d8217ee76b53fe46c100acec304

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			fmt.Println("a")
 43			// decoding error or control character - not a text file
 44			return false
 45		}
 46	}
 47	return true
 48}
 49
 50func isGemini(filename string) bool {
 51	extension := path.Ext(filename)
 52	return extension == ".gmi" || extension == ".gemini"
 53}
 54
 55func timeago(t *time.Time) string {
 56	d := time.Since(*t)
 57	if d.Seconds() < 60 {
 58		seconds := int(d.Seconds())
 59		if seconds == 1 {
 60			return "1 second ago"
 61		}
 62		return fmt.Sprintf("%d seconds ago", seconds)
 63	} else if d.Minutes() < 60 {
 64		minutes := int(d.Minutes())
 65		if minutes == 1 {
 66			return "1 minute ago"
 67		}
 68		return fmt.Sprintf("%d minutes ago", minutes)
 69	} else if d.Hours() < 24 {
 70		hours := int(d.Hours())
 71		if hours == 1 {
 72			return "1 hour ago"
 73		}
 74		return fmt.Sprintf("%d hours ago", hours)
 75	} else {
 76		days := int(d.Hours()) / 24
 77		if days == 1 {
 78			return "1 day ago"
 79		}
 80		return fmt.Sprintf("%d days ago", days)
 81	}
 82}
 83
 84// safe
 85func getUserDirectory(username string) string {
 86	// extra filepath.clean just to be safe
 87	userFolder := path.Join(c.FilesDirectory, filepath.Clean(username))
 88	return userFolder
 89}
 90
 91// ugh idk
 92func safeGetFilePath(username string, filename string) string {
 93	return path.Join(getUserDirectory(username), filepath.Clean(filename))
 94}
 95
 96// TODO move into checkIfValidFile. rename it
 97func userHasSpace(user string, newBytes int) bool {
 98	userPath := path.Join(c.FilesDirectory, user)
 99	size, err := dirSize(userPath)
100	if err != nil || size+int64(newBytes) > c.MaxUserBytes {
101		return false
102	}
103	return true
104}
105
106func dirSize(path string) (int64, error) {
107	var size int64
108	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
109		if err != nil {
110			return err
111		}
112		if !info.IsDir() {
113			size += info.Size()
114		}
115		return err
116	})
117	return size, err
118}
119
120/// Perform some checks to make sure the file is OK
121func checkIfValidFile(filename string, fileBytes []byte) error {
122	if len(filename) == 0 {
123		return fmt.Errorf("Please enter a filename")
124	}
125	if len(filename) > 256 { // arbitrarily chosen
126		return fmt.Errorf("Filename is too long")
127	}
128	ext := strings.ToLower(path.Ext(filename))
129	found := false
130	for _, mimetype := range c.OkExtensions {
131		if ext == mimetype {
132			found = true
133		}
134	}
135	if !found {
136		return fmt.Errorf("Invalid file extension: %s", ext)
137	}
138	if len(fileBytes) > c.MaxFileBytes {
139		return fmt.Errorf("File too large. File was %d bytes, Max file size is %d", len(fileBytes), c.MaxFileBytes)
140	}
141	//
142	return nil
143}
144
145func zipit(source string, target io.Writer) error {
146	archive := zip.NewWriter(target)
147
148	info, err := os.Stat(source)
149	if err != nil {
150		return nil
151	}
152
153	var baseDir string
154	if info.IsDir() {
155		baseDir = filepath.Base(source)
156	}
157
158	filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
159		if err != nil {
160			return err
161		}
162
163		header, err := zip.FileInfoHeader(info)
164		if err != nil {
165			return err
166		}
167
168		if baseDir != "" {
169			header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
170		}
171
172		if info.IsDir() {
173			header.Name += "/"
174		} else {
175			header.Method = zip.Deflate
176		}
177
178		writer, err := archive.CreateHeader(header)
179		if err != nil {
180			return err
181		}
182
183		if info.IsDir() {
184			return nil
185		}
186
187		file, err := os.Open(path)
188		if err != nil {
189			return err
190		}
191		defer file.Close()
192		_, err = io.Copy(writer, file)
193		return err
194	})
195
196	archive.Close()
197
198	return err
199}