1// Copyright 2015 Light Code Labs, LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package caddyfile 16 17import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "sort" 22 "strconv" 23 "strings" 24) 25 26const filename = "Caddyfile" 27 28// ToJSON converts caddyfile to its JSON representation. 29func ToJSON(caddyfile []byte) ([]byte, error) { 30 var j EncodedCaddyfile 31 32 serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil) 33 if err != nil { 34 return nil, err 35 } 36 37 for _, sb := range serverBlocks { 38 block := EncodedServerBlock{ 39 Keys: sb.Keys, 40 Body: [][]interface{}{}, 41 } 42 43 // Extract directives deterministically by sorting them 44 var directives = make([]string, len(sb.Tokens)) 45 for dir := range sb.Tokens { 46 directives = append(directives, dir) 47 } 48 sort.Strings(directives) 49 50 // Convert each directive's tokens into our JSON structure 51 for _, dir := range directives { 52 disp := NewDispenserTokens(filename, sb.Tokens[dir]) 53 for disp.Next() { 54 block.Body = append(block.Body, constructLine(&disp)) 55 } 56 } 57 58 // tack this block onto the end of the list 59 j = append(j, block) 60 } 61 62 result, err := json.Marshal(j) 63 if err != nil { 64 return nil, err 65 } 66 67 return result, nil 68} 69 70// constructLine transforms tokens into a JSON-encodable structure; 71// but only one line at a time, to be used at the top-level of 72// a server block only (where the first token on each line is a 73// directive) - not to be used at any other nesting level. 74func constructLine(d *Dispenser) []interface{} { 75 var args []interface{} 76 77 args = append(args, d.Val()) 78 79 for d.NextArg() { 80 if d.Val() == "{" { 81 args = append(args, constructBlock(d)) 82 continue 83 } 84 args = append(args, d.Val()) 85 } 86 87 return args 88} 89 90// constructBlock recursively processes tokens into a 91// JSON-encodable structure. To be used in a directive's 92// block. Goes to end of block. 93func constructBlock(d *Dispenser) [][]interface{} { 94 block := [][]interface{}{} 95 96 for d.Next() { 97 if d.Val() == "}" { 98 break 99 } 100 block = append(block, constructLine(d)) 101 } 102 103 return block 104} 105 106// FromJSON converts JSON-encoded jsonBytes to Caddyfile text 107func FromJSON(jsonBytes []byte) ([]byte, error) { 108 var j EncodedCaddyfile 109 var result string 110 111 err := json.Unmarshal(jsonBytes, &j) 112 if err != nil { 113 return nil, err 114 } 115 116 for sbPos, sb := range j { 117 if sbPos > 0 { 118 result += "\n\n" 119 } 120 for i, key := range sb.Keys { 121 if i > 0 { 122 result += ", " 123 } 124 //result += standardizeScheme(key) 125 result += key 126 } 127 result += jsonToText(sb.Body, 1) 128 } 129 130 return []byte(result), nil 131} 132 133// jsonToText recursively transforms a scope of JSON into plain 134// Caddyfile text. 135func jsonToText(scope interface{}, depth int) string { 136 var result string 137 138 switch val := scope.(type) { 139 case string: 140 if strings.ContainsAny(val, "\" \n\t\r") { 141 result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"` 142 } else { 143 result += val 144 } 145 case int: 146 result += strconv.Itoa(val) 147 case float64: 148 result += fmt.Sprintf("%v", val) 149 case bool: 150 result += fmt.Sprintf("%t", val) 151 case [][]interface{}: 152 result += " {\n" 153 for _, arg := range val { 154 result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n" 155 } 156 result += strings.Repeat("\t", depth-1) + "}" 157 case []interface{}: 158 for i, v := range val { 159 if block, ok := v.([]interface{}); ok { 160 result += "{\n" 161 for _, arg := range block { 162 result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n" 163 } 164 result += strings.Repeat("\t", depth-1) + "}" 165 continue 166 } 167 result += jsonToText(v, depth) 168 if i < len(val)-1 { 169 result += " " 170 } 171 } 172 } 173 174 return result 175} 176 177// TODO: Will this function come in handy somewhere else? 178/* 179// standardizeScheme turns an address like host:https into https://host, 180// or "host:" into "host". 181func standardizeScheme(addr string) string { 182 if hostname, port, err := net.SplitHostPort(addr); err == nil { 183 if port == "http" || port == "https" { 184 addr = port + "://" + hostname 185 } 186 } 187 return strings.TrimSuffix(addr, ":") 188} 189*/ 190 191// EncodedCaddyfile encapsulates a slice of EncodedServerBlocks. 192type EncodedCaddyfile []EncodedServerBlock 193 194// EncodedServerBlock represents a server block ripe for encoding. 195type EncodedServerBlock struct { 196 Keys []string `json:"keys"` 197 Body [][]interface{} `json:"body"` 198} 199