Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
5706 lines (4941 sloc) 180 KB
package context
import (
"bytes"
stdContext "context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
"unsafe"
"github.com/kataras/iris/v12/core/memstore"
"github.com/kataras/iris/v12/core/netutil"
"github.com/Shopify/goreferrer"
"github.com/fatih/structs"
"github.com/iris-contrib/schema"
jsoniter "github.com/json-iterator/go"
"github.com/kataras/golog"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday/v2"
"github.com/vmihailenco/msgpack/v5"
"golang.org/x/net/publicsuffix"
"golang.org/x/time/rate"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gopkg.in/yaml.v3"
)
type (
// BodyDecoder is an interface which any struct can implement in order to customize the decode action
// from ReadJSON and ReadXML
//
// Trivial example of this could be:
// type User struct { Username string }
//
// func (u *User) Decode(data []byte) error {
// return json.Unmarshal(data, u)
// }
//
// the 'Context.ReadJSON/ReadXML(&User{})' will call the User's
// Decode option to decode the request body
//
// Note: This is totally optionally, the default decoders
// for ReadJSON is the encoding/json and for ReadXML is the encoding/xml.
//
// Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-custom-per-type/main.go
BodyDecoder interface {
Decode(data []byte) error
}
// Unmarshaler is the interface implemented by types that can unmarshal any raw data.
// TIP INFO: Any pointer to a value which implements the BodyDecoder can be override the unmarshaler.
Unmarshaler interface {
Unmarshal(data []byte, outPtr interface{}) error
}
// UnmarshalerFunc a shortcut for the Unmarshaler interface
//
// See 'Unmarshaler' and 'BodyDecoder' for more.
//
// Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-custom-via-unmarshaler/main.go
UnmarshalerFunc func(data []byte, outPtr interface{}) error
// DecodeFunc is a generic type of decoder function.
// When the returned error is not nil the decode operation
// is terminated and the error is received by the ReadJSONStream method,
// otherwise it continues to read the next available object.
// Look the `Context.ReadJSONStream` method.
DecodeFunc func(outPtr interface{}) error
)
// Unmarshal parses the X-encoded data and stores the result in the value pointed to by v.
// Unmarshal uses the inverse of the encodings that Marshal uses, allocating maps,
// slices, and pointers as necessary.
func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error {
return u(data, v)
}
// LimitRequestBodySize is a middleware which sets a request body size limit
// for all next handlers in the chain.
var LimitRequestBodySize = func(maxRequestBodySizeBytes int64) Handler {
return func(ctx *Context) {
ctx.SetMaxRequestBodySize(maxRequestBodySizeBytes)
ctx.Next()
}
}
// Map is just a type alias of the map[string]interface{} type.
type Map = map[string]interface{}
// Context is the midle-man server's "object" dealing with incoming requests.
//
// A New context is being acquired from a sync.Pool on each connection.
// The Context is the most important thing on the iris's http flow.
//
// Developers send responses to the client's request through a Context.
// Developers get request information from the client's request a Context.
type Context struct {
// the http.ResponseWriter wrapped by custom writer.
writer ResponseWriter
// the original http.Request
request *http.Request
// the current route registered to this request path.
currentRoute RouteReadOnly
// the local key-value storage
params RequestParams // url named parameters.
values memstore.Store // generic storage, middleware communication.
query url.Values // GET url query temp cache, useful on many URLParamXXX calls.
// the underline application app.
app Application
// the route's handlers
handlers Handlers
// the current position of the handler's chain
currentHandlerIndex int
// proceeded reports whether `Proceed` method
// called before a `Next`. It is a flash field and it is set
// to true on `Next` call when its called on the last handler in the chain.
// Reports whether a `Next` is called,
// even if the handler index remains the same (last handler).
proceeded uint32
}
// NewContext returns a new Context instance.
func NewContext(app Application) *Context {
return &Context{app: app}
}
/* Not required, unless requested.
// SetApplication sets an Iris Application on-fly.
// Do NOT use it after ServeHTTPC is fired.
func (ctx *Context) SetApplication(app Application) {
ctx.app = app
}
*/
// Clone returns a copy of the context that
// can be safely used outside the request's scope.
// Note that if the request-response lifecycle terminated
// or request canceled by the client (can be checked by `ctx.IsCanceled()`)
// then the response writer is totally useless.
// The http.Request pointer value is shared.
func (ctx *Context) Clone() *Context {
valuesCopy := make(memstore.Store, len(ctx.values))
copy(valuesCopy, ctx.values)
paramsCopy := make(memstore.Store, len(ctx.params.Store))
copy(paramsCopy, ctx.params.Store)
queryCopy := make(url.Values, len(ctx.query))
for k, v := range ctx.query {
queryCopy[k] = v
}
req := ctx.request.Clone(ctx.request.Context())
return &Context{
app: ctx.app,
values: valuesCopy,
params: RequestParams{Store: paramsCopy},
query: queryCopy,
writer: ctx.writer.Clone(),
request: req,
currentHandlerIndex: stopExecutionIndex,
proceeded: atomic.LoadUint32(&ctx.proceeded),
currentRoute: ctx.currentRoute,
}
}
// BeginRequest is executing once for each request
// it should prepare the (new or acquired from pool) context's fields for the new request.
// Do NOT call it manually. Framework calls it automatically.
//
// Resets
// 1. handlers to nil.
// 2. values to empty.
// 3. the defer function.
// 4. response writer to the http.ResponseWriter.
// 5. request to the *http.Request.
func (ctx *Context) BeginRequest(w http.ResponseWriter, r *http.Request) {
ctx.currentRoute = nil
ctx.handlers = nil // will be filled by router.Serve/HTTP
ctx.values = ctx.values[0:0] // >> >> by context.Values().Set
ctx.params.Store = ctx.params.Store[0:0]
ctx.query = nil
ctx.request = r
ctx.currentHandlerIndex = 0
ctx.proceeded = 0
ctx.writer = AcquireResponseWriter()
ctx.writer.BeginResponse(w)
}
// EndRequest is executing once after a response to the request was sent and this context is useless or released.
// Do NOT call it manually. Framework calls it automatically.
//
// 1. executes the OnClose function (if any).
// 2. flushes the response writer's result or fire any error handler.
// 3. releases the response writer.
func (ctx *Context) EndRequest() {
if !ctx.app.ConfigurationReadOnly().GetDisableAutoFireStatusCode() &&
StatusCodeNotSuccessful(ctx.GetStatusCode()) {
ctx.app.FireErrorCode(ctx)
}
ctx.writer.FlushResponse()
ctx.writer.EndResponse()
}
// IsCanceled reports whether the client canceled the request
// or the underlying connection has gone.
// Note that it will always return true
// when called from a goroutine after the request-response lifecycle.
func (ctx *Context) IsCanceled() bool {
if reqCtx := ctx.request.Context(); reqCtx != nil {
err := reqCtx.Err()
if err != nil && errors.Is(err, stdContext.Canceled) {
return true
}
}
return false
}
// OnConnectionClose registers the "cb" Handler
// which will be fired on its on goroutine on a cloned Context
// when the underlying connection has gone away.
//
// The code inside the given callback is running on its own routine,
// as explained above, therefore the callback should NOT
// try to access to handler's Context response writer.
//
// This mechanism can be used to cancel long operations on the server
// if the client has disconnected before the response is ready.
//
// It depends on the Request's Context.Done() channel.
//
// Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported).
// The "cb" will not fire for sure if the output value is false.
//
// Note that you can register only one callback per route.
//
// See `OnClose` too.
func (ctx *Context) OnConnectionClose(cb Handler) bool {
if cb == nil {
return false
}
reqCtx := ctx.Request().Context()
if reqCtx == nil {
return false
}
notifyClose := reqCtx.Done()
if notifyClose == nil {
return false
}
go func() {
<-notifyClose
// Note(@kataras): No need to clone if not canceled,
// EndRequest will be called on the end of the handler chain,
// no matter the cancelation.
// therefore the context will still be there.
cb(ctx.Clone())
}()
return true
}
// OnConnectionCloseErr same as `OnConnectionClose` but instead it
// receives a function which returns an error.
// If error is not nil, it will be logged as a debug message.
func (ctx *Context) OnConnectionCloseErr(cb func() error) bool {
if cb == nil {
return false
}
reqCtx := ctx.Request().Context()
if reqCtx == nil {
return false
}
notifyClose := reqCtx.Done()
if notifyClose == nil {
return false
}
go func() {
<-notifyClose
if err := cb(); err != nil {
// Can be ignored.
ctx.app.Logger().Debugf("OnConnectionCloseErr: received error: %v", err)
}
}()
return true
}
// OnClose registers a callback which
// will be fired when the underlying connection has gone away(request canceled)
// on its own goroutine or in the end of the request-response lifecylce
// on the handler's routine itself (Context access).
//
// See `OnConnectionClose` too.
func (ctx *Context) OnClose(cb Handler) {
if cb == nil {
return
}
// Note(@kataras):
// - on normal request-response lifecycle
// the `SetBeforeFlush` will be called first
// and then `OnConnectionClose`,
// - when request was canceled before handler finish its job
// then the `OnConnectionClose` will be called first instead,
// and when the handler function completed then `SetBeforeFlush` is fired.
// These are synchronized, they cannot be executed the same exact time,
// below we just make sure the "cb" is executed once
// by simple boolean check or an atomic one.
var executed uint32
callback := func(ctx *Context) {
if atomic.CompareAndSwapUint32(&executed, 0, 1) {
cb(ctx)
}
}
ctx.OnConnectionClose(callback)
onFlush := func() {
callback(ctx)
}
ctx.writer.SetBeforeFlush(onFlush)
}
// OnCloseErr same as `OnClose` but instead it
// receives a function which returns an error.
// If error is not nil, it will be logged as a debug message.
func (ctx *Context) OnCloseErr(cb func() error) {
if cb == nil {
return
}
var executed uint32
callback := func() error {
if atomic.CompareAndSwapUint32(&executed, 0, 1) {
return cb()
}
return nil
}
ctx.OnConnectionCloseErr(callback)
onFlush := func() {
if err := callback(); err != nil {
// Can be ignored.
ctx.app.Logger().Debugf("OnClose: SetBeforeFlush: received error: %v", err)
}
}
ctx.writer.SetBeforeFlush(onFlush)
}
/* Note(@kataras): just leave end-developer decide.
const goroutinesContextKey = "iris.goroutines"
type goroutines struct {
wg *sync.WaitGroup
length int
mu sync.RWMutex
}
var acquireGoroutines = func() interface{} {
return &goroutines{wg: new(sync.WaitGroup)}
}
func (ctx *Context) Go(fn func(cancelCtx stdContext.Context)) (running int) {
g := ctx.values.GetOrSet(goroutinesContextKey, acquireGoroutines).(*goroutines)
if fn != nil {
g.wg.Add(1)
g.mu.Lock()
g.length++
g.mu.Unlock()
ctx.waitFunc = g.wg.Wait
go func(reqCtx stdContext.Context) {
fn(reqCtx)
g.wg.Done()
g.mu.Lock()
g.length--
g.mu.Unlock()
}(ctx.request.Context())
}
g.mu.RLock()
running = g.length
g.mu.RUnlock()
return
}
*/
// ResponseWriter returns an http.ResponseWriter compatible response writer, as expected.
func (ctx *Context) ResponseWriter() ResponseWriter {
return ctx.writer
}
// ResetResponseWriter sets a new ResponseWriter implementation
// to this Context to use as its writer.
// Note, to change the underline http.ResponseWriter use
// ctx.ResponseWriter().SetWriter(http.ResponseWRiter) instead.
func (ctx *Context) ResetResponseWriter(newResponseWriter ResponseWriter) {
ctx.writer = newResponseWriter
}
// Request returns the original *http.Request, as expected.
func (ctx *Context) Request() *http.Request {
return ctx.request
}
// ResetRequest sets the Context's Request,
// It is useful to store the new request created by a std *http.Request#WithContext() into Iris' Context.
// Use `ResetRequest` when for some reason you want to make a full
// override of the *http.Request.
// Note that: when you just want to change one of each fields you can use the Request() which returns a pointer to Request,
// so the changes will have affect without a full override.
// Usage: you use a native http handler which uses the standard "context" package
// to get values instead of the Iris' Context#Values():
// r := ctx.Request()
// stdCtx := context.WithValue(r.Context(), key, val)
// ctx.ResetRequest(r.WithContext(stdCtx)).
func (ctx *Context) ResetRequest(r *http.Request) {
ctx.request = r
}
// SetCurrentRoute sets the route internally,
// See `GetCurrentRoute()` method too.
// It's being initialized by the Router.
// See `Exec` or `SetHandlers/AddHandler` methods to simulate a request.
func (ctx *Context) SetCurrentRoute(route RouteReadOnly) {
ctx.currentRoute = route
}
// GetCurrentRoute returns the current "read-only" route that
// was registered to this request's path.
func (ctx *Context) GetCurrentRoute() RouteReadOnly {
return ctx.currentRoute
}
// Do sets the "handlers" as the chain
// and executes the first handler,
// handlers should not be empty.
//
// It's used by the router, developers may use that
// to replace and execute handlers immediately.
func (ctx *Context) Do(handlers Handlers) {
if len(handlers) == 0 {
return
}
ctx.handlers = handlers
handlers[0](ctx)
}
// AddHandler can add handler(s)
// to the current request in serve-time,
// these handlers are not persistenced to the router.
//
// Router is calling this function to add the route's handler.
// If AddHandler called then the handlers will be inserted
// to the end of the already-defined route's handler.
//
func (ctx *Context) AddHandler(handlers ...Handler) {
ctx.handlers = append(ctx.handlers, handlers...)
}
// SetHandlers replaces all handlers with the new.
func (ctx *Context) SetHandlers(handlers Handlers) {
ctx.handlers = handlers
}
// Handlers keeps tracking of the current handlers.
func (ctx *Context) Handlers() Handlers {
return ctx.handlers
}
// HandlerIndex sets the current index of the
// current context's handlers chain.
// If n < 0 or the current handlers length is 0 then it just returns the
// current handler index without change the current index.
//
// Look Handlers(), Next() and StopExecution() too.
func (ctx *Context) HandlerIndex(n int) (currentIndex int) {
if n < 0 || n > len(ctx.handlers)-1 {
return ctx.currentHandlerIndex
}
ctx.currentHandlerIndex = n
return n
}
// Proceed is an alternative way to check if a particular handler
// has been executed.
// The given "h" Handler can report a failure with `StopXXX` methods
// or ignore calling a `Next` (see `iris.ExecutionRules` too).
//
// This is useful only when you run a handler inside
// another handler. It justs checks for before index and the after index.
//
// A usecase example is when you want to execute a middleware
// inside controller's `BeginRequest` that calls the `ctx.Next` inside it.
// The Controller looks the whole flow (BeginRequest, method handler, EndRequest)
// as one handler, so `ctx.Next` will not be reflected to the method handler
// if called from the `BeginRequest`.
//
// Although `BeginRequest` should NOT be used to call other handlers,
// the `BeginRequest` has been introduced to be able to set
// common data to all method handlers before their execution.
// Controllers can accept middleware(s) from the MVC's Application's Router as normally.
//
// That said let's see an example of `ctx.Proceed`:
//
// var authMiddleware = basicauth.New(basicauth.Config{
// Users: map[string]string{
// "admin": "password",
// },
// })
//
// func (c *UsersController) BeginRequest(ctx iris.Context) {
// if !ctx.Proceed(authMiddleware) {
// ctx.StopExecution()
// }
// }
// This Get() will be executed in the same handler as `BeginRequest`,
// internally controller checks for `ctx.StopExecution`.
// So it will not be fired if BeginRequest called the `StopExecution`.
// func(c *UsersController) Get() []models.User {
// return c.Service.GetAll()
//}
// Alternative way is `!ctx.IsStopped()` if middleware make use of the `ctx.StopExecution()` on failure.
func (ctx *Context) Proceed(h Handler) bool {
beforeIdx := ctx.currentHandlerIndex
atomic.StoreUint32(&ctx.proceeded, 0)
h(ctx)
if ctx.currentHandlerIndex == stopExecutionIndex {
return false
}
if ctx.currentHandlerIndex <= beforeIdx {
// If "h" didn't call its Next
// or it doesn't have a next handler,
// that index will be the same,
// so we check if at least once the
// Next is called on the last handler.
return atomic.CompareAndSwapUint32(&ctx.proceeded, 1, 0)
}
return true
}
// HandlerName returns the current handler's name, helpful for debugging.
func (ctx *Context) HandlerName() string {
return HandlerName(ctx.handlers[ctx.currentHandlerIndex])
}
// HandlerFileLine returns the current running handler's function source file and line information.
// Useful mostly when debugging.
func (ctx *Context) HandlerFileLine() (file string, line int) {
return HandlerFileLine(ctx.handlers[ctx.currentHandlerIndex])
}
// RouteName returns the route name that this handler is running on.
// Note that it may return empty on not found handlers.
func (ctx *Context) RouteName() string {
if ctx.currentRoute == nil {
return ""
}
return ctx.currentRoute.Name()
}
// Next calls the next handler from the handlers chain,
// it should be used inside a middleware.
func (ctx *Context) Next() {
if ctx.IsStopped() {
return
}
nextIndex := ctx.currentHandlerIndex + 1
handlers := ctx.handlers
if n := len(handlers); nextIndex == n {
atomic.StoreUint32(&ctx.proceeded, 1) // last handler but Next is called.
} else if nextIndex < n {
ctx.currentHandlerIndex = nextIndex
handlers[nextIndex](ctx)
}
}
// NextOr checks if chain has a next handler, if so then it executes it
// otherwise it sets a new chain assigned to this Context based on the given handler(s)
// and executes its first handler.
//
// Returns true if next handler exists and executed, otherwise false.
//
// Note that if no next handler found and handlers are missing then
// it sends a Status Not Found (404) to the client and it stops the execution.
func (ctx *Context) NextOr(handlers ...Handler) bool {
if next := ctx.NextHandler(); next != nil {
ctx.Skip() // skip this handler from the chain.
next(ctx)
return true
}
if len(handlers) == 0 {
ctx.NotFound()
ctx.StopExecution()
return false
}
ctx.Do(handlers)
return false
}
// NextOrNotFound checks if chain has a next handler, if so then it executes it
// otherwise it sends a Status Not Found (404) to the client and stops the execution.
//
// Returns true if next handler exists and executed, otherwise false.
func (ctx *Context) NextOrNotFound() bool { return ctx.NextOr() }
// NextHandler returns (it doesn't execute) the next handler from the handlers chain.
//
// Use .Skip() to skip this handler if needed to execute the next of this returning handler.
func (ctx *Context) NextHandler() Handler {
if ctx.IsStopped() {
return nil
}
nextIndex := ctx.currentHandlerIndex + 1
// check if it has a next middleware
if nextIndex < len(ctx.handlers) {
return ctx.handlers[nextIndex]
}
return nil
}
// Skip skips/ignores the next handler from the handlers chain,
// it should be used inside a middleware.
func (ctx *Context) Skip() {
ctx.HandlerIndex(ctx.currentHandlerIndex + 1)
}
const stopExecutionIndex = -1 // I don't set to a max value because we want to be able to reuse the handlers even if stopped with .Skip
// StopExecution stops the handlers chain of this request.
// Meaning that any following `Next` calls are ignored,
// as a result the next handlers in the chain will not be fire.
func (ctx *Context) StopExecution() {
ctx.currentHandlerIndex = stopExecutionIndex
}
// IsStopped reports whether the current position of the context's handlers is -1,
// means that the StopExecution() was called at least once.
func (ctx *Context) IsStopped() bool {
return ctx.currentHandlerIndex == stopExecutionIndex
}
// StopWithStatus stops the handlers chain and writes the "statusCode".
//
// If the status code is a failure one then
// it will also fire the specified error code handler.
func (ctx *Context) StopWithStatus(statusCode int) {
ctx.StopExecution()
ctx.StatusCode(statusCode)
}
// StopWithText stops the handlers chain and writes the "statusCode"
// among with a fmt-style text of "format" and optional arguments.
//
// If the status code is a failure one then
// it will also fire the specified error code handler.
func (ctx *Context) StopWithText(statusCode int, format string, args ...interface{}) {
ctx.StopWithStatus(statusCode)
ctx.WriteString(fmt.Sprintf(format, args...))
}
// StopWithError stops the handlers chain and writes the "statusCode"
// among with the error "err".
// It Calls the `SetErr` method so error handlers can access the given error.
//
// If the status code is a failure one then
// it will also fire the specified error code handler.
//
// If the given "err" is private then the
// status code's text is rendered instead (unless a registered error handler overrides it).
func (ctx *Context) StopWithError(statusCode int, err error) {
if err == nil {
return
}
ctx.SetErr(err)
if _, ok := err.(ErrPrivate); ok {
// error is private, we SHOULD not render it,
// leave the error handler alone to
// render the code's text instead.
ctx.StopWithStatus(statusCode)
return
}
ctx.StopWithText(statusCode, err.Error())
}
// StopWithPlainError like `StopWithError` but it does NOT
// write anything to the response writer, it stores the error
// so any error handler matching the given "statusCode" can handle it by its own.
func (ctx *Context) StopWithPlainError(statusCode int, err error) {
if err == nil {
return
}
ctx.SetErr(err)
ctx.StopWithStatus(statusCode)
}
// StopWithJSON stops the handlers chain, writes the status code
// and sends a JSON response.
//
// If the status code is a failure one then
// it will also fire the specified error code handler.
func (ctx *Context) StopWithJSON(statusCode int, jsonObject interface{}) {
ctx.StopWithStatus(statusCode)
ctx.JSON(jsonObject)
}
// StopWithProblem stops the handlers chain, writes the status code
// and sends an application/problem+json response.
// See `iris.NewProblem` to build a "problem" value correctly.
//
// If the status code is a failure one then
// it will also fire the specified error code handler.
func (ctx *Context) StopWithProblem(statusCode int, problem Problem) {
ctx.StopWithStatus(statusCode)
problem.Status(statusCode)
ctx.Problem(problem)
}
// +------------------------------------------------------------+
// | Current "user/request" storage |
// | and share information between the handlers - Values(). |
// | Save and get named path parameters - Params() |
// +------------------------------------------------------------+
// Params returns the current url's named parameters key-value storage.
// Named path parameters are being saved here.
// This storage, as the whole context, is per-request lifetime.
func (ctx *Context) Params() *RequestParams {
return &ctx.params
}
// Values returns the current "user" storage.
// Named path parameters and any optional data can be saved here.
// This storage, as the whole context, is per-request lifetime.
//
// You can use this function to Set and Get local values
// that can be used to share information between handlers and middleware.
func (ctx *Context) Values() *memstore.Store {
return &ctx.values
}
// +------------------------------------------------------------+
// | Path, Host, Subdomain, IP, Headers etc... |
// +------------------------------------------------------------+
// Method returns the request.Method, the client's http method to the server.
func (ctx *Context) Method() string {
return ctx.request.Method
}
// Path returns the full request path,
// escaped if EnablePathEscape config field is true.
func (ctx *Context) Path() string {
return ctx.RequestPath(ctx.app.ConfigurationReadOnly().GetEnablePathEscape())
}
// DecodeQuery returns the uri parameter as url (string)
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
// use it only for special cases, when the default behavior doesn't suits you.
//
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
// it uses just the url.QueryUnescape
func DecodeQuery(path string) string {
if path == "" {
return ""
}
encodedPath, err := url.QueryUnescape(path)
if err != nil {
return path
}
return encodedPath
}
// DecodeURL returns the decoded uri
// useful when you want to pass something to a database and be valid to retrieve it via context.Param
// use it only for special cases, when the default behavior doesn't suits you.
//
// http://www.blooberry.com/indexdot/html/topics/urlencoding.htm
// it uses just the url.Parse
func DecodeURL(uri string) string {
u, err := url.Parse(uri)
if err != nil {
return uri
}
return u.String()
}
// RequestPath returns the full request path,
// based on the 'escape'.
func (ctx *Context) RequestPath(escape bool) string {
if escape {
return ctx.request.URL.EscapedPath() // DecodeQuery(ctx.request.URL.EscapedPath())
}
return ctx.request.URL.Path // RawPath returns empty, requesturi can be used instead also.
}
const sufscheme = "://"
// GetScheme returns the full scheme of the request URL (https://, http:// or ws:// and e.t.c.``).
func GetScheme(r *http.Request) string {
scheme := r.URL.Scheme
if scheme == "" {
if r.TLS != nil {
scheme = netutil.SchemeHTTPS
} else {
scheme = netutil.SchemeHTTP
}
}
return scheme + sufscheme
}
// Scheme returns the full scheme of the request (including :// suffix).
func (ctx *Context) Scheme() string {
return GetScheme(ctx.Request())
}
// PathPrefixMap accepts a map of string and a handler.
// The key of "m" is the key, which is the prefix, regular expressions are not valid.
// The value of "m" is the handler that will be executed if HasPrefix(context.Path).
// func (ctx *Context) PathPrefixMap(m map[string]context.Handler) bool {
// path := ctx.Path()
// for k, v := range m {
// if strings.HasPrefix(path, k) {
// v(ctx)
// return true
// }
// }
// return false
// } no, it will not work because map is a random peek data structure.
// GetHost returns the host part of the current URI.
func GetHost(r *http.Request) string {
// contains subdomain.
if host := r.URL.Host; host != "" {
return host
}
return r.Host
}
// Host returns the host:port part of the request URI, calls the `Request().Host`.
// To get the subdomain part as well use the `Request().URL.Host` method instead.
// To get the subdomain only use the `Subdomain` method instead.
// This method makes use of the `Configuration.HostProxyHeaders` field too.
func (ctx *Context) Host() string {
for header, ok := range ctx.app.ConfigurationReadOnly().GetHostProxyHeaders() {
if !ok {
continue
}
if host := ctx.GetHeader(header); host != "" {
return host
}
}
return GetHost(ctx.request)
}
// GetDomain resolves and returns the server's domain.
func GetDomain(hostport string) string {
host := hostport
if tmp, _, err := net.SplitHostPort(hostport); err == nil {
host = tmp
}
switch host {
// We could use the netutil.LoopbackRegex but leave it as it's for now, it's faster.
case "localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]", "0:0:0:0:0:0:0:0", "0:0:0:0:0:0:0:1":
// loopback.
return "localhost"
default:
if domain, err := publicsuffix.EffectiveTLDPlusOne(host); err == nil {
host = domain
}
return host
}
}
// Domain returns the root level domain.
func (ctx *Context) Domain() string {
return GetDomain(ctx.Host())
}
// GetSubdomainFull returns the full subdomain level, e.g.
// [test.user.]mydomain.com.
func GetSubdomainFull(r *http.Request) string {
host := GetHost(r) // host:port
rootDomain := GetDomain(host) // mydomain.com
rootDomainIdx := strings.Index(host, rootDomain)
if rootDomainIdx == -1 {
return ""
}
return host[0:rootDomainIdx]
}
// SubdomainFull returns the full subdomain level, e.g.
// [test.user.]mydomain.com.
// Note that HostProxyHeaders are being respected here.
func (ctx *Context) SubdomainFull() string {
host := ctx.Host() // host:port
rootDomain := GetDomain(host) // mydomain.com
rootDomainIdx := strings.Index(host, rootDomain)
if rootDomainIdx == -1 {
return ""
}
return host[0:rootDomainIdx]
}
// Subdomain returns the first subdomain of this request,
// e.g. [user.]mydomain.com.
// See `SubdomainFull` too.
func (ctx *Context) Subdomain() (subdomain string) {
host := ctx.Host()
if index := strings.IndexByte(host, '.'); index > 0 {
subdomain = host[0:index]
}
// listening on mydomain.com:80
// subdomain = mydomain, but it's wrong, it should return ""
vhost := ctx.app.ConfigurationReadOnly().GetVHost()
if strings.Contains(vhost, subdomain) { // then it's not subdomain
return ""
}
return
}
// FindClosest returns a list of "n" paths close to
// this request based on subdomain and request path.
//
// Order may change.
// Example: https://github.com/kataras/iris/tree/master/_examples/routing/intelligence/manual
func (ctx *Context) FindClosest(n int) []string {
return ctx.app.FindClosestPaths(ctx.Subdomain(), ctx.Path(), n)
}
// IsWWW returns true if the current subdomain (if any) is www.
func (ctx *Context) IsWWW() bool {
host := ctx.Host()
if index := strings.IndexByte(host, '.'); index > 0 {
// if it has a subdomain and it's www then return true.
if subdomain := host[0:index]; !strings.Contains(ctx.app.ConfigurationReadOnly().GetVHost(), subdomain) {
return subdomain == "www"
}
}
return false
}
// FullRequestURI returns the full URI,
// including the scheme, the host and the relative requested path/resource.
func (ctx *Context) FullRequestURI() string {
return ctx.AbsoluteURI(ctx.Path())
}
// RemoteAddr tries to parse and return the real client's request IP.
//
// Based on allowed headers names that can be modified from Configuration.RemoteAddrHeaders.
//
// If parse based on these headers fail then it will return the Request's `RemoteAddr` field
// which is filled by the server before the HTTP handler,
// unless the Configuration.RemoteAddrHeadersForce was set to true
// which will force this method to return the first IP from RemoteAddrHeaders
// even if it's part of a private network.
//
// Look `Configuration.RemoteAddrHeaders`,
// `Configuration.RemoteAddrHeadersForce`,
// `Configuration.WithRemoteAddrHeader(...)`,
// `Configuration.WithoutRemoteAddrHeader(...)` and
// `Configuration.RemoteAddrPrivateSubnets` for more.
func (ctx *Context) RemoteAddr() string {
if remoteHeaders := ctx.app.ConfigurationReadOnly().GetRemoteAddrHeaders(); len(remoteHeaders) > 0 {
privateSubnets := ctx.app.ConfigurationReadOnly().GetRemoteAddrPrivateSubnets()
for _, headerName := range remoteHeaders {
ipAddresses := strings.Split(ctx.GetHeader(headerName), ",")
if ip, ok := netutil.GetIPAddress(ipAddresses, privateSubnets); ok {
return ip
}
}
if ctx.app.ConfigurationReadOnly().GetRemoteAddrHeadersForce() {
for _, headerName := range remoteHeaders {
// return the first valid IP,
// even if it's a part of a private network.
ipAddresses := strings.Split(ctx.GetHeader(headerName), ",")
for _, addr := range ipAddresses {
if ip, _, err := net.SplitHostPort(addr); err == nil {
return ip
}
}
}
}
}
addr := strings.TrimSpace(ctx.request.RemoteAddr)
if addr != "" {
// if addr has port use the net.SplitHostPort otherwise(error occurs) take as it is
if ip, _, err := net.SplitHostPort(addr); err == nil {
return ip
}
}
return addr
}
// TrimHeaderValue returns the "v[0:first space or semicolon]".
func TrimHeaderValue(v string) string {
for i, char := range v {
if char == ' ' || char == ';' {
return v[:i]
}
}
return v
}
// GetHeader returns the request header's value based on its name.
func (ctx *Context) GetHeader(name string) string {
return ctx.request.Header.Get(name)
}
// IsAjax returns true if this request is an 'ajax request'( XMLHttpRequest)
//
// There is no a 100% way of knowing that a request was made via Ajax.
// You should never trust data coming from the client, they can be easily overcome by spoofing.
//
// Note that "X-Requested-With" Header can be modified by any client(because of "X-"),
// so don't rely on IsAjax for really serious stuff,
// try to find another way of detecting the type(i.e, content type),
// there are many blogs that describe these problems and provide different kind of solutions,
// it's always depending on the application you're building,
// this is the reason why this `IsAjax`` is simple enough for general purpose use.
//
// Read more at: https://developer.mozilla.org/en-US/docs/AJAX
// and https://xhr.spec.whatwg.org/
func (ctx *Context) IsAjax() bool {
return ctx.GetHeader("X-Requested-With") == "XMLHttpRequest"
}
var isMobileRegex = regexp.MustCompile("(?:hpw|i|web)os|alamofire|alcatel|amoi|android|avantgo|blackberry|blazer|cell|cfnetwork|darwin|dolfin|dolphin|fennec|htc|ip(?:hone|od|ad)|ipaq|j2me|kindle|midp|minimo|mobi|motorola|nec-|netfront|nokia|opera m(ob|in)i|palm|phone|pocket|portable|psp|silk-accelerated|skyfire|sony|ucbrowser|up.browser|up.link|windows ce|xda|zte|zune")
// IsMobile checks if client is using a mobile device(phone or tablet) to communicate with this server.
// If the return value is true that means that the http client using a mobile
// device to communicate with the server, otherwise false.
//
// Keep note that this checks the "User-Agent" request header.
func (ctx *Context) IsMobile() bool {
s := strings.ToLower(ctx.GetHeader("User-Agent"))
return isMobileRegex.MatchString(s)
}
var isScriptRegex = regexp.MustCompile("curl|wget|collectd|python|urllib|java|jakarta|httpclient|phpcrawl|libwww|perl|go-http|okhttp|lua-resty|winhttp|awesomium")
// IsScript reports whether a client is a script.
func (ctx *Context) IsScript() bool {
s := strings.ToLower(ctx.GetHeader("User-Agent"))
return isScriptRegex.MatchString(s)
}
// IsSSL reports whether the client is running under HTTPS SSL.
//
// See `IsHTTP2` too.
func (ctx *Context) IsSSL() bool {
ssl := strings.EqualFold(ctx.request.URL.Scheme, "https") || ctx.request.TLS != nil
if !ssl {
for k, v := range ctx.app.ConfigurationReadOnly().GetSSLProxyHeaders() {
if ctx.GetHeader(k) == v {
ssl = true
break
}
}
}
return ssl
}
// IsHTTP2 reports whether the protocol version for incoming request was HTTP/2.
// The client code always uses either HTTP/1.1 or HTTP/2.
//
// See `IsSSL` too.
func (ctx *Context) IsHTTP2() bool {
return ctx.request.ProtoMajor == 2
}
// IsGRPC reports whether the request came from a gRPC client.
func (ctx *Context) IsGRPC() bool {
return ctx.IsHTTP2() && ctx.GetContentTypeRequested() == ContentGRPCHeaderValue
}
type (
// Referrer contains the extracted information from the `GetReferrer`
//
// The structure contains struct tags for JSON, form, XML, YAML and TOML.
// Look the `GetReferrer() Referrer` and `goreferrer` external package.
Referrer struct {
// The raw refer(r)er URL.
Raw string `json:"raw" form:"raw" xml:"Raw" yaml:"Raw" toml:"Raw"`
Type ReferrerType `json:"type" form:"referrer_type" xml:"Type" yaml:"Type" toml:"Type"`
Label string `json:"label" form:"referrer_form" xml:"Label" yaml:"Label" toml:"Label"`
URL string `json:"url" form:"referrer_url" xml:"URL" yaml:"URL" toml:"URL"`
Subdomain string `json:"subdomain" form:"referrer_subdomain" xml:"Subdomain" yaml:"Subdomain" toml:"Subdomain"`
Domain string `json:"domain" form:"referrer_domain" xml:"Domain" yaml:"Domain" toml:"Domain"`
Tld string `json:"tld" form:"referrer_tld" xml:"Tld" yaml:"Tld" toml:"Tld"`
Path string `json:"path" form:"referrer_path" xml:"Path" yaml:"Path" toml:"Path"`
Query string `json:"query" form:"referrer_query" xml:"Query" yaml:"Query" toml:"GoogleType"`
GoogleType ReferrerGoogleSearchType `json:"googleType" form:"referrer_google_type" xml:"GoogleType" yaml:"GoogleType" toml:"GoogleType"`
}
// ReferrerType is the goreferrer enum for a referrer type (indirect, direct, email, search, social).
ReferrerType = goreferrer.ReferrerType
// ReferrerGoogleSearchType is the goreferrer enum for a google search type (organic, adwords).
ReferrerGoogleSearchType = goreferrer.GoogleSearchType
)
// String returns the raw ref url.
func (ref Referrer) String() string {
return ref.Raw
}
// Contains the available values of the goreferrer enums.
const (
ReferrerInvalid ReferrerType = iota
ReferrerIndirect
ReferrerDirect
ReferrerEmail
ReferrerSearch
ReferrerSocial
ReferrerNotGoogleSearch ReferrerGoogleSearchType = iota
ReferrerGoogleOrganicSearch
ReferrerGoogleAdwords
)
// unnecessary but good to know the default values upfront.
var emptyReferrer = Referrer{Type: ReferrerInvalid, GoogleType: ReferrerNotGoogleSearch}
// GetReferrer extracts and returns the information from the "Referer" (or "Referrer") header
// and url query parameter as specified in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy.
func (ctx *Context) GetReferrer() Referrer {
// the underline net/http follows the https://tools.ietf.org/html/rfc7231#section-5.5.2,
// so there is nothing special left to do.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
refURL := ctx.GetHeader("Referer")
if refURL == "" {
refURL = ctx.GetHeader("Referrer")
if refURL == "" {
refURL = ctx.URLParam("referer")
if refURL == "" {
refURL = ctx.URLParam("referrer")
}
}
}
if refURL == "" {
return emptyReferrer
}
if ref := goreferrer.DefaultRules.Parse(refURL); ref.Type > goreferrer.Invalid {
return Referrer{
Raw: refURL,
Type: ReferrerType(ref.Type),
Label: ref.Label,
URL: ref.URL,
Subdomain: ref.Subdomain,
Domain: ref.Domain,
Tld: ref.Tld,
Path: ref.Path,
Query: ref.Query,
GoogleType: ReferrerGoogleSearchType(ref.GoogleType),
}
}
return emptyReferrer
}
// SetLanguage force-sets the language for i18n, can be used inside a middleare.
// It has the highest priority over the rest and if it is empty then it is ignored,
// if it set to a static string of "default" or to the default language's code
// then the rest of the language extractors will not be called at all and
// the default language will be set instead.
//
// See `i18n.ExtractFunc` for a more organised way of the same feature.
func (ctx *Context) SetLanguage(langCode string) {
ctx.values.Set(ctx.app.ConfigurationReadOnly().GetLanguageContextKey(), langCode)
}
// GetLocale returns the current request's `Locale` found by i18n middleware.
// It always fallbacks to the default one.
// See `Tr` too.
func (ctx *Context) GetLocale() Locale {
// Cache the Locale itself for multiple calls of `Tr` method.
contextKey := ctx.app.ConfigurationReadOnly().GetLocaleContextKey()
if v := ctx.values.Get(contextKey); v != nil {
if locale, ok := v.(Locale); ok {
return locale
}
}
if locale := ctx.app.I18nReadOnly().GetLocale(ctx); locale != nil {
ctx.values.Set(contextKey, locale)
return locale
}
return nil
}
// Tr returns a i18n localized message based on format with optional arguments.
// See `GetLocale` too.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/i18n
func (ctx *Context) Tr(key string, args ...interface{}) string {
return ctx.app.I18nReadOnly().TrContext(ctx, key, args...)
}
// +------------------------------------------------------------+
// | Response Headers helpers |
// +------------------------------------------------------------+
// Header adds a header to the response, if value is empty
// it removes the header by its name.
func (ctx *Context) Header(name string, value string) {
if value == "" {
ctx.writer.Header().Del(name)
return
}
ctx.writer.Header().Add(name, value)
}
const contentTypeContextKey = "iris.content_type"
func shouldAppendCharset(cType string) bool {
if idx := strings.IndexRune(cType, '/'); idx > 1 && len(cType) > idx+1 {
typ := cType[0:idx]
if typ == "application" {
switch cType[idx+1:] {
case "json", "xml", "yaml", "problem+json", "problem+xml":
return true
default:
return false
}
}
}
return true
}
func (ctx *Context) contentTypeOnce(cType string, charset string) {
if charset == "" {
charset = ctx.app.ConfigurationReadOnly().GetCharset()
}
if shouldAppendCharset(cType) {
cType += "; charset=" + charset
}
ctx.values.Set(contentTypeContextKey, cType)
ctx.writer.Header().Set(ContentTypeHeaderKey, cType)
}
// ContentType sets the response writer's
// header "Content-Type" to the 'cType'.
func (ctx *Context) ContentType(cType string) {
if cType == "" {
return
}
if _, wroteOnce := ctx.values.GetEntry(contentTypeContextKey); wroteOnce {
return
}
// 1. if it's path or a filename or an extension,
// then take the content type from that,
// ^ No, it's not always a file,e .g. vnd.$type
// if strings.Contains(cType, ".") {
// ext := filepath.Ext(cType)
// cType = mime.TypeByExtension(ext)
// }
// if doesn't contain a charset already then append it
if shouldAppendCharset(cType) {
if !strings.Contains(cType, "charset") {
cType += "; charset=" + ctx.app.ConfigurationReadOnly().GetCharset()
}
}
ctx.writer.Header().Set(ContentTypeHeaderKey, cType)
}
// GetContentType returns the response writer's
// header value of "Content-Type".
func (ctx *Context) GetContentType() string {
return ctx.writer.Header().Get(ContentTypeHeaderKey)
}
// GetContentTypeRequested returns the request's
// trim-ed(without the charset and priority values)
// header value of "Content-Type".
func (ctx *Context) GetContentTypeRequested() string {
// could use mime.ParseMediaType too.
return TrimHeaderValue(ctx.GetHeader(ContentTypeHeaderKey))
}
// GetContentLength returns the request's
// header value of "Content-Length".
func (ctx *Context) GetContentLength() int64 {
if v := ctx.GetHeader(ContentLengthHeaderKey); v != "" {
n, _ := strconv.ParseInt(v, 10, 64)
return n
}
return 0
}
// StatusCode sets the status code header to the response.
// Look .GetStatusCode & .FireStatusCode too.
//
// Remember, the last one before .Write matters except recorder and transactions.
func (ctx *Context) StatusCode(statusCode int) {
ctx.writer.WriteHeader(statusCode)
}
// NotFound emits an error 404 to the client, using the specific custom error error handler.
// Note that you may need to call ctx.StopExecution() if you don't want the next handlers
// to be executed. Next handlers are being executed on iris because you can alt the
// error code and change it to a more specific one, i.e
// users := app.Party("/users")
// users.Done(func(ctx iris.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
func (ctx *Context) NotFound() {
ctx.StatusCode(http.StatusNotFound)
}
// GetStatusCode returns the current status code of the response.
// Look StatusCode too.
func (ctx *Context) GetStatusCode() int {
return ctx.writer.StatusCode()
}
// +------------------------------------------------------------+
// | Various Request and Post Data |
// +------------------------------------------------------------+
func (ctx *Context) getQuery() url.Values {
if ctx.query == nil {
ctx.query = ctx.request.URL.Query()
}
return ctx.query
}
// URLParamExists returns true if the url parameter exists, otherwise false.
func (ctx *Context) URLParamExists(name string) bool {
_, exists := ctx.getQuery()[name]
return exists
}
// URLParamDefault returns the get parameter from a request, if not found then "def" is returned.
func (ctx *Context) URLParamDefault(name string, def string) string {
if v := ctx.getQuery().Get(name); v != "" {
return v
}
return def
}
// URLParam returns the get parameter from a request, if any.
func (ctx *Context) URLParam(name string) string {
return ctx.URLParamDefault(name, "")
}
// URLParamSlice a shortcut of ctx.Request().URL.Query()[name].
// Like `URLParam` but it returns all values instead of a single string separated by commas.
// Returns the values of a url query of the given "name" as string slice, e.g.
// ?name=john&name=doe&name=kataras will return [ john doe kataras].
//
// See `URLParamsSorted` for sorted values.
func (ctx *Context) URLParamSlice(name string) []string {
return ctx.getQuery()[name]
}
// URLParamTrim returns the url query parameter with trailing white spaces removed from a request.
func (ctx *Context) URLParamTrim(name string) string {
return strings.TrimSpace(ctx.URLParam(name))
}
// URLParamEscape returns the escaped url query parameter from a request.
func (ctx *Context) URLParamEscape(name string) string {
return DecodeQuery(ctx.URLParam(name))
}
// ErrNotFound is the type error which API users can make use of
// to check if a `Context` action of a `Handler` is type of Not Found,
// e.g. URL Query Parameters.
// Example:
//
// n, err := context.URLParamInt("url_query_param_name")
// if errors.Is(err, context.ErrNotFound) {
// // [handle error...]
// }
// Another usage would be `err == context.ErrNotFound`
// HOWEVER prefer use the new `errors.Is` as API details may change in the future.
var ErrNotFound = errors.New("not found")
// URLParamInt returns the url query parameter as int value from a request,
// returns -1 and an error if parse failed or not found.
func (ctx *Context) URLParamInt(name string) (int, error) {
if v := ctx.URLParam(name); v != "" {
n, err := strconv.Atoi(v)
if err != nil {
return -1, err
}
return n, nil
}
return -1, ErrNotFound
}
// URLParamIntDefault returns the url query parameter as int value from a request,
// if not found or parse failed then "def" is returned.
func (ctx *Context) URLParamIntDefault(name string, def int) int {
v, err := ctx.URLParamInt(name)
if err != nil {
return def
}
return v
}
// URLParamInt32Default returns the url query parameter as int32 value from a request,
// if not found or parse failed then "def" is returned.
func (ctx *Context) URLParamInt32Default(name string, def int32) int32 {
if v := ctx.URLParam(name); v != "" {
n, err := strconv.ParseInt(v, 10, 32)
if err != nil {
return def
}
return int32(n)
}
return def
}
// URLParamInt64 returns the url query parameter as int64 value from a request,
// returns -1 and an error if parse failed or not found.
func (ctx *Context) URLParamInt64(name string) (int64, error) {
if v := ctx.URLParam(name); v != "" {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return -1, err
}
return n, nil
}
return -1, ErrNotFound
}
// URLParamInt64Default returns the url query parameter as int64 value from a request,
// if not found or parse failed then "def" is returned.
func (ctx *Context) URLParamInt64Default(name string, def int64) int64 {
v, err := ctx.URLParamInt64(name)
if err != nil {
return def
}
return v
}
// URLParamUint64 returns the url query parameter as uint64 value from a request.
// Returns 0 on parse errors or when the URL parameter does not exist in the Query.
func (ctx *Context) URLParamUint64(name string) uint64 {
if v := ctx.URLParam(name); v != "" {
n, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return 0
}
return n
}
return 0
}
// URLParamFloat64 returns the url query parameter as float64 value from a request,
// returns an error and -1 if parse failed.
func (ctx *Context) URLParamFloat64(name string) (float64, error) {
if v := ctx.URLParam(name); v != "" {
n, err := strconv.ParseFloat(v, 64)
if err != nil {
return -1, err
}
return n, nil
}
return -1, ErrNotFound
}
// URLParamFloat64Default returns the url query parameter as float64 value from a request,
// if not found or parse failed then "def" is returned.
func (ctx *Context) URLParamFloat64Default(name string, def float64) float64 {
v, err := ctx.URLParamFloat64(name)
if err != nil {
return def
}
return v
}
// URLParamBool returns the url query parameter as boolean value from a request,
// returns an error if parse failed.
func (ctx *Context) URLParamBool(name string) (bool, error) {
return strconv.ParseBool(ctx.URLParam(name))
}
// URLParams returns a map of URL Query parameters.
// If the value of a URL parameter is a slice,
// then it is joined as one separated by comma.
// It returns an empty map on empty URL query.
//
// See URLParamsSorted too.
func (ctx *Context) URLParams() map[string]string {
q := ctx.getQuery()
values := make(map[string]string, len(q))
for k, v := range q {
values[k] = strings.Join(v, ",")
}
return values
}
// URLParamsSorted returns a sorted (by key) slice
// of key-value entries of the URL Query parameters.
func (ctx *Context) URLParamsSorted() []memstore.StringEntry {
q := ctx.getQuery()
n := len(q)
if n == 0 {
return nil
}
keys := make([]string, 0, n)
for key := range q {
keys = append(keys, key)
}
sort.Strings(keys)
entries := make([]memstore.StringEntry, 0, n)
for _, key := range keys {
value := q[key]
entries = append(entries, memstore.StringEntry{
Key: key,
Value: strings.Join(value, ","),
})
}
return entries
}
// ResetQuery clears the GET URL Query request, temporary, cache.
// Any new URLParamXXX calls will receive the new parsed values.
func (ctx *Context) ResetQuery() {
ctx.query = nil
}
// No need anymore, net/http checks for the Form already.
// func (ctx *Context) askParseForm() error {
// if ctx.request.Form == nil {
// if err := ctx.request.ParseForm(); err != nil {
// return err
// }
// }
// return nil
// }
// FormValueDefault returns a single parsed form value by its "name",
// including both the URL field's query parameters and the POST or PUT form data.
//
// Returns the "def" if not found.
func (ctx *Context) FormValueDefault(name string, def string) string {
if form, has := ctx.form(); has {
if v := form[name]; len(v) > 0 {
return v[0]
}
}
return def
}
// FormValueDefault retruns a single parsed form value.
func FormValueDefault(r *http.Request, name string, def string, postMaxMemory int64, resetBody bool) string {
if form, has := GetForm(r, postMaxMemory, resetBody); has {
if v := form[name]; len(v) > 0 {
return v[0]
}
}
return def
}
// FormValue returns a single parsed form value by its "name",
// including both the URL field's query parameters and the POST or PUT form data.
func (ctx *Context) FormValue(name string) string {
return ctx.FormValueDefault(name, "")
}
// FormValues returns the parsed form data, including both the URL
// field's query parameters and the POST or PUT form data.
//
// The default form's memory maximum size is 32MB, it can be changed by the
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
// NOTE: A check for nil is necessary.
func (ctx *Context) FormValues() map[string][]string {
form, _ := ctx.form()
return form
}
// Form contains the parsed form data, including both the URL
// field's query parameters and the POST or PUT form data.
func (ctx *Context) form() (form map[string][]string, found bool) {
return GetForm(ctx.request, ctx.app.ConfigurationReadOnly().GetPostMaxMemory(), ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal())
}
// GetForm returns the request form (url queries, post or multipart) values.
func GetForm(r *http.Request, postMaxMemory int64, resetBody bool) (form map[string][]string, found bool) {
/*
net/http/request.go#1219
for k, v := range f.Value {
r.Form[k] = append(r.Form[k], v...)
// r.PostForm should also be populated. See Issue 9305.
r.PostForm[k] = append(r.PostForm[k], v...)
}
*/
if form := r.Form; len(form) > 0 {
return form, true
}
if form := r.PostForm; len(form) > 0 {
return form, true
}
if m := r.MultipartForm; m != nil {
if len(m.Value) > 0 {
return m.Value, true
}
}
var bodyCopy []byte
if resetBody {
// on POST, PUT and PATCH it will read the form values from request body otherwise from URL queries.
if m := r.Method; m == "POST" || m == "PUT" || m == "PATCH" {
bodyCopy, _ = GetBody(r, resetBody)
if len(bodyCopy) == 0 {
return nil, false
}
// r.Body = ioutil.NopCloser(io.TeeReader(r.Body, buf))
} else {
resetBody = false
}
}
// ParseMultipartForm calls `request.ParseForm` automatically
// therefore we don't need to call it here, although it doesn't hurt.
// After one call to ParseMultipartForm or ParseForm,
// subsequent calls have no effect, are idempotent.
err := r.ParseMultipartForm(postMaxMemory)
if resetBody {
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyCopy))
}
if err != nil && err != http.ErrNotMultipart {
return nil, false
}
if form := r.Form; len(form) > 0 {
return form, true
}
if form := r.PostForm; len(form) > 0 {
return form, true
}
if m := r.MultipartForm; m != nil {
if len(m.Value) > 0 {
return m.Value, true
}
}
return nil, false
}
// PostValues returns all the parsed form data from POST, PATCH,
// or PUT body parameters based on a "name" as a string slice.
//
// The default form's memory maximum size is 32MB, it can be changed by the
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
//
// In addition, it reports whether the form was empty
// or when the "name" does not exist
// or whether the available values are empty.
// It strips any empty key-values from the slice before return.
//
// Look ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
// See `PostValueMany` method too.
func (ctx *Context) PostValues(name string) ([]string, error) {
_, ok := ctx.form()
if !ok {
if !ctx.app.ConfigurationReadOnly().GetFireEmptyFormError() {
return nil, nil
}
return nil, ErrEmptyForm // empty form.
}
values, ok := ctx.request.PostForm[name]
if !ok {
return nil, ErrNotFound // field does not exist
}
if len(values) == 0 ||
// Fast check for its first empty value (see below).
strings.TrimSpace(values[0]) == "" {
return nil, fmt.Errorf("%w: %s", ErrEmptyFormField, name)
}
for _, value := range values {
if value == "" { // if at least one empty value, then perform the strip from the beginning.
result := make([]string, 0, len(values))
for _, value := range values {
if strings.TrimSpace(value) != "" {
result = append(result, value) // we store the value as it is, not space-trimmed.
}
}
if len(result) == 0 {
return nil, fmt.Errorf("%w: %s", ErrEmptyFormField, name)
}
return result, nil
}
}
return values, nil
}
// PostValueMany is like `PostValues` method, it returns the post data of a given key.
// In addition to `PostValues` though, the returned value is a single string
// separated by commas on multiple values.
//
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueMany(name string) (string, error) {
values, err := ctx.PostValues(name)
if err != nil || len(values) == 0 {
return "", err
}
return strings.Join(values, ","), nil
}
// PostValueDefault returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name".
//
// If not found then "def" is returned instead.
func (ctx *Context) PostValueDefault(name string, def string) string {
values, err := ctx.PostValues(name)
if err != nil || len(values) == 0 {
return def // it returns "def" even if it's empty here.
}
return values[len(values)-1]
}
// PostValue returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name".
//
// See `PostValueMany` too.
func (ctx *Context) PostValue(name string) string {
return ctx.PostValueDefault(name, "")
}
// PostValueTrim returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", without trailing spaces.
func (ctx *Context) PostValueTrim(name string) string {
return strings.TrimSpace(ctx.PostValue(name))
}
// PostValueInt returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", as int.
//
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueInt(name string) (int, error) {
values, err := ctx.PostValues(name)
if err != nil || len(values) == 0 {
return 0, err
}
return strconv.Atoi(values[len(values)-1])
}
// PostValueIntDefault returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", as int.
//
// If not found or parse errors returns the "def".
func (ctx *Context) PostValueIntDefault(name string, def int) int {
if v, err := ctx.PostValueInt(name); err == nil {
return v
}
return def
}
// PostValueInt64 returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", as float64.
//
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueInt64(name string) (int64, error) {
values, err := ctx.PostValues(name)
if err != nil || len(values) == 0 {
return 0, err
}
return strconv.ParseInt(values[len(values)-1], 10, 64)
}
// PostValueInt64Default returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", as int64.
//
// If not found or parse errors returns the "def".
func (ctx *Context) PostValueInt64Default(name string, def int64) int64 {
if v, err := ctx.PostValueInt64(name); err == nil {
return v
}
return def
}
// PostValueFloat64 returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", as float64.
//
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueFloat64(name string) (float64, error) {
values, err := ctx.PostValues(name)
if err != nil || len(values) == 0 {
return 0, err
}
return strconv.ParseFloat(values[len(values)-1], 64)
}
// PostValueFloat64Default returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", as float64.
//
// If not found or parse errors returns the "def".
func (ctx *Context) PostValueFloat64Default(name string, def float64) float64 {
if v, err := ctx.PostValueFloat64(name); err == nil {
return v
}
return def
}
// PostValueBool returns the last parsed form data from POST, PATCH,
// or PUT body parameters based on a "name", as bool.
// If more than one value was binded to "name", then it returns the last one.
//
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueBool(name string) (bool, error) {
values, err := ctx.PostValues(name)
if err != nil || len(values) == 0 {
return false, err
}
return strconv.ParseBool(values[len(values)-1]) // values cannot be empty on this state.
}
// FormFile returns the first uploaded file that received from the client.
//
//
// The default form's memory maximum size is 32MB, it can be changed by the
// `iris#WithPostMaxMemory` configurator at main configuration passed on `app.Run`'s second argument.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-file
func (ctx *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
// we don't have access to see if the request is body stream
// and then the ParseMultipartForm can be useless
// here but do it in order to apply the post limit,
// the internal request.FormFile will not do it if that's filled
// and it's not a stream body.
if err := ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory()); err != nil {
return nil, nil, err
}
return ctx.request.FormFile(key)
}
// UploadFormFiles uploads any received file(s) from the client
// to the system physical location "destDirectory".
//
// The second optional argument "before" gives caller the chance to
// modify or cancel the *miltipart.FileHeader before saving to the disk,
// it can be used to change a file's name based on the current request,
// all FileHeader's options can be changed. You can ignore it if
// you don't need to use this capability before saving a file to the disk.
//
// Note that it doesn't check if request body streamed.
//
// Returns the copied length as int64 and
// a not nil error if at least one new file
// can't be created due to the operating system's permissions or
// http.ErrMissingFile if no file received.
//
// If you want to receive & accept files and manage them manually you can use the `context#FormFile`
// instead and create a copy function that suits your needs or use the `SaveFormFile` method,
// the below is for generic usage.
//
// The default form's memory maximum size is 32MB, it can be changed by
// the `WithPostMaxMemory` configurator or by `SetMaxRequestBodySize` or
// by the `LimitRequestBodySize` middleware (depends the use case).
//
// See `FormFile` to a more controlled way to receive a file.
//
// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/upload-files
func (ctx *Context) UploadFormFiles(destDirectory string, before ...func(*Context, *multipart.FileHeader) bool) (uploaded []*multipart.FileHeader, n int64, err error) {
err = ctx.request.ParseMultipartForm(ctx.app.ConfigurationReadOnly().GetPostMaxMemory())
if err != nil {
return nil, 0, err
}
if ctx.request.MultipartForm != nil {
if fhs := ctx.request.MultipartForm.File; fhs != nil {
for _, files := range fhs {
innerLoop:
for _, file := range files {
// Fix an issue that net/http has,
// an attacker can push a filename
// which could lead to override existing system files
// by ../../$file.
// Reported by Frank through security reports.
file.Filename = strings.ReplaceAll(file.Filename, "../", "")
file.Filename = strings.ReplaceAll(file.Filename, "..\\", "")
for _, b := range before {
if !b(ctx, file) {
continue innerLoop
}
}
n0, err0 := ctx.SaveFormFile(file, filepath.Join(destDirectory, file.Filename))
if err0 != nil {
return nil, 0, err0
}
n += n0
uploaded = append(uploaded, file)
}
}
return uploaded, n, nil
}
}
return nil, 0, http.ErrMissingFile
}
// SaveFormFile saves a result of `FormFile` to the "dest" disk full path (directory + filename).
// See `FormFile` and `UploadFormFiles` too.
func (ctx *Context) SaveFormFile(fh *multipart.FileHeader, dest string) (int64, error) {
src, err := fh.Open()
if err != nil {
return 0, err
}
defer src.Close()
out, err := os.Create(dest)
if err != nil {
return 0, err
}
defer out.Close()
return io.Copy(out, src)
}
// AbsoluteURI parses the "s" and returns its absolute URI form.
func (ctx *Context) AbsoluteURI(s string) string {
if s == "" {
return ""
}
if s[0] == '/' {
scheme := ctx.request.URL.Scheme
if scheme == "" {
if ctx.request.TLS != nil {
scheme = "https:"
} else {
scheme = "http:"