aboutsummaryrefslogtreecommitdiff
path: root/vendors/jquery-file-upload/server/gae-go
diff options
context:
space:
mode:
authorSem <sembrestels@riseup.net>2012-07-29 10:15:17 +0200
committerSem <sembrestels@riseup.net>2012-07-29 10:15:17 +0200
commitd00e34131d6177f6d22eb3cf32c50216820aafba (patch)
tree8adfab83855f19bc1de2273216707da99bec9c1d /vendors/jquery-file-upload/server/gae-go
parent24ff6662195222479b4d83d41fa89edc8a3c05d1 (diff)
downloadelgg-d00e34131d6177f6d22eb3cf32c50216820aafba.tar.gz
elgg-d00e34131d6177f6d22eb3cf32c50216820aafba.tar.bz2
Added jquery file upload plugin: https://github.com/blueimp/jQuery-File-Upload
Diffstat (limited to 'vendors/jquery-file-upload/server/gae-go')
-rw-r--r--vendors/jquery-file-upload/server/gae-go/app.yaml12
-rw-r--r--vendors/jquery-file-upload/server/gae-go/app/main.go361
-rw-r--r--vendors/jquery-file-upload/server/gae-go/resize/resize.go247
-rw-r--r--vendors/jquery-file-upload/server/gae-go/static/favicon.icobin0 -> 1150 bytes
-rw-r--r--vendors/jquery-file-upload/server/gae-go/static/robots.txt2
5 files changed, 622 insertions, 0 deletions
diff --git a/vendors/jquery-file-upload/server/gae-go/app.yaml b/vendors/jquery-file-upload/server/gae-go/app.yaml
new file mode 100644
index 000000000..2d09daa56
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-go/app.yaml
@@ -0,0 +1,12 @@
+application: jquery-file-upload
+version: 2
+runtime: go
+api_version: go1
+
+handlers:
+- url: /(favicon\.ico|robots\.txt)
+ static_files: static/\1
+ upload: static/(.*)
+ expiration: '1d'
+- url: /.*
+ script: _go_app
diff --git a/vendors/jquery-file-upload/server/gae-go/app/main.go b/vendors/jquery-file-upload/server/gae-go/app/main.go
new file mode 100644
index 000000000..01dc2f204
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-go/app/main.go
@@ -0,0 +1,361 @@
+/*
+ * jQuery File Upload Plugin GAE Go Example 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 app
+
+import (
+ "appengine"
+ "appengine/blobstore"
+ "appengine/memcache"
+ "appengine/taskqueue"
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "image"
+ "image/png"
+ "io"
+ "log"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "regexp"
+ "resize"
+ "strings"
+ "time"
+)
+
+import _ "image/gif"
+import _ "image/jpeg"
+
+const (
+ WEBSITE = "http://blueimp.github.com/jQuery-File-Upload/"
+ MIN_FILE_SIZE = 1 // bytes
+ MAX_FILE_SIZE = 5000000 // bytes
+ IMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)"
+ ACCEPT_FILE_TYPES = IMAGE_TYPES
+ EXPIRATION_TIME = 300 // seconds
+ THUMBNAIL_MAX_WIDTH = 80
+ THUMBNAIL_MAX_HEIGHT = THUMBNAIL_MAX_WIDTH
+)
+
+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:"thumbnail_url,omitempty"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Size int64 `json:"size"`
+ Error string `json:"error,omitempty"`
+ DeleteUrl string `json:"delete_url,omitempty"`
+ DeleteType string `json:"delete_type,omitempty"`
+}
+
+func (fi *FileInfo) ValidateType() (valid bool) {
+ if acceptFileTypes.MatchString(fi.Type) {
+ return true
+ }
+ fi.Error = "acceptFileTypes"
+ return false
+}
+
+func (fi *FileInfo) ValidateSize() (valid bool) {
+ if fi.Size < MIN_FILE_SIZE {
+ fi.Error = "minFileSize"
+ } else if fi.Size > MAX_FILE_SIZE {
+ fi.Error = "maxFileSize"
+ } 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
+ fi.DeleteType = "DELETE"
+ if fi.ThumbnailUrl != "" && -1 == strings.Index(
+ r.Header.Get("Accept"),
+ "application/json",
+ ) {
+ fi.ThumbnailUrl = uString + "thumbnails/" +
+ escape(string(fi.Key))
+ }
+}
+
+func (fi *FileInfo) CreateThumbnail(r io.Reader, c appengine.Context) (data []byte, err error) {
+ defer func() {
+ if rec := recover(); rec != nil {
+ log.Println(rec)
+ // 1x1 pixel transparent GIf, bas64 encoded:
+ s := "R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+ data, _ = base64.StdEncoding.DecodeString(s)
+ fi.ThumbnailUrl = "data:image/gif;base64," + s
+ }
+ memcache.Add(c, &memcache.Item{
+ Key: string(fi.Key),
+ Value: data,
+ Expiration: EXPIRATION_TIME,
+ })
+ }()
+ img, _, err := image.Decode(r)
+ check(err)
+ if bounds := img.Bounds(); bounds.Dx() > THUMBNAIL_MAX_WIDTH ||
+ bounds.Dy() > THUMBNAIL_MAX_HEIGHT {
+ w, h := THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT
+ if bounds.Dx() > bounds.Dy() {
+ h = bounds.Dy() * h / bounds.Dx()
+ } else {
+ w = bounds.Dx() * w / bounds.Dy()
+ }
+ img = resize.Resize(img, img.Bounds(), w, h)
+ }
+ var b bytes.Buffer
+ err = png.Encode(&b, img)
+ check(err)
+ data = b.Bytes()
+ fi.ThumbnailUrl = "data:image/png;base64," +
+ base64.StdEncoding.EncodeToString(data)
+ return
+}
+
+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()
+ }
+ }()
+ var b bytes.Buffer
+ 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.N
+ fi.Key, err = w.Key()
+ check(err)
+ if !fi.ValidateSize() {
+ err := blobstore.Delete(context, fi.Key)
+ check(err)
+ return
+ }
+ delayedDelete(context, fi)
+ if b.Len() > 0 {
+ fi.CreateThumbnail(&b, context)
+ }
+ fi.CreateUrls(r, context)
+ }()
+ check(err)
+ var wr io.Writer = w
+ if imageTypes.MatchString(fi.Type) {
+ wr = io.MultiWriter(&b, w)
+ }
+ _, err = io.Copy(wr, lr)
+ return
+}
+
+func getFormValue(p *multipart.Part) string {
+ var b bytes.Buffer
+ io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB
+ return 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(
+ "Cache-Control",
+ fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME),
+ )
+ if imageTypes.MatchString(bi.ContentType) {
+ w.Header().Add("X-Content-Type-Options", "nosniff")
+ } else {
+ w.Header().Add("Content-Type", "application/octet-stream")
+ w.Header().Add(
+ "Content-Disposition:",
+ fmt.Sprintf("attachment; filename=%s;", parts[2]),
+ )
+ }
+ blobstore.Send(w, appengine.BlobKey(key))
+ return
+ }
+ }
+ }
+ http.Error(w, "404 Not Found", http.StatusNotFound)
+}
+
+func post(w http.ResponseWriter, r *http.Request) {
+ b, err := json.Marshal(handleUploads(r))
+ check(err)
+ if redirect := r.FormValue("redirect"); redirect != "" {
+ http.Redirect(w, r, fmt.Sprintf(
+ redirect,
+ escape(string(b)),
+ ), http.StatusFound)
+ return
+ }
+ 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
+ }
+ if key := parts[1]; key != "" {
+ c := appengine.NewContext(r)
+ blobstore.Delete(c, appengine.BlobKey(key))
+ memcache.Delete(c, key)
+ }
+}
+
+func serveThumbnail(w http.ResponseWriter, r *http.Request) {
+ parts := strings.Split(r.URL.Path, "/")
+ if len(parts) == 3 {
+ if key := parts[2]; key != "" {
+ var data []byte
+ c := appengine.NewContext(r)
+ item, err := memcache.Get(c, key)
+ if err == nil {
+ data = item.Value
+ } else {
+ blobKey := appengine.BlobKey(key)
+ if _, err = blobstore.Stat(c, blobKey); err == nil {
+ fi := FileInfo{Key: blobKey}
+ data, _ = fi.CreateThumbnail(
+ blobstore.NewReader(c, blobKey),
+ c,
+ )
+ }
+ }
+ if err == nil && len(data) > 3 {
+ w.Header().Add(
+ "Cache-Control",
+ fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME),
+ )
+ contentType := "image/png"
+ if string(data[:3]) == "GIF" {
+ contentType = "image/gif"
+ } else if string(data[1:4]) != "PNG" {
+ contentType = "image/jpeg"
+ }
+ w.Header().Set("Content-Type", contentType)
+ fmt.Fprintln(w, string(data))
+ return
+ }
+ }
+ }
+ http.Error(w, "404 Not Found", http.StatusNotFound)
+}
+
+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",
+ )
+ 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)
+ http.HandleFunc("/thumbnails/", serveThumbnail)
+}
diff --git a/vendors/jquery-file-upload/server/gae-go/resize/resize.go b/vendors/jquery-file-upload/server/gae-go/resize/resize.go
new file mode 100644
index 000000000..dcb627870
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-go/resize/resize.go
@@ -0,0 +1,247 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package resize
+
+import (
+ "image"
+ "image/color"
+)
+
+// Resize returns a scaled copy of the image slice r of m.
+// The returned image has width w and height h.
+func Resize(m image.Image, r image.Rectangle, w, h int) image.Image {
+ if w < 0 || h < 0 {
+ return nil
+ }
+ if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 {
+ return image.NewRGBA64(image.Rect(0, 0, w, h))
+ }
+ switch m := m.(type) {
+ case *image.RGBA:
+ return resizeRGBA(m, r, w, h)
+ case *image.YCbCr:
+ if m, ok := resizeYCbCr(m, r, w, h); ok {
+ return m
+ }
+ }
+ ww, hh := uint64(w), uint64(h)
+ dx, dy := uint64(r.Dx()), uint64(r.Dy())
+ // The scaling algorithm is to nearest-neighbor magnify the dx * dy source
+ // to a (ww*dx) * (hh*dy) intermediate image and then minify the intermediate
+ // image back down to a ww * hh destination with a simple box filter.
+ // The intermediate image is implied, we do not physically allocate a slice
+ // of length ww*dx*hh*dy.
+ // For example, consider a 4*3 source image. Label its pixels from a-l:
+ // abcd
+ // efgh
+ // ijkl
+ // To resize this to a 3*2 destination image, the intermediate is 12*6.
+ // Whitespace has been added to delineate the destination pixels:
+ // aaab bbcc cddd
+ // aaab bbcc cddd
+ // eeef ffgg ghhh
+ //
+ // eeef ffgg ghhh
+ // iiij jjkk klll
+ // iiij jjkk klll
+ // Thus, the 'b' source pixel contributes one third of its value to the
+ // (0, 0) destination pixel and two thirds to (1, 0).
+ // The implementation is a two-step process. First, the source pixels are
+ // iterated over and each source pixel's contribution to 1 or more
+ // destination pixels are summed. Second, the sums are divided by a scaling
+ // factor to yield the destination pixels.
+ // TODO: By interleaving the two steps, instead of doing all of
+ // step 1 first and all of step 2 second, we could allocate a smaller sum
+ // slice of length 4*w*2 instead of 4*w*h, although the resultant code
+ // would become more complicated.
+ n, sum := dx*dy, make([]uint64, 4*w*h)
+ for y := r.Min.Y; y < r.Max.Y; y++ {
+ for x := r.Min.X; x < r.Max.X; x++ {
+ // Get the source pixel.
+ r32, g32, b32, a32 := m.At(x, y).RGBA()
+ r64 := uint64(r32)
+ g64 := uint64(g32)
+ b64 := uint64(b32)
+ a64 := uint64(a32)
+ // Spread the source pixel over 1 or more destination rows.
+ py := uint64(y) * hh
+ for remy := hh; remy > 0; {
+ qy := dy - (py % dy)
+ if qy > remy {
+ qy = remy
+ }
+ // Spread the source pixel over 1 or more destination columns.
+ px := uint64(x) * ww
+ index := 4 * ((py/dy)*ww + (px / dx))
+ for remx := ww; remx > 0; {
+ qx := dx - (px % dx)
+ if qx > remx {
+ qx = remx
+ }
+ sum[index+0] += r64 * qx * qy
+ sum[index+1] += g64 * qx * qy
+ sum[index+2] += b64 * qx * qy
+ sum[index+3] += a64 * qx * qy
+ index += 4
+ px += qx
+ remx -= qx
+ }
+ py += qy
+ remy -= qy
+ }
+ }
+ }
+ return average(sum, w, h, n*0x0101)
+}
+
+// average convert the sums to averages and returns the result.
+func average(sum []uint64, w, h int, n uint64) image.Image {
+ ret := image.NewRGBA(image.Rect(0, 0, w, h))
+ for y := 0; y < h; y++ {
+ for x := 0; x < w; x++ {
+ index := 4 * (y*w + x)
+ ret.SetRGBA(x, y, color.RGBA{
+ uint8(sum[index+0] / n),
+ uint8(sum[index+1] / n),
+ uint8(sum[index+2] / n),
+ uint8(sum[index+3] / n),
+ })
+ }
+ }
+ return ret
+}
+
+// resizeYCbCr returns a scaled copy of the YCbCr image slice r of m.
+// The returned image has width w and height h.
+func resizeYCbCr(m *image.YCbCr, r image.Rectangle, w, h int) (image.Image, bool) {
+ var verticalRes int
+ switch m.SubsampleRatio {
+ case image.YCbCrSubsampleRatio420:
+ verticalRes = 2
+ case image.YCbCrSubsampleRatio422:
+ verticalRes = 1
+ default:
+ return nil, false
+ }
+ ww, hh := uint64(w), uint64(h)
+ dx, dy := uint64(r.Dx()), uint64(r.Dy())
+ // See comment in Resize.
+ n, sum := dx*dy, make([]uint64, 4*w*h)
+ for y := r.Min.Y; y < r.Max.Y; y++ {
+ Y := m.Y[y*m.YStride:]
+ Cb := m.Cb[y/verticalRes*m.CStride:]
+ Cr := m.Cr[y/verticalRes*m.CStride:]
+ for x := r.Min.X; x < r.Max.X; x++ {
+ // Get the source pixel.
+ r8, g8, b8 := color.YCbCrToRGB(Y[x], Cb[x/2], Cr[x/2])
+ r64 := uint64(r8)
+ g64 := uint64(g8)
+ b64 := uint64(b8)
+ // Spread the source pixel over 1 or more destination rows.
+ py := uint64(y) * hh
+ for remy := hh; remy > 0; {
+ qy := dy - (py % dy)
+ if qy > remy {
+ qy = remy
+ }
+ // Spread the source pixel over 1 or more destination columns.
+ px := uint64(x) * ww
+ index := 4 * ((py/dy)*ww + (px / dx))
+ for remx := ww; remx > 0; {
+ qx := dx - (px % dx)
+ if qx > remx {
+ qx = remx
+ }
+ qxy := qx * qy
+ sum[index+0] += r64 * qxy
+ sum[index+1] += g64 * qxy
+ sum[index+2] += b64 * qxy
+ sum[index+3] += 0xFFFF * qxy
+ index += 4
+ px += qx
+ remx -= qx
+ }
+ py += qy
+ remy -= qy
+ }
+ }
+ }
+ return average(sum, w, h, n), true
+}
+
+// resizeRGBA returns a scaled copy of the RGBA image slice r of m.
+// The returned image has width w and height h.
+func resizeRGBA(m *image.RGBA, r image.Rectangle, w, h int) image.Image {
+ ww, hh := uint64(w), uint64(h)
+ dx, dy := uint64(r.Dx()), uint64(r.Dy())
+ // See comment in Resize.
+ n, sum := dx*dy, make([]uint64, 4*w*h)
+ for y := r.Min.Y; y < r.Max.Y; y++ {
+ pixOffset := m.PixOffset(r.Min.X, y)
+ for x := r.Min.X; x < r.Max.X; x++ {
+ // Get the source pixel.
+ r64 := uint64(m.Pix[pixOffset+0])
+ g64 := uint64(m.Pix[pixOffset+1])
+ b64 := uint64(m.Pix[pixOffset+2])
+ a64 := uint64(m.Pix[pixOffset+3])
+ pixOffset += 4
+ // Spread the source pixel over 1 or more destination rows.
+ py := uint64(y) * hh
+ for remy := hh; remy > 0; {
+ qy := dy - (py % dy)
+ if qy > remy {
+ qy = remy
+ }
+ // Spread the source pixel over 1 or more destination columns.
+ px := uint64(x) * ww
+ index := 4 * ((py/dy)*ww + (px / dx))
+ for remx := ww; remx > 0; {
+ qx := dx - (px % dx)
+ if qx > remx {
+ qx = remx
+ }
+ qxy := qx * qy
+ sum[index+0] += r64 * qxy
+ sum[index+1] += g64 * qxy
+ sum[index+2] += b64 * qxy
+ sum[index+3] += a64 * qxy
+ index += 4
+ px += qx
+ remx -= qx
+ }
+ py += qy
+ remy -= qy
+ }
+ }
+ }
+ return average(sum, w, h, n)
+}
+
+// Resample returns a resampled copy of the image slice r of m.
+// The returned image has width w and height h.
+func Resample(m image.Image, r image.Rectangle, w, h int) image.Image {
+ if w < 0 || h < 0 {
+ return nil
+ }
+ if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 {
+ return image.NewRGBA64(image.Rect(0, 0, w, h))
+ }
+ curw, curh := r.Dx(), r.Dy()
+ img := image.NewRGBA(image.Rect(0, 0, w, h))
+ for y := 0; y < h; y++ {
+ for x := 0; x < w; x++ {
+ // Get a source pixel.
+ subx := x * curw / w
+ suby := y * curh / h
+ r32, g32, b32, a32 := m.At(subx, suby).RGBA()
+ r := uint8(r32 >> 8)
+ g := uint8(g32 >> 8)
+ b := uint8(b32 >> 8)
+ a := uint8(a32 >> 8)
+ img.SetRGBA(x, y, color.RGBA{r, g, b, a})
+ }
+ }
+ return img
+}
diff --git a/vendors/jquery-file-upload/server/gae-go/static/favicon.ico b/vendors/jquery-file-upload/server/gae-go/static/favicon.ico
new file mode 100644
index 000000000..1a71ea772
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-go/static/favicon.ico
Binary files differ
diff --git a/vendors/jquery-file-upload/server/gae-go/static/robots.txt b/vendors/jquery-file-upload/server/gae-go/static/robots.txt
new file mode 100644
index 000000000..eb0536286
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-go/static/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: