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. this could be a little wonky
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}