Contains the Concourse pipeline definition for building a line-server container
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

369 lines
9.6 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/url"
  10. "path"
  11. "path/filepath"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/andreimarcu/linx-server/backends"
  17. "github.com/andreimarcu/linx-server/expiry"
  18. "github.com/dchest/uniuri"
  19. "github.com/zenazn/goji/web"
  20. "gopkg.in/h2non/filetype.v1"
  21. )
  22. var FileTooLargeError = errors.New("File too large.")
  23. var fileBlacklist = map[string]bool{
  24. "favicon.ico": true,
  25. "index.htm": true,
  26. "index.html": true,
  27. "index.php": true,
  28. "robots.txt": true,
  29. "crossdomain.xml": true,
  30. }
  31. // Describes metadata directly from the user request
  32. type UploadRequest struct {
  33. src io.Reader
  34. size int64
  35. filename string
  36. expiry time.Duration // Seconds until expiry, 0 = never
  37. deleteKey string // Empty string if not defined
  38. randomBarename bool
  39. }
  40. // Metadata associated with a file as it would actually be stored
  41. type Upload struct {
  42. Filename string // Final filename on disk
  43. Metadata backends.Metadata
  44. }
  45. func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) {
  46. if !strictReferrerCheck(r, getSiteURL(r), []string{"Linx-Delete-Key", "Linx-Expiry", "Linx-Randomize", "X-Requested-With"}) {
  47. badRequestHandler(c, w, r, RespAUTO, "")
  48. return
  49. }
  50. upReq := UploadRequest{}
  51. uploadHeaderProcess(r, &upReq)
  52. contentType := r.Header.Get("Content-Type")
  53. if strings.HasPrefix(contentType, "multipart/form-data") {
  54. file, headers, err := r.FormFile("file")
  55. if err != nil {
  56. oopsHandler(c, w, r, RespHTML, "Could not upload file.")
  57. return
  58. }
  59. defer file.Close()
  60. upReq.src = file
  61. upReq.size = headers.Size
  62. upReq.filename = headers.Filename
  63. } else {
  64. if r.PostFormValue("content") == "" {
  65. badRequestHandler(c, w, r, RespAUTO, "Empty file")
  66. return
  67. }
  68. extension := r.PostFormValue("extension")
  69. if extension == "" {
  70. extension = "txt"
  71. }
  72. content := r.PostFormValue("content")
  73. upReq.src = strings.NewReader(content)
  74. upReq.size = int64(len(content))
  75. upReq.filename = r.PostFormValue("filename") + "." + extension
  76. }
  77. upReq.expiry = parseExpiry(r.PostFormValue("expires"))
  78. if r.PostFormValue("randomize") == "true" {
  79. upReq.randomBarename = true
  80. }
  81. upload, err := processUpload(upReq)
  82. if strings.EqualFold("application/json", r.Header.Get("Accept")) {
  83. if err == FileTooLargeError || err == backends.FileEmptyError {
  84. badRequestHandler(c, w, r, RespJSON, err.Error())
  85. return
  86. } else if err != nil {
  87. oopsHandler(c, w, r, RespJSON, "Could not upload file: "+err.Error())
  88. return
  89. }
  90. js := generateJSONresponse(upload, r)
  91. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  92. w.Write(js)
  93. } else {
  94. if err == FileTooLargeError || err == backends.FileEmptyError {
  95. badRequestHandler(c, w, r, RespHTML, err.Error())
  96. return
  97. } else if err != nil {
  98. oopsHandler(c, w, r, RespHTML, "Could not upload file: "+err.Error())
  99. return
  100. }
  101. http.Redirect(w, r, Config.sitePath+upload.Filename, 303)
  102. }
  103. }
  104. func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) {
  105. upReq := UploadRequest{}
  106. uploadHeaderProcess(r, &upReq)
  107. defer r.Body.Close()
  108. upReq.filename = c.URLParams["name"]
  109. upReq.src = http.MaxBytesReader(w, r.Body, Config.maxSize)
  110. upload, err := processUpload(upReq)
  111. if strings.EqualFold("application/json", r.Header.Get("Accept")) {
  112. if err == FileTooLargeError || err == backends.FileEmptyError {
  113. badRequestHandler(c, w, r, RespJSON, err.Error())
  114. return
  115. } else if err != nil {
  116. oopsHandler(c, w, r, RespJSON, "Could not upload file: "+err.Error())
  117. return
  118. }
  119. js := generateJSONresponse(upload, r)
  120. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  121. w.Write(js)
  122. } else {
  123. if err == FileTooLargeError || err == backends.FileEmptyError {
  124. badRequestHandler(c, w, r, RespPLAIN, err.Error())
  125. return
  126. } else if err != nil {
  127. oopsHandler(c, w, r, RespPLAIN, "Could not upload file: "+err.Error())
  128. return
  129. }
  130. fmt.Fprintf(w, "%s\n", getSiteURL(r)+upload.Filename)
  131. }
  132. }
  133. func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) {
  134. if Config.remoteAuthFile != "" {
  135. result, err := checkAuth(remoteAuthKeys, r.FormValue("key"))
  136. if err != nil || !result {
  137. unauthorizedHandler(c, w, r)
  138. return
  139. }
  140. }
  141. if r.FormValue("url") == "" {
  142. http.Redirect(w, r, Config.sitePath, 303)
  143. return
  144. }
  145. upReq := UploadRequest{}
  146. grabUrl, _ := url.Parse(r.FormValue("url"))
  147. resp, err := http.Get(grabUrl.String())
  148. if err != nil {
  149. oopsHandler(c, w, r, RespAUTO, "Could not retrieve URL")
  150. return
  151. }
  152. upReq.filename = filepath.Base(grabUrl.Path)
  153. upReq.src = http.MaxBytesReader(w, resp.Body, Config.maxSize)
  154. upReq.deleteKey = r.FormValue("deletekey")
  155. upReq.randomBarename = r.FormValue("randomize") == "yes"
  156. upReq.expiry = parseExpiry(r.FormValue("expiry"))
  157. upload, err := processUpload(upReq)
  158. if strings.EqualFold("application/json", r.Header.Get("Accept")) {
  159. if err != nil {
  160. oopsHandler(c, w, r, RespJSON, "Could not upload file: "+err.Error())
  161. return
  162. }
  163. js := generateJSONresponse(upload, r)
  164. w.Header().Set("Content-Type", "application/json; charset=UTF-8")
  165. w.Write(js)
  166. } else {
  167. if err != nil {
  168. oopsHandler(c, w, r, RespHTML, "Could not upload file: "+err.Error())
  169. return
  170. }
  171. http.Redirect(w, r, Config.sitePath+upload.Filename, 303)
  172. }
  173. }
  174. func uploadHeaderProcess(r *http.Request, upReq *UploadRequest) {
  175. if r.Header.Get("Linx-Randomize") == "yes" {
  176. upReq.randomBarename = true
  177. }
  178. upReq.deleteKey = r.Header.Get("Linx-Delete-Key")
  179. // Get seconds until expiry. Non-integer responses never expire.
  180. expStr := r.Header.Get("Linx-Expiry")
  181. upReq.expiry = parseExpiry(expStr)
  182. }
  183. func processUpload(upReq UploadRequest) (upload Upload, err error) {
  184. if upReq.size > Config.maxSize {
  185. return upload, FileTooLargeError
  186. }
  187. // Determine the appropriate filename, then write to disk
  188. barename, extension := barePlusExt(upReq.filename)
  189. if upReq.randomBarename || len(barename) == 0 {
  190. barename = generateBarename()
  191. }
  192. var header []byte
  193. if len(extension) == 0 {
  194. // Pull the first 512 bytes off for use in MIME detection
  195. header = make([]byte, 512)
  196. n, _ := upReq.src.Read(header)
  197. if n == 0 {
  198. return upload, backends.FileEmptyError
  199. }
  200. header = header[:n]
  201. // Determine the type of file from header
  202. kind, err := filetype.Match(header)
  203. if err != nil || kind.Extension == "unknown" {
  204. extension = "file"
  205. } else {
  206. extension = kind.Extension
  207. }
  208. }
  209. upload.Filename = strings.Join([]string{barename, extension}, ".")
  210. upload.Filename = strings.Replace(upload.Filename, " ", "", -1)
  211. fileexists, _ := storageBackend.Exists(upload.Filename)
  212. // Check if the delete key matches, in which case overwrite
  213. if fileexists {
  214. metad, merr := storageBackend.Head(upload.Filename)
  215. if merr == nil {
  216. if upReq.deleteKey == metad.DeleteKey {
  217. fileexists = false
  218. }
  219. }
  220. }
  221. for fileexists {
  222. counter, err := strconv.Atoi(string(barename[len(barename)-1]))
  223. if err != nil {
  224. barename = barename + "1"
  225. } else {
  226. barename = barename[:len(barename)-1] + strconv.Itoa(counter+1)
  227. }
  228. upload.Filename = strings.Join([]string{barename, extension}, ".")
  229. fileexists, err = storageBackend.Exists(upload.Filename)
  230. }
  231. if fileBlacklist[strings.ToLower(upload.Filename)] {
  232. return upload, errors.New("Prohibited filename")
  233. }
  234. // Get the rest of the metadata needed for storage
  235. var fileExpiry time.Time
  236. if upReq.expiry == 0 {
  237. fileExpiry = expiry.NeverExpire
  238. } else {
  239. fileExpiry = time.Now().Add(upReq.expiry)
  240. }
  241. if upReq.deleteKey == "" {
  242. upReq.deleteKey = uniuri.NewLen(30)
  243. }
  244. upload.Metadata, err = storageBackend.Put(upload.Filename, io.MultiReader(bytes.NewReader(header), upReq.src), fileExpiry, upReq.deleteKey)
  245. if err != nil {
  246. return upload, err
  247. }
  248. return
  249. }
  250. func generateBarename() string {
  251. return uniuri.NewLenChars(8, []byte("abcdefghijklmnopqrstuvwxyz0123456789"))
  252. }
  253. func generateJSONresponse(upload Upload, r *http.Request) []byte {
  254. js, _ := json.Marshal(map[string]string{
  255. "url": getSiteURL(r) + upload.Filename,
  256. "direct_url": getSiteURL(r) + Config.selifPath + upload.Filename,
  257. "filename": upload.Filename,
  258. "delete_key": upload.Metadata.DeleteKey,
  259. "expiry": strconv.FormatInt(upload.Metadata.Expiry.Unix(), 10),
  260. "size": strconv.FormatInt(upload.Metadata.Size, 10),
  261. "mimetype": upload.Metadata.Mimetype,
  262. "sha256sum": upload.Metadata.Sha256sum,
  263. })
  264. return js
  265. }
  266. var bareRe = regexp.MustCompile(`[^A-Za-z0-9\-]`)
  267. var extRe = regexp.MustCompile(`[^A-Za-z0-9\-\.]`)
  268. var compressedExts = map[string]bool{
  269. ".bz2": true,
  270. ".gz": true,
  271. ".xz": true,
  272. }
  273. var archiveExts = map[string]bool{
  274. ".tar": true,
  275. }
  276. func barePlusExt(filename string) (barename, extension string) {
  277. filename = strings.TrimSpace(filename)
  278. filename = strings.ToLower(filename)
  279. extension = path.Ext(filename)
  280. barename = filename[:len(filename)-len(extension)]
  281. if compressedExts[extension] {
  282. ext2 := path.Ext(barename)
  283. if archiveExts[ext2] {
  284. barename = barename[:len(barename)-len(ext2)]
  285. extension = ext2 + extension
  286. }
  287. }
  288. extension = extRe.ReplaceAllString(extension, "")
  289. barename = bareRe.ReplaceAllString(barename, "")
  290. extension = strings.Trim(extension, "-.")
  291. barename = strings.Trim(barename, "-")
  292. return
  293. }
  294. func parseExpiry(expStr string) time.Duration {
  295. if expStr == "" {
  296. return time.Duration(Config.maxExpiry) * time.Second
  297. } else {
  298. fileExpiry, err := strconv.ParseUint(expStr, 10, 64)
  299. if err != nil {
  300. return time.Duration(Config.maxExpiry) * time.Second
  301. } else {
  302. if Config.maxExpiry > 0 && (fileExpiry > Config.maxExpiry || fileExpiry == 0) {
  303. fileExpiry = Config.maxExpiry
  304. }
  305. return time.Duration(fileExpiry) * time.Second
  306. }
  307. }
  308. }