mongodb-core
Advanced tools
Comparing version 3.1.5 to 3.1.6
@@ -5,2 +5,13 @@ # Change Log | ||
<a name="3.1.6"></a> | ||
## [3.1.6](https://github.com/mongodb-js/mongodb-core/compare/v3.1.4...v3.1.6) (2018-10-09) | ||
### Bug Fixes | ||
* **srv-parsing:** ensure parse options are propogated to txt lookup ([923ceb0](https://github.com/mongodb-js/mongodb-core/commit/923ceb0)) | ||
* **uri-parser:** add exemption list for number coercion in options ([82896ea](https://github.com/mongodb-js/mongodb-core/commit/82896ea)) | ||
<a name="3.1.5"></a> | ||
@@ -7,0 +18,0 @@ ## [3.1.5](https://github.com/mongodb-js/mongodb-core/compare/v3.1.4...v3.1.5) (2018-09-15) |
@@ -283,3 +283,3 @@ 'use strict'; | ||
// Handle a message once it is recieved | ||
// Handle a message once it is received | ||
var emitMessageHandler = function(self, message) { | ||
@@ -286,0 +286,0 @@ var msgHeader = parseHeader(message); |
@@ -127,3 +127,3 @@ 'use strict'; | ||
* | ||
* @param {(MongoError|Error})} error | ||
* @param {MongoError|Error} error | ||
*/ | ||
@@ -130,0 +130,0 @@ function isRetryableError(error) { |
@@ -89,3 +89,3 @@ 'use strict'; | ||
serverDescriptions, | ||
options.replicaset, | ||
options.replicaSet, | ||
null, | ||
@@ -388,4 +388,4 @@ null, | ||
function topologyTypeFromSeedlist(seedlist, options) { | ||
if (seedlist.length === 1 && !options.replicaset) return TopologyType.Single; | ||
if (options.replicaset) return TopologyType.ReplicaSetNoPrimary; | ||
if (seedlist.length === 1 && !options.replicaSet) return TopologyType.Single; | ||
if (options.replicaSet) return TopologyType.ReplicaSetNoPrimary; | ||
return TopologyType.Unknown; | ||
@@ -392,0 +392,0 @@ } |
@@ -6,2 +6,3 @@ 'use strict'; | ||
const MongoParseError = require('./error').MongoParseError; | ||
const ReadPreference = require('./topologies/read_preference'); | ||
@@ -127,3 +128,3 @@ /** | ||
parseConnectionString(connectionString, callback); | ||
parseConnectionString(connectionString, options, callback); | ||
}); | ||
@@ -136,6 +137,7 @@ }); | ||
* | ||
* @param {string} key The key for the parsed value | ||
* @param {Array|String} value The value to parse | ||
* @return {Array|Object|String} The parsed value | ||
*/ | ||
function parseQueryStringItemValue(value) { | ||
function parseQueryStringItemValue(key, value) { | ||
if (Array.isArray(value)) { | ||
@@ -148,3 +150,3 @@ // deduplicate and simplify arrays | ||
const parts = pair.split(':'); | ||
result[parts[0]] = parseQueryStringItemValue(parts[1]); | ||
result[parts[0]] = parseQueryStringItemValue(key, parts[1]); | ||
return result; | ||
@@ -154,7 +156,7 @@ }, {}); | ||
value = value.split(',').map(v => { | ||
return parseQueryStringItemValue(v); | ||
return parseQueryStringItemValue(key, v); | ||
}); | ||
} else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') { | ||
value = value.toLowerCase() === 'true'; | ||
} else if (!Number.isNaN(value)) { | ||
} else if (!Number.isNaN(value) && !STRING_OPTIONS.has(key)) { | ||
const numericValue = parseFloat(value); | ||
@@ -169,9 +171,217 @@ if (!Number.isNaN(numericValue)) { | ||
// Options that are known boolean types | ||
const BOOLEAN_OPTIONS = new Set([ | ||
'slaveok', | ||
'slave_ok', | ||
'sslvalidate', | ||
'fsync', | ||
'safe', | ||
'retrywrites', | ||
'j' | ||
]); | ||
// Known string options, only used to bypass Number coercion in `parseQueryStringItemValue` | ||
const STRING_OPTIONS = new Set(['authsource']); | ||
// Supported text representations of auth mechanisms | ||
// NOTE: this list exists in native already, if it is merged here we should deduplicate | ||
const AUTH_MECHANISMS = new Set([ | ||
'GSSAPI', | ||
'MONGODB-X509', | ||
'MONGODB-CR', | ||
'DEFAULT', | ||
'SCRAM-SHA-1', | ||
'SCRAM-SHA-256', | ||
'PLAIN' | ||
]); | ||
// Lookup table used to translate normalized (lower-cased) forms of connection string | ||
// options to their expected camelCase version | ||
const CASE_TRANSLATION = { | ||
replicaset: 'replicaSet', | ||
connecttimeoutms: 'connectTimeoutMS', | ||
sockettimeoutms: 'socketTimeoutMS', | ||
maxpoolsize: 'maxPoolSize', | ||
minpoolsize: 'minPoolSize', | ||
maxidletimems: 'maxIdleTimeMS', | ||
waitqueuemultiple: 'waitQueueMultiple', | ||
waitqueuetimeoutms: 'waitQueueTimeoutMS', | ||
wtimeoutms: 'wtimeoutMS', | ||
readconcern: 'readConcern', | ||
readconcernlevel: 'readConcernLevel', | ||
readpreference: 'readPreference', | ||
maxstalenessseconds: 'maxStalenessSeconds', | ||
readpreferencetags: 'readPreferenceTags', | ||
authsource: 'authSource', | ||
authmechanism: 'authMechanism', | ||
authmechanismproperties: 'authMechanismProperties', | ||
gssapiservicename: 'gssapiServiceName', | ||
localthresholdms: 'localThresholdMS', | ||
serverselectiontimeoutms: 'serverSelectionTimeoutMS', | ||
serverselectiontryonce: 'serverSelectionTryOnce', | ||
heartbeatfrequencyms: 'heartbeatFrequencyMS', | ||
appname: 'appName', | ||
retrywrites: 'retryWrites', | ||
uuidrepresentation: 'uuidRepresentation', | ||
zlibcompressionlevel: 'zlibCompressionLevel' | ||
}; | ||
/** | ||
* Sets the value for `key`, allowing for any required translation | ||
* | ||
* @param {object} obj The object to set the key on | ||
* @param {string} key The key to set the value for | ||
* @param {*} value The value to set | ||
* @param {object} options The options used for option parsing | ||
*/ | ||
function applyConnectionStringOption(obj, key, value, options) { | ||
// simple key translation | ||
if (key === 'journal') { | ||
key = 'j'; | ||
} else if (key === 'wtimeoutms') { | ||
key = 'wtimeout'; | ||
} | ||
// more complicated translation | ||
if (BOOLEAN_OPTIONS.has(key)) { | ||
value = value === 'true' || value === true; | ||
} else if (key === 'appname') { | ||
value = decodeURIComponent(value); | ||
} else if (key === 'readconcernlevel') { | ||
key = 'readconcern'; | ||
value = { level: value }; | ||
} | ||
// simple validation | ||
if (key === 'compressors') { | ||
value = Array.isArray(value) ? value : [value]; | ||
if (!value.every(c => c === 'snappy' || c === 'zlib')) { | ||
throw new MongoParseError( | ||
'Value for `compressors` must be at least one of: `snappy`, `zlib`' | ||
); | ||
} | ||
} | ||
if (key === 'authmechanism' && !AUTH_MECHANISMS.has(value)) { | ||
throw new MongoParseError( | ||
'Value for `authMechanism` must be one of: `DEFAULT`, `GSSAPI`, `PLAIN`, `MONGODB-X509`, `SCRAM-SHA-1`, `SCRAM-SHA-256`' | ||
); | ||
} | ||
if (key === 'readpreference' && !ReadPreference.isValid(value)) { | ||
throw new MongoParseError( | ||
'Value for `readPreference` must be one of: `primary`, `primaryPreferred`, `secondary`, `secondaryPreferred`, `nearest`' | ||
); | ||
} | ||
if (key === 'zlibcompressionlevel' && (value < -1 || value > 9)) { | ||
throw new MongoParseError('zlibCompressionLevel must be an integer between -1 and 9'); | ||
} | ||
// special cases | ||
if (key === 'compressors' || key === 'zlibcompressionlevel') { | ||
obj.compression = obj.compression || {}; | ||
obj = obj.compression; | ||
} | ||
if (key === 'authmechanismproperties') { | ||
if (typeof value.SERVICE_NAME === 'string') obj.gssapiServiceName = value.SERVICE_NAME; | ||
if (typeof value.SERVICE_REALM === 'string') obj.gssapiServiceRealm = value.SERVICE_REALM; | ||
if (typeof value.CANONICALIZE_HOST_NAME !== 'undefined') { | ||
obj.gssapiCanonicalizeHostName = value.CANONICALIZE_HOST_NAME; | ||
} | ||
} | ||
// set the actual value | ||
if (options.caseTranslate && CASE_TRANSLATION[key]) { | ||
obj[CASE_TRANSLATION[key]] = value; | ||
return; | ||
} | ||
obj[key] = value; | ||
} | ||
const USERNAME_REQUIRED_MECHANISMS = new Set([ | ||
'GSSAPI', | ||
'MONGODB-CR', | ||
'PLAIN', | ||
'SCRAM-SHA-1', | ||
'SCRAM-SHA-256' | ||
]); | ||
/** | ||
* Modifies the parsed connection string object taking into account expectations we | ||
* have for authentication-related options. | ||
* | ||
* @param {object} parsed The parsed connection string result | ||
* @return The parsed connection string result possibly modified for auth expectations | ||
*/ | ||
function applyAuthExpectations(parsed) { | ||
if (parsed.options == null) { | ||
return; | ||
} | ||
const options = parsed.options; | ||
const authSource = options.authsource || options.authSource; | ||
if (authSource != null) { | ||
parsed.auth = Object.assign({}, parsed.auth, { db: authSource }); | ||
} | ||
const authMechanism = options.authmechanism || options.authMechanism; | ||
if (authMechanism != null) { | ||
if ( | ||
USERNAME_REQUIRED_MECHANISMS.has(authMechanism) && | ||
(!parsed.auth || parsed.auth.username == null) | ||
) { | ||
throw new MongoParseError(`Username required for mechanism \`${authMechanism}\``); | ||
} | ||
if (authMechanism === 'GSSAPI') { | ||
if (authSource != null && authSource !== '$external') { | ||
throw new MongoParseError( | ||
`Invalid source \`${authSource}\` for mechanism \`${authMechanism}\` specified.` | ||
); | ||
} | ||
parsed.auth = Object.assign({}, parsed.auth, { db: '$external' }); | ||
} | ||
if (authMechanism === 'MONGODB-X509') { | ||
if (parsed.auth && parsed.auth.password != null) { | ||
throw new MongoParseError(`Password not allowed for mechanism \`${authMechanism}\``); | ||
} | ||
if (authSource != null && authSource !== '$external') { | ||
throw new MongoParseError( | ||
`Invalid source \`${authSource}\` for mechanism \`${authMechanism}\` specified.` | ||
); | ||
} | ||
parsed.auth = Object.assign({}, parsed.auth, { db: '$external' }); | ||
} | ||
if (authMechanism === 'PLAIN') { | ||
if (parsed.auth && parsed.auth.db == null) { | ||
parsed.auth = Object.assign({}, parsed.auth, { db: '$external' }); | ||
} | ||
} | ||
} | ||
// default to `admin` if nothing else was resolved | ||
if (parsed.auth && parsed.auth.db == null) { | ||
parsed.auth = Object.assign({}, parsed.auth, { db: 'admin' }); | ||
} | ||
return parsed; | ||
} | ||
/** | ||
* Parses a query string according the connection string spec. | ||
* | ||
* @param {String} query The query string to parse | ||
* @param {object} [options] The options used for options parsing | ||
* @return {Object|Error} The parsed query string as an object, or an error if one was encountered | ||
*/ | ||
function parseQueryString(query) { | ||
function parseQueryString(query, options) { | ||
const result = {}; | ||
@@ -183,6 +393,8 @@ let parsedQueryString = qs.parse(query); | ||
if (value === '' || value == null) { | ||
return new MongoParseError('Incomplete key value pair for option'); | ||
throw new MongoParseError('Incomplete key value pair for option'); | ||
} | ||
result[key.toLowerCase()] = parseQueryStringItemValue(value); | ||
const normalizedKey = key.toLowerCase(); | ||
const parsedValue = parseQueryStringItemValue(normalizedKey, value); | ||
applyConnectionStringOption(result, normalizedKey, parsedValue, options); | ||
} | ||
@@ -208,2 +420,3 @@ | ||
* @param {object} [options] Optional settings. | ||
* @param {boolean} [options.caseTranslate] Whether the parser should translate options back into camelCase after normalization | ||
* @param {parseCallback} callback | ||
@@ -213,3 +426,3 @@ */ | ||
if (typeof options === 'function') (callback = options), (options = {}); | ||
options = options || {}; | ||
options = Object.assign({}, { caseTranslate: true }, options); | ||
@@ -240,9 +453,12 @@ // Check for bad uris before we parse | ||
const query = dbAndQuery.length > 1 ? dbAndQuery[1] : null; | ||
let parsedOptions = parseQueryString(query); | ||
if (parsedOptions instanceof MongoParseError) { | ||
return callback(parsedOptions); | ||
let parsedOptions; | ||
try { | ||
parsedOptions = parseQueryString(query, options); | ||
} catch (parseError) { | ||
return callback(parseError); | ||
} | ||
parsedOptions = Object.assign({}, parsedOptions, options); | ||
const auth = { username: null, password: null, db: db && db !== '' ? qs.unescape(db) : 'admin' }; | ||
const auth = { username: null, password: null, db: db && db !== '' ? qs.unescape(db) : null }; | ||
if (cap[4].split('?')[0].indexOf('@') !== -1) { | ||
@@ -321,9 +537,17 @@ return callback(new MongoParseError('Unescaped slash in userinfo section')); | ||
callback(null, { | ||
const result = { | ||
hosts: hosts, | ||
auth: auth.db || auth.username ? auth : null, | ||
options: Object.keys(parsedOptions).length ? parsedOptions : null | ||
}); | ||
}; | ||
try { | ||
applyAuthExpectations(result); | ||
} catch (authError) { | ||
return callback(authError); | ||
} | ||
callback(null, result); | ||
} | ||
module.exports = parseConnectionString; |
{ | ||
"name": "mongodb-core", | ||
"version": "3.1.5", | ||
"version": "3.1.6", | ||
"description": "Core MongoDB driver functionality, no bells and whistles and meant for integration not end applications", | ||
@@ -44,3 +44,3 @@ "main": "index.js", | ||
"sinon": "^6.0.0", | ||
"snappy": "^6.0.1", | ||
"snappy": "^6.1.1", | ||
"standard-version": "^4.4.0" | ||
@@ -51,3 +51,3 @@ }, | ||
"mongodb-extjson": "^2.1.2", | ||
"snappy": "^6.0.1", | ||
"snappy": "^6.1.1", | ||
"bson-ext": "^2.0.0" | ||
@@ -54,0 +54,0 @@ }, |
615626
15390