Chromium Code Reviews| 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 |