helper.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. 'use strict';
  2. var path = require('path')
  3. , Stream = require('stream').Stream
  4. , split = require('split2')
  5. , util = require('util')
  6. , defaultPort = 5432
  7. , isWin = (process.platform === 'win32')
  8. , warnStream = process.stderr
  9. ;
  10. var S_IRWXG = 56 // 00070(8)
  11. , S_IRWXO = 7 // 00007(8)
  12. , S_IFMT = 61440 // 00170000(8)
  13. , S_IFREG = 32768 // 0100000(8)
  14. ;
  15. function isRegFile(mode) {
  16. return ((mode & S_IFMT) == S_IFREG);
  17. }
  18. var fieldNames = [ 'host', 'port', 'database', 'user', 'password' ];
  19. var nrOfFields = fieldNames.length;
  20. var passKey = fieldNames[ nrOfFields -1 ];
  21. function warn() {
  22. var isWritable = (
  23. warnStream instanceof Stream &&
  24. true === warnStream.writable
  25. );
  26. if (isWritable) {
  27. var args = Array.prototype.slice.call(arguments).concat("\n");
  28. warnStream.write( util.format.apply(util, args) );
  29. }
  30. }
  31. Object.defineProperty(module.exports, 'isWin', {
  32. get : function() {
  33. return isWin;
  34. } ,
  35. set : function(val) {
  36. isWin = val;
  37. }
  38. });
  39. module.exports.warnTo = function(stream) {
  40. var old = warnStream;
  41. warnStream = stream;
  42. return old;
  43. };
  44. module.exports.getFileName = function(rawEnv){
  45. var env = rawEnv || process.env;
  46. var file = env.PGPASSFILE || (
  47. isWin ?
  48. path.join( env.APPDATA || './' , 'postgresql', 'pgpass.conf' ) :
  49. path.join( env.HOME || './', '.pgpass' )
  50. );
  51. return file;
  52. };
  53. module.exports.usePgPass = function(stats, fname) {
  54. if (Object.prototype.hasOwnProperty.call(process.env, 'PGPASSWORD')) {
  55. return false;
  56. }
  57. if (isWin) {
  58. return true;
  59. }
  60. fname = fname || '<unkn>';
  61. if (! isRegFile(stats.mode)) {
  62. warn('WARNING: password file "%s" is not a plain file', fname);
  63. return false;
  64. }
  65. if (stats.mode & (S_IRWXG | S_IRWXO)) {
  66. /* If password file is insecure, alert the user and ignore it. */
  67. warn('WARNING: password file "%s" has group or world access; permissions should be u=rw (0600) or less', fname);
  68. return false;
  69. }
  70. return true;
  71. };
  72. var matcher = module.exports.match = function(connInfo, entry) {
  73. return fieldNames.slice(0, -1).reduce(function(prev, field, idx){
  74. if (idx == 1) {
  75. // the port
  76. if ( Number( connInfo[field] || defaultPort ) === Number( entry[field] ) ) {
  77. return prev && true;
  78. }
  79. }
  80. return prev && (
  81. entry[field] === '*' ||
  82. entry[field] === connInfo[field]
  83. );
  84. }, true);
  85. };
  86. module.exports.getPassword = function(connInfo, stream, cb) {
  87. var pass;
  88. var lineStream = stream.pipe(split());
  89. function onLine(line) {
  90. var entry = parseLine(line);
  91. if (entry && isValidEntry(entry) && matcher(connInfo, entry)) {
  92. pass = entry[passKey];
  93. lineStream.end(); // -> calls onEnd(), but pass is set now
  94. }
  95. }
  96. var onEnd = function() {
  97. stream.destroy();
  98. cb(pass);
  99. };
  100. var onErr = function(err) {
  101. stream.destroy();
  102. warn('WARNING: error on reading file: %s', err);
  103. cb(undefined);
  104. };
  105. stream.on('error', onErr);
  106. lineStream
  107. .on('data', onLine)
  108. .on('end', onEnd)
  109. .on('error', onErr)
  110. ;
  111. };
  112. var parseLine = module.exports.parseLine = function(line) {
  113. if (line.length < 11 || line.match(/^\s+#/)) {
  114. return null;
  115. }
  116. var curChar = '';
  117. var prevChar = '';
  118. var fieldIdx = 0;
  119. var startIdx = 0;
  120. var endIdx = 0;
  121. var obj = {};
  122. var isLastField = false;
  123. var addToObj = function(idx, i0, i1) {
  124. var field = line.substring(i0, i1);
  125. if (! Object.hasOwnProperty.call(process.env, 'PGPASS_NO_DEESCAPE')) {
  126. field = field.replace(/\\([:\\])/g, '$1');
  127. }
  128. obj[ fieldNames[idx] ] = field;
  129. };
  130. for (var i = 0 ; i < line.length-1 ; i += 1) {
  131. curChar = line.charAt(i+1);
  132. prevChar = line.charAt(i);
  133. isLastField = (fieldIdx == nrOfFields-1);
  134. if (isLastField) {
  135. addToObj(fieldIdx, startIdx);
  136. break;
  137. }
  138. if (i >= 0 && curChar == ':' && prevChar !== '\\') {
  139. addToObj(fieldIdx, startIdx, i+1);
  140. startIdx = i+2;
  141. fieldIdx += 1;
  142. }
  143. }
  144. obj = ( Object.keys(obj).length === nrOfFields ) ? obj : null;
  145. return obj;
  146. };
  147. var isValidEntry = module.exports.isValidEntry = function(entry){
  148. var rules = {
  149. // host
  150. 0 : function(x){
  151. return x.length > 0;
  152. } ,
  153. // port
  154. 1 : function(x){
  155. if (x === '*') {
  156. return true;
  157. }
  158. x = Number(x);
  159. return (
  160. isFinite(x) &&
  161. x > 0 &&
  162. x < 9007199254740992 &&
  163. Math.floor(x) === x
  164. );
  165. } ,
  166. // database
  167. 2 : function(x){
  168. return x.length > 0;
  169. } ,
  170. // username
  171. 3 : function(x){
  172. return x.length > 0;
  173. } ,
  174. // password
  175. 4 : function(x){
  176. return x.length > 0;
  177. }
  178. };
  179. for (var idx = 0 ; idx < fieldNames.length ; idx += 1) {
  180. var rule = rules[idx];
  181. var value = entry[ fieldNames[idx] ] || '';
  182. var res = rule(value);
  183. if (!res) {
  184. return false;
  185. }
  186. }
  187. return true;
  188. };