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