json.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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. package caddyfile
  15. import (
  16. "bytes"
  17. "encoding/json"
  18. "fmt"
  19. "sort"
  20. "strconv"
  21. "strings"
  22. )
  23. const filename = "Caddyfile"
  24. // ToJSON converts caddyfile to its JSON representation.
  25. func ToJSON(caddyfile []byte) ([]byte, error) {
  26. var j EncodedCaddyfile
  27. serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil)
  28. if err != nil {
  29. return nil, err
  30. }
  31. for _, sb := range serverBlocks {
  32. block := EncodedServerBlock{
  33. Keys: sb.Keys,
  34. Body: [][]interface{}{},
  35. }
  36. // Extract directives deterministically by sorting them
  37. var directives = make([]string, len(sb.Tokens))
  38. for dir := range sb.Tokens {
  39. directives = append(directives, dir)
  40. }
  41. sort.Strings(directives)
  42. // Convert each directive's tokens into our JSON structure
  43. for _, dir := range directives {
  44. disp := NewDispenserTokens(filename, sb.Tokens[dir])
  45. for disp.Next() {
  46. block.Body = append(block.Body, constructLine(&disp))
  47. }
  48. }
  49. // tack this block onto the end of the list
  50. j = append(j, block)
  51. }
  52. result, err := json.Marshal(j)
  53. if err != nil {
  54. return nil, err
  55. }
  56. return result, nil
  57. }
  58. // constructLine transforms tokens into a JSON-encodable structure;
  59. // but only one line at a time, to be used at the top-level of
  60. // a server block only (where the first token on each line is a
  61. // directive) - not to be used at any other nesting level.
  62. func constructLine(d *Dispenser) []interface{} {
  63. var args []interface{}
  64. args = append(args, d.Val())
  65. for d.NextArg() {
  66. if d.Val() == "{" {
  67. args = append(args, constructBlock(d))
  68. continue
  69. }
  70. args = append(args, d.Val())
  71. }
  72. return args
  73. }
  74. // constructBlock recursively processes tokens into a
  75. // JSON-encodable structure. To be used in a directive's
  76. // block. Goes to end of block.
  77. func constructBlock(d *Dispenser) [][]interface{} {
  78. block := [][]interface{}{}
  79. for d.Next() {
  80. if d.Val() == "}" {
  81. break
  82. }
  83. block = append(block, constructLine(d))
  84. }
  85. return block
  86. }
  87. // FromJSON converts JSON-encoded jsonBytes to Caddyfile text
  88. func FromJSON(jsonBytes []byte) ([]byte, error) {
  89. var j EncodedCaddyfile
  90. var result string
  91. err := json.Unmarshal(jsonBytes, &j)
  92. if err != nil {
  93. return nil, err
  94. }
  95. for sbPos, sb := range j {
  96. if sbPos > 0 {
  97. result += "\n\n"
  98. }
  99. for i, key := range sb.Keys {
  100. if i > 0 {
  101. result += ", "
  102. }
  103. //result += standardizeScheme(key)
  104. result += key
  105. }
  106. result += jsonToText(sb.Body, 1)
  107. }
  108. return []byte(result), nil
  109. }
  110. // jsonToText recursively transforms a scope of JSON into plain
  111. // Caddyfile text.
  112. func jsonToText(scope interface{}, depth int) string {
  113. var result string
  114. switch val := scope.(type) {
  115. case string:
  116. if strings.ContainsAny(val, "\" \n\t\r") {
  117. result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"`
  118. } else {
  119. result += val
  120. }
  121. case int:
  122. result += strconv.Itoa(val)
  123. case float64:
  124. result += fmt.Sprintf("%v", val)
  125. case bool:
  126. result += fmt.Sprintf("%t", val)
  127. case [][]interface{}:
  128. result += " {\n"
  129. for _, arg := range val {
  130. result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
  131. }
  132. result += strings.Repeat("\t", depth-1) + "}"
  133. case []interface{}:
  134. for i, v := range val {
  135. if block, ok := v.([]interface{}); ok {
  136. result += "{\n"
  137. for _, arg := range block {
  138. result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
  139. }
  140. result += strings.Repeat("\t", depth-1) + "}"
  141. continue
  142. }
  143. result += jsonToText(v, depth)
  144. if i < len(val)-1 {
  145. result += " "
  146. }
  147. }
  148. }
  149. return result
  150. }
  151. // TODO: Will this function come in handy somewhere else?
  152. /*
  153. // standardizeScheme turns an address like host:https into https://host,
  154. // or "host:" into "host".
  155. func standardizeScheme(addr string) string {
  156. if hostname, port, err := net.SplitHostPort(addr); err == nil {
  157. if port == "http" || port == "https" {
  158. addr = port + "://" + hostname
  159. }
  160. }
  161. return strings.TrimSuffix(addr, ":")
  162. }
  163. */
  164. // EncodedCaddyfile encapsulates a slice of EncodedServerBlocks.
  165. type EncodedCaddyfile []EncodedServerBlock
  166. // EncodedServerBlock represents a server block ripe for encoding.
  167. type EncodedServerBlock struct {
  168. Keys []string `json:"keys"`
  169. Body [][]interface{} `json:"body"`
  170. }