utils.go (6204B) - raw
1 package main 2 3 import ( 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 18 func 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 31 func 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 53 func 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 ) 86 func getLocalPath(filesPath string) string { 87 l := len(strings.Split(c.FilesDirectory, "/")) 88 return strings.Join(strings.Split(filesPath, "/")[l+1:], "/") 89 } 90 91 func getCreator(filePath string) string { 92 l := len(strings.Split(c.FilesDirectory, "/")) 93 r := strings.Split(filePath, "/")[l] 94 return r 95 } 96 97 func isGemini(filename string) bool { 98 extension := path.Ext(filename) 99 return extension == ".gmi" || extension == ".gemini" 100 } 101 102 const solarYearSecs = 31556926 103 104 func timeago(t *time.Time) string { 105 d := time.Since(*t) 106 var metric string 107 var amount int 108 if d.Seconds() < 60 { 109 amount = int(d.Seconds()) 110 metric = "second" 111 } else if d.Minutes() < 60 { 112 amount = int(d.Minutes()) 113 metric = "minute" 114 } else if d.Hours() < 24 { 115 amount = int(d.Hours()) 116 metric = "hour" 117 } else if d.Seconds() < solarYearSecs { 118 amount = int(d.Hours()) / 24 119 metric = "day" 120 } else { 121 amount = int(d.Seconds()) / solarYearSecs 122 metric = "year" 123 } 124 if amount == 1 { 125 return fmt.Sprintf("%d %s ago", amount, metric) 126 } else { 127 return fmt.Sprintf("%d %ss ago", amount, metric) 128 } 129 } 130 func 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 139 func getUserDirectory(username string) string { 140 // extra filepath.clean just to be safe 141 userFolder := path.Join(c.FilesDirectory, cleanPath(username)) 142 return userFolder 143 } 144 145 func safeGetFilePath(username string, filename string) string { 146 return path.Join(getUserDirectory(username), cleanPath(filename)) 147 } 148 149 // Safe 150 func cleanPath(thepath string) string { 151 res := filepath.FromSlash(path.Clean("/" + strings.Trim(thepath, "/"))) 152 if strings.Contains(res, "..") { // sanity check 153 return "" 154 } 155 return res 156 } 157 158 func dirSize(path string) (int64, error) { 159 var size int64 160 err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { 161 if err != nil { 162 return err 163 } 164 if !info.IsDir() { 165 size += info.Size() 166 } 167 return err 168 }) 169 return size, err 170 } 171 172 /// Perform some checks to make sure the file is OK to upload 173 func checkIfValidFile(username string, filename string, fileBytes []byte) error { 174 if len(filename) == 0 { 175 return fmt.Errorf("Please enter a filename") 176 } 177 if len(filename) > 256 { // arbitrarily chosen 178 return fmt.Errorf("Filename is too long") 179 } 180 ext := strings.ToLower(path.Ext(filename)) 181 found := false 182 for _, mimetype := range c.OkExtensions { 183 if ext == mimetype { 184 found = true 185 } 186 } 187 if username == "alex" && ext == ".html" { // obviously a hack TODO 188 found = true 189 } 190 if !found { 191 return fmt.Errorf("Invalid file extension: %s", ext) 192 } 193 if len(fileBytes) > c.MaxFileBytes { 194 return fmt.Errorf("File too large. File was %d bytes, Max file size is %d", len(fileBytes), c.MaxFileBytes) 195 } 196 userFolder := getUserDirectory(username) 197 myFiles, err := getMyFilesRecursive(userFolder, username) 198 if err != nil { 199 return err 200 } 201 if len(myFiles) >= c.MaxFilesPerUser { 202 return fmt.Errorf("You have reached the max number of files. Delete some before uploading") 203 } 204 size, err := dirSize(userFolder) 205 if err != nil || size+int64(len(fileBytes)) > c.MaxUserBytes { 206 return fmt.Errorf("You are out of storage space. Delete some files before continuing.") 207 } 208 return nil 209 } 210 211 func zipit(source string, target io.Writer) error { 212 archive := zip.NewWriter(target) 213 214 info, err := os.Stat(source) 215 if err != nil { 216 return nil 217 } 218 219 var baseDir string 220 if info.IsDir() { 221 baseDir = filepath.Base(source) 222 } 223 224 filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 225 if err != nil { 226 return err 227 } 228 229 header, err := zip.FileInfoHeader(info) 230 if err != nil { 231 return err 232 } 233 234 if baseDir != "" { 235 header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source)) 236 } 237 238 if info.IsDir() { 239 header.Name += "/" 240 } else { 241 header.Method = zip.Deflate 242 } 243 244 writer, err := archive.CreateHeader(header) 245 if err != nil { 246 return err 247 } 248 249 if info.IsDir() { 250 return nil 251 } 252 253 file, err := os.Open(path) 254 if err != nil { 255 return err 256 } 257 defer file.Close() 258 _, err = io.Copy(writer, file) 259 return err 260 }) 261 262 archive.Close() 263 264 return err 265 }