| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | // Copyright 2014 The Macaron Authors
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Licensed under the Apache License, Version 2.0 (the "License"): you may
 | 
					
						
							|  |  |  | // not use this file except in compliance with the License. You may obtain
 | 
					
						
							|  |  |  | // a copy of the License at
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Unless required by applicable law or agreed to in writing, software
 | 
					
						
							|  |  |  | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
					
						
							|  |  |  | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
					
						
							|  |  |  | // License for the specific language governing permissions and limitations
 | 
					
						
							|  |  |  | // under the License.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-13 22:56:14 +08:00
										 |  |  | package web | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-08-10 19:29:46 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2023-03-31 21:38:09 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2023-03-23 03:40:00 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-08-10 19:29:46 +08:00
										 |  |  | 	"html/template" | 
					
						
							| 
									
										
										
										
											2022-05-24 01:18:33 +08:00
										 |  |  | 	"net" | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2023-03-31 21:38:09 +08:00
										 |  |  | 	"syscall" | 
					
						
							| 
									
										
										
										
											2022-11-07 23:14:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-13 12:11:35 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/errutil" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/util/errhttp" | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Context represents the runtime context of current request of Macaron instance.
 | 
					
						
							|  |  |  | // It is the integration of most frequently used middlewares and helper methods.
 | 
					
						
							|  |  |  | type Context struct { | 
					
						
							| 
									
										
										
										
											2022-08-09 20:58:50 +08:00
										 |  |  | 	mws []Middleware | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 17:18:30 +08:00
										 |  |  | 	Req      *http.Request | 
					
						
							| 
									
										
										
										
											2021-08-10 19:29:46 +08:00
										 |  |  | 	Resp     ResponseWriter | 
					
						
							|  |  |  | 	template *template.Template | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 18:52:24 +08:00
										 |  |  | var errMissingWrite = errutil.Internal("web.missingWrite") | 
					
						
							| 
									
										
										
										
											2022-11-07 23:14:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 20:58:50 +08:00
										 |  |  | func (ctx *Context) run() { | 
					
						
							|  |  |  | 	h := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) | 
					
						
							|  |  |  | 	for i := len(ctx.mws) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		h = ctx.mws[i](h) | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 20:58:50 +08:00
										 |  |  | 	rw := ctx.Resp | 
					
						
							|  |  |  | 	h.ServeHTTP(ctx.Resp, ctx.Req) | 
					
						
							| 
									
										
										
										
											2022-05-25 03:35:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 20:58:50 +08:00
										 |  |  | 	// Prevent the handler chain from not writing anything.
 | 
					
						
							|  |  |  | 	// This indicates nearly always that a middleware is misbehaving and not calling its next.ServeHTTP().
 | 
					
						
							|  |  |  | 	// In rare cases where a blank http.StatusOK without any body is wished, explicitly state that using w.WriteStatus(http.StatusOK)
 | 
					
						
							|  |  |  | 	if !rw.Written() { | 
					
						
							| 
									
										
										
										
											2022-11-07 23:14:41 +08:00
										 |  |  | 		errhttp.Write( | 
					
						
							|  |  |  | 			ctx.Req.Context(), | 
					
						
							|  |  |  | 			errMissingWrite.Errorf("chain did not write HTTP response: %s", ctx.Req.URL.Path), | 
					
						
							|  |  |  | 			rw, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RemoteAddr returns more real IP address.
 | 
					
						
							|  |  |  | func (ctx *Context) RemoteAddr() string { | 
					
						
							| 
									
										
										
										
											2023-01-04 23:10:43 +08:00
										 |  |  | 	return RemoteAddr(ctx.Req) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func RemoteAddr(req *http.Request) string { | 
					
						
							|  |  |  | 	addr := req.Header.Get("X-Real-IP") | 
					
						
							| 
									
										
										
										
											2022-02-09 03:37:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if len(addr) == 0 { | 
					
						
							|  |  |  | 		// X-Forwarded-For may contain multiple IP addresses, separated by
 | 
					
						
							|  |  |  | 		// commas.
 | 
					
						
							| 
									
										
										
										
											2023-01-04 23:10:43 +08:00
										 |  |  | 		addr = strings.TrimSpace(strings.Split(req.Header.Get("X-Forwarded-For"), ",")[0]) | 
					
						
							| 
									
										
										
										
											2022-02-09 03:37:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-24 01:18:33 +08:00
										 |  |  | 	// parse user inputs from headers to prevent log forgery
 | 
					
						
							|  |  |  | 	if len(addr) > 0 { | 
					
						
							|  |  |  | 		if parsedIP := net.ParseIP(addr); parsedIP == nil { | 
					
						
							|  |  |  | 			// if parsedIP is nil we clean addr and populate with RemoteAddr below
 | 
					
						
							|  |  |  | 			addr = "" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 	if len(addr) == 0 { | 
					
						
							| 
									
										
										
										
											2023-01-04 23:10:43 +08:00
										 |  |  | 		addr = req.RemoteAddr | 
					
						
							| 
									
										
										
										
											2022-02-09 03:37:19 +08:00
										 |  |  | 		if i := strings.LastIndex(addr, ":"); i > -1 { | 
					
						
							|  |  |  | 			addr = addr[:i] | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-05-24 01:18:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 	return addr | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 19:29:46 +08:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	headerContentType = "Content-Type" | 
					
						
							|  |  |  | 	contentTypeJSON   = "application/json; charset=UTF-8" | 
					
						
							|  |  |  | 	contentTypeHTML   = "text/html; charset=UTF-8" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HTML renders the HTML with default template set.
 | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | func (ctx *Context) HTML(status int, name string, data any) { | 
					
						
							| 
									
										
										
										
											2021-08-10 19:29:46 +08:00
										 |  |  | 	ctx.Resp.Header().Set(headerContentType, contentTypeHTML) | 
					
						
							|  |  |  | 	ctx.Resp.WriteHeader(status) | 
					
						
							|  |  |  | 	if err := ctx.template.ExecuteTemplate(ctx.Resp, name, data); err != nil { | 
					
						
							| 
									
										
										
										
											2023-03-31 21:38:09 +08:00
										 |  |  | 		if errors.Is(err, syscall.EPIPE) { // Client has stopped listening.
 | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-03-23 03:40:00 +08:00
										 |  |  | 		panic(fmt.Sprintf("Context.HTML - Error rendering template: %s. You may need to build frontend assets \n %s", name, err.Error())) | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | func (ctx *Context) JSON(status int, data any) { | 
					
						
							| 
									
										
										
										
											2021-08-10 19:29:46 +08:00
										 |  |  | 	ctx.Resp.Header().Set(headerContentType, contentTypeJSON) | 
					
						
							|  |  |  | 	ctx.Resp.WriteHeader(status) | 
					
						
							|  |  |  | 	enc := json.NewEncoder(ctx.Resp) | 
					
						
							|  |  |  | 	if Env != PROD { | 
					
						
							|  |  |  | 		enc.SetIndent("", "  ") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := enc.Encode(data); err != nil { | 
					
						
							|  |  |  | 		panic("Context.JSON: " + err.Error()) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Redirect sends a redirect response
 | 
					
						
							|  |  |  | func (ctx *Context) Redirect(location string, status ...int) { | 
					
						
							|  |  |  | 	code := http.StatusFound | 
					
						
							|  |  |  | 	if len(status) == 1 { | 
					
						
							|  |  |  | 		code = status[0] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-01 17:18:30 +08:00
										 |  |  | 	http.Redirect(ctx.Resp, ctx.Req, location, code) | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MaxMemory is the maximum amount of memory to use when parsing a multipart form.
 | 
					
						
							|  |  |  | // Set this to whatever value you prefer; default is 10 MB.
 | 
					
						
							|  |  |  | var MaxMemory = int64(1024 * 1024 * 10) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ctx *Context) parseForm() { | 
					
						
							|  |  |  | 	if ctx.Req.Form != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 19:29:46 +08:00
										 |  |  | 	contentType := ctx.Req.Header.Get(headerContentType) | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | 	if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") && | 
					
						
							|  |  |  | 		len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") { | 
					
						
							|  |  |  | 		_ = ctx.Req.ParseMultipartForm(MaxMemory) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		_ = ctx.Req.ParseForm() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Query querys form parameter.
 | 
					
						
							|  |  |  | func (ctx *Context) Query(name string) string { | 
					
						
							|  |  |  | 	ctx.parseForm() | 
					
						
							|  |  |  | 	return ctx.Req.Form.Get(name) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // QueryStrings returns a list of results by given query name.
 | 
					
						
							|  |  |  | func (ctx *Context) QueryStrings(name string) []string { | 
					
						
							|  |  |  | 	ctx.parseForm() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vals, ok := ctx.Req.Form[name] | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return []string{} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return vals | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // QueryBool returns query result in bool type.
 | 
					
						
							|  |  |  | func (ctx *Context) QueryBool(name string) bool { | 
					
						
							|  |  |  | 	v, _ := strconv.ParseBool(ctx.Query(name)) | 
					
						
							|  |  |  | 	return v | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // QueryInt returns query result in int type.
 | 
					
						
							|  |  |  | func (ctx *Context) QueryInt(name string) int { | 
					
						
							|  |  |  | 	n, _ := strconv.Atoi(ctx.Query(name)) | 
					
						
							|  |  |  | 	return n | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 22:04:49 +08:00
										 |  |  | // QueryIntWithDefault returns query result in int type, including a default when the query param is not a valid int.
 | 
					
						
							|  |  |  | func (ctx *Context) QueryIntWithDefault(name string, d int) int { | 
					
						
							|  |  |  | 	n, err := strconv.Atoi(ctx.Query(name)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return d | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return n | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | // QueryInt64 returns query result in int64 type.
 | 
					
						
							|  |  |  | func (ctx *Context) QueryInt64(name string) int64 { | 
					
						
							|  |  |  | 	n, _ := strconv.ParseInt(ctx.Query(name), 10, 64) | 
					
						
							|  |  |  | 	return n | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 22:04:49 +08:00
										 |  |  | // QueryInt64WithDefault returns query result in int64 type, including a default when the query param is not a valid int64.
 | 
					
						
							| 
									
										
										
										
											2023-04-18 00:45:06 +08:00
										 |  |  | func (ctx *Context) QueryInt64WithDefault(name string, d int64) int64 { | 
					
						
							|  |  |  | 	n, err := strconv.ParseInt(ctx.Query(name), 10, 64) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return d | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return n | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-08 20:19:40 +08:00
										 |  |  | // GetCookie returns given cookie value from request header.
 | 
					
						
							|  |  |  | func (ctx *Context) GetCookie(name string) string { | 
					
						
							|  |  |  | 	cookie, err := ctx.Req.Cookie(name) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	val, _ := url.QueryUnescape(cookie.Value) | 
					
						
							|  |  |  | 	return val | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-02-26 20:27:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // QueryFloat64 returns query result in float64 type.
 | 
					
						
							|  |  |  | func (ctx *Context) QueryFloat64(name string) float64 { | 
					
						
							|  |  |  | 	n, _ := strconv.ParseFloat(ctx.Query(name), 64) | 
					
						
							|  |  |  | 	return n | 
					
						
							|  |  |  | } |