aboutsummaryrefslogtreecommitdiff
path: root/vendors/jquery-file-upload/server
diff options
context:
space:
mode:
authorSem <sembrestels@riseup.net>2012-08-02 21:44:44 +0200
committerSem <sembrestels@riseup.net>2012-08-02 21:44:44 +0200
commitdad0209bbd7994b623bc0126e3e37b1f521d2977 (patch)
tree1e954b32439cbfc2acbbd63ed62dc372a260f37e /vendors/jquery-file-upload/server
parentfa81c487488064f684649272a6579079d3a88e2c (diff)
parent28b1669c4b1afaed7429da2cc9580340dcb13b6e (diff)
downloadelgg-dad0209bbd7994b623bc0126e3e37b1f521d2977.tar.gz
elgg-dad0209bbd7994b623bc0126e3e37b1f521d2977.tar.bz2
Merge branch 'jquery-uploader' of git://github.com/sembrestels/Tidypics
Conflicts: start.php views/default/photos/css.php
Diffstat (limited to 'vendors/jquery-file-upload/server')
-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
-rw-r--r--vendors/jquery-file-upload/server/gae-python/app.yaml16
-rw-r--r--vendors/jquery-file-upload/server/gae-python/main.py149
-rw-r--r--vendors/jquery-file-upload/server/gae-python/static/favicon.icobin0 -> 1150 bytes
-rw-r--r--vendors/jquery-file-upload/server/gae-python/static/robots.txt2
-rw-r--r--vendors/jquery-file-upload/server/node/.gitignore2
-rw-r--r--vendors/jquery-file-upload/server/node/package.json41
-rw-r--r--vendors/jquery-file-upload/server/node/public/files/thumbnail/.gitignore0
-rwxr-xr-xvendors/jquery-file-upload/server/node/server.js285
-rw-r--r--vendors/jquery-file-upload/server/node/tmp/.gitignore0
-rw-r--r--vendors/jquery-file-upload/server/php/files/.htaccess4
-rw-r--r--vendors/jquery-file-upload/server/php/index.php46
-rw-r--r--vendors/jquery-file-upload/server/php/thumbnails/.htaccess0
-rw-r--r--vendors/jquery-file-upload/server/php/upload.class.php436
18 files changed, 1603 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:
diff --git a/vendors/jquery-file-upload/server/gae-python/app.yaml b/vendors/jquery-file-upload/server/gae-python/app.yaml
new file mode 100644
index 000000000..5fe123f59
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-python/app.yaml
@@ -0,0 +1,16 @@
+application: jquery-file-upload
+version: 1
+runtime: python27
+api_version: 1
+threadsafe: true
+
+builtins:
+- deferred: on
+
+handlers:
+- url: /(favicon\.ico|robots\.txt)
+ static_files: static/\1
+ upload: static/(.*)
+ expiration: '1d'
+- url: /.*
+ script: main.app
diff --git a/vendors/jquery-file-upload/server/gae-python/main.py b/vendors/jquery-file-upload/server/gae-python/main.py
new file mode 100644
index 000000000..37218c827
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-python/main.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+#
+# jQuery File Upload Plugin GAE Python Example 1.1.4
+# 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
+#
+
+from __future__ import with_statement
+from google.appengine.api import files, images
+from google.appengine.ext import blobstore, deferred
+from google.appengine.ext.webapp import blobstore_handlers
+import json, re, urllib, webapp2
+
+WEBSITE = 'http://blueimp.github.com/jQuery-File-Upload/'
+MIN_FILE_SIZE = 1 # bytes
+MAX_FILE_SIZE = 5000000 # bytes
+IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)')
+ACCEPT_FILE_TYPES = IMAGE_TYPES
+THUMBNAIL_MODIFICATOR = '=s80' # max width / height
+EXPIRATION_TIME = 300 # seconds
+
+def cleanup(blob_keys):
+ blobstore.delete(blob_keys)
+
+class UploadHandler(webapp2.RequestHandler):
+
+ def initialize(self, request, response):
+ super(UploadHandler, self).initialize(request, response)
+ self.response.headers['Access-Control-Allow-Origin'] = '*'
+ self.response.headers[
+ 'Access-Control-Allow-Methods'
+ ] = 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
+
+ def validate(self, file):
+ if file['size'] < MIN_FILE_SIZE:
+ file['error'] = 'minFileSize'
+ elif file['size'] > MAX_FILE_SIZE:
+ file['error'] = 'maxFileSize'
+ elif not ACCEPT_FILE_TYPES.match(file['type']):
+ file['error'] = 'acceptFileTypes'
+ else:
+ return True
+ return False
+
+ def get_file_size(self, file):
+ file.seek(0, 2) # Seek to the end of the file
+ size = file.tell() # Get the position of EOF
+ file.seek(0) # Reset the file position to the beginning
+ return size
+
+ def write_blob(self, data, info):
+ blob = files.blobstore.create(
+ mime_type=info['type'],
+ _blobinfo_uploaded_filename=info['name']
+ )
+ with files.open(blob, 'a') as f:
+ f.write(data)
+ files.finalize(blob)
+ return files.blobstore.get_blob_key(blob)
+
+ def handle_upload(self):
+ results = []
+ blob_keys = []
+ for name, fieldStorage in self.request.POST.items():
+ if type(fieldStorage) is unicode:
+ continue
+ result = {}
+ result['name'] = re.sub(r'^.*\\', '',
+ fieldStorage.filename)
+ result['type'] = fieldStorage.type
+ result['size'] = self.get_file_size(fieldStorage.file)
+ if self.validate(result):
+ blob_key = str(
+ self.write_blob(fieldStorage.value, result)
+ )
+ blob_keys.append(blob_key)
+ result['delete_type'] = 'DELETE'
+ result['delete_url'] = self.request.host_url +\
+ '/?key=' + urllib.quote(blob_key, '')
+ if (IMAGE_TYPES.match(result['type'])):
+ try:
+ result['url'] = images.get_serving_url(
+ blob_key,
+ secure_url=self.request.host_url\
+ .startswith('https')
+ )
+ result['thumbnail_url'] = result['url'] +\
+ THUMBNAIL_MODIFICATOR
+ except: # Could not get an image serving url
+ pass
+ if not 'url' in result:
+ result['url'] = self.request.host_url +\
+ '/' + blob_key + '/' + urllib.quote(
+ result['name'].encode('utf-8'), '')
+ results.append(result)
+ deferred.defer(
+ cleanup,
+ blob_keys,
+ _countdown=EXPIRATION_TIME
+ )
+ return results
+
+ def options(self):
+ pass
+
+ def head(self):
+ pass
+
+ def get(self):
+ self.redirect(WEBSITE)
+
+ def post(self):
+ if (self.request.get('_method') == 'DELETE'):
+ return self.delete()
+ s = json.dumps(self.handle_upload(), separators=(',',':'))
+ redirect = self.request.get('redirect')
+ if redirect:
+ return self.redirect(str(
+ redirect.replace('%s', urllib.quote(s, ''), 1)
+ ))
+ if 'application/json' in self.request.headers.get('Accept'):
+ self.response.headers['Content-Type'] = 'application/json'
+ self.response.write(s)
+
+ def delete(self):
+ blobstore.delete(self.request.get('key') or '')
+
+class DownloadHandler(blobstore_handlers.BlobstoreDownloadHandler):
+ def get(self, key, filename):
+ if not blobstore.get(key):
+ self.error(404)
+ else:
+ # Cache for the expiration time:
+ self.response.headers['Cache-Control'] =\
+ 'public,max-age=%d' % EXPIRATION_TIME
+ self.send_blob(key, save_as=filename)
+
+app = webapp2.WSGIApplication(
+ [
+ ('/', UploadHandler),
+ ('/([^/]+)/([^/]+)', DownloadHandler)
+ ],
+ debug=True
+) \ No newline at end of file
diff --git a/vendors/jquery-file-upload/server/gae-python/static/favicon.ico b/vendors/jquery-file-upload/server/gae-python/static/favicon.ico
new file mode 100644
index 000000000..1a71ea772
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-python/static/favicon.ico
Binary files differ
diff --git a/vendors/jquery-file-upload/server/gae-python/static/robots.txt b/vendors/jquery-file-upload/server/gae-python/static/robots.txt
new file mode 100644
index 000000000..eb0536286
--- /dev/null
+++ b/vendors/jquery-file-upload/server/gae-python/static/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/vendors/jquery-file-upload/server/node/.gitignore b/vendors/jquery-file-upload/server/node/.gitignore
new file mode 100644
index 000000000..9daa8247d
--- /dev/null
+++ b/vendors/jquery-file-upload/server/node/.gitignore
@@ -0,0 +1,2 @@
+.DS_Store
+node_modules
diff --git a/vendors/jquery-file-upload/server/node/package.json b/vendors/jquery-file-upload/server/node/package.json
new file mode 100644
index 000000000..0e0c1aaae
--- /dev/null
+++ b/vendors/jquery-file-upload/server/node/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "blueimp-file-upload-node",
+ "version": "1.0.2",
+ "title": "jQuery File Upload Node.js example",
+ "description": "Node.js implementation example of a file upload handler for jQuery File Upload.",
+ "keywords": [
+ "file",
+ "upload",
+ "cross-domain",
+ "cross-site",
+ "node"
+ ],
+ "homepage": "https://github.com/blueimp/jQuery-File-Upload",
+ "author": {
+ "name": "Sebastian Tschan",
+ "url": "https://blueimp.net"
+ },
+ "maintainers": [
+ {
+ "name": "Sebastian Tschan",
+ "url": "https://blueimp.net"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/blueimp/jQuery-File-Upload.git"
+ },
+ "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues",
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://www.opensource.org/licenses/MIT"
+ }
+ ],
+ "dependencies": {
+ "formidable": ">=1.0.8",
+ "node-static": ">=0.5.9",
+ "imagemagick": ">=0.1.2"
+ },
+ "main": "server.js"
+}
diff --git a/vendors/jquery-file-upload/server/node/public/files/thumbnail/.gitignore b/vendors/jquery-file-upload/server/node/public/files/thumbnail/.gitignore
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vendors/jquery-file-upload/server/node/public/files/thumbnail/.gitignore
diff --git a/vendors/jquery-file-upload/server/node/server.js b/vendors/jquery-file-upload/server/node/server.js
new file mode 100755
index 000000000..f1bec542b
--- /dev/null
+++ b/vendors/jquery-file-upload/server/node/server.js
@@ -0,0 +1,285 @@
+#!/usr/bin/env node
+/*
+ * jQuery File Upload Plugin Node.js Example 1.0.2
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/*jslint nomen: true, regexp: true, unparam: true */
+/*global require, __dirname, unescape */
+
+(function (port) {
+ 'use strict';
+ var path = require('path'),
+ fs = require('fs'),
+ // Since Node 0.8, .existsSync() moved from path to fs:
+ _existsSync = fs.existsSync || path.existsSync,
+ formidable = require('formidable'),
+ nodeStatic = require('node-static'),
+ imageMagick = require('imagemagick'),
+ options = {
+ tmpDir: __dirname + '/tmp',
+ publicDir: __dirname + '/public',
+ uploadDir: __dirname + '/public/files',
+ uploadUrl: '/files/',
+ maxPostSize: 500000000, // 500 MB
+ minFileSize: 1,
+ maxFileSize: 100000000, // 100 MB
+ acceptFileTypes: /.+/i,
+ // Files not matched by this regular expression force a download dialog,
+ // to prevent executing any scripts in the context of the service domain:
+ safeFileTypes: /\.(gif|jpe?g|png)$/i,
+ imageTypes: /\.(gif|jpe?g|png)$/i,
+ imageVersions: {
+ 'thumbnail': {
+ width: 80,
+ height: 80
+ }
+ },
+ accessControl: {
+ allowOrigin: '*',
+ allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
+ },
+ /* Uncomment and edit this section to provide the service via HTTPS:
+ ssl: {
+ key: fs.readFileSync('/Applications/XAMPP/etc/ssl.key/server.key'),
+ cert: fs.readFileSync('/Applications/XAMPP/etc/ssl.crt/server.crt')
+ },
+ */
+ nodeStatic: {
+ cache: 3600 // seconds to cache served files
+ }
+ },
+ utf8encode = function (str) {
+ return unescape(encodeURIComponent(str));
+ },
+ fileServer = new nodeStatic.Server(options.publicDir, options.nodeStatic),
+ nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/,
+ nameCountFunc = function (s, index, ext) {
+ return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || '');
+ },
+ FileInfo = function (file) {
+ this.name = file.name;
+ this.size = file.size;
+ this.type = file.type;
+ this.delete_type = 'DELETE';
+ },
+ UploadHandler = function (req, res, callback) {
+ this.req = req;
+ this.res = res;
+ this.callback = callback;
+ },
+ serve = function (req, res) {
+ res.setHeader(
+ 'Access-Control-Allow-Origin',
+ options.accessControl.allowOrigin
+ );
+ res.setHeader(
+ 'Access-Control-Allow-Methods',
+ options.accessControl.allowMethods
+ );
+ var handleResult = function (result, redirect) {
+ if (redirect) {
+ res.writeHead(302, {
+ 'Location': redirect.replace(
+ /%s/,
+ encodeURIComponent(JSON.stringify(result))
+ )
+ });
+ res.end();
+ } else {
+ res.writeHead(200, {
+ 'Content-Type': req.headers.accept
+ .indexOf('application/json') !== -1 ?
+ 'application/json' : 'text/plain'
+ });
+ res.end(JSON.stringify(result));
+ }
+ },
+ setNoCacheHeaders = function () {
+ res.setHeader('Pragma', 'no-cache');
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
+ res.setHeader('Content-Disposition', 'inline; filename="files.json"');
+ },
+ handler = new UploadHandler(req, res, handleResult);
+ switch (req.method) {
+ case 'OPTIONS':
+ res.end();
+ break;
+ case 'HEAD':
+ case 'GET':
+ if (req.url === '/') {
+ setNoCacheHeaders();
+ if (req.method === 'GET') {
+ handler.get();
+ } else {
+ res.end();
+ }
+ } else {
+ fileServer.serve(req, res);
+ }
+ break;
+ case 'POST':
+ setNoCacheHeaders();
+ handler.post();
+ break;
+ case 'DELETE':
+ handler.destroy();
+ break;
+ default:
+ res.statusCode = 405;
+ res.end();
+ }
+ };
+ fileServer.respond = function (pathname, status, _headers, files, stat, req, res, finish) {
+ if (!options.safeFileTypes.test(files[0])) {
+ // Force a download dialog for unsafe file extensions:
+ res.setHeader(
+ 'Content-Disposition',
+ 'attachment; filename="' + utf8encode(path.basename(files[0])) + '"'
+ );
+ } else {
+ // Prevent Internet Explorer from MIME-sniffing the content-type:
+ res.setHeader('X-Content-Type-Options', 'nosniff');
+ }
+ nodeStatic.Server.prototype.respond
+ .call(this, pathname, status, _headers, files, stat, req, res, finish);
+ };
+ FileInfo.prototype.validate = function () {
+ if (options.minFileSize && options.minFileSize > this.size) {
+ this.error = 'minFileSize';
+ } else if (options.maxFileSize && options.maxFileSize < this.size) {
+ this.error = 'maxFileSize';
+ } else if (!options.acceptFileTypes.test(this.name)) {
+ this.error = 'acceptFileTypes';
+ }
+ return !this.error;
+ };
+ FileInfo.prototype.safeName = function () {
+ // Prevent directory traversal and creating hidden system files:
+ this.name = path.basename(this.name).replace(/^\.+/, '');
+ // Prevent overwriting existing files:
+ while (_existsSync(options.uploadDir + '/' + this.name)) {
+ this.name = this.name.replace(nameCountRegexp, nameCountFunc);
+ }
+ };
+ FileInfo.prototype.initUrls = function (req) {
+ if (!this.error) {
+ var that = this,
+ baseUrl = (options.ssl ? 'https:' : 'http:') +
+ '//' + req.headers.host + options.uploadUrl;
+ this.url = this.delete_url = baseUrl + encodeURIComponent(this.name);
+ Object.keys(options.imageVersions).forEach(function (version) {
+ if (_existsSync(
+ options.uploadDir + '/' + version + '/' + that.name
+ )) {
+ that[version + '_url'] = baseUrl + version + '/' +
+ encodeURIComponent(that.name);
+ }
+ });
+ }
+ };
+ UploadHandler.prototype.get = function () {
+ var handler = this,
+ files = [];
+ fs.readdir(options.uploadDir, function (err, list) {
+ list.forEach(function (name) {
+ var stats = fs.statSync(options.uploadDir + '/' + name),
+ fileInfo;
+ if (stats.isFile()) {
+ fileInfo = new FileInfo({
+ name: name,
+ size: stats.size
+ });
+ fileInfo.initUrls(handler.req);
+ files.push(fileInfo);
+ }
+ });
+ handler.callback(files);
+ });
+ };
+ UploadHandler.prototype.post = function () {
+ var handler = this,
+ form = new formidable.IncomingForm(),
+ tmpFiles = [],
+ files = [],
+ map = {},
+ counter = 1,
+ redirect,
+ finish = function () {
+ counter -= 1;
+ if (!counter) {
+ files.forEach(function (fileInfo) {
+ fileInfo.initUrls(handler.req);
+ });
+ handler.callback(files, redirect);
+ }
+ };
+ form.uploadDir = options.tmpDir;
+ form.on('fileBegin', function (name, file) {
+ tmpFiles.push(file.path);
+ var fileInfo = new FileInfo(file, handler.req, true);
+ fileInfo.safeName();
+ map[path.basename(file.path)] = fileInfo;
+ files.push(fileInfo);
+ }).on('field', function (name, value) {
+ if (name === 'redirect') {
+ redirect = value;
+ }
+ }).on('file', function (name, file) {
+ var fileInfo = map[path.basename(file.path)];
+ fileInfo.size = file.size;
+ if (!fileInfo.validate()) {
+ fs.unlink(file.path);
+ return;
+ }
+ fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name);
+ if (options.imageTypes.test(fileInfo.name)) {
+ Object.keys(options.imageVersions).forEach(function (version) {
+ counter += 1;
+ var opts = options.imageVersions[version];
+ imageMagick.resize({
+ width: opts.width,
+ height: opts.height,
+ srcPath: options.uploadDir + '/' + fileInfo.name,
+ dstPath: options.uploadDir + '/' + version + '/' +
+ fileInfo.name
+ }, finish);
+ });
+ }
+ }).on('aborted', function () {
+ tmpFiles.forEach(function (file) {
+ fs.unlink(file);
+ });
+ }).on('progress', function (bytesReceived, bytesExpected) {
+ if (bytesReceived > options.maxPostSize) {
+ handler.req.connection.destroy();
+ }
+ }).on('end', finish).parse(handler.req);
+ };
+ UploadHandler.prototype.destroy = function () {
+ var handler = this,
+ fileName;
+ if (handler.req.url.slice(0, options.uploadUrl.length) === options.uploadUrl) {
+ fileName = path.basename(decodeURIComponent(handler.req.url));
+ fs.unlink(options.uploadDir + '/' + fileName, function (ex) {
+ Object.keys(options.imageVersions).forEach(function (version) {
+ fs.unlink(options.uploadDir + '/' + version + '/' + fileName);
+ });
+ handler.callback(!ex);
+ });
+ } else {
+ handler.callback(false);
+ }
+ };
+ if (options.ssl) {
+ require('https').createServer(options.ssl, serve).listen(port);
+ } else {
+ require('http').createServer(serve).listen(port);
+ }
+}(8888));
diff --git a/vendors/jquery-file-upload/server/node/tmp/.gitignore b/vendors/jquery-file-upload/server/node/tmp/.gitignore
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vendors/jquery-file-upload/server/node/tmp/.gitignore
diff --git a/vendors/jquery-file-upload/server/php/files/.htaccess b/vendors/jquery-file-upload/server/php/files/.htaccess
new file mode 100644
index 000000000..a6a9f6a75
--- /dev/null
+++ b/vendors/jquery-file-upload/server/php/files/.htaccess
@@ -0,0 +1,4 @@
+ForceType application/octet-stream
+<FilesMatch "(?i)\.(gif|jpe?g|png)$">
+ ForceType none
+</FilesMatch> \ No newline at end of file
diff --git a/vendors/jquery-file-upload/server/php/index.php b/vendors/jquery-file-upload/server/php/index.php
new file mode 100644
index 000000000..1601c76f3
--- /dev/null
+++ b/vendors/jquery-file-upload/server/php/index.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ * jQuery File Upload Plugin PHP Example 5.7
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+error_reporting(E_ALL | E_STRICT);
+
+require('upload.class.php');
+
+$upload_handler = new UploadHandler();
+
+header('Pragma: no-cache');
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Content-Disposition: inline; filename="files.json"');
+header('X-Content-Type-Options: nosniff');
+header('Access-Control-Allow-Origin: *');
+header('Access-Control-Allow-Methods: OPTIONS, HEAD, GET, POST, PUT, DELETE');
+header('Access-Control-Allow-Headers: X-File-Name, X-File-Type, X-File-Size');
+
+switch ($_SERVER['REQUEST_METHOD']) {
+ case 'OPTIONS':
+ break;
+ case 'HEAD':
+ case 'GET':
+ $upload_handler->get();
+ break;
+ case 'POST':
+ if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') {
+ $upload_handler->delete();
+ } else {
+ $upload_handler->post();
+ }
+ break;
+ case 'DELETE':
+ $upload_handler->delete();
+ break;
+ default:
+ header('HTTP/1.1 405 Method Not Allowed');
+}
diff --git a/vendors/jquery-file-upload/server/php/thumbnails/.htaccess b/vendors/jquery-file-upload/server/php/thumbnails/.htaccess
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/vendors/jquery-file-upload/server/php/thumbnails/.htaccess
diff --git a/vendors/jquery-file-upload/server/php/upload.class.php b/vendors/jquery-file-upload/server/php/upload.class.php
new file mode 100644
index 000000000..c4efacbdb
--- /dev/null
+++ b/vendors/jquery-file-upload/server/php/upload.class.php
@@ -0,0 +1,436 @@
+<?php
+/*
+ * jQuery File Upload Plugin PHP Class 5.11.2
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+class UploadHandler
+{
+ protected $options;
+
+ function __construct($options=null) {
+ $this->options = array(
+ 'script_url' => $this->getFullUrl().'/',
+ 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/',
+ 'upload_url' => $this->getFullUrl().'/files/',
+ 'param_name' => 'files',
+ // Set the following option to 'POST', if your server does not support
+ // DELETE requests. This is a parameter sent to the client:
+ 'delete_type' => 'DELETE',
+ // The php.ini settings upload_max_filesize and post_max_size
+ // take precedence over the following max_file_size setting:
+ 'max_file_size' => null,
+ 'min_file_size' => 1,
+ 'accept_file_types' => '/.+$/i',
+ // The maximum number of files for the upload directory:
+ 'max_number_of_files' => null,
+ // Image resolution restrictions:
+ 'max_width' => null,
+ 'max_height' => null,
+ 'min_width' => 1,
+ 'min_height' => 1,
+ // Set the following option to false to enable resumable uploads:
+ 'discard_aborted_uploads' => true,
+ // Set to true to rotate images based on EXIF meta data, if available:
+ 'orient_image' => false,
+ 'image_versions' => array(
+ // Uncomment the following version to restrict the size of
+ // uploaded images. You can also add additional versions with
+ // their own upload directories:
+ /*
+ 'large' => array(
+ 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/files/',
+ 'upload_url' => $this->getFullUrl().'/files/',
+ 'max_width' => 1920,
+ 'max_height' => 1200,
+ 'jpeg_quality' => 95
+ ),
+ */
+ 'thumbnail' => array(
+ 'upload_dir' => dirname($_SERVER['SCRIPT_FILENAME']).'/thumbnails/',
+ 'upload_url' => $this->getFullUrl().'/thumbnails/',
+ 'max_width' => 80,
+ 'max_height' => 80
+ )
+ )
+ );
+ if ($options) {
+ $this->options = array_replace_recursive($this->options, $options);
+ }
+ }
+
+ protected function getFullUrl() {
+ $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
+ return
+ ($https ? 'https://' : 'http://').
+ (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
+ (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
+ ($https && $_SERVER['SERVER_PORT'] === 443 ||
+ $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
+ substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
+ }
+
+ protected function set_file_delete_url($file) {
+ $file->delete_url = $this->options['script_url']
+ .'?file='.rawurlencode($file->name);
+ $file->delete_type = $this->options['delete_type'];
+ if ($file->delete_type !== 'DELETE') {
+ $file->delete_url .= '&_method=DELETE';
+ }
+ }
+
+ protected function get_file_object($file_name) {
+ $file_path = $this->options['upload_dir'].$file_name;
+ if (is_file($file_path) && $file_name[0] !== '.') {
+ $file = new stdClass();
+ $file->name = $file_name;
+ $file->size = filesize($file_path);
+ $file->url = $this->options['upload_url'].rawurlencode($file->name);
+ foreach($this->options['image_versions'] as $version => $options) {
+ if (is_file($options['upload_dir'].$file_name)) {
+ $file->{$version.'_url'} = $options['upload_url']
+ .rawurlencode($file->name);
+ }
+ }
+ $this->set_file_delete_url($file);
+ return $file;
+ }
+ return null;
+ }
+
+ protected function get_file_objects() {
+ return array_values(array_filter(array_map(
+ array($this, 'get_file_object'),
+ scandir($this->options['upload_dir'])
+ )));
+ }
+
+ protected function create_scaled_image($file_name, $options) {
+ $file_path = $this->options['upload_dir'].$file_name;
+ $new_file_path = $options['upload_dir'].$file_name;
+ list($img_width, $img_height) = @getimagesize($file_path);
+ if (!$img_width || !$img_height) {
+ return false;
+ }
+ $scale = min(
+ $options['max_width'] / $img_width,
+ $options['max_height'] / $img_height
+ );
+ if ($scale >= 1) {
+ if ($file_path !== $new_file_path) {
+ return copy($file_path, $new_file_path);
+ }
+ return true;
+ }
+ $new_width = $img_width * $scale;
+ $new_height = $img_height * $scale;
+ $new_img = @imagecreatetruecolor($new_width, $new_height);
+ switch (strtolower(substr(strrchr($file_name, '.'), 1))) {
+ case 'jpg':
+ case 'jpeg':
+ $src_img = @imagecreatefromjpeg($file_path);
+ $write_image = 'imagejpeg';
+ $image_quality = isset($options['jpeg_quality']) ?
+ $options['jpeg_quality'] : 75;
+ break;
+ case 'gif':
+ @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
+ $src_img = @imagecreatefromgif($file_path);
+ $write_image = 'imagegif';
+ $image_quality = null;
+ break;
+ case 'png':
+ @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
+ @imagealphablending($new_img, false);
+ @imagesavealpha($new_img, true);
+ $src_img = @imagecreatefrompng($file_path);
+ $write_image = 'imagepng';
+ $image_quality = isset($options['png_quality']) ?
+ $options['png_quality'] : 9;
+ break;
+ default:
+ $src_img = null;
+ }
+ $success = $src_img && @imagecopyresampled(
+ $new_img,
+ $src_img,
+ 0, 0, 0, 0,
+ $new_width,
+ $new_height,
+ $img_width,
+ $img_height
+ ) && $write_image($new_img, $new_file_path, $image_quality);
+ // Free up memory (imagedestroy does not delete files):
+ @imagedestroy($src_img);
+ @imagedestroy($new_img);
+ return $success;
+ }
+
+ protected function validate($uploaded_file, $file, $error, $index) {
+ if ($error) {
+ $file->error = $error;
+ return false;
+ }
+ if (!$file->name) {
+ $file->error = 'missingFileName';
+ return false;
+ }
+ if (!preg_match($this->options['accept_file_types'], $file->name)) {
+ $file->error = 'acceptFileTypes';
+ return false;
+ }
+ if ($uploaded_file && is_uploaded_file($uploaded_file)) {
+ $file_size = filesize($uploaded_file);
+ } else {
+ $file_size = $_SERVER['CONTENT_LENGTH'];
+ }
+ if ($this->options['max_file_size'] && (
+ $file_size > $this->options['max_file_size'] ||
+ $file->size > $this->options['max_file_size'])
+ ) {
+ $file->error = 'maxFileSize';
+ return false;
+ }
+ if ($this->options['min_file_size'] &&
+ $file_size < $this->options['min_file_size']) {
+ $file->error = 'minFileSize';
+ return false;
+ }
+ if (is_int($this->options['max_number_of_files']) && (
+ count($this->get_file_objects()) >= $this->options['max_number_of_files'])
+ ) {
+ $file->error = 'maxNumberOfFiles';
+ return false;
+ }
+ list($img_width, $img_height) = @getimagesize($uploaded_file);
+ if (is_int($img_width)) {
+ if ($this->options['max_width'] && $img_width > $this->options['max_width'] ||
+ $this->options['max_height'] && $img_height > $this->options['max_height']) {
+ $file->error = 'maxResolution';
+ return false;
+ }
+ if ($this->options['min_width'] && $img_width < $this->options['min_width'] ||
+ $this->options['min_height'] && $img_height < $this->options['min_height']) {
+ $file->error = 'minResolution';
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected function upcount_name_callback($matches) {
+ $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1;
+ $ext = isset($matches[2]) ? $matches[2] : '';
+ return ' ('.$index.')'.$ext;
+ }
+
+ protected function upcount_name($name) {
+ return preg_replace_callback(
+ '/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/',
+ array($this, 'upcount_name_callback'),
+ $name,
+ 1
+ );
+ }
+
+ protected function trim_file_name($name, $type, $index) {
+ // Remove path information and dots around the filename, to prevent uploading
+ // into different directories or replacing hidden system files.
+ // Also remove control characters and spaces (\x00..\x20) around the filename:
+ $file_name = trim(basename(stripslashes($name)), ".\x00..\x20");
+ // Add missing file extension for known image types:
+ if (strpos($file_name, '.') === false &&
+ preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
+ $file_name .= '.'.$matches[1];
+ }
+ if ($this->options['discard_aborted_uploads']) {
+ while(is_file($this->options['upload_dir'].$file_name)) {
+ $file_name = $this->upcount_name($file_name);
+ }
+ }
+ return $file_name;
+ }
+
+ protected function handle_form_data($file, $index) {
+ // Handle form data, e.g. $_REQUEST['description'][$index]
+ }
+
+ protected function orient_image($file_path) {
+ $exif = @exif_read_data($file_path);
+ if ($exif === false) {
+ return false;
+ }
+ $orientation = intval(@$exif['Orientation']);
+ if (!in_array($orientation, array(3, 6, 8))) {
+ return false;
+ }
+ $image = @imagecreatefromjpeg($file_path);
+ switch ($orientation) {
+ case 3:
+ $image = @imagerotate($image, 180, 0);
+ break;
+ case 6:
+ $image = @imagerotate($image, 270, 0);
+ break;
+ case 8:
+ $image = @imagerotate($image, 90, 0);
+ break;
+ default:
+ return false;
+ }
+ $success = imagejpeg($image, $file_path);
+ // Free up memory (imagedestroy does not delete files):
+ @imagedestroy($image);
+ return $success;
+ }
+
+ protected function handle_file_upload($uploaded_file, $name, $size, $type, $error, $index = null) {
+ $file = new stdClass();
+ $file->name = $this->trim_file_name($name, $type, $index);
+ $file->size = intval($size);
+ $file->type = $type;
+ if ($this->validate($uploaded_file, $file, $error, $index)) {
+ $this->handle_form_data($file, $index);
+ $file_path = $this->options['upload_dir'].$file->name;
+ $append_file = !$this->options['discard_aborted_uploads'] &&
+ is_file($file_path) && $file->size > filesize($file_path);
+ clearstatcache();
+ if ($uploaded_file && is_uploaded_file($uploaded_file)) {
+ // multipart/formdata uploads (POST method uploads)
+ if ($append_file) {
+ file_put_contents(
+ $file_path,
+ fopen($uploaded_file, 'r'),
+ FILE_APPEND
+ );
+ } else {
+ move_uploaded_file($uploaded_file, $file_path);
+ }
+ } else {
+ // Non-multipart uploads (PUT method support)
+ file_put_contents(
+ $file_path,
+ fopen('php://input', 'r'),
+ $append_file ? FILE_APPEND : 0
+ );
+ }
+ $file_size = filesize($file_path);
+ if ($file_size === $file->size) {
+ if ($this->options['orient_image']) {
+ $this->orient_image($file_path);
+ }
+ $file->url = $this->options['upload_url'].rawurlencode($file->name);
+ foreach($this->options['image_versions'] as $version => $options) {
+ if ($this->create_scaled_image($file->name, $options)) {
+ if ($this->options['upload_dir'] !== $options['upload_dir']) {
+ $file->{$version.'_url'} = $options['upload_url']
+ .rawurlencode($file->name);
+ } else {
+ clearstatcache();
+ $file_size = filesize($file_path);
+ }
+ }
+ }
+ } else if ($this->options['discard_aborted_uploads']) {
+ unlink($file_path);
+ $file->error = 'abort';
+ }
+ $file->size = $file_size;
+ $this->set_file_delete_url($file);
+ }
+ return $file;
+ }
+
+ public function get() {
+ $file_name = isset($_REQUEST['file']) ?
+ basename(stripslashes($_REQUEST['file'])) : null;
+ if ($file_name) {
+ $info = $this->get_file_object($file_name);
+ } else {
+ $info = $this->get_file_objects();
+ }
+ header('Content-type: application/json');
+ echo json_encode($info);
+ }
+
+ public function post() {
+ if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') {
+ return $this->delete();
+ }
+ $upload = isset($_FILES[$this->options['param_name']]) ?
+ $_FILES[$this->options['param_name']] : null;
+ $info = array();
+ if ($upload && is_array($upload['tmp_name'])) {
+ // param_name is an array identifier like "files[]",
+ // $_FILES is a multi-dimensional array:
+ foreach ($upload['tmp_name'] as $index => $value) {
+ $info[] = $this->handle_file_upload(
+ $upload['tmp_name'][$index],
+ isset($_SERVER['HTTP_X_FILE_NAME']) ?
+ $_SERVER['HTTP_X_FILE_NAME'] : $upload['name'][$index],
+ isset($_SERVER['HTTP_X_FILE_SIZE']) ?
+ $_SERVER['HTTP_X_FILE_SIZE'] : $upload['size'][$index],
+ isset($_SERVER['HTTP_X_FILE_TYPE']) ?
+ $_SERVER['HTTP_X_FILE_TYPE'] : $upload['type'][$index],
+ $upload['error'][$index],
+ $index
+ );
+ }
+ } elseif ($upload || isset($_SERVER['HTTP_X_FILE_NAME'])) {
+ // param_name is a single object identifier like "file",
+ // $_FILES is a one-dimensional array:
+ $info[] = $this->handle_file_upload(
+ isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
+ isset($_SERVER['HTTP_X_FILE_NAME']) ?
+ $_SERVER['HTTP_X_FILE_NAME'] : (isset($upload['name']) ?
+ $upload['name'] : null),
+ isset($_SERVER['HTTP_X_FILE_SIZE']) ?
+ $_SERVER['HTTP_X_FILE_SIZE'] : (isset($upload['size']) ?
+ $upload['size'] : null),
+ isset($_SERVER['HTTP_X_FILE_TYPE']) ?
+ $_SERVER['HTTP_X_FILE_TYPE'] : (isset($upload['type']) ?
+ $upload['type'] : null),
+ isset($upload['error']) ? $upload['error'] : null
+ );
+ }
+ header('Vary: Accept');
+ $json = json_encode($info);
+ $redirect = isset($_REQUEST['redirect']) ?
+ stripslashes($_REQUEST['redirect']) : null;
+ if ($redirect) {
+ header('Location: '.sprintf($redirect, rawurlencode($json)));
+ return;
+ }
+ if (isset($_SERVER['HTTP_ACCEPT']) &&
+ (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) {
+ header('Content-type: application/json');
+ } else {
+ header('Content-type: text/plain');
+ }
+ echo $json;
+ }
+
+ public function delete() {
+ $file_name = isset($_REQUEST['file']) ?
+ basename(stripslashes($_REQUEST['file'])) : null;
+ $file_path = $this->options['upload_dir'].$file_name;
+ $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path);
+ if ($success) {
+ foreach($this->options['image_versions'] as $version => $options) {
+ $file = $options['upload_dir'].$file_name;
+ if (is_file($file)) {
+ unlink($file);
+ }
+ }
+ }
+ header('Content-type: application/json');
+ echo json_encode($success);
+ }
+
+}