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
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
142// TODO move into checkIfValidFile. rename it
143func userHasSpace(user string, newBytes int) bool {
144 userPath := path.Join(c.FilesDirectory, user)
145 size, err := dirSize(userPath)
146 if err != nil || size+int64(newBytes) > c.MaxUserBytes {
147 return false
148 }
149 return true
150}
151
152func dirSize(path string) (int64, error) {
153 var size int64
154 err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
155 if err != nil {
156 return err
157 }
158 if !info.IsDir() {
159 size += info.Size()
160 }
161 return err
162 })
163 return size, err
164}
165
166/// Perform some checks to make sure the file is OK
167func checkIfValidFile(filename string, fileBytes []byte) error {
168 if len(filename) == 0 {
169 return fmt.Errorf("Please enter a filename")
170 }
171 if len(filename) > 256 { // arbitrarily chosen
172 return fmt.Errorf("Filename is too long")
173 }
174 ext := strings.ToLower(path.Ext(filename))
175 found := false
176 for _, mimetype := range c.OkExtensions {
177 if ext == mimetype {
178 found = true
179 }
180 }
181 if !found {
182 return fmt.Errorf("Invalid file extension: %s", ext)
183 }
184 if len(fileBytes) > c.MaxFileBytes {
185 return fmt.Errorf("File too large. File was %d bytes, Max file size is %d", len(fileBytes), c.MaxFileBytes)
186 }
187 //
188 return nil
189}
190
191func zipit(source string, target io.Writer) error {
192 archive := zip.NewWriter(target)
193
194 info, err := os.Stat(source)
195 if err != nil {
196 return nil
197 }
198
199 var baseDir string
200 if info.IsDir() {
201 baseDir = filepath.Base(source)
202 }
203
204 filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
205 if err != nil {
206 return err
207 }
208
209 header, err := zip.FileInfoHeader(info)
210 if err != nil {
211 return err
212 }
213
214 if baseDir != "" {
215 header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
216 }
217
218 if info.IsDir() {
219 header.Name += "/"
220 } else {
221 header.Method = zip.Deflate
222 }
223
224 writer, err := archive.CreateHeader(header)
225 if err != nil {
226 return err
227 }
228
229 if info.IsDir() {
230 return nil
231 }
232
233 file, err := os.Open(path)
234 if err != nil {
235 return err
236 }
237 defer file.Close()
238 _, err = io.Copy(writer, file)
239 return err
240 })
241
242 archive.Close()
243
244 return err
245}