Blame | Last modification | View Log | Download | RSS feed
/** jQuery File Upload Plugin GAE Go Example 3.2.0* https://github.com/blueimp/jQuery-File-Upload** Copyright 2011, Sebastian Tschan* https://blueimp.net** Licensed under the MIT license:* http://www.opensource.org/licenses/MIT*/package appimport ("appengine""appengine/blobstore""appengine/image""appengine/taskqueue""bytes""encoding/json""fmt""io""log""mime/multipart""net/http""net/url""regexp""strings""time")const (WEBSITE = "https://blueimp.github.io/jQuery-File-Upload/"MIN_FILE_SIZE = 1 // bytesMAX_FILE_SIZE = 5000000 // bytesIMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)"ACCEPT_FILE_TYPES = IMAGE_TYPESEXPIRATION_TIME = 300 // secondsTHUMBNAIL_PARAM = "=s80")var (imageTypes = regexp.MustCompile(IMAGE_TYPES)acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES))type FileInfo struct {Key appengine.BlobKey `json:"-"`Url string `json:"url,omitempty"`ThumbnailUrl string `json:"thumbnailUrl,omitempty"`Name string `json:"name"`Type string `json:"type"`Size int64 `json:"size"`Error string `json:"error,omitempty"`DeleteUrl string `json:"deleteUrl,omitempty"`DeleteType string `json:"deleteType,omitempty"`}func (fi *FileInfo) ValidateType() (valid bool) {if acceptFileTypes.MatchString(fi.Type) {return true}fi.Error = "Filetype not allowed"return false}func (fi *FileInfo) ValidateSize() (valid bool) {if fi.Size < MIN_FILE_SIZE {fi.Error = "File is too small"} else if fi.Size > MAX_FILE_SIZE {fi.Error = "File is too big"} else {return true}return false}func (fi *FileInfo) CreateUrls(r *http.Request, c appengine.Context) {u := &url.URL{Scheme: r.URL.Scheme,Host: appengine.DefaultVersionHostname(c),Path: "/",}uString := u.String()fi.Url = uString + escape(string(fi.Key)) + "/" +escape(string(fi.Name))fi.DeleteUrl = fi.Url + "?delete=true"fi.DeleteType = "DELETE"if imageTypes.MatchString(fi.Type) {servingUrl, err := image.ServingURL(c,fi.Key,&image.ServingURLOptions{Secure: strings.HasSuffix(u.Scheme, "s"),Size: 0,Crop: false,},)check(err)fi.ThumbnailUrl = servingUrl.String() + THUMBNAIL_PARAM}}func check(err error) {if err != nil {panic(err)}}func escape(s string) string {return strings.Replace(url.QueryEscape(s), "+", "%20", -1)}func delayedDelete(c appengine.Context, fi *FileInfo) {if key := string(fi.Key); key != "" {task := &taskqueue.Task{Path: "/" + escape(key) + "/-",Method: "DELETE",Delay: time.Duration(EXPIRATION_TIME) * time.Second,}taskqueue.Add(c, task, "")}}func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) {fi = &FileInfo{Name: p.FileName(),Type: p.Header.Get("Content-Type"),}if !fi.ValidateType() {return}defer func() {if rec := recover(); rec != nil {log.Println(rec)fi.Error = rec.(error).Error()}}()lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1}context := appengine.NewContext(r)w, err := blobstore.Create(context, fi.Type)defer func() {w.Close()fi.Size = MAX_FILE_SIZE + 1 - lr.Nfi.Key, err = w.Key()check(err)if !fi.ValidateSize() {err := blobstore.Delete(context, fi.Key)check(err)return}delayedDelete(context, fi)fi.CreateUrls(r, context)}()check(err)_, err = io.Copy(w, lr)return}func getFormValue(p *multipart.Part) string {var b bytes.Bufferio.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiBreturn b.String()}func handleUploads(r *http.Request) (fileInfos []*FileInfo) {fileInfos = make([]*FileInfo, 0)mr, err := r.MultipartReader()check(err)r.Form, err = url.ParseQuery(r.URL.RawQuery)check(err)part, err := mr.NextPart()for err == nil {if name := part.FormName(); name != "" {if part.FileName() != "" {fileInfos = append(fileInfos, handleUpload(r, part))} else {r.Form[name] = append(r.Form[name], getFormValue(part))}}part, err = mr.NextPart()}return}func get(w http.ResponseWriter, r *http.Request) {if r.URL.Path == "/" {http.Redirect(w, r, WEBSITE, http.StatusFound)return}parts := strings.Split(r.URL.Path, "/")if len(parts) == 3 {if key := parts[1]; key != "" {blobKey := appengine.BlobKey(key)bi, err := blobstore.Stat(appengine.NewContext(r), blobKey)if err == nil {w.Header().Add("X-Content-Type-Options", "nosniff")if !imageTypes.MatchString(bi.ContentType) {w.Header().Add("Content-Type", "application/octet-stream")w.Header().Add("Content-Disposition",fmt.Sprintf("attachment; filename=\"%s\"", parts[2]),)}w.Header().Add("Cache-Control",fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME),)blobstore.Send(w, blobKey)return}}}http.Error(w, "404 Not Found", http.StatusNotFound)}func post(w http.ResponseWriter, r *http.Request) {result := make(map[string][]*FileInfo, 1)result["files"] = handleUploads(r)b, err := json.Marshal(result)check(err)if redirect := r.FormValue("redirect"); redirect != "" {if strings.Contains(redirect, "%s") {redirect = fmt.Sprintf(redirect,escape(string(b)),)}http.Redirect(w, r, redirect, http.StatusFound)return}w.Header().Set("Cache-Control", "no-cache")jsonType := "application/json"if strings.Index(r.Header.Get("Accept"), jsonType) != -1 {w.Header().Set("Content-Type", jsonType)}fmt.Fprintln(w, string(b))}func delete(w http.ResponseWriter, r *http.Request) {parts := strings.Split(r.URL.Path, "/")if len(parts) != 3 {return}result := make(map[string]bool, 1)if key := parts[1]; key != "" {c := appengine.NewContext(r)blobKey := appengine.BlobKey(key)err := blobstore.Delete(c, blobKey)check(err)err = image.DeleteServingURL(c, blobKey)check(err)result[key] = true}jsonType := "application/json"if strings.Index(r.Header.Get("Accept"), jsonType) != -1 {w.Header().Set("Content-Type", jsonType)}b, err := json.Marshal(result)check(err)fmt.Fprintln(w, string(b))}func handle(w http.ResponseWriter, r *http.Request) {params, err := url.ParseQuery(r.URL.RawQuery)check(err)w.Header().Add("Access-Control-Allow-Origin", "*")w.Header().Add("Access-Control-Allow-Methods","OPTIONS, HEAD, GET, POST, PUT, DELETE",)w.Header().Add("Access-Control-Allow-Headers","Content-Type, Content-Range, Content-Disposition",)switch r.Method {case "OPTIONS":case "HEAD":case "GET":get(w, r)case "POST":if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" {delete(w, r)} else {post(w, r)}case "DELETE":delete(w, r)default:http.Error(w, "501 Not Implemented", http.StatusNotImplemented)}}func init() {http.HandleFunc("/", handle)}