Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 1
/*
2
 * jQuery File Upload Plugin GAE Go Example 3.2.0
3
 * https://github.com/blueimp/jQuery-File-Upload
4
 *
5
 * Copyright 2011, Sebastian Tschan
6
 * https://blueimp.net
7
 *
8
 * Licensed under the MIT license:
9
 * http://www.opensource.org/licenses/MIT
10
 */
11
 
12
package app
13
 
14
import (
15
	"appengine"
16
	"appengine/blobstore"
17
	"appengine/image"
18
	"appengine/taskqueue"
19
	"bytes"
20
	"encoding/json"
21
	"fmt"
22
	"io"
23
	"log"
24
	"mime/multipart"
25
	"net/http"
26
	"net/url"
27
	"regexp"
28
	"strings"
29
	"time"
30
)
31
 
32
const (
33
	WEBSITE           = "https://blueimp.github.io/jQuery-File-Upload/"
34
	MIN_FILE_SIZE     = 1       // bytes
35
	MAX_FILE_SIZE     = 5000000 // bytes
36
	IMAGE_TYPES       = "image/(gif|p?jpeg|(x-)?png)"
37
	ACCEPT_FILE_TYPES = IMAGE_TYPES
38
	EXPIRATION_TIME   = 300 // seconds
39
	THUMBNAIL_PARAM   = "=s80"
40
)
41
 
42
var (
43
	imageTypes      = regexp.MustCompile(IMAGE_TYPES)
44
	acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES)
45
)
46
 
47
type FileInfo struct {
48
	Key          appengine.BlobKey `json:"-"`
49
	Url          string            `json:"url,omitempty"`
50
	ThumbnailUrl string            `json:"thumbnailUrl,omitempty"`
51
	Name         string            `json:"name"`
52
	Type         string            `json:"type"`
53
	Size         int64             `json:"size"`
54
	Error        string            `json:"error,omitempty"`
55
	DeleteUrl    string            `json:"deleteUrl,omitempty"`
56
	DeleteType   string            `json:"deleteType,omitempty"`
57
}
58
 
59
func (fi *FileInfo) ValidateType() (valid bool) {
60
	if acceptFileTypes.MatchString(fi.Type) {
61
		return true
62
	}
63
	fi.Error = "Filetype not allowed"
64
	return false
65
}
66
 
67
func (fi *FileInfo) ValidateSize() (valid bool) {
68
	if fi.Size < MIN_FILE_SIZE {
69
		fi.Error = "File is too small"
70
	} else if fi.Size > MAX_FILE_SIZE {
71
		fi.Error = "File is too big"
72
	} else {
73
		return true
74
	}
75
	return false
76
}
77
 
78
func (fi *FileInfo) CreateUrls(r *http.Request, c appengine.Context) {
79
	u := &url.URL{
80
		Scheme: r.URL.Scheme,
81
		Host:   appengine.DefaultVersionHostname(c),
82
		Path:   "/",
83
	}
84
	uString := u.String()
85
	fi.Url = uString + escape(string(fi.Key)) + "/" +
86
		escape(string(fi.Name))
87
	fi.DeleteUrl = fi.Url + "?delete=true"
88
	fi.DeleteType = "DELETE"
89
	if imageTypes.MatchString(fi.Type) {
90
		servingUrl, err := image.ServingURL(
91
			c,
92
			fi.Key,
93
			&image.ServingURLOptions{
94
				Secure: strings.HasSuffix(u.Scheme, "s"),
95
				Size:   0,
96
				Crop:   false,
97
			},
98
		)
99
		check(err)
100
		fi.ThumbnailUrl = servingUrl.String() + THUMBNAIL_PARAM
101
	}
102
}
103
 
104
func check(err error) {
105
	if err != nil {
106
		panic(err)
107
	}
108
}
109
 
110
func escape(s string) string {
111
	return strings.Replace(url.QueryEscape(s), "+", "%20", -1)
112
}
113
 
114
func delayedDelete(c appengine.Context, fi *FileInfo) {
115
	if key := string(fi.Key); key != "" {
116
		task := &taskqueue.Task{
117
			Path:   "/" + escape(key) + "/-",
118
			Method: "DELETE",
119
			Delay:  time.Duration(EXPIRATION_TIME) * time.Second,
120
		}
121
		taskqueue.Add(c, task, "")
122
	}
123
}
124
 
125
func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) {
126
	fi = &FileInfo{
127
		Name: p.FileName(),
128
		Type: p.Header.Get("Content-Type"),
129
	}
130
	if !fi.ValidateType() {
131
		return
132
	}
133
	defer func() {
134
		if rec := recover(); rec != nil {
135
			log.Println(rec)
136
			fi.Error = rec.(error).Error()
137
		}
138
	}()
139
	lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1}
140
	context := appengine.NewContext(r)
141
	w, err := blobstore.Create(context, fi.Type)
142
	defer func() {
143
		w.Close()
144
		fi.Size = MAX_FILE_SIZE + 1 - lr.N
145
		fi.Key, err = w.Key()
146
		check(err)
147
		if !fi.ValidateSize() {
148
			err := blobstore.Delete(context, fi.Key)
149
			check(err)
150
			return
151
		}
152
		delayedDelete(context, fi)
153
		fi.CreateUrls(r, context)
154
	}()
