aboutsummaryrefslogtreecommitdiff
path: root/core/caddyfile/json.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/caddyfile/json.go')
-rw-r--r--core/caddyfile/json.go173
1 files changed, 173 insertions, 0 deletions
diff --git a/core/caddyfile/json.go b/core/caddyfile/json.go
new file mode 100644
index 000000000..6f4e66771
--- /dev/null
+++ b/core/caddyfile/json.go
@@ -0,0 +1,173 @@
+package caddyfile
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/miekg/coredns/core/parse"
+)
+
+const filename = "Caddyfile"
+
+// ToJSON converts caddyfile to its JSON representation.
+func ToJSON(caddyfile []byte) ([]byte, error) {
+ var j Caddyfile
+
+ serverBlocks, err := parse.ServerBlocks(filename, bytes.NewReader(caddyfile), false)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, sb := range serverBlocks {
+ block := ServerBlock{Body: [][]interface{}{}}
+
+ // Fill up host list
+ for _, host := range sb.HostList() {
+ block.Hosts = append(block.Hosts, host)
+ }
+
+ // Extract directives deterministically by sorting them
+ var directives = make([]string, len(sb.Tokens))
+ for dir := range sb.Tokens {
+ directives = append(directives, dir)
+ }
+ sort.Strings(directives)
+
+ // Convert each directive's tokens into our JSON structure
+ for _, dir := range directives {
+ disp := parse.NewDispenserTokens(filename, sb.Tokens[dir])
+ for disp.Next() {
+ block.Body = append(block.Body, constructLine(&disp))
+ }
+ }
+
+ // tack this block onto the end of the list
+ j = append(j, block)
+ }
+
+ result, err := json.Marshal(j)
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
+
+// constructLine transforms tokens into a JSON-encodable structure;
+// but only one line at a time, to be used at the top-level of
+// a server block only (where the first token on each line is a
+// directive) - not to be used at any other nesting level.
+func constructLine(d *parse.Dispenser) []interface{} {
+ var args []interface{}
+
+ args = append(args, d.Val())
+
+ for d.NextArg() {
+ if d.Val() == "{" {
+ args = append(args, constructBlock(d))
+ continue
+ }
+ args = append(args, d.Val())
+ }
+
+ return args
+}
+
+// constructBlock recursively processes tokens into a
+// JSON-encodable structure. To be used in a directive's
+// block. Goes to end of block.
+func constructBlock(d *parse.Dispenser) [][]interface{} {
+ block := [][]interface{}{}
+
+ for d.Next() {
+ if d.Val() == "}" {
+ break
+ }
+ block = append(block, constructLine(d))
+ }
+
+ return block
+}
+
+// FromJSON converts JSON-encoded jsonBytes to Caddyfile text
+func FromJSON(jsonBytes []byte) ([]byte, error) {
+ var j Caddyfile
+ var result string
+
+ err := json.Unmarshal(jsonBytes, &j)
+ if err != nil {
+ return nil, err
+ }
+
+ for sbPos, sb := range j {
+ if sbPos > 0 {
+ result += "\n\n"
+ }
+ for i, host := range sb.Hosts {
+ if i > 0 {
+ result += ", "
+ }
+ result += host
+ }
+ result += jsonToText(sb.Body, 1)
+ }
+
+ return []byte(result), nil
+}
+
+// jsonToText recursively transforms a scope of JSON into plain
+// Caddyfile text.
+func jsonToText(scope interface{}, depth int) string {
+ var result string
+
+ switch val := scope.(type) {
+ case string:
+ if strings.ContainsAny(val, "\" \n\t\r") {
+ result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"`
+ } else {
+ result += val
+ }
+ case int:
+ result += strconv.Itoa(val)
+ case float64:
+ result += fmt.Sprintf("%v", val)
+ case bool:
+ result += fmt.Sprintf("%t", val)
+ case [][]interface{}:
+ result += " {\n"
+ for _, arg := range val {
+ result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
+ }
+ result += strings.Repeat("\t", depth-1) + "}"
+ case []interface{}:
+ for i, v := range val {
+ if block, ok := v.([]interface{}); ok {
+ result += "{\n"
+ for _, arg := range block {
+ result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
+ }
+ result += strings.Repeat("\t", depth-1) + "}"
+ continue
+ }
+ result += jsonToText(v, depth)
+ if i < len(val)-1 {
+ result += " "
+ }
+ }
+ }
+
+ return result
+}
+
+// Caddyfile encapsulates a slice of ServerBlocks.
+type Caddyfile []ServerBlock
+
+// ServerBlock represents a server block.
+type ServerBlock struct {
+ Hosts []string `json:"hosts"`
+ Body [][]interface{} `json:"body"`
+}