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