diff --git a/HISTORY.md b/HISTORY.md index 405f3e65..debdd376 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -612,6 +612,7 @@ New Package-level Variables: New Context Methods: +- `Context.TextYAML(interface{}) error` same as `Context.YAML` but with set the Content-Type to `text/yaml` instead (Google Chrome renders it as text). - `Context.IsDebug() bool` reports whether the application is running under debug/development mode. It is a shortcut of Application.Logger().Level >= golog.DebugLevel. - `Context.IsRecovered() bool` reports whether the current request was recovered from the [recover middleware](https://github.com/kataras/iris/tree/master/middleware/recover). Also the `iris.IsErrPrivate` function and `iris.ErrPrivate` interface have been introduced. - `Context.RecordBody()` same as the Application's `DisableBodyConsumptionOnUnmarshal` configuration field but registers per chain of handlers. It makes the request body readable more than once. @@ -657,6 +658,8 @@ New Context Methods: Breaking Changes: +- The `Context.ContentType` does not accept filenames to resolve the mime type anymore (caused issues with vendor-specific(vnd) MIME types). +- The `Configuration.RemoteAddrPrivateSubnets.IPRange.Start and End` are now type of `string` instead of `net.IP`. The `WithRemoteAddrPrivateSubnet` option remains as it is, already accepts `string`s. - The `i18n#LoaderConfig.FuncMap template.FuncMap` field was replaced with `Funcs func(iris.Locale) template.FuncMap` in order to give current locale access to the template functions. A new `app.I18n.Loader` was introduced too, in order to make it easier for end-developers to customize the translation key values. - Request Logger's `Columns bool` field has been removed. Use the new [accesslog](https://github.com/kataras/iris/tree/master/_examples/logging/request-logger/accesslog/main.go) middleware instead. - The `.Binary` method of all view engines was removed: pass the go-bindata's latest version `AssetFile()` exported function as the first argument instead of string. All examples updated. diff --git a/_examples/README.md b/_examples/README.md index dec2fd27..ec2a9628 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -31,6 +31,7 @@ * Configuration * [Functional](configuration/functional/main.go) * [Configuration Struct](configuration/from-configuration-structure/main.go) + * [Using Viper](configuration/viper) * [Import from YAML](configuration/from-yaml-file/main.go) * [Share Configuration across instances](configuration/from-yaml-file/shared-configuration/main.go) * [Import from TOML](configuration/from-toml-file/main.go) diff --git a/_examples/apidoc/yaag/go.mod b/_examples/apidoc/yaag/go.mod index 69607b87..fe436dd2 100644 --- a/_examples/apidoc/yaag/go.mod +++ b/_examples/apidoc/yaag/go.mod @@ -4,5 +4,5 @@ go 1.15 require ( github.com/betacraft/yaag v1.0.1-0.20200719063524-47d781406108 - github.com/kataras/iris/v12 v12.2.0-alpha + github.com/kataras/iris/v12 master ) diff --git a/_examples/configuration/viper/config.yml b/_examples/configuration/viper/config.yml new file mode 100644 index 00000000..e0a913c1 --- /dev/null +++ b/_examples/configuration/viper/config.yml @@ -0,0 +1,50 @@ +Addr: + Internal: + IP: 127.0.0.1 + Plain: 8080 + Secure: 443 +Locale: + Pattern: "./locales/*/*.ini" + Default: "en-US" + Supported: + - "en-US" + - "el-GR" +Iris: + LogLevel: debug + SocketSharding: true + EnableOptimizations: true + DisableStartupLog: false + FireMethodNotAllowed: true + ForceLowercaseRouting: true + EnablePathIntelligence: true + Charset: "utf-8" + TimeFormat: "2006-01-02 15:04:05" + DisableBodyConsumptionOnUnmarshal: true + FireEmptyFormError: true + PostMaxMemory: 67108864 + RemoteAddrHeaders: + - "X-Real-Ip" + - "X-Forwarded-For" + - "CF-Connecting-IP" + - "True-Client-Ip" + IgnoreServerErrors: + - "http: Server closed" + # Tunneling: + # WebInterface: "http://127.0.0.1:4040" + # AuthToken: "" + # Tunnels: + # - Name: "My awesome App" + # Addr: "localhost:8080" + # - Name: "My Second awesome App" + # Addr: "localhost:9090" + RemoteAddrPrivateSubnets: + - Start: "192.168.0.0" + End: "192.168.255.255" + - Start: "198.18.0.0" + End: "198.19.255.255" + SSLProxyHeaders: + X-Forwarded-Proto: "https" + HostProxyHeaders: + X-Host: true + Other: + ServerName: "My awesome Iris web server" \ No newline at end of file diff --git a/_examples/configuration/viper/config/config.go b/_examples/configuration/viper/config/config.go new file mode 100644 index 00000000..33a0575b --- /dev/null +++ b/_examples/configuration/viper/config/config.go @@ -0,0 +1,54 @@ +package config + +import ( + "fmt" + + "github.com/kataras/iris/v12" + // Viper is a third-party package: + // go get github.com/spf13/viper + "github.com/spf13/viper" +) + +func init() { + loadConfiguration() +} + +// C is the configuration of the app. +var C = struct { + Iris iris.Configuration + Addr struct { + Internal struct { + IP string + Plain int + Secure int + } + } + Locale struct { + Pattern string + Default string + Supported []string + } +}{ + Iris: iris.DefaultConfiguration(), + // other default values... +} + +func loadConfiguration() { + viper.SetConfigName("config") // name of config file (without extension) + viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name + viper.AddConfigPath("/etc/app/") // path to look for the config file in + viper.AddConfigPath("$HOME/.app") // call multiple times to add many search paths + viper.AddConfigPath(".") // optionally look for config in the working directory + err := viper.ReadInConfig() // Find and read the config file + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + } else { + panic(fmt.Errorf("load configuration: %w", err)) + } + } + + err = viper.Unmarshal(&C) + if err != nil { + panic(fmt.Errorf("load configuration: unmarshal: %w", err)) + } +} diff --git a/_examples/configuration/viper/go.mod b/_examples/configuration/viper/go.mod new file mode 100644 index 00000000..1d4a8f74 --- /dev/null +++ b/_examples/configuration/viper/go.mod @@ -0,0 +1,8 @@ +module app + +go 1.15 + +require ( + github.com/kataras/iris/v12 master + github.com/spf13/viper v1.3.2 +) \ No newline at end of file diff --git a/_examples/configuration/viper/main.go b/_examples/configuration/viper/main.go new file mode 100644 index 00000000..97f6730d --- /dev/null +++ b/_examples/configuration/viper/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + + "app/config" + + "github.com/kataras/iris/v12" +) + +func main() { + app := iris.New() + app.Get("/", func(ctx iris.Context) { + ctx.TextYAML(config.C) + }) + + addr := fmt.Sprintf("%s:%d", config.C.Addr.Internal.IP, config.C.Addr.Internal.Plain) + app.Listen(addr, iris.WithConfiguration(config.C.Iris)) +} diff --git a/_examples/database/mongodb/go.mod b/_examples/database/mongodb/go.mod index b3d9e83c..b5723756 100644 --- a/_examples/database/mongodb/go.mod +++ b/_examples/database/mongodb/go.mod @@ -4,6 +4,6 @@ go 1.15 require ( github.com/joho/godotenv v1.3.0 - github.com/kataras/iris/v12 v12.2.0-alpha + github.com/kataras/iris/v12 master go.mongodb.org/mongo-driver v1.3.4 ) diff --git a/_examples/database/mysql/go.mod b/_examples/database/mysql/go.mod index 899536e1..8129ff16 100644 --- a/_examples/database/mysql/go.mod +++ b/_examples/database/mysql/go.mod @@ -4,6 +4,6 @@ go 1.15 require ( github.com/go-sql-driver/mysql v1.5.0 - github.com/kataras/iris/v12 v12.2.0-alpha + github.com/kataras/iris/v12 master github.com/mailgun/groupcache/v2 v2.1.0 ) diff --git a/_examples/kafka-api/go.mod b/_examples/kafka-api/go.mod index cd12d110..2b5c1485 100644 --- a/_examples/kafka-api/go.mod +++ b/_examples/kafka-api/go.mod @@ -4,5 +4,5 @@ go 1.15 require ( github.com/Shopify/sarama v1.26.4 - github.com/kataras/iris/v12 v12.2.0-alpha + github.com/kataras/iris/v12 master ) diff --git a/_examples/logging/rollbar/go.mod b/_examples/logging/rollbar/go.mod index b28a35e1..68ff05ee 100644 --- a/_examples/logging/rollbar/go.mod +++ b/_examples/logging/rollbar/go.mod @@ -3,6 +3,6 @@ module github.com/kataras/iris/examples/logging/rollbar go 1.15 require ( - github.com/kataras/iris/v12 v12.2.0-alpha + github.com/kataras/iris/v12 master github.com/rollbar/rollbar-go v1.2.0 ) diff --git a/_examples/mvc/overview/go.mod b/_examples/mvc/overview/go.mod index a2f33581..f377fd38 100644 --- a/_examples/mvc/overview/go.mod +++ b/_examples/mvc/overview/go.mod @@ -2,4 +2,4 @@ module app go 1.15 -require github.com/kataras/iris/v12 v12.2.0-alpha +require github.com/kataras/iris/v12 master diff --git a/_examples/mvc/vuejs-todo-mvc/src/go.mod b/_examples/mvc/vuejs-todo-mvc/src/go.mod index 5f0a86b9..2f01466e 100644 --- a/_examples/mvc/vuejs-todo-mvc/src/go.mod +++ b/_examples/mvc/vuejs-todo-mvc/src/go.mod @@ -2,4 +2,4 @@ module github.com/kataras/iris/_examples/mvc/vuejs-todo-mvc/src go 1.15 -require github.com/kataras/iris/v12 v12.2.0-alpha +require github.com/kataras/iris/v12 master diff --git a/_examples/routing/subdomains/redirect/multi-instances/go.mod b/_examples/routing/subdomains/redirect/multi-instances/go.mod index 74a941c0..9fe37ee4 100644 --- a/_examples/routing/subdomains/redirect/multi-instances/go.mod +++ b/_examples/routing/subdomains/redirect/multi-instances/go.mod @@ -2,6 +2,4 @@ module github.com/kataras/iris/_examples/routing/subdomains/redirect/multi-insta go 1.15 -require github.com/kataras/iris/v12 v12.2.0-alpha - -replace github.com/kataras/iris/v12 => ../../../../../ \ No newline at end of file +require github.com/kataras/iris/v12 master \ No newline at end of file diff --git a/_examples/sessions/database/redis/go.mod b/_examples/sessions/database/redis/go.mod index 2e3f9650..6697407d 100644 --- a/_examples/sessions/database/redis/go.mod +++ b/_examples/sessions/database/redis/go.mod @@ -2,5 +2,5 @@ module app go 1.15 -require github.com/kataras/iris/v12 v12.2.0-alpha +require github.com/kataras/iris/v12 master diff --git a/_examples/websocket/basic/go.mod b/_examples/websocket/basic/go.mod index 9f0a6eef..cd013ba0 100644 --- a/_examples/websocket/basic/go.mod +++ b/_examples/websocket/basic/go.mod @@ -4,5 +4,5 @@ go 1.15 require ( github.com/iris-contrib/middleware/jwt v0.0.0-20200710202437-92b01b85baaf - github.com/kataras/iris/v12 v12.2.0-alpha // indirect + github.com/kataras/iris/v12 master ) diff --git a/_examples/websocket/socketio/go.mod b/_examples/websocket/socketio/go.mod index 92024da9..2fb0f3cd 100644 --- a/_examples/websocket/socketio/go.mod +++ b/_examples/websocket/socketio/go.mod @@ -4,5 +4,5 @@ go 1.15 require ( github.com/googollee/go-socket.io v1.4.3-0.20191109153049-7451e2f8c2e0 - github.com/kataras/iris/v12 v12.2.0-alpha + github.com/kataras/iris/v12 master ) diff --git a/configuration.go b/configuration.go index d0385287..0e59a6a3 100644 --- a/configuration.go +++ b/configuration.go @@ -3,7 +3,6 @@ package iris import ( "fmt" "io/ioutil" - "net" "os" "os/user" "path/filepath" @@ -391,8 +390,8 @@ func WithoutRemoteAddrHeader(headerName string) Configurator { func WithRemoteAddrPrivateSubnet(startIP, endIP string) Configurator { return func(app *Application) { app.config.RemoteAddrPrivateSubnets = append(app.config.RemoteAddrPrivateSubnets, netutil.IPRange{ - Start: net.ParseIP(startIP), - End: net.ParseIP(endIP), + Start: startIP, + End: endIP, }) } } @@ -825,28 +824,28 @@ type Configuration struct { // For details please navigate through: https://github.com/kataras/iris/issues/1453 // Defaults to: // { - // Start: net.ParseIP("10.0.0.0"), - // End: net.ParseIP("10.255.255.255"), + // Start: "10.0.0.0", + // End: "10.255.255.255", // }, // { - // Start: net.ParseIP("100.64.0.0"), - // End: net.ParseIP("100.127.255.255"), + // Start: "100.64.0.0", + // End: "100.127.255.255", // }, // { - // Start: net.ParseIP("172.16.0.0"), - // End: net.ParseIP("172.31.255.255"), + // Start: "172.16.0.0", + // End: "172.31.255.255", // }, // { - // Start: net.ParseIP("192.0.0.0"), - // End: net.ParseIP("192.0.0.255"), + // Start: "192.0.0.0", + // End: "192.0.0.255", // }, // { - // Start: net.ParseIP("192.168.0.0"), - // End: net.ParseIP("192.168.255.255"), + // Start: "192.168.0.0", + // End: "192.168.255.255", // }, // { - // Start: net.ParseIP("198.18.0.0"), - // End: net.ParseIP("198.19.255.255"), + // Start: "198.18.0.0", + // End: "198.19.255.255", // } // // Look `Context.RemoteAddr()` for more. @@ -1218,28 +1217,28 @@ func DefaultConfiguration() Configuration { RemoteAddrHeadersForce: false, RemoteAddrPrivateSubnets: []netutil.IPRange{ { - Start: net.ParseIP("10.0.0.0"), - End: net.ParseIP("10.255.255.255"), + Start: "10.0.0.0", + End: "10.255.255.255", }, { - Start: net.ParseIP("100.64.0.0"), - End: net.ParseIP("100.127.255.255"), + Start: "100.64.0.0", + End: "100.127.255.255", }, { - Start: net.ParseIP("172.16.0.0"), - End: net.ParseIP("172.31.255.255"), + Start: "172.16.0.0", + End: "172.31.255.255", }, { - Start: net.ParseIP("192.0.0.0"), - End: net.ParseIP("192.0.0.255"), + Start: "192.0.0.0", + End: "192.0.0.255", }, { - Start: net.ParseIP("192.168.0.0"), - End: net.ParseIP("192.168.255.255"), + Start: "192.168.0.0", + End: "192.168.255.255", }, { - Start: net.ParseIP("198.18.0.0"), - End: net.ParseIP("198.19.255.255"), + Start: "198.18.0.0", + End: "198.19.255.255", }, }, SSLProxyHeaders: make(map[string]string), diff --git a/context/context.go b/context/context.go index adb63e1e..b5f66961 100644 --- a/context/context.go +++ b/context/context.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "io/ioutil" - "mime" "mime/multipart" "net" "net/http" @@ -1323,14 +1322,15 @@ func (ctx *Context) ContentType(cType string) { } // 1. if it's path or a filename or an extension, - // then take the content type from that - if strings.Contains(cType, ".") { - ext := filepath.Ext(cType) - cType = mime.TypeByExtension(ext) - } + // 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 !strings.Contains(cType, "charset") { - if shouldAppendCharset(cType) { + if shouldAppendCharset(cType) { + if !strings.Contains(cType, "charset") { cType += "; charset=" + ctx.app.ConfigurationReadOnly().GetCharset() } } @@ -2431,7 +2431,7 @@ func (ctx *Context) ReadBody(ptr interface{}) error { case ContentXMLHeaderValue, ContentXMLUnreadableHeaderValue: return ctx.ReadXML(ptr) // "%v reflect.Indirect(reflect.ValueOf(ptr)).Interface()) - case ContentYAMLHeaderValue: + case ContentYAMLHeaderValue, ContentYAMLTextHeaderValue: return ctx.ReadYAML(ptr) case ContentFormHeaderValue, ContentFormMultipartHeaderValue: return ctx.ReadForm(ptr) @@ -3009,6 +3009,8 @@ const ( ContentMarkdownHeaderValue = "text/markdown" // ContentYAMLHeaderValue header value for YAML data. ContentYAMLHeaderValue = "application/x-yaml" + // ContentYAMLTextHeaderValue header value for YAML plain text. + ContentYAMLTextHeaderValue = "text/yaml" // ContentProtobufHeaderValue header value for Protobuf messages data. ContentProtobufHeaderValue = "application/x-protobuf" // ContentMsgPackHeaderValue header value for MsgPack data. @@ -3484,7 +3486,8 @@ func (ctx *Context) Markdown(markdownB []byte, opts ...Markdown) (int, error) { return n, err } -// YAML marshals the "v" using the yaml marshaler and renders its result to the client. +// YAML marshals the "v" using the yaml marshaler +// and sends the result to the client. func (ctx *Context) YAML(v interface{}) (int, error) { out, err := yaml.Marshal(v) if err != nil { @@ -3497,6 +3500,13 @@ func (ctx *Context) YAML(v interface{}) (int, error) { return ctx.Write(out) } +// TextYAML marshals the "v" using the yaml marshaler +// and renders to the client. +func (ctx *Context) TextYAML(v interface{}) (int, error) { + ctx.contentTypeOnce(ContentYAMLTextHeaderValue, "") + return ctx.YAML(v) +} + // Protobuf parses the "v" of proto Message and renders its result to the client. func (ctx *Context) Protobuf(v proto.Message) (int, error) { out, err := proto.Marshal(v) @@ -3735,6 +3745,8 @@ func (ctx *Context) Negotiate(v interface{}) (int, error) { return ctx.XML(v) case ContentYAMLHeaderValue: return ctx.YAML(v) + case ContentYAMLTextHeaderValue: + return ctx.TextYAML(v) case ContentProtobufHeaderValue: msg, ok := v.(proto.Message) if !ok { @@ -3918,6 +3930,19 @@ func (n *NegotiationBuilder) YAML(v ...interface{}) *NegotiationBuilder { return n.MIME(ContentYAMLHeaderValue, content) } +// TextYAML registers the "text/yaml" content type and, optionally, +// a value that `Context.Negotiate` will render +// when a client accepts the "application/x-yaml" content type. +// +// Returns itself for recursive calls. +func (n *NegotiationBuilder) TextYAML(v ...interface{}) *NegotiationBuilder { + var content interface{} + if len(v) > 0 { + content = v[0] + } + return n.MIME(ContentYAMLTextHeaderValue, content) +} + // Protobuf registers the "application/x-protobuf" content type and, optionally, // a value that `Context.Negotiate` will render // when a client accepts the "application/x-protobuf" content type. @@ -4131,6 +4156,12 @@ func (n *NegotiationAcceptBuilder) YAML() *NegotiationAcceptBuilder { return n.MIME(ContentYAMLHeaderValue) } +// TextYAML adds the "text/yaml" as accepted client content type. +// Returns itself. +func (n *NegotiationAcceptBuilder) TextYAML() *NegotiationAcceptBuilder { + return n.MIME(ContentYAMLTextHeaderValue) +} + // Protobuf adds the "application/x-protobuf" as accepted client content type. // Returns itself. func (n *NegotiationAcceptBuilder) Protobuf() *NegotiationAcceptBuilder { diff --git a/core/netutil/ip.go b/core/netutil/ip.go index ef8b25bf..77173735 100644 --- a/core/netutil/ip.go +++ b/core/netutil/ip.go @@ -4,6 +4,7 @@ import ( "bytes" "net" "strings" + "unsafe" ) /* Based on: @@ -13,13 +14,18 @@ https://github.com/kataras/iris/issues/1453 // IPRange is a structure that holds the start and end of a range of IP Addresses. type IPRange struct { - Start net.IP `ini:"start" json:"start" yaml:"Start" toml:"Start"` - End net.IP `ini:"end" json:"end" yaml:"End" toml:"End"` + Start string `ini:"start" json:"start" yaml:"Start" toml:"Start"` + End string `ini:"end" json:"end" yaml:"End" toml:"End"` +} + +func unsafeCompare(a []byte, b string) int { + bb := *(*[]byte)(unsafe.Pointer(&b)) + return bytes.Compare(a, bb) } // IPInRange reports whether a given IP Address is within a range given. func IPInRange(r IPRange, ipAddress net.IP) bool { - return bytes.Compare(ipAddress, r.Start) >= 0 && bytes.Compare(ipAddress, r.End) < 0 + return unsafeCompare(ipAddress, r.Start) >= 0 && unsafeCompare(ipAddress, r.End) < 0 } // IPIsPrivateSubnet reports whether this "ipAddress" is in a private subnet.