mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 18:47:20 -04:00 
			
		
		
		
	I am not a lawyer, but according to the appendix of the license, these boilerplate notices should be included with every source file.
		
			
				
	
	
		
			199 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Light Code Labs, LLC
 | |
| //
 | |
| // 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 caddyfile
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| const filename = "Caddyfile"
 | |
| 
 | |
| // ToJSON converts caddyfile to its JSON representation.
 | |
| func ToJSON(caddyfile []byte) ([]byte, error) {
 | |
| 	var j EncodedCaddyfile
 | |
| 
 | |
| 	serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, sb := range serverBlocks {
 | |
| 		block := EncodedServerBlock{
 | |
| 			Keys: sb.Keys,
 | |
| 			Body: [][]interface{}{},
 | |
| 		}
 | |
| 
 | |
| 		// 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 := 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 *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 *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 EncodedCaddyfile
 | |
| 	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, key := range sb.Keys {
 | |
| 			if i > 0 {
 | |
| 				result += ", "
 | |
| 			}
 | |
| 			//result += standardizeScheme(key)
 | |
| 			result += key
 | |
| 		}
 | |
| 		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
 | |
| }
 | |
| 
 | |
| // TODO: Will this function come in handy somewhere else?
 | |
| /*
 | |
| // standardizeScheme turns an address like host:https into https://host,
 | |
| // or "host:" into "host".
 | |
| func standardizeScheme(addr string) string {
 | |
| 	if hostname, port, err := net.SplitHostPort(addr); err == nil {
 | |
| 		if port == "http" || port == "https" {
 | |
| 			addr = port + "://" + hostname
 | |
| 		}
 | |
| 	}
 | |
| 	return strings.TrimSuffix(addr, ":")
 | |
| }
 | |
| */
 | |
| 
 | |
| // EncodedCaddyfile encapsulates a slice of EncodedServerBlocks.
 | |
| type EncodedCaddyfile []EncodedServerBlock
 | |
| 
 | |
| // EncodedServerBlock represents a server block ripe for encoding.
 | |
| type EncodedServerBlock struct {
 | |
| 	Keys []string        `json:"keys"`
 | |
| 	Body [][]interface{} `json:"body"`
 | |
| }
 |