OLD | NEW |
---|---|
1 package frontend | 1 package frontend |
2 | 2 |
3 import ( | 3 import ( |
4 "bytes" | 4 "bytes" |
5 "encoding/json" | 5 "encoding/json" |
6 "fmt" | 6 "fmt" |
7 "infra/appengine/test-results/model" | 7 "infra/appengine/test-results/model" |
8 "io" | 8 "io" |
9 "net/http" | 9 "net/http" |
10 "regexp" | |
11 "time" | 10 "time" |
12 | 11 |
13 "github.com/luci/gae/service/datastore" | 12 "github.com/luci/gae/service/datastore" |
14 "github.com/luci/luci-go/common/logging" | 13 "github.com/luci/luci-go/common/logging" |
15 "github.com/luci/luci-go/server/router" | 14 "github.com/luci/luci-go/server/router" |
16 "github.com/luci/luci-go/server/templates" | 15 "github.com/luci/luci-go/server/templates" |
17 "golang.org/x/net/context" | 16 "golang.org/x/net/context" |
18 ) | 17 ) |
19 | 18 |
20 const ( | 19 const ( |
21 // paramsTimeFormat is the time format string in incoming GET | 20 // paramsTimeFormat is the time format string in incoming GET |
22 // /testfile requests. | 21 // /testfile requests. |
23 paramsTimeFormat = "2006-01-02T15:04:05Z" // RFC3339, but enforce Z for timezone. | 22 paramsTimeFormat = "2006-01-02T15:04:05Z" // RFC3339, but enforce Z for timezone. |
24 | 23 |
25 // httpTimeFormat is the time format used in HTTP headers. | 24 // httpTimeFormat is the time format used in HTTP headers. |
26 // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html | 25 // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html |
27 // (Section 14.18 Date). | 26 // (Section 14.18 Date). |
28 httpTimeFormat = time.RFC1123 | 27 httpTimeFormat = time.RFC1123 |
29 | 28 |
30 // httpNoTZTimeFormat is httpTimeFormat with the timezone removed. | 29 // httpNoTZTimeFormat is httpTimeFormat with the timezone removed. |
31 httpNoTZTimeFormat = "Mon, 02 Jan 2006 15:04:05" | 30 httpNoTZTimeFormat = "Mon, 02 Jan 2006 15:04:05" |
32 ) | 31 ) |
33 | 32 |
34 // callbackNameRx matches start of strings that look like | 33 // getHandler is the HTTP handler for GET /testfile requests. |
35 // JavaScript function names. Not a comprehensive solution. | 34 func getHandler(ctx *router.Context) { |
36 var callbackNameRx = regexp.MustCompile(`^[A-Za-z0-9_]+$`) | |
estaab
2016/08/16 15:33:23
What's the consequence of removing this? Any secur
| |
37 | |
38 // GetHandler is the HTTP handler for GET /testfile requests. | |
39 func GetHandler(ctx *router.Context) { | |
40 c, w, r := ctx.Context, ctx.Writer, ctx.Request | 35 c, w, r := ctx.Context, ctx.Writer, ctx.Request |
41 if err := r.ParseForm(); err != nil { | 36 if err := r.ParseForm(); err != nil { |
42 http.Error(w, err.Error(), http.StatusInternalServerError) | 37 http.Error(w, err.Error(), http.StatusInternalServerError) |
43 logging.Errorf(c, "failed to parse form: %v", err) | 38 logging.Errorf(c, "failed to parse form: %v", err) |
44 return | 39 return |
45 } | 40 } |
46 | 41 |
47 params, err := NewURLParams(r.Form) | 42 params, err := NewURLParams(r.Form) |
48 if err != nil { | 43 if err != nil { |
49 e := fmt.Sprintf("failed to parse URL parameters: %+v: %v", para ms, err) | 44 e := fmt.Sprintf("failed to parse URL parameters: %+v: %v", para ms, err) |
(...skipping 18 matching lines...) Expand all Loading... | |
68 | 63 |
69 key, err := datastore.NewKeyEncoded(params.Key) | 64 key, err := datastore.NewKeyEncoded(params.Key) |
70 if err != nil { | 65 if err != nil { |
71 http.Error(w, err.Error(), http.StatusBadRequest) | 66 http.Error(w, err.Error(), http.StatusBadRequest) |
72 logging.Errorf(c, "failed to encode key: %v: %v", key, err) | 67 logging.Errorf(c, "failed to encode key: %v: %v", key, err) |
73 return | 68 return |
74 } | 69 } |
75 | 70 |
76 tf := model.TestFile{ID: key.IntID()} | 71 tf := model.TestFile{ID: key.IntID()} |
77 | 72 |
78 » if err := datastore.Get(c).Get(&tf); err == datastore.ErrNoSuchEntity { | 73 » if err := datastore.Get(c).Get(&tf); err != nil { |
79 » » http.Error(w, err.Error(), http.StatusNotFound) | 74 » » if err == datastore.ErrNoSuchEntity { |
80 » » logging.Errorf(c, "TestFile with ID %v not found: %v", key.IntID (), err) | 75 » » » http.Error(w, err.Error(), http.StatusNotFound) |
81 » » return | 76 » » » logging.Errorf(c, "TestFile with ID %v not found: %v", k ey.IntID(), err) |
82 » } else if err != nil { | 77 » » » return |
78 » » } | |
83 http.Error(w, err.Error(), http.StatusInternalServerError) | 79 http.Error(w, err.Error(), http.StatusInternalServerError) |
84 logging.Errorf(c, "failed to get TestFile with ID %v: %v", key.I ntID(), err) | 80 logging.Errorf(c, "failed to get TestFile with ID %v: %v", key.I ntID(), err) |
85 return | 81 return |
86 } | 82 } |
87 | 83 |
88 modTime, err := time.Parse(r.Header.Get("If-Modified-Since"), httpTimeFo rmat) | 84 modTime, err := time.Parse(r.Header.Get("If-Modified-Since"), httpTimeFo rmat) |
89 if err == nil && !tf.LastMod.After(modTime) { | 85 if err == nil && !tf.LastMod.After(modTime) { |
90 w.WriteHeader(http.StatusNotModified) | 86 w.WriteHeader(http.StatusNotModified) |
91 return | 87 return |
92 } | 88 } |
(...skipping 15 matching lines...) Expand all Loading... | |
108 logging.Errorf(c, "GetAll failed for query: %+v: %v", q, err) | 104 logging.Errorf(c, "GetAll failed for query: %+v: %v", q, err) |
109 return | 105 return |
110 } | 106 } |
111 if len(testFiles) == 0 { | 107 if len(testFiles) == 0 { |
112 e := fmt.Sprintf("no TestFile found for query: %+v", q) | 108 e := fmt.Sprintf("no TestFile found for query: %+v", q) |
113 http.Error(w, e, http.StatusNotFound) | 109 http.Error(w, e, http.StatusNotFound) |
114 logging.Errorf(c, e) | 110 logging.Errorf(c, e) |
115 return | 111 return |
116 } | 112 } |
117 | 113 |
118 args := templates.Args{ | |
119 "Master": params.Master, | |
120 "Builder": params.Builder, | |
121 "TestType": params.TestType, | |
122 "BuildNumber": params.BuildNumber, | |
123 "Name": params.Name, | |
124 "Files": testFiles, | |
125 } | |
126 | |
127 if params.Callback != "" { | 114 if params.Callback != "" { |
128 b, err := keysJSON(c, testFiles) | 115 b, err := keysJSON(c, testFiles) |
129 if err != nil { | 116 if err != nil { |
130 http.Error(w, err.Error(), http.StatusInternalServerErro r) | 117 http.Error(w, err.Error(), http.StatusInternalServerErro r) |
131 logging.Errorf(c, "failed to create callback JSON: %v: % v", testFiles, err) | 118 logging.Errorf(c, "failed to create callback JSON: %v: % v", testFiles, err) |
132 return | 119 return |
133 } | 120 } |
134 respondJSON(c, w, bytes.NewReader(b), testFiles[0].LastMod, para ms.Callback) | 121 respondJSON(c, w, bytes.NewReader(b), testFiles[0].LastMod, para ms.Callback) |
135 return | 122 return |
136 } | 123 } |
137 | 124 |
138 » templates.MustRender(c, w, "pages/showfilelist.html", args) | 125 » templates.MustRender(c, w, "pages/showfilelist.html", templates.Args{ |
126 » » "Master": params.Master, | |
127 » » "Builder": params.Builder, | |
128 » » "TestType": params.TestType, | |
129 » » "BuildNumber": params.BuildNumber, | |
130 » » "Name": params.Name, | |
131 » » "Files": testFiles, | |
132 » }) | |
139 } | 133 } |
140 | 134 |
141 func keysJSON(c context.Context, tfiles []*model.TestFile) ([]byte, error) { | 135 func keysJSON(c context.Context, tfiles []*model.TestFile) ([]byte, error) { |
142 type K struct { | 136 type K struct { |
143 Key string `json:"key"` | 137 Key string `json:"key"` |
144 } | 138 } |
145 keys := make([]K, len(tfiles)) | 139 keys := make([]K, len(tfiles)) |
146 for i, tf := range tfiles { | 140 for i, tf := range tfiles { |
147 keys[i] = K{datastore.Get(c).KeyForObj(tf).Encode()} | 141 keys[i] = K{datastore.Get(c).KeyForObj(tf).Encode()} |
148 } | 142 } |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
214 http.Error(w, err.Error(), http.StatusInternalServerErro r) | 208 http.Error(w, err.Error(), http.StatusInternalServerErro r) |
215 logging.Errorf(c, "failed to clean test results JSON: %v ", err) | 209 logging.Errorf(c, "failed to clean test results JSON: %v ", err) |
216 return | 210 return |
217 } | 211 } |
218 aggr := model.AggregateResult{Builder: params.Builder} | 212 aggr := model.AggregateResult{Builder: params.Builder} |
219 if err := json.NewDecoder(data).Decode(&aggr); err != nil { | 213 if err := json.NewDecoder(data).Decode(&aggr); err != nil { |
220 http.Error(w, err.Error(), http.StatusInternalServerErro r) | 214 http.Error(w, err.Error(), http.StatusInternalServerErro r) |
221 logging.Errorf(c, "failed to unmarshal test results JSON : %+v: %v", data, err) | 215 logging.Errorf(c, "failed to unmarshal test results JSON : %+v: %v", data, err) |
222 return | 216 return |
223 } | 217 } |
224 » » aggr.Tests.ToTestList() | 218 |
219 » » tl := aggr.TestList() | |
225 buf := &bytes.Buffer{} | 220 buf := &bytes.Buffer{} |
226 » » if err := json.NewEncoder(buf).Encode(aggr.Tests); err != nil { | 221 » » if err := json.NewEncoder(buf).Encode(&tl); err != nil { |
227 http.Error(w, err.Error(), http.StatusInternalServerErro r) | 222 http.Error(w, err.Error(), http.StatusInternalServerErro r) |
228 logging.Errorf(c, "failed to marshal test list JSON: %+v , %v", aggr.Tests, err) | 223 logging.Errorf(c, "failed to marshal test list JSON: %+v , %v", aggr.Tests, err) |
229 return | 224 return |
230 } | 225 } |
231 finalData = buf | 226 finalData = buf |
232 } | 227 } |
233 | 228 |
234 respondJSON(c, w, finalData, tf.LastMod, params.Callback) | 229 respondJSON(c, w, finalData, tf.LastMod, params.Callback) |
235 } | 230 } |
236 | 231 |
(...skipping 14 matching lines...) Expand all Loading... | |
251 return nil, err | 246 return nil, err |
252 } | 247 } |
253 if len(tfs) == 0 { | 248 if len(tfs) == 0 { |
254 e := ErrNoMatches(fmt.Sprintf("no TestFile found for query: %+v" , q)) | 249 e := ErrNoMatches(fmt.Sprintf("no TestFile found for query: %+v" , q)) |
255 logging.Errorf(c, e.Error()) | 250 logging.Errorf(c, e.Error()) |
256 return nil, e | 251 return nil, e |
257 } | 252 } |
258 return tfs[0], nil | 253 return tfs[0], nil |
259 } | 254 } |
260 | 255 |
261 // respondJSON writes the supplied JSON data to w. If the supplied callback stri ng matches | 256 // respondJSON writes the supplied JSON data to w. If the supplied |
262 // callbackNameRx, data is wrapped in a JSONP-style function with the supplied c allback | 257 // callback string is not empty, data is wrapped in a JSONP-style |
263 // string as the function name. | 258 // function with the supplied callback string as the function name. |
264 func respondJSON(c context.Context, w http.ResponseWriter, data io.Reader, lastM od time.Time, callback string) { | 259 func respondJSON(c context.Context, w http.ResponseWriter, data io.Reader, lastM od time.Time, callback string) { |
265 » if callbackNameRx.MatchString(callback) { | 260 » if callback != "" { |
266 data = wrapCallback(data, callback) | 261 data = wrapCallback(data, callback) |
267 } | 262 } |
268 w.Header().Set("Last-Modified", lastMod.Format(httpNoTZTimeFormat)+" GMT ") | 263 w.Header().Set("Last-Modified", lastMod.Format(httpNoTZTimeFormat)+" GMT ") |
269 w.Header().Set("Content-Type", "application/json") | 264 w.Header().Set("Content-Type", "application/json") |
270 n, err := io.Copy(w, data) | 265 n, err := io.Copy(w, data) |
271 if err != nil { | 266 if err != nil { |
272 logging.Errorf(c, "error writing JSON response: %#v, %v, wrote % d bytes", data, err, n) | 267 logging.Errorf(c, "error writing JSON response: %#v, %v, wrote % d bytes", data, err, n) |
273 } | 268 } |
274 } | 269 } |
275 | 270 |
276 // wrapCallback returns an io.Reader that wraps the data in r in a | 271 // wrapCallback returns an io.Reader that wraps the data in r in a |
277 // JavaScript-style function call with the supplied name as the function name. | 272 // JavaScript-style function call with the supplied name as the function name. |
278 func wrapCallback(r io.Reader, name string) io.Reader { | 273 func wrapCallback(r io.Reader, name string) io.Reader { |
279 start := bytes.NewReader([]byte(name + "(")) | 274 start := bytes.NewReader([]byte(name + "(")) |
280 end := bytes.NewReader([]byte(");")) | 275 end := bytes.NewReader([]byte(");")) |
281 return io.MultiReader(start, r, end) | 276 return io.MultiReader(start, r, end) |
282 } | 277 } |
OLD | NEW |