all repos — flounder @ 52f506e0dfebd6baab208d612b4cd7f713210528

A small site builder for the Gemini protocol

utils.go (view raw)

  1package main
  2
  3import (
  4	"archive/zip"
  5	"bufio"
  6	"fmt"
  7	"io"
  8	"mime"
  9	"os"
 10	"path"
 11	"path/filepath"
 12	"strings"
 13	"time"
 14	"unicode/utf8"
 15)
 16
 17func buildbaseURL(user string) {
 18}
 19
 20func getSchemedFlounderLinkLines(r io.Reader) []string {
 21	scanner := bufio.NewScanner(r)
 22	result := []string{}
 23	for scanner.Scan() {
 24		text := scanner.Text()
 25		// TODO use actual parser
 26		if strings.HasPrefix(text, "=>") && strings.Contains(text, "."+c.Host) && (strings.Contains(text, "gemini://") || strings.Contains(text, "https://")) {
 27			result = append(result, text)
 28		}
 29	}
 30	return result
 31}
 32
 33func isOkUsername(s string) error {
 34	if len(s) < 1 {
 35		return fmt.Errorf("Username is too short")
 36	}
 37	if len(s) > 32 {
 38		return fmt.Errorf("Username is too long. 32 char max.")
 39	}
 40	for _, char := range s {
 41		if !strings.Contains(ok, strings.ToLower(string(char))) {
 42			return fmt.Errorf("Username contains invalid characters. Valid characters include lowercase letters, numbers, and hyphens.")
 43		}
 44	}
 45	for _, username := range bannedUsernames {
 46		if username == s {
 47			return fmt.Errorf("Username is not allowed.")
 48		}
 49	}
 50	return nil
 51}
 52
 53// Check if it is a text file, first by checking mimetype, then by reading bytes
 54// Stolen from https://github.com/golang/tools/blob/master/godoc/util/util.go
 55func isTextFile(fullPath string) bool {
 56	isText := strings.HasPrefix(mime.TypeByExtension(path.Ext(fullPath)), "text")
 57	if isText {
 58		return true
 59	}
 60	const max = 1024 // at least utf8.UTFMax
 61	s := make([]byte, 1024)
 62	f, err := os.Open(fullPath)
 63	if os.IsNotExist(err) {
 64		return true // for the purposes of editing, we return true
 65	}
 66	n, err := f.Read(s)
 67	s = s[0:n]
 68	if err != nil {
 69		return false
 70	}
 71	f.Close()
 72
 73	for i, c := range string(s) {
 74		if i+utf8.UTFMax > len(s) {
 75			// last char may be incomplete - ignore
 76			break
 77		}
 78		if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
 79			// decoding error or control character - not a text file
 80			return false
 81		}
 82	}
 83	return true
 84}
 85
 86// get the user-reltaive local path from the filespath
 87// NOTE -- dont use on unsafe input ( I think )
 88func getLocalPath(filesPath string) string {
 89	l := len(strings.Split(c.FilesDirectory, "/"))
 90	return strings.Join(strings.Split(filesPath, "/")[l+1:], "/")
 91}
 92
 93func getCreator(filePath string) string {
 94	l := len(strings.Split(c.FilesDirectory, "/"))
 95	r := strings.Split(filePath, "/")[l]
 96	return r
 97}
 98
 99func isGemini(filename string) bool {
100	extension := path.Ext(filename)
101	return extension == ".gmi" || extension == ".gemini"
102}
103
104func timeago(t *time.Time) string {
105	d := time.Since(*t)
106	if d.Seconds() < 60 {
107		seconds := int(d.Seconds())
108		if seconds == 1 {
109			return "1 second ago"
110		}
111		return fmt.Sprintf("%d seconds ago", seconds)
112	} else if d.Minutes() < 60 {
113		minutes := int(d.Minutes())
114		if minutes == 1 {
115			return "1 minute ago"
116		}
117		return fmt.Sprintf("%d minutes ago", minutes)
118	} else if d.Hours() < 24 {
119		hours := int(d.Hours())
120		if hours == 1 {
121			return "1 hour ago"
122		}
123		return fmt.Sprintf("%d hours ago", hours)
124	} else {
125		days := int(d.Hours()) / 24
126		if days == 1 {
127			return "1 day ago"
128		}
129		return fmt.Sprintf("%d days ago", days)
130	}
131}
132
133// safe
134func getUserDirectory(username string) string {
135	// extra filepath.clean just to be safe
136	userFolder := path.Join(c.FilesDirectory, filepath.Clean(username))
137	return userFolder
138}
139
140// ugh idk
141func safeGetFilePath(username string, filename string) string {
142	return path.Join(getUserDirectory(username), filepath.Clean(filename))
143}
144
145func dirSize(path string) (int64, error) {
146	var size int64
147	err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
148		if err != nil {
149			return err
150		}
151		if !info.IsDir() {
152			size += info.Size()
153		}
154		return err
155	})
156	return size, err
157}
158
159/// Perform some checks to make sure the file is OK to upload
160func checkIfValidFile(username string, filename string, fileBytes []byte) error {
161	if len(filename) == 0 {
162		return fmt.Errorf("Please enter a filename")
163	}
164	if len(filename) > 256 { // arbitrarily chosen
165		return fmt.Errorf("Filename is too long")
166	}
167	ext := strings.ToLower(path.Ext(filename))
168	found := false
169	for _, mimetype := range c.OkExtensions {
170		if ext == mimetype {
171			found = true
172		}
173	}
174	if !found {
175		return fmt.Errorf("Invalid file extension: %s", ext)
176	}
177	if len(fileBytes) > c.MaxFileBytes {
178		return fmt.Errorf("File too large. File was %d bytes, Max file size is %d", len(fileBytes), c.MaxFileBytes)
179	}
180	userFolder := getUserDirectory(username)
181	myFiles, err := getMyFilesRecursive(userFolder, username)
182	if err != nil {
183		return err
184	}
185	if len(myFiles) >= c.MaxFilesPerUser {
186		return fmt.Errorf("You have reached the max number of files. Delete some before uploading")
187	}
188	size, err := dirSize(userFolder)
189	if err != nil || size+int64(len(fileBytes)) > c.MaxUserBytes {
190		return fmt.Errorf("You are out of storage space. Delete some files before continuing.")
191	}
192	return nil
193}
194
195func zipit(source string, target io.Writer) error {
196	archive := zip.NewWriter(target)
197
198	info, err := os.Stat(source)
199	if err != nil {
200		return nil
201	}
202
203	var baseDir string
204	if info.IsDir() {
205		baseDir = filepath.Base(source)
206	}
207
208	filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
209		if err != nil {
210			return err
211		}
212
213		header, err := zip.FileInfoHeader(info)
214		if err != nil {
215			return err
216		}
217
218		if baseDir != "" {
219			header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
220		}
221
222		if info.IsDir() {
223			header.Name += "/"
224		} else {
225			header.Method = zip.Deflate
226		}
227
228		writer, err := archive.CreateHeader(header)
229		if err != nil {
230			return err
231		}
232
233		if info.IsDir() {
234			return nil
235		}
236
237		file, err := os.Open(path)
238		if err != nil {
239			return err
240		}
241		defer file.Close()
242		_, err = io.Copy(writer, file)
243		return err
244	})
245
246	archive.Close()
247
248	return err
249}