url_parser.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. "use strict";
  2. var ReadPreference = require('./read_preference');
  3. module.exports = function(url, options) {
  4. // Ensure we have a default options object if none set
  5. options = options || {};
  6. // Variables
  7. var connection_part = '';
  8. var auth_part = '';
  9. var query_string_part = '';
  10. var dbName = 'admin';
  11. // Must start with mongodb
  12. if(url.indexOf("mongodb://") != 0)
  13. throw Error("URL must be in the format mongodb://user:pass@host:port/dbname");
  14. // If we have a ? mark cut the query elements off
  15. if(url.indexOf("?") != -1) {
  16. query_string_part = url.substr(url.indexOf("?") + 1);
  17. connection_part = url.substring("mongodb://".length, url.indexOf("?"))
  18. } else {
  19. connection_part = url.substring("mongodb://".length);
  20. }
  21. // Check if we have auth params
  22. if(connection_part.indexOf("@") != -1) {
  23. auth_part = connection_part.split("@")[0];
  24. connection_part = connection_part.split("@")[1];
  25. }
  26. // Check if the connection string has a db
  27. if(connection_part.indexOf(".sock") != -1) {
  28. if(connection_part.indexOf(".sock/") != -1) {
  29. dbName = connection_part.split(".sock/")[1];
  30. connection_part = connection_part.split("/", connection_part.indexOf(".sock") + ".sock".length);
  31. }
  32. } else if(connection_part.indexOf("/") != -1) {
  33. dbName = connection_part.split("/")[1];
  34. connection_part = connection_part.split("/")[0];
  35. }
  36. // Result object
  37. var object = {};
  38. // Pick apart the authentication part of the string
  39. var authPart = auth_part || '';
  40. var auth = authPart.split(':', 2);
  41. // Decode the URI components
  42. auth[0] = decodeURIComponent(auth[0]);
  43. if(auth[1]){
  44. auth[1] = decodeURIComponent(auth[1]);
  45. }
  46. // Add auth to final object if we have 2 elements
  47. if(auth.length == 2) object.auth = {user: auth[0], password: auth[1]};
  48. // Variables used for temporary storage
  49. var hostPart;
  50. var urlOptions;
  51. var servers;
  52. var serverOptions = {socketOptions: {}};
  53. var dbOptions = {read_preference_tags: []};
  54. var replSetServersOptions = {socketOptions: {}};
  55. // Add server options to final object
  56. object.server_options = serverOptions;
  57. object.db_options = dbOptions;
  58. object.rs_options = replSetServersOptions;
  59. object.mongos_options = {};
  60. // Let's check if we are using a domain socket
  61. if(url.match(/\.sock/)) {
  62. // Split out the socket part
  63. var domainSocket = url.substring(
  64. url.indexOf("mongodb://") + "mongodb://".length
  65. , url.lastIndexOf(".sock") + ".sock".length);
  66. // Clean out any auth stuff if any
  67. if(domainSocket.indexOf("@") != -1) domainSocket = domainSocket.split("@")[1];
  68. servers = [{domain_socket: domainSocket}];
  69. } else {
  70. // Split up the db
  71. hostPart = connection_part;
  72. // Parse all server results
  73. servers = hostPart.split(',').map(function(h) {
  74. var _host, _port, ipv6match;
  75. //check if it matches [IPv6]:port, where the port number is optional
  76. if ((ipv6match = /\[([^\]]+)\](?:\:(.+))?/.exec(h))) {
  77. _host = ipv6match[1];
  78. _port = parseInt(ipv6match[2], 10) || 27017;
  79. } else {
  80. //otherwise assume it's IPv4, or plain hostname
  81. var hostPort = h.split(':', 2);
  82. _host = hostPort[0] || 'localhost';
  83. _port = hostPort[1] != null ? parseInt(hostPort[1], 10) : 27017;
  84. // Check for localhost?safe=true style case
  85. if(_host.indexOf("?") != -1) _host = _host.split(/\?/)[0];
  86. }
  87. // Return the mapped object
  88. return {host: _host, port: _port};
  89. });
  90. }
  91. // Get the db name
  92. object.dbName = dbName || 'admin';
  93. // Split up all the options
  94. urlOptions = (query_string_part || '').split(/[&;]/);
  95. // Ugh, we have to figure out which options go to which constructor manually.
  96. urlOptions.forEach(function(opt) {
  97. if(!opt) return;
  98. var splitOpt = opt.split('='), name = splitOpt[0], value = splitOpt[1];
  99. // Options implementations
  100. switch(name) {
  101. case 'slaveOk':
  102. case 'slave_ok':
  103. serverOptions.slave_ok = (value == 'true');
  104. dbOptions.slaveOk = (value == 'true');
  105. break;
  106. case 'maxPoolSize':
  107. case 'poolSize':
  108. serverOptions.poolSize = parseInt(value, 10);
  109. replSetServersOptions.poolSize = parseInt(value, 10);
  110. break;
  111. case 'autoReconnect':
  112. case 'auto_reconnect':
  113. serverOptions.auto_reconnect = (value == 'true');
  114. break;
  115. case 'minPoolSize':
  116. throw new Error("minPoolSize not supported");
  117. case 'maxIdleTimeMS':
  118. throw new Error("maxIdleTimeMS not supported");
  119. case 'waitQueueMultiple':
  120. throw new Error("waitQueueMultiple not supported");
  121. case 'waitQueueTimeoutMS':
  122. throw new Error("waitQueueTimeoutMS not supported");
  123. case 'uuidRepresentation':
  124. throw new Error("uuidRepresentation not supported");
  125. case 'ssl':
  126. if(value == 'prefer') {
  127. serverOptions.ssl = value;
  128. replSetServersOptions.ssl = value;
  129. break;
  130. }
  131. serverOptions.ssl = (value == 'true');
  132. replSetServersOptions.ssl = (value == 'true');
  133. break;
  134. case 'replicaSet':
  135. case 'rs_name':
  136. replSetServersOptions.rs_name = value;
  137. break;
  138. case 'reconnectWait':
  139. replSetServersOptions.reconnectWait = parseInt(value, 10);
  140. break;
  141. case 'retries':
  142. replSetServersOptions.retries = parseInt(value, 10);
  143. break;
  144. case 'readSecondary':
  145. case 'read_secondary':
  146. replSetServersOptions.read_secondary = (value == 'true');
  147. break;
  148. case 'fsync':
  149. dbOptions.fsync = (value == 'true');
  150. break;
  151. case 'journal':
  152. dbOptions.j = (value == 'true');
  153. break;
  154. case 'safe':
  155. dbOptions.safe = (value == 'true');
  156. break;
  157. case 'nativeParser':
  158. case 'native_parser':
  159. dbOptions.native_parser = (value == 'true');
  160. break;
  161. case 'connectTimeoutMS':
  162. serverOptions.socketOptions.connectTimeoutMS = parseInt(value, 10);
  163. replSetServersOptions.socketOptions.connectTimeoutMS = parseInt(value, 10);
  164. break;
  165. case 'socketTimeoutMS':
  166. serverOptions.socketOptions.socketTimeoutMS = parseInt(value, 10);
  167. replSetServersOptions.socketOptions.socketTimeoutMS = parseInt(value, 10);
  168. break;
  169. case 'w':
  170. dbOptions.w = parseInt(value, 10);
  171. if(isNaN(dbOptions.w)) dbOptions.w = value;
  172. break;
  173. case 'authSource':
  174. dbOptions.authSource = value;
  175. break;
  176. case 'gssapiServiceName':
  177. dbOptions.gssapiServiceName = value;
  178. break;
  179. case 'authMechanism':
  180. if(value == 'GSSAPI') {
  181. // If no password provided decode only the principal
  182. if(object.auth == null) {
  183. var urlDecodeAuthPart = decodeURIComponent(authPart);
  184. if(urlDecodeAuthPart.indexOf("@") == -1) throw new Error("GSSAPI requires a provided principal");
  185. object.auth = {user: urlDecodeAuthPart, password: null};
  186. } else {
  187. object.auth.user = decodeURIComponent(object.auth.user);
  188. }
  189. } else if(value == 'MONGODB-X509') {
  190. object.auth = {user: decodeURIComponent(authPart)};
  191. }
  192. // Only support GSSAPI or MONGODB-CR for now
  193. if(value != 'GSSAPI'
  194. && value != 'MONGODB-X509'
  195. && value != 'MONGODB-CR'
  196. && value != 'SCRAM-SHA-1'
  197. && value != 'PLAIN')
  198. throw new Error("only GSSAPI, PLAIN, MONGODB-X509, SCRAM-SHA-1 or MONGODB-CR is supported by authMechanism");
  199. // Authentication mechanism
  200. dbOptions.authMechanism = value;
  201. break;
  202. case 'authMechanismProperties':
  203. // Split up into key, value pairs
  204. var values = value.split(',');
  205. var o = {};
  206. // For each value split into key, value
  207. values.forEach(function(x) {
  208. var v = x.split(':');
  209. o[v[0]] = v[1];
  210. });
  211. // Set all authMechanismProperties
  212. dbOptions.authMechanismProperties = o;
  213. // Set the service name value
  214. if(typeof o.SERVICE_NAME == 'string') dbOptions.gssapiServiceName = o.SERVICE_NAME;
  215. break;
  216. case 'wtimeoutMS':
  217. dbOptions.wtimeout = parseInt(value, 10);
  218. break;
  219. case 'readPreference':
  220. if(!ReadPreference.isValid(value)) throw new Error("readPreference must be either primary/primaryPreferred/secondary/secondaryPreferred/nearest");
  221. dbOptions.read_preference = value;
  222. break;
  223. case 'readPreferenceTags':
  224. // Decode the value
  225. value = decodeURIComponent(value);
  226. // Contains the tag object
  227. var tagObject = {};
  228. if(value == null || value == '') {
  229. dbOptions.read_preference_tags.push(tagObject);
  230. break;
  231. }
  232. // Split up the tags
  233. var tags = value.split(/\,/);
  234. for(var i = 0; i < tags.length; i++) {
  235. var parts = tags[i].trim().split(/\:/);
  236. tagObject[parts[0]] = parts[1];
  237. }
  238. // Set the preferences tags
  239. dbOptions.read_preference_tags.push(tagObject);
  240. break;
  241. default:
  242. break;
  243. }
  244. });
  245. // No tags: should be null (not [])
  246. if(dbOptions.read_preference_tags.length === 0) {
  247. dbOptions.read_preference_tags = null;
  248. }
  249. // Validate if there are an invalid write concern combinations
  250. if((dbOptions.w == -1 || dbOptions.w == 0) && (
  251. dbOptions.journal == true
  252. || dbOptions.fsync == true
  253. || dbOptions.safe == true)) throw new Error("w set to -1 or 0 cannot be combined with safe/w/journal/fsync")
  254. // If no read preference set it to primary
  255. if(!dbOptions.read_preference) dbOptions.read_preference = 'primary';
  256. // Add servers to result
  257. object.servers = servers;
  258. // Returned parsed object
  259. return object;
  260. }