1exports.parse = exports.decode = decode 2 3exports.stringify = exports.encode = encode 4 5exports.safe = safe 6exports.unsafe = unsafe 7 8var eol = typeof process !== 'undefined' && 9 process.platform === 'win32' ? '\r\n' : '\n' 10 11function encode (obj, opt) { 12 var children = [] 13 var out = '' 14 15 if (typeof opt === 'string') { 16 opt = { 17 section: opt, 18 whitespace: false, 19 } 20 } else { 21 opt = opt || {} 22 opt.whitespace = opt.whitespace === true 23 } 24 25 var separator = opt.whitespace ? ' = ' : '=' 26 27 Object.keys(obj).forEach(function (k, _, __) { 28 var val = obj[k] 29 if (val && Array.isArray(val)) { 30 val.forEach(function (item) { 31 out += safe(k + '[]') + separator + safe(item) + '\n' 32 }) 33 } else if (val && typeof val === 'object') 34 children.push(k) 35 else 36 out += safe(k) + separator + safe(val) + eol 37 }) 38 39 if (opt.section && out.length) 40 out = '[' + safe(opt.section) + ']' + eol + out 41 42 children.forEach(function (k, _, __) { 43 var nk = dotSplit(k).join('\\.') 44 var section = (opt.section ? opt.section + '.' : '') + nk 45 var child = encode(obj[k], { 46 section: section, 47 whitespace: opt.whitespace, 48 }) 49 if (out.length && child.length) 50 out += eol 51 52 out += child 53 }) 54 55 return out 56} 57 58function dotSplit (str) { 59 return str.replace(/\1/g, '\u0002LITERAL\\1LITERAL\u0002') 60 .replace(/\\\./g, '\u0001') 61 .split(/\./).map(function (part) { 62 return part.replace(/\1/g, '\\.') 63 .replace(/\2LITERAL\\1LITERAL\2/g, '\u0001') 64 }) 65} 66 67function decode (str) { 68 var out = {} 69 var p = out 70 var section = null 71 // section |key = value 72 var re = /^\[([^\]]*)\]$|^([^=]+)(=(.*))?$/i 73 var lines = str.split(/[\r\n]+/g) 74 75 lines.forEach(function (line, _, __) { 76 if (!line || line.match(/^\s*[;#]/)) 77 return 78 var match = line.match(re) 79 if (!match) 80 return 81 if (match[1] !== undefined) { 82 section = unsafe(match[1]) 83 if (section === '__proto__') { 84 // not allowed 85 // keep parsing the section, but don't attach it. 86 p = {} 87 return 88 } 89 p = out[section] = out[section] || {} 90 return 91 } 92 var key = unsafe(match[2]) 93 if (key === '__proto__') 94 return 95 var value = match[3] ? unsafe(match[4]) : true 96 switch (value) { 97 case 'true': 98 case 'false': 99 case 'null': value = JSON.parse(value) 100 } 101 102 // Convert keys with '[]' suffix to an array 103 if (key.length > 2 && key.slice(-2) === '[]') { 104 key = key.substring(0, key.length - 2) 105 if (key === '__proto__') 106 return 107 if (!p[key]) 108 p[key] = [] 109 else if (!Array.isArray(p[key])) 110 p[key] = [p[key]] 111 } 112 113 // safeguard against resetting a previously defined 114 // array by accidentally forgetting the brackets 115 if (Array.isArray(p[key])) 116 p[key].push(value) 117 else 118 p[key] = value 119 }) 120 121 // {a:{y:1},"a.b":{x:2}} --> {a:{y:1,b:{x:2}}} 122 // use a filter to return the keys that have to be deleted. 123 Object.keys(out).filter(function (k, _, __) { 124 if (!out[k] || 125 typeof out[k] !== 'object' || 126 Array.isArray(out[k])) 127 return false 128 129 // see if the parent section is also an object. 130 // if so, add it to that, and mark this one for deletion 131 var parts = dotSplit(k) 132 var p = out 133 var l = parts.pop() 134 var nl = l.replace(/\\\./g, '.') 135 parts.forEach(function (part, _, __) { 136 if (part === '__proto__') 137 return 138 if (!p[part] || typeof p[part] !== 'object') 139 p[part] = {} 140 p = p[part] 141 }) 142 if (p === out && nl === l) 143 return false 144 145 p[nl] = out[k] 146 return true 147 }).forEach(function (del, _, __) { 148 delete out[del] 149 }) 150 151 return out 152} 153 154function isQuoted (val) { 155 return (val.charAt(0) === '"' && val.slice(-1) === '"') || 156 (val.charAt(0) === "'" && val.slice(-1) === "'") 157} 158 159function safe (val) { 160 return (typeof val !== 'string' || 161 val.match(/[=\r\n]/) || 162 val.match(/^\[/) || 163 (val.length > 1 && 164 isQuoted(val)) || 165 val !== val.trim()) 166 ? JSON.stringify(val) 167 : val.replace(/;/g, '\\;').replace(/#/g, '\\#') 168} 169 170function unsafe (val, doUnesc) { 171 val = (val || '').trim() 172 if (isQuoted(val)) { 173 // remove the single quotes before calling JSON.parse 174 if (val.charAt(0) === "'") 175 val = val.substr(1, val.length - 2) 176 177 try { 178 val = JSON.parse(val) 179 } catch (_) {} 180 } else { 181 // walk the val to find the first not-escaped ; character 182 var esc = false 183 var unesc = '' 184 for (var i = 0, l = val.length; i < l; i++) { 185 var c = val.charAt(i) 186 if (esc) { 187 if ('\\;#'.indexOf(c) !== -1) 188 unesc += c 189 else 190 unesc += '\\' + c 191 192 esc = false 193 } else if (';#'.indexOf(c) !== -1) 194 break 195 else if (c === '\\') 196 esc = true 197 else 198 unesc += c 199 } 200 if (esc) 201 unesc += '\\' 202 203 return unesc.trim() 204 } 205 return val 206} 207