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}