| [62b2964] | 1 | 'use strict'
|
|---|
| 2 |
|
|---|
| 3 | //Parse method copied from https://github.com/brianc/node-postgres
|
|---|
| 4 | //Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com)
|
|---|
| 5 | //MIT License
|
|---|
| 6 |
|
|---|
| 7 | //parses a connection string
|
|---|
| 8 | function parse(str, options = {}) {
|
|---|
| 9 | //unix socket
|
|---|
| 10 | if (str.charAt(0) === '/') {
|
|---|
| 11 | const config = str.split(' ')
|
|---|
| 12 | return { host: config[0], database: config[1] }
|
|---|
| 13 | }
|
|---|
| 14 |
|
|---|
| 15 | // Check for empty host in URL
|
|---|
| 16 |
|
|---|
| 17 | const config = Object.create(null)
|
|---|
| 18 | let result
|
|---|
| 19 | let dummyHost = false
|
|---|
| 20 | if (/ |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str)) {
|
|---|
| 21 | // Ensure spaces are encoded as %20
|
|---|
| 22 | str = encodeURI(str).replace(/%25(\d\d)/g, '%$1')
|
|---|
| 23 | }
|
|---|
| 24 |
|
|---|
| 25 | try {
|
|---|
| 26 | try {
|
|---|
| 27 | result = new URL(str, 'postgres://base')
|
|---|
| 28 | } catch (e) {
|
|---|
| 29 | // The URL is invalid so try again with a dummy host
|
|---|
| 30 | result = new URL(str.replace('@/', '@___DUMMY___/'), 'postgres://base')
|
|---|
| 31 | dummyHost = true
|
|---|
| 32 | }
|
|---|
| 33 | } catch (err) {
|
|---|
| 34 | // Remove the input from the error message to avoid leaking sensitive information
|
|---|
| 35 | err.input && (err.input = '*****REDACTED*****')
|
|---|
| 36 | throw err
|
|---|
| 37 | }
|
|---|
| 38 |
|
|---|
| 39 | // We'd like to use Object.fromEntries() here but Node.js 10 does not support it
|
|---|
| 40 | for (const entry of result.searchParams.entries()) {
|
|---|
| 41 | config[entry[0]] = entry[1]
|
|---|
| 42 | }
|
|---|
| 43 |
|
|---|
| 44 | config.user = config.user || decodeURIComponent(result.username)
|
|---|
| 45 | config.password = config.password || decodeURIComponent(result.password)
|
|---|
| 46 |
|
|---|
| 47 | if (result.protocol == 'socket:') {
|
|---|
| 48 | config.host = decodeURI(result.pathname)
|
|---|
| 49 | config.database = result.searchParams.get('db')
|
|---|
| 50 | config.client_encoding = result.searchParams.get('encoding')
|
|---|
| 51 | return config
|
|---|
| 52 | }
|
|---|
| 53 | const hostname = dummyHost ? '' : result.hostname
|
|---|
| 54 | if (!config.host) {
|
|---|
| 55 | // Only set the host if there is no equivalent query param.
|
|---|
| 56 | config.host = decodeURIComponent(hostname)
|
|---|
| 57 | } else if (hostname && /^%2f/i.test(hostname)) {
|
|---|
| 58 | // Only prepend the hostname to the pathname if it is not a URL encoded Unix socket host.
|
|---|
| 59 | result.pathname = hostname + result.pathname
|
|---|
| 60 | }
|
|---|
| 61 | if (!config.port) {
|
|---|
| 62 | // Only set the port if there is no equivalent query param.
|
|---|
| 63 | config.port = result.port
|
|---|
| 64 | }
|
|---|
| 65 |
|
|---|
| 66 | const pathname = result.pathname.slice(1) || null
|
|---|
| 67 | config.database = pathname ? decodeURI(pathname) : null
|
|---|
| 68 |
|
|---|
| 69 | if (config.ssl === 'true' || config.ssl === '1') {
|
|---|
| 70 | config.ssl = true
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | if (config.ssl === '0') {
|
|---|
| 74 | config.ssl = false
|
|---|
| 75 | }
|
|---|
| 76 |
|
|---|
| 77 | if (config.sslcert || config.sslkey || config.sslrootcert || config.sslmode) {
|
|---|
| 78 | config.ssl = {}
|
|---|
| 79 | }
|
|---|
| 80 |
|
|---|
| 81 | // sslnegotiation=direct implies SSL is in use (libpq requires sslmode>=require),
|
|---|
| 82 | // so enable SSL if the connection string did not otherwise configure it.
|
|---|
| 83 | if (config.sslnegotiation === 'direct' && config.ssl === undefined) {
|
|---|
| 84 | config.ssl = true
|
|---|
| 85 | }
|
|---|
| 86 |
|
|---|
| 87 | // Only try to load fs if we expect to read from the disk
|
|---|
| 88 | const fs = config.sslcert || config.sslkey || config.sslrootcert ? require('fs') : null
|
|---|
| 89 |
|
|---|
| 90 | if (config.sslcert) {
|
|---|
| 91 | config.ssl.cert = fs.readFileSync(config.sslcert).toString()
|
|---|
| 92 | }
|
|---|
| 93 |
|
|---|
| 94 | if (config.sslkey) {
|
|---|
| 95 | config.ssl.key = fs.readFileSync(config.sslkey).toString()
|
|---|
| 96 | }
|
|---|
| 97 |
|
|---|
| 98 | if (config.sslrootcert) {
|
|---|
| 99 | config.ssl.ca = fs.readFileSync(config.sslrootcert).toString()
|
|---|
| 100 | }
|
|---|
| 101 |
|
|---|
| 102 | if (options.useLibpqCompat && config.uselibpqcompat) {
|
|---|
| 103 | throw new Error('Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.')
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | if (config.uselibpqcompat === 'true' || options.useLibpqCompat) {
|
|---|
| 107 | switch (config.sslmode) {
|
|---|
| 108 | case 'disable': {
|
|---|
| 109 | config.ssl = false
|
|---|
| 110 | break
|
|---|
| 111 | }
|
|---|
| 112 | case 'prefer': {
|
|---|
| 113 | config.ssl.rejectUnauthorized = false
|
|---|
| 114 | break
|
|---|
| 115 | }
|
|---|
| 116 | case 'require': {
|
|---|
| 117 | if (config.sslrootcert) {
|
|---|
| 118 | // If a root CA is specified, behavior of `sslmode=require` will be the same as that of `verify-ca`
|
|---|
| 119 | config.ssl.checkServerIdentity = function () {}
|
|---|
| 120 | } else {
|
|---|
| 121 | config.ssl.rejectUnauthorized = false
|
|---|
| 122 | }
|
|---|
| 123 | break
|
|---|
| 124 | }
|
|---|
| 125 | case 'verify-ca': {
|
|---|
| 126 | if (!config.ssl.ca) {
|
|---|
| 127 | throw new Error(
|
|---|
| 128 | 'SECURITY WARNING: Using sslmode=verify-ca requires specifying a CA with sslrootcert. If a public CA is used, verify-ca allows connections to a server that somebody else may have registered with the CA, making you vulnerable to Man-in-the-Middle attacks. Either specify a custom CA certificate with sslrootcert parameter or use sslmode=verify-full for proper security.'
|
|---|
| 129 | )
|
|---|
| 130 | }
|
|---|
| 131 | config.ssl.checkServerIdentity = function () {}
|
|---|
| 132 | break
|
|---|
| 133 | }
|
|---|
| 134 | case 'verify-full': {
|
|---|
| 135 | break
|
|---|
| 136 | }
|
|---|
| 137 | }
|
|---|
| 138 | } else {
|
|---|
| 139 | switch (config.sslmode) {
|
|---|
| 140 | case 'disable': {
|
|---|
| 141 | config.ssl = false
|
|---|
| 142 | break
|
|---|
| 143 | }
|
|---|
| 144 | case 'prefer':
|
|---|
| 145 | case 'require':
|
|---|
| 146 | case 'verify-ca':
|
|---|
| 147 | case 'verify-full': {
|
|---|
| 148 | if (config.sslmode !== 'verify-full') {
|
|---|
| 149 | deprecatedSslModeWarning(config.sslmode)
|
|---|
| 150 | }
|
|---|
| 151 | break
|
|---|
| 152 | }
|
|---|
| 153 | case 'no-verify': {
|
|---|
| 154 | config.ssl.rejectUnauthorized = false
|
|---|
| 155 | break
|
|---|
| 156 | }
|
|---|
| 157 | }
|
|---|
| 158 | }
|
|---|
| 159 |
|
|---|
| 160 | return config
|
|---|
| 161 | }
|
|---|
| 162 |
|
|---|
| 163 | // convert pg-connection-string ssl config to a ClientConfig.ConnectionOptions
|
|---|
| 164 | function toConnectionOptions(sslConfig) {
|
|---|
| 165 | const connectionOptions = Object.entries(sslConfig).reduce((c, [key, value]) => {
|
|---|
| 166 | // we explicitly check for undefined and null instead of `if (value)` because some
|
|---|
| 167 | // options accept falsy values. Example: `ssl.rejectUnauthorized = false`
|
|---|
| 168 | if (value !== undefined && value !== null) {
|
|---|
| 169 | c[key] = value
|
|---|
| 170 | }
|
|---|
| 171 |
|
|---|
| 172 | return c
|
|---|
| 173 | }, Object.create(null))
|
|---|
| 174 |
|
|---|
| 175 | return connectionOptions
|
|---|
| 176 | }
|
|---|
| 177 |
|
|---|
| 178 | // convert pg-connection-string config to a ClientConfig
|
|---|
| 179 | function toClientConfig(config) {
|
|---|
| 180 | const poolConfig = Object.entries(config).reduce((c, [key, value]) => {
|
|---|
| 181 | if (key === 'ssl') {
|
|---|
| 182 | const sslConfig = value
|
|---|
| 183 |
|
|---|
| 184 | if (typeof sslConfig === 'boolean') {
|
|---|
| 185 | c[key] = sslConfig
|
|---|
| 186 | }
|
|---|
| 187 |
|
|---|
| 188 | if (typeof sslConfig === 'object') {
|
|---|
| 189 | c[key] = toConnectionOptions(sslConfig)
|
|---|
| 190 | }
|
|---|
| 191 | } else if (value !== undefined && value !== null) {
|
|---|
| 192 | if (key === 'port') {
|
|---|
| 193 | // when port is not specified, it is converted into an empty string
|
|---|
| 194 | // we want to avoid NaN or empty string as a values in ClientConfig
|
|---|
| 195 | if (value !== '') {
|
|---|
| 196 | const v = parseInt(value, 10)
|
|---|
| 197 | if (isNaN(v)) {
|
|---|
| 198 | throw new Error(`Invalid ${key}: ${value}`)
|
|---|
| 199 | }
|
|---|
| 200 |
|
|---|
| 201 | c[key] = v
|
|---|
| 202 | }
|
|---|
| 203 | } else {
|
|---|
| 204 | c[key] = value
|
|---|
| 205 | }
|
|---|
| 206 | }
|
|---|
| 207 |
|
|---|
| 208 | return c
|
|---|
| 209 | }, Object.create(null))
|
|---|
| 210 |
|
|---|
| 211 | return poolConfig
|
|---|
| 212 | }
|
|---|
| 213 |
|
|---|
| 214 | // parses a connection string into ClientConfig
|
|---|
| 215 | function parseIntoClientConfig(str) {
|
|---|
| 216 | return toClientConfig(parse(str))
|
|---|
| 217 | }
|
|---|
| 218 |
|
|---|
| 219 | function deprecatedSslModeWarning(sslmode) {
|
|---|
| 220 | if (!deprecatedSslModeWarning.warned && typeof process !== 'undefined' && process.emitWarning) {
|
|---|
| 221 | deprecatedSslModeWarning.warned = true
|
|---|
| 222 | process.emitWarning(`SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'.
|
|---|
| 223 | In the next major version (pg-connection-string v3.0.0 and pg v9.0.0), these modes will adopt standard libpq semantics, which have weaker security guarantees.
|
|---|
| 224 |
|
|---|
| 225 | To prepare for this change:
|
|---|
| 226 | - If you want the current behavior, explicitly use 'sslmode=verify-full'
|
|---|
| 227 | - If you want libpq compatibility now, use 'uselibpqcompat=true&sslmode=${sslmode}'
|
|---|
| 228 |
|
|---|
| 229 | See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode definitions.`)
|
|---|
| 230 | }
|
|---|
| 231 | }
|
|---|
| 232 |
|
|---|
| 233 | module.exports = parse
|
|---|
| 234 |
|
|---|
| 235 | parse.parse = parse
|
|---|
| 236 | parse.toClientConfig = toClientConfig
|
|---|
| 237 | parse.parseIntoClientConfig = parseIntoClientConfig
|
|---|