From 29c8f44c9316cc05b08111dae820b0f6d125557b Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 24 Dec 2022 05:04:16 +0200 Subject: [PATCH] add iris.TrimParamFilePart read more at: #2024 --- HISTORY.md | 2 + README.md | 2 +- _examples/routing/dynamic-path/main.go | 11 +++ aliases.go | 7 +- context/request_params.go | 107 +++++++++++++++++++++++++ macro/interpreter/parser/parser.go | 18 ++++- 6 files changed, 144 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index fe4c3436..68c1651f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,8 @@ The codebase for Dependency Injection, Internationalization and localization and ## Fixes and Improvements +- Add `iris.TrimParamFilePart` to handle cases like [#2024](https://github.com/kataras/iris/issues/2024) and improve the [_examples/routing/dynamic-path/main.go](_examples/routing/dynamic-path/main.go#L356) example to include that case as well. + - **Breaking-change**: HTML template functions `yield`, `part`, `partial`, `partial_r` and `render` now accept (and require for some cases) a second argument of the binding data context too. Convert: `{{ yield }}` to `{{ yield . }}`, `{{ render "templates/mytemplate.html" }}` to `{{ render "templates/mytemplate.html" . }}`, `{{ partial "partials/mypartial.html" }}` to `{{ partial "partials/mypartial.html" . }}` and so on. - Add new `URLParamSeparator` to the configuration. Defaults to "," but can be set to an empty string to disable splitting query values on `Context.URLParamSlice` method. diff --git a/README.md b/README.md index df0e181b..6944d02e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ -Iris is a fast, simple yet fully featured and very efficient web framework for Go. **With a personal promise of active lifetime maintenance, [unlike other](https://github.com/gorilla/.github/blob/master/profile/README.md) free software packages offered in Go ecosystem**. +Iris is a fast, simple yet fully featured and very efficient web framework for Go. **With the promise of active lifetime maintenance, unlike other free software packages offered in Go ecosystem**. It provides a beautifully expressive and easy to use foundation for your next website or API. diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 63be863f..12da42e7 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -352,6 +352,17 @@ func main() { }) // for wildcard path (any number of path segments) without validation you can use: // /myfiles/* + // http://localhost:8080/trimmed/42.html + app.Get("/trimmed/{uid:string regexp(^[0-9]{1,20}.html$)}", iris.TrimParamFilePart, func(ctx iris.Context) { + // + // The above line is useless now that we've registered the TrimParamFilePart middleware: + // uid := ctx.Params().GetTrimFileUint64("uid") + // TrimParamFilePart can be registered to a Party (group of routes) too. + + uid := ctx.Params().GetUint64Default("uid", 0) + ctx.Writef("Param value: %d\n", uid) + }) + // "{param}"'s performance is exactly the same of ":param"'s. // alternatives -> ":param" for single path parameter and "*" for wildcard path parameter. diff --git a/aliases.go b/aliases.go index 211880e9..a1e4d79e 100644 --- a/aliases.go +++ b/aliases.go @@ -618,8 +618,13 @@ var ( // A shortcut for the `context#ErrPushNotSupported`. ErrPushNotSupported = context.ErrPushNotSupported // PrivateError accepts an error and returns a wrapped private one. - // A shortcut for the `context#PrivateError`. + // A shortcut for the `context#PrivateError` function. PrivateError = context.PrivateError + + // TrimParamFilePart is a middleware which trims any last part after a dot (.) character + // of the current route's dynamic path parameters. + // A shortcut for the `context#TrimParamFilePart` function. + TrimParamFilePart Handler = context.TrimParamFilePart ) // HTTP Methods copied from `net/http`. diff --git a/context/request_params.go b/context/request_params.go index a86c83ab..293ece03 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -91,6 +91,113 @@ func (r *RequestParams) GetDecoded(key string) string { return r.GetEscape(key) } +// TrimParamFilePart is a middleware which replaces all route dynamic path parameters +// with values that do not contain any part after the last dot (.) character. +// +// Example Code: +// +// package main +// +// import ( +// "github.com/kataras/iris/v12" +// ) +// +// func main() { +// app := iris.New() +// app.Get("/{uid:string regexp(^[0-9]{1,20}.html$)}", iris.TrimParamFilePart, handler) +// // TrimParamFilePart can be registered as a middleware to a Party (group of routes) as well. +// app.Listen(":8080") +// } +// +// func handler(ctx iris.Context) { +// // +// // The above line is useless now that we've registered the TrimParamFilePart middleware: +// // uid := ctx.Params().GetTrimFileUint64("uid") +// // +// +// uid := ctx.Params().GetUint64Default("uid", 0) +// ctx.Writef("Param value: %d\n", uid) +// } +func TrimParamFilePart(ctx *Context) { // See #2024. + params := ctx.Params() + + for i, param := range params.Store { + if value, ok := param.ValueRaw.(string); ok { + if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ { + value = value[0:idx] + param.ValueRaw = value + } + } + + params.Store[i] = param + } + + ctx.Next() +} + +// GetTrimFile returns a parameter value but without the last ".ANYTHING_HERE" part. +func (r *RequestParams) GetTrimFile(key string) string { + value := r.Get(key) + + if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ { + return value[0:idx] + } + + return value +} + +// GetTrimFileInt same as GetTrimFile but it returns the value as int. +func (r *RequestParams) GetTrimFileInt(key string) int { + value := r.Get(key) + + if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ { + value = value[0:idx] + } + + v, _ := strconv.Atoi(value) + return v +} + +// GetTrimFileUint64 same as GetTrimFile but it returns the value as uint64. +func (r *RequestParams) GetTrimFileUint64(key string) uint64 { + value := r.Get(key) + + if idx := strings.LastIndexByte(value, '.'); idx > 1 /* at least .h */ { + value = value[0:idx] + } + + v, err := strconv.ParseUint(value, 10, strconv.IntSize) + if err != nil { + return 0 + } + + return v +} + +// GetTrimFileUint64 same as GetTrimFile but it returns the value as uint. +func (r *RequestParams) GetTrimFileUint(key string) uint { + return uint(r.GetTrimFileUint64(key)) +} + +func (r *RequestParams) getRightTrimmed(key string, cutset string) string { + return strings.TrimRight(strings.ToLower(r.Get(key)), cutset) +} + +// GetTrimHTML returns a parameter value but without the last ".html" part. +func (r *RequestParams) GetTrimHTML(key string) string { + return r.getRightTrimmed(key, ".html") +} + +// GetTrimJSON returns a parameter value but without the last ".json" part. +func (r *RequestParams) GetTrimJSON(key string) string { + return r.getRightTrimmed(key, ".json") +} + +// GetTrimXML returns a parameter value but without the last ".xml" part. +func (r *RequestParams) GetTrimXML(key string) string { + return r.getRightTrimmed(key, ".xml") +} + // GetIntUnslashed same as Get but it removes the first slash if found. // Usage: Get an id from a wildcard path. // diff --git a/macro/interpreter/parser/parser.go b/macro/interpreter/parser/parser.go index c50d9696..99862663 100644 --- a/macro/interpreter/parser/parser.go +++ b/macro/interpreter/parser/parser.go @@ -27,10 +27,26 @@ func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, } // if it's not a named path parameter of the new syntax then continue to the next - if s[0] != lexer.Begin || s[len(s)-1] != lexer.End { + // if s[0] != lexer.Begin || s[len(s)-1] != lexer.End { + // continue + // } + + // Modified to show an error on a certain invalid action. + if s[0] != lexer.Begin { continue } + if s[len(s)-1] != lexer.End { + if idx := strings.LastIndexByte(s, lexer.End); idx > 2 && idx < len(s)-1 /* at least {x}*/ { + // Do NOT allow something more than a dynamic path parameter in the same path segment, + // e.g. /{param}-other-static-part/. See #2024. + // this allows it but NO (see trie insert): s = s[0 : idx+1] + return nil, fmt.Errorf("%s: invalid path part: dynamic path parameter and other parameters or static parts are not allowed in the same exact request path part, use the {regexp} function alone instead", s) + } else { + continue + } + } + p.Reset(s) stmt, err := p.Parse(paramTypes) if err != nil {