UI for Zipcoin Blue

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. 'use strict'
  2. const glob = require('./lib/glob')
  3. const RE_SPACE = /\s/
  4. const RE_LINE_BREAK = /\r|\n/
  5. const RE_SECTION_DIRECTIVE = /^(Host|Match)$/i
  6. const RE_QUOTED = /^(")(.*)\1$/
  7. const DIRECTIVE = 1
  8. const COMMENT = 2
  9. class SSHConfig extends Array {
  10. /**
  11. * Query ssh config by host.
  12. *
  13. * @return {Object} The applied options of current Host
  14. */
  15. compute(host) {
  16. let obj = {}
  17. let setProperty = (name, value) => {
  18. if (name === 'IdentityFile') {
  19. let list = obj[name] || (obj[name] = [])
  20. list.push(value)
  21. }
  22. else if (obj[name] === undefined) {
  23. obj[name] = value
  24. }
  25. }
  26. for (let i = 0; i < this.length; i++) {
  27. let line = this[i]
  28. if (line.type !== DIRECTIVE) {
  29. continue
  30. }
  31. else if (line.param === 'Host') {
  32. if (glob(line.value, host)) {
  33. setProperty(line.param, line.value)
  34. line.config
  35. .filter(line => line.type === DIRECTIVE)
  36. .forEach(line => setProperty(line.param, line.value))
  37. }
  38. }
  39. else if (line.param === 'Match') {
  40. // TODO
  41. }
  42. else {
  43. setProperty(line.param, line.value)
  44. }
  45. }
  46. return obj
  47. }
  48. /**
  49. * find section by Host or Match
  50. */
  51. find(opts) {
  52. let result = this.constructor.find(this, opts)
  53. return result ? result[0] : null
  54. }
  55. /**
  56. * Remove section
  57. */
  58. remove(opts) {
  59. let result = this.constructor.find(this, opts)
  60. if (result) {
  61. return this.splice(result[1], 1)
  62. }
  63. }
  64. /**
  65. * toString()
  66. */
  67. toString() {
  68. return this.constructor.stringify(this)
  69. }
  70. /**
  71. * Append new section to existing ssh config.
  72. */
  73. append(opts) {
  74. let config = this
  75. let configWas = this
  76. let indent = ' '
  77. outer:
  78. for (const line of this) {
  79. if (RE_SECTION_DIRECTIVE.test(line.param)) {
  80. for (const subline of line.config) {
  81. if (subline.before) {
  82. indent = subline.before
  83. break outer
  84. }
  85. }
  86. }
  87. }
  88. for (const param in opts) {
  89. const line = {
  90. type: DIRECTIVE,
  91. param,
  92. separator: ' ',
  93. value: opts[param],
  94. before: '',
  95. after: '\n'
  96. }
  97. if (RE_SECTION_DIRECTIVE.test(param)) {
  98. config = configWas
  99. config.push(line)
  100. config = line.config = new SSHConfig()
  101. } else {
  102. line.before = indent
  103. config.push(line)
  104. }
  105. }
  106. config[config.length - 1].after += '\n'
  107. return configWas
  108. }
  109. static find(config, opts) {
  110. if (!(opts && ('Host' in opts || 'Match' in opts))) {
  111. throw new Error('Can only find by Host or Match')
  112. }
  113. for (let i = 0; i < config.length; i++) {
  114. const line = config[i]
  115. if (line.type === DIRECTIVE &&
  116. RE_SECTION_DIRECTIVE.test(line.param) &&
  117. line.param in opts &&
  118. opts[line.param] === line.value) {
  119. return [line, i]
  120. }
  121. }
  122. return null
  123. }
  124. /**
  125. * Stringify structured object into ssh config text
  126. */
  127. static stringify(config) {
  128. let str = ''
  129. let format = line => {
  130. str += line.before
  131. if (line.type === COMMENT) {
  132. str += line.content
  133. }
  134. else if (line.type === DIRECTIVE) {
  135. str += line.quoted || (line.param == 'IdentityFile' && RE_SPACE.test(line.value))
  136. ? `${line.param}${line.separator}"${line.value}"`
  137. : `${line.param}${line.separator}${line.value}`
  138. }
  139. str += line.after
  140. if (line.config) {
  141. line.config.forEach(format)
  142. }
  143. }
  144. config.forEach(format)
  145. return str
  146. }
  147. static get DIRECTIVE() {
  148. return DIRECTIVE
  149. }
  150. static get COMMENT() {
  151. return COMMENT
  152. }
  153. /**
  154. * Parse ssh config text into structured object.
  155. */
  156. static parse(str) {
  157. let i = 0
  158. let chr = next()
  159. let config = new SSHConfig()
  160. let configWas = config
  161. function next() {
  162. return str[i++]
  163. }
  164. function space() {
  165. let spaces = ''
  166. while (RE_SPACE.test(chr)) {
  167. spaces += chr
  168. chr = next()
  169. }
  170. return spaces
  171. }
  172. function linebreak() {
  173. let breaks = ''
  174. while (RE_LINE_BREAK.test(chr)) {
  175. breaks += chr
  176. chr = next()
  177. }
  178. return breaks
  179. }
  180. function option() {
  181. let opt = ''
  182. while (chr && chr !== ' ' && chr !== '=') {
  183. opt += chr
  184. chr = next()
  185. }
  186. return opt
  187. }
  188. function separator() {
  189. let sep = space()
  190. if (chr === '=') {
  191. sep += chr
  192. chr = next()
  193. }
  194. return sep + space()
  195. }
  196. function value() {
  197. let val = ''
  198. while (chr && !RE_LINE_BREAK.test(chr)) {
  199. val += chr
  200. chr = next()
  201. }
  202. return val.trim()
  203. }
  204. function comment() {
  205. let type = COMMENT
  206. let content = ''
  207. while (chr && !RE_LINE_BREAK.test(chr)) {
  208. content += chr
  209. chr = next()
  210. }
  211. return { type, content }
  212. }
  213. function directive() {
  214. let type = DIRECTIVE
  215. return {
  216. type,
  217. param: option(),
  218. separator: separator(),
  219. value: value()
  220. }
  221. }
  222. function line() {
  223. let before = space()
  224. let node = chr === '#' ? comment() : directive()
  225. let after = linebreak()
  226. node.before = before
  227. node.after = after
  228. if (RE_QUOTED.test(node.value)) {
  229. node.value = node.value.replace(RE_QUOTED, '$2')
  230. node.quoted = true
  231. }
  232. return node
  233. }
  234. while (chr) {
  235. let node = line()
  236. if (node.type === DIRECTIVE && RE_SECTION_DIRECTIVE.test(node.param)) {
  237. config = configWas
  238. config.push(node)
  239. config = node.config = new SSHConfig()
  240. }
  241. else {
  242. config.push(node)
  243. }
  244. }
  245. return configWas
  246. }
  247. }
  248. module.exports = SSHConfig