From 8e821b5039bd6983814a6b1d11894a649c44f74a Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 2 Sep 2019 12:21:41 -0600 Subject: caddyconfig: Add JSON5 and JSON-C adapters (closes #2735) --- admin.go | 59 ++++++++++++++++++++++++++-------------------- caddyconfig/json5/json5.go | 43 +++++++++++++++++++++++++++++++++ caddyconfig/jsonc/jsonc.go | 49 ++++++++++++++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 4 ++++ 5 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 caddyconfig/json5/json5.go create mode 100644 caddyconfig/jsonc/jsonc.go diff --git a/admin.go b/admin.go index c87ef95..b2894be 100644 --- a/admin.go +++ b/admin.go @@ -22,6 +22,7 @@ import ( "io" "io/ioutil" "log" + "mime" "net" "net/http" "net/http/pprof" @@ -170,38 +171,44 @@ func handleLoadConfig(w http.ResponseWriter, r *http.Request) { // if the config is formatted other than Caddy's native // JSON, we need to adapt it before loading it - ct := r.Header.Get("Content-Type") - if !strings.Contains(ct, "/json") { - slashIdx := strings.Index(ct, "/") - if slashIdx < 0 { - http.Error(w, "Malformed Content-Type", http.StatusBadRequest) - return - } - adapterName := ct[slashIdx+1:] - cfgAdapter := caddyconfig.GetAdapter(adapterName) - if cfgAdapter == nil { - http.Error(w, "Unrecognized config adapter: "+adapterName, http.StatusBadRequest) - return - } - body, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 1024*1024)) - if err != nil { - http.Error(w, "Error reading request body: "+err.Error(), http.StatusBadRequest) - return - } - result, warnings, err := cfgAdapter.Adapt(body, nil) + if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" { + ct, _, err := mime.ParseMediaType(ctHeader) if err != nil { - log.Printf("[ADMIN][ERROR] adapting config from %s: %v", adapterName, err) - http.Error(w, fmt.Sprintf("Adapting config from %s: %v", adapterName, err), http.StatusBadRequest) + http.Error(w, "Invalid Content-Type: "+err.Error(), http.StatusBadRequest) return } - if len(warnings) > 0 { - respBody, err := json.Marshal(warnings) + if !strings.HasSuffix(ct, "/json") { + slashIdx := strings.Index(ct, "/") + if slashIdx < 0 { + http.Error(w, "Malformed Content-Type", http.StatusBadRequest) + return + } + adapterName := ct[slashIdx+1:] + cfgAdapter := caddyconfig.GetAdapter(adapterName) + if cfgAdapter == nil { + http.Error(w, "Unrecognized config adapter: "+adapterName, http.StatusBadRequest) + return + } + body, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 1024*1024)) if err != nil { - log.Printf("[ADMIN][ERROR] marshaling warnings: %v", err) + http.Error(w, "Error reading request body: "+err.Error(), http.StatusBadRequest) + return + } + result, warnings, err := cfgAdapter.Adapt(body, nil) + if err != nil { + log.Printf("[ADMIN][ERROR] adapting config from %s: %v", adapterName, err) + http.Error(w, fmt.Sprintf("Adapting config from %s: %v", adapterName, err), http.StatusBadRequest) + return + } + if len(warnings) > 0 { + respBody, err := json.Marshal(warnings) + if err != nil { + log.Printf("[ADMIN][ERROR] marshaling warnings: %v", err) + } + w.Write(respBody) } - w.Write(respBody) + payload = bytes.NewReader(result) } - payload = bytes.NewReader(result) } err := Load(payload) diff --git a/caddyconfig/json5/json5.go b/caddyconfig/json5/json5.go new file mode 100644 index 0000000..2c86301 --- /dev/null +++ b/caddyconfig/json5/json5.go @@ -0,0 +1,43 @@ +// Copyright 2015 Matthew Holt and The Caddy 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. + +package json5adapter + +import ( + "encoding/json" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/ilibs/json5" +) + +func init() { + caddyconfig.RegisterAdapter("json5", Adapter{}) +} + +// Adapter adapts JSON5 to Caddy JSON. +type Adapter struct{} + +// Adapt converts the JSON5 config in body to Caddy JSON. +func (a Adapter) Adapt(body []byte, options map[string]interface{}) (result []byte, warnings []caddyconfig.Warning, err error) { + var decoded interface{} + err = json5.Unmarshal(body, &decoded) + if err != nil { + return + } + result, err = json.Marshal(decoded) + return +} + +// Interface guard +var _ caddyconfig.Adapter = (*Adapter)(nil) diff --git a/caddyconfig/jsonc/jsonc.go b/caddyconfig/jsonc/jsonc.go new file mode 100644 index 0000000..4f72c05 --- /dev/null +++ b/caddyconfig/jsonc/jsonc.go @@ -0,0 +1,49 @@ +// Copyright 2015 Matthew Holt and The Caddy 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. + +package jsoncadapter + +import ( + "encoding/json" + + "github.com/caddyserver/caddy/v2/caddyconfig" + "github.com/muhammadmuzzammil1998/jsonc" +) + +func init() { + caddyconfig.RegisterAdapter("jsonc", Adapter{}) +} + +// Adapter adapts JSON-C to Caddy JSON. +type Adapter struct{} + +// Adapt converts the JSON-C config in body to Caddy JSON. +func (a Adapter) Adapt(body []byte, options map[string]interface{}) (result []byte, warnings []caddyconfig.Warning, err error) { + result = jsonc.ToJSON(body) + + // any errors in the JSON will be + // reported during config load, but + // we can at least warn here that + // it is not valid JSON + if !json.Valid(result) { + warnings = append(warnings, caddyconfig.Warning{ + Message: "Resulting JSON is invalid.", + }) + } + + return +} + +// Interface guard +var _ caddyconfig.Adapter = (*Adapter)(nil) diff --git a/go.mod b/go.mod index d63097a..d647344 100644 --- a/go.mod +++ b/go.mod @@ -14,12 +14,14 @@ require ( github.com/google/go-cmp v0.3.1 // indirect github.com/google/uuid v1.1.1 // indirect github.com/huandu/xstrings v1.2.0 // indirect + github.com/ilibs/json5 v1.0.1 github.com/imdario/mergo v0.3.7 // indirect github.com/klauspost/compress v1.7.1-0.20190613161414-0b31f265a57b github.com/klauspost/cpuid v1.2.1 github.com/kr/pretty v0.1.0 // indirect github.com/mholt/certmagic v0.6.2 github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 + github.com/muhammadmuzzammil1998/jsonc v0.0.0-20190902132743-e4903c4dea48 github.com/rs/cors v1.6.0 github.com/russross/blackfriday/v2 v2.0.1 github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect diff --git a/go.sum b/go.sum index 4180648..dfe572f 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/ilibs/json5 v1.0.1 h1:3e14wUQM8PyK6Hf1bM+zAQFxfG+N5oZj35x5vCNeQ58= +github.com/ilibs/json5 v1.0.1/go.mod h1:kXsGuzHMPuZZTN15l0IQzy5PR8DrDhPB24tFgwpdKME= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/klauspost/compress v1.7.1-0.20190613161414-0b31f265a57b h1:LHpBANNM/cw1PAXJtKV9dgfp6ztOKfdGXcltGmqU9aE= @@ -45,6 +47,8 @@ github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo= github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/muhammadmuzzammil1998/jsonc v0.0.0-20190902132743-e4903c4dea48 h1:BM/fjd7MfvZuyoHXLv3YlWNIuNb47PLp6EyFBL1KIMg= +github.com/muhammadmuzzammil1998/jsonc v0.0.0-20190902132743-e4903c4dea48/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= -- cgit v1.2.3