155
	check(err)
156
	_, err = io.Copy(w, lr)
157
	return
158
}
159
 
160
func getFormValue(p *multipart.Part) string {
161
	var b bytes.Buffer
162
	io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB
163
	return b.String()
164
}
165
 
166
func handleUploads(r *http.Request) (fileInfos []*FileInfo) {
167
	fileInfos = make([]*FileInfo, 0)
168
	mr, err := r.MultipartReader()
169
	check(err)
170
	r.Form, err = url.ParseQuery(r.URL.RawQuery)
171
	check(err)
172
	part, err := mr.NextPart()
173
	for err == nil {
174
		if name := part.FormName(); name != "" {
175
			if part.FileName() != "" {
176
				fileInfos = append(fileInfos, handleUpload(r, part))
177
			} else {
178
				r.Form[name] = append(r.Form[name], getFormValue(part))
179
			}
180
		}
181
		part, err = mr.NextPart()
182
	}
183
	return
184
}
185
 
186
func get(w http.ResponseWriter, r *http.Request) {
187
	if r.URL.Path == "/" {
188
		http.Redirect(w, r, WEBSITE, http.StatusFound)
189
		return
190
	}
191
	parts := strings.Split(r.URL.Path, "/")
192
	if len(parts) == 3 {
193
		if key := parts[1]; key != "" {
194
			blobKey := appengine.BlobKey(key)
195
			bi, err := blobstore.Stat(appengine.NewContext(r), blobKey)
196
			if err == nil {
197
				w.Header().Add("X-Content-Type-Options", "nosniff")
198
				if !imageTypes.MatchString(bi.ContentType) {
199
					w.Header().Add("Content-Type", "application/octet-stream")
200
					w.Header().Add(
201
						"Content-Disposition",
202
						fmt.Sprintf("attachment; filename=\"%s\"", parts[2]),
203
					)
204
				}
205
				w.Header().Add(
206
					"Cache-Control",
207
					fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME),
208
				)
209
				blobstore.Send(w, blobKey)
210
				return
211
			}
212
		}
213
	}
214
	http.Error(w, "404 Not Found", http.StatusNotFound)
215
}
216
 
217
func post(w http.ResponseWriter, r *http.Request) {
218
    result := make(map[string][]*FileInfo, 1)
219
    result["files"] = handleUploads(r)
220
	b, err := json.Marshal(result)
221
	check(err)
222
	if redirect := r.FormValue("redirect"); redirect != "" {
223
	    if strings.Contains(redirect, "%s") {
224
	        redirect = fmt.Sprintf(
225
    			redirect,
226
    			escape(string(b)),
227
    		)
228
	    }
229
		http.Redirect(w, r, redirect, http.StatusFound)
230
		return
231
	}
232
	w.Header().Set("Cache-Control", "no-cache")
233
	jsonType := "application/json"
234
	if strings.Index(r.Header.Get("Accept"), jsonType) != -1 {
235
		w.Header().Set("Content-Type", jsonType)
236
	}
237
	fmt.Fprintln(w, string(b))
238
}
239
 
240
func delete(w http.ResponseWriter, r *http.Request) {
241
	parts := strings.Split(r.URL.Path, "/")
242
	if len(parts) != 3 {
243
		return
244
	}
245
	result := make(map[string]bool, 1)
246
	if key := parts[1]; key != "" {
247
		c := appengine.NewContext(r)
248
		blobKey := appengine.BlobKey(key)
249
		err := blobstore.Delete(c, blobKey)
250
		check(err)
251
		err = image.DeleteServingURL(c, blobKey)
252
		check(err)
253
		result[key] = true
254
	}
255
	jsonType := "application/json"
256
	if strings.Index(r.Header.Get("Accept"), jsonType) != -1 {
257
		w.Header().Set("Content-Type", jsonType)
258
	}
259
	b, err := json.Marshal(result)
260
	check(err)
261
	fmt.Fprintln(w, string(b))
262
}
263
 
264
func handle(w http.ResponseWriter, r *http.Request) {
265
	params, err := url.ParseQuery(r.URL.RawQuery)
266
	check(err)
267
	w.Header().Add("Access-Control-Allow-Origin", "*")
268
	w.Header().Add(
269
		"Access-Control-Allow-Methods",
270
		"OPTIONS, HEAD, GET, POST, PUT, DELETE",
271
	)
272
	w.Header().Add(
273
		"Access-Control-Allow-Headers",
274
		"Content-Type, Content-Range, Content-Disposition",
275
	)
276
	switch r.Method {
277
	case "OPTIONS":
278
	case "HEAD":
279
	case "GET":
280
		get(w, r)
281
	case "POST":
282
		if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" {
283
			delete(w, r)
284
		} else {
285
			post(w, r)
286
		}
287
	case "DELETE":
288
		delete(w, r)
289
	default:
290
		http.Error(w, "501 Not Implemented", http.StatusNotImplemented)
291
	}
292
}
293
 
294
func init() {
295
	http.HandleFunc("/", handle)
296
}