<commit_before>
var crypto = require('crypto')
  , fs = require('fs')
  , util = require('util');

var CannedPolicy = function(url, expireTime) {
  this.url = url;
  this.expireTime = expireTime;

  this.toJson = function() {
    var policy = {
      "Statement":[{
        "Resource": this.url,
        "Condition": {
          "DateLessThan": {
            "AWS:EpochTime": this.expireTime
          }
        }
      }]
    };

    return JSON.stringify(policy);
  }
}

// TODO: Add functionality for custom policies.
exports.getSignedUrl = function(url, params) {
  // If an expiration time isn't set, default to 30 seconds.
  var defaultExpireTime = Math.round((new Date().getTime() + 30000) / 1000)
    , expireTime = params.expireTime || defaultExpireTime
    , policy = new CannedPolicy(url, expireTime)
    , sign = crypto.createSign('RSA-SHA1')
    , privateKeyString = params.privateKeyString;
  sign.update(policy.toJson());

  // Stringify private key if supplied as a file path.
  if (params.privateKeyPath) {
    var pem = fs.readFileSync(params.privateKeyPath)
      , privateKeyString = pem.toString('ascii');
  }

  var signature = sign.sign(privateKeyString, 'base64');
  var urlParams = [
    'Expires=' + expireTime,
    'Signature=' + signature.replace(/\+/g,"-").replace(/=/g,"_").replace(/\//g,"~"),
    'Key-Pair-Id=' + params.keypairId
  ];
  var signedUrl = util.format('%s?%s', url, urlParams.join('&'));

  return signedUrl;
}<commit_msg>Move signature normalization into util<commit_after>
var crypto = require('crypto')
  , fs = require('fs')
  , util = require('util');

var CannedPolicy = function(url, expireTime) {
  this.url = url;
  this.expireTime = expireTime;

  this.toJson = function() {
    var policy = {
      "Statement":[{
        "Resource": this.url,
        "Condition": {
          "DateLessThan": {
            "AWS:EpochTime": this.expireTime
          }
        }
      }]
    };

    return JSON.stringify(policy);
  }
}

// TODO: Add functionality for custom policies.
exports.getSignedUrl = function(url, params) {
  // If an expiration time isn't set, default to 30 seconds.
  var defaultExpireTime = Math.round((new Date().getTime() + 30000) / 1000)
    , expireTime = params.expireTime || defaultExpireTime
    , policy = new CannedPolicy(url, expireTime)
    , sign = crypto.createSign('RSA-SHA1')
    , privateKeyString = params.privateKeyString;
  sign.update(policy.toJson());

  // Stringify private key if supplied as a file path.
  if (params.privateKeyPath) {
    var pem = fs.readFileSync(params.privateKeyPath)
      , privateKeyString = pem.toString('ascii');
  }

  var signature = sign.sign(privateKeyString, 'base64');
  var urlParams = [
    'Expires=' + expireTime,
    'Signature=' + normalizeSignature(signature),
    'Key-Pair-Id=' + params.keypairId
  ];
  var signedUrl = util.format('%s?%s', url, urlParams.join('&'));

  return signedUrl;
}

/**
 * Normalize all invalid AWS signature characters.
 *
 * For more information on AWS signatures, see
 * http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html
 *
 * @param {String}: Base64 encoded signature string
 */
function normalizeSignature(sig) {
  var badCharMap = {'+': '-', '=': '_', '/': '~'}
  Object.keys(badCharMap).forEach(function(badChar) {
    var re = new RegExp('\\' + badChar, 'g')
    sig = sig.replace(re, badCharMap[badChar])
  })
  return sig
}
{'fraction_non_alphanumeric': 0.15189142362113775, 'fraction_numerical': 0.008663008951775917, 'mean_word_length': 3.234718826405868, 'pattern_counts': {'":': 10, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4474665', 'n_tokens_mistral': 1157, 'n_tokens_neox': 1088, 'n_words': 242}