all repos — flounder @ add182fc510af8887560e8eacc910e379d80f49b

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