diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c93135..44ba359 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,26 @@
# Change Log
All notable changes to this project will be documented in this file.
-## [0.0.3] - 2015-06-15
+## 2.0.0 - 2016-09-09
+
+### Added
+- MailHops SSL
+- MailHops API v2
+- Test for no IPs in header
+- Time Traveled, total time it took for the email to reach you
+- Bump version 2.0 to match API v2
+
+### Fixed
+- Received IP sorting
+- Received IP duplicates
+
+## 0.0.3 - 2015-06-15
### Added
- Header IP parsing
-## [0.0.1] - 2015-06-14
+## 0.0.1 - 2015-06-14
### Added
- MailHops API lookup endpoint call
-- MailHops API config
\ No newline at end of file
+- MailHops API config
diff --git a/README.md b/README.md
index de55653..5630a9c 100644
--- a/README.md
+++ b/README.md
@@ -2,21 +2,22 @@
[www.MailHops.com](http://www.mailhops.com)
[](https://travis-ci.org/avantassel/mailhops-node)
+[](https://www.npmjs.com/package/mailhops)
-
+
A nodejs module for interacting with the MailHops API.
-##Getting Started
+## Getting Started
-###Installation
+### Installation
```
$ npm install mailhops
```
-###Configuration
-Simply require the mailhops module, instantiate a new MailHops object, configure it if necessary, and start making calls.
+### Configuration
+Simply require the mailhops module, instantiate a new MailHops object.
New MailHops objects can be instantiated with configuration parameters. Here is an example:
@@ -24,15 +25,14 @@ New MailHops objects can be instantiated with configuration parameters. Here is
var MailHops = require("mailhops");
var mailhops = new MailHops({
api_key: "aWN8Pb27Xj6GfV8D6ARsjokonYwbWUNbz9rM",
- api_version: 1,
+ api_version: 2,
proxy: "http://myproxy:3128",
- app_name: "Node App v1.0.0",
- forecastio_api_key: "",
- show_client: 1
+ app_name: "Node App v2.0.0",
+ forecastio_api_key: ""
});
```
-MailHops objects can also be configured via the ```.configure(options)``` method. Here is an exmaple:
+MailHops objects can also be configured via the `.configure(options)` method. Here is an exmaple:
```javascript
var MailHops = require("mailhops");
@@ -43,25 +43,27 @@ var options = {
}
mailhops.configure(options);
+
// get IPs from a full header where headerText is the full header
var ips = mailhops.getIPsFromHeader(headerText);
// or pass in an array of IP addresses
var ips = ['216.58.217.46','98.138.253.109'];
-mailhops.lookup(ips,function(err,response){
- console.log(response);
+mailhops.lookup(ips,function(err, res, body){
+ if(err)
+ console.log('MailHops Error',err);
+ console.log(body);
});
var mapUrl = mailhops.mapUrl('216.58.217.46,98.138.253.109');
```
-###Running Tests
+### Running Tests
```
$ npm test
```
## Other MailHops projects
-- [API](https://github.com/avantassel/mailhops-api)
-- [Postbox & Thunderbird plugin](https://github.com/avantassel/mailhops-plugin)
\ No newline at end of file
+- https://github.com/mailhops
diff --git a/config.json b/config.json
index 0c72b87..1f84532 100644
--- a/config.json
+++ b/config.json
@@ -1,8 +1,7 @@
{
- "base_uri": "http://api.mailhops.com"
- ,"app_name": "Node App v1.0.0"
- ,"api_version": 1
+ "base_uri": "https://api.mailhops.com"
+ ,"app_name": "Node App v2.0.0"
+ ,"api_version": 2
,"api_key": ""
,"forecastio_api_key": ""
- ,"show_client": 1
-}
\ No newline at end of file
+}
diff --git a/lib/api.js b/lib/api.js
index ceffde8..f2ac341 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -5,6 +5,8 @@ var request = require([__dirname, "request"].join("/"));
module.exports = {
+ time_traveled: null,
+
lookup: function(route, options, fn){
if(_.isFunction(options) && _.isUndefined(fn)){
fn = options;
@@ -12,12 +14,13 @@ module.exports = {
}
var qs = options;
- qs.api_key = this.api_key || '';
- qs.c = this.show_client;
+ qs.api_key = qs.api_key || this.api_key || '';
+ qs.c = this.show_client || 1;
qs.r = Array.isArray(route) ? route.join(',').replace(" ", "") : route.replace(" ", "");
-
- if(this.forecastio_api_key)
- qs.fkey = this.forecastio_api_key;
+ if(!!this.forecastio_api_key)
+ qs.fkey = this.forecastio_api_key;
+ if(!!this.time_traveled)
+ qs.t = this.time_traveled;
var config = {
uri: [this.api_version, "lookup"].join("/"),
@@ -31,12 +34,12 @@ module.exports = {
//just returns a map url that can be used as an iframe src
mapUrl: function(route, options){
var qs = options || {};
- qs.api_key = this.api_key || '';
- qs.c = this.show_client;
+ qs.api_key = qs.api_key || this.api_key || '';
+ qs.c = this.show_client || 1;
qs.r = Array.isArray(route) ? route.join(',').replace(" ", "") : route.replace(" ", "");
- if(this.forecastio_api_key)
- qs.fkey = this.forecastio_api_key;
+ if(!!this.forecastio_api_key)
+ qs.fkey = this.forecastio_api_key;
return [this.base_uri, this.api_version, "map", '?'+querystring.stringify(qs)].join("/");
},
@@ -48,17 +51,25 @@ module.exports = {
,regexIp=/(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)(\/(?:[012]\d?|3[012]?|[456789])){0,1}$/
,regexAllIp = /(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)\.(1\d{0,2}|2(?:[0-4]\d{0,1}|[6789]|5[0-5]?)?|[3-9]\d?|0)(\/(?:[012]\d?|3[012]?|[456789])){0,1}/g
,regexIPV6 = /(::|(([a-fA-F0-9]{1,4}):){7}(([a-fA-F0-9]{1,4}))|(:(:([a-fA-F0-9]{1,4})){1,6})|((([a-fA-F0-9]{1,4}):){1,6}:)|((([a-fA-F0-9]{1,4}):)(:([a-fA-F0-9]{1,4})){1,6})|((([a-fA-F0-9]{1,4}):){2}(:([a-fA-F0-9]{1,4})){1,5})|((([a-fA-F0-9]{1,4}):){3}(:([a-fA-F0-9]{1,4})){1,4})|((([a-fA-F0-9]{1,4}):){4}(:([a-fA-F0-9]{1,4})){1,3})|((([a-fA-F0-9]{1,4}):){5}(:([a-fA-F0-9]{1,4})){1,2}))/;
-
+
_.each(receivedHeaders, function(line){
-
+
//IPV6 check
if(line.match(regexIPV6)){
- ips.push( line.match(regexIPV6)[0] );
+ ips.unshift( line.match(regexIPV6)[0] );
return;
}
-
+
var received_ips = line.match(regexAllIp);
+ if(!received_ips)
+ return;
+
+ //get unique IPs for each Received header
+ received_ips = received_ips.filter(function(item, pos) {
+ return received_ips.indexOf(item) == pos;
+ });
+
//maybe multiple IPs in one Received: line
_.each(received_ips, function(ip){
@@ -69,28 +80,32 @@ module.exports = {
// Microsoft SMTP Server id 14.3.195.1; something like this should not be included
if(!firstchar.match(/\.|\d|\-/)
&& !lastchar.match(/\.|\d|\-/)
- && ( firstchar != '?' && lastchar != '?' )
+ && ( firstchar != '?' && lastchar != '?' )
&& lastchar != ';'
- && regexIp.test(ip)
- && ips.indexOf(ip)===-1){
+ && regexIp.test(ip)){
- ips.push( ip );
+ ips.unshift( ip );
+
+ } else if(regexIp.test(ip)
+ && line.indexOf(ip) !== line.lastIndexOf(ip)){
+ //check for duplicate IPs in one line
+ ips.unshift( ip );
}
});
});
-
+
return ips;
},
getReceivedHeaders: function(header){
var receivedHeaders = [];
- var rline = '';
+ var rline = '',firstDate,lastDate;
if ( header ){
var headers = header.split("\n");
_.each(headers,function(line){
-
+
//if the header line begins with Received, X-Received or X-Originating-IP then push that to our array
if(line.indexOf('Received:')===0 || line.indexOf('X-Received:')===0){
if(rline != ''){
@@ -111,6 +126,14 @@ module.exports = {
} else {
rline += line;
receivedHeaders.push(rline);
+
+ // first and last dates are used to calculate time_traveled Traveled
+ if(rline.indexOf(';')!==-1){
+ if(!lastDate)
+ lastDate = rline.substring(rline.indexOf(';')+1).trim();
+ firstDate = rline.substring(rline.indexOf(';')+1).trim();
+ }
+
rline = '';
return;
}
@@ -119,6 +142,24 @@ module.exports = {
if(rline != '')
receivedHeaders.push(rline);
}
+
+ // parse dates
+ if(firstDate && firstDate.indexOf('(')!==-1)
+ firstDate = firstDate.substring(0,firstDate.indexOf('(')).trim();
+ if(lastDate && lastDate.indexOf('(')!==-1)
+ lastDate = lastDate.substring(0,lastDate.indexOf('(')).trim();
+ if(firstDate && lastDate){
+ try {
+ firstDate = new Date(firstDate);
+ lastDate = new Date(lastDate);
+ this.time_traveled = parseInt(lastDate-firstDate);
+ } catch(e){
+ this.time_traveled = null;
+ }
+ } else {
+ this.time_traveled = null;
+ }
+
return receivedHeaders;
}
-}
\ No newline at end of file
+}
diff --git a/lib/request.js b/lib/request.js
index 132a11a..7c570b0 100644
--- a/lib/request.js
+++ b/lib/request.js
@@ -7,10 +7,11 @@ exports.create = function(config, fn){
uri: [configuration.base_uri, config.uri].join("/"),
method: "GET",
qs: config.qs || {},
- proxy: config.proxy
- }
-
- request(options, function(err, response, body){
- fn(err, JSON.parse(body));
+ timeout: 4000
+ };
+ if(config.proxy)
+ options.proxy = config.proxy;
+ request(options, function(error, response, body){
+ fn(error, response, JSON.parse(body));
});
}
diff --git a/mailhops.js b/mailhops.js
index f4925b8..bed0d25 100644
--- a/mailhops.js
+++ b/mailhops.js
@@ -6,10 +6,10 @@ function MailHops(config){
}
MailHops.prototype.configure = function(config){
- this.base_uri = config.base_uri;
+ this.base_uri = config.base_uri || 'https://api.mailhops.com';
this.api_key = config.api_key || undefined;
this.proxy = config.proxy || undefined;
- this.api_version = config.api_version || 1;
+ this.api_version = config.api_version || 2;
this.api_version = ["v", this.api_version].join("");
this.app_name = config.app_name;
this.forecastio_api_key = config.forecastio_api_key || undefined;
diff --git a/package.json b/package.json
index 49e7879..933dfab 100644
--- a/package.json
+++ b/package.json
@@ -1,22 +1,14 @@
{
"name": "mailhops",
- "version": "0.0.4",
+ "version": "2.0.0",
"description": "A nodejs module for interacting with the MailHops API.",
"main": "main.js",
- "dependencies": {
- "assert": "^1.1.1",
- "async": "^0.6.2",
- "lodash": "^2.4.1",
- "mocha": "^2.2.4",
- "qs": "^3.1.0",
- "request": "https://registry.npmjs.org/request/-/request-2.34.0.tgz"
- },
"scripts": {
"test": "mocha"
},
"repository": {
"type": "git",
- "url": "git://github.com/avantassel/mailhops-node.git"
+ "url": "git://github.com/mailhops/mailhops-node.git"
},
"keywords": [
"mailhops",
@@ -30,11 +22,11 @@
"name": "Andrew Van Tassel",
"email": "andrew@mailhops.com"
},
- "license": "GPLv2",
+ "license": "MIT",
"bugs": {
- "url": "https://github.com/avantassel/mailhops-node/issues"
+ "url": "https://github.com/mailhops/mailhops-node/issues"
},
- "homepage": "https://github.com/avantassel/mailhops-node",
+ "homepage": "https://github.com/mailhops/mailhops-node",
"_npmUser": {
"name": "avantassel",
"email": "andrew@andrewvantassel.com"
@@ -44,5 +36,15 @@
"name": "avantassel",
"email": "andrew@mailhops.com"
}
- ]
+ ],
+ "dependencies": {
+ "async": "^2.0.1",
+ "lodash": "^4.15.0",
+ "qs": "^6.2.1",
+ "request": "^2.7.4"
+ },
+ "devDependencies": {
+ "assert": "^1.4.1",
+ "mocha": "^3.0.2"
+ }
}
diff --git a/test/header-test-no-ips.eml b/test/header-test-no-ips.eml
new file mode 100644
index 0000000..6ce20a6
--- /dev/null
+++ b/test/header-test-no-ips.eml
@@ -0,0 +1,5 @@
+Received: (maildir_expire.pl 32008); Sat, 3 Sep 2016 22:21:29 -0800
+From: Inbox Archiver
+Date: Sat, 3 Sep 2016 22:21:29 -0800
+To: x15007238
+Subject: [Inbox archiver: 2 old messages moved to .old-messages/]
diff --git a/test/header-test.eml b/test/header-test.eml
index 7bfbe46..6049d07 100644
--- a/test/header-test.eml
+++ b/test/header-test.eml
@@ -2,14 +2,14 @@ Return-Path: ; Mon, 15 Jun 2015 10:38:03 -0700 (PDT)
-DKIM-Signature: v=1; a=rsa-sha1; c=relaxed;
- d=delivery.klaviyomail.com;
- h=content-type:mime-version:from:to:subject; s=smtpapi;
+ for ; Thu, 3 Sep 2014 16:38:38 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha1; c=relaxed;
+ d=delivery.klaviyomail.com;
+ h=content-type:mime-version:from:to:subject; s=smtpapi;
bh=lu70JKUbpAjCA9G5JXKZECq8PFc=; b=PNXTO8t5e2Bh/kmKAmOYvgQ+MDGfj
4DAOoP00xozjkYc6KMkyOLFUrfRrZEpBYXQC0m6IXDsyiTuk8SkopTpcc8YYitoa
yv4eaW1h9XRG0VYvl3RbBX5QFZsikRmReBO9Yu8gdLizZ8X83SF5TCwDLU99V/OL
@@ -18,10 +18,10 @@ Received: by filter0500p1mdw1.sendgrid.net with SMTP id filter0500p1mdw1.14444.5
2015-06-15 17:37:59.069788226 +0000 UTC
Received: from queue-worker-166.servers.clovesoftware.com (ec2-54-157-138-253.compute-1.amazonaws.com [54.157.138.253])
by ismtpd-013 (SG) with ESMTP id 14df84c98b9.582.18b0cc
- for ; Mon, 15 Jun 2015 17:37:58 +0000 (UTC)
+ for ; Thu, 4 Sep 2014 17:37:58 +0000 (UTC)
Received: from ip-10-157-90-72.ec2.internal (localhost [127.0.0.1])
by queue-worker-166.servers.clovesoftware.com (Postfix) with ESMTP id E2343271B8
- for ; Mon, 15 Jun 2015 17:37:58 +0000 (UTC)
+ for ; Thu, 4 Sep 2014 17:37:58 +0000 (UTC)
Received: from edge01.net.lu.se (130.235.56.196) by uwcas04.uw.lu.se
(130.235.59.236) with Microsoft SMTP Server (TLS) id 14.3.195.1; Thu, 4 Sep
2014 11:38:30 +0200
@@ -31,14 +31,14 @@ Received: from muon.isy.liu.se (130.236.48.25) by edge01.net.lu.se
Received: from ip227-36.wireless.lu.se (ip227-36.wireless.lu.se
[130.235.227.36]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (No
client certificate requested) by muon.isy.liu.se (Postfix) with ESMTPSA id
- D4B3D802; Thu, 4 Sep 2014 11:38:28 +0200 (MEST)
+ D4B3D802; Thu, 4 Sep 2014 1:38:28 +0200 (MEST)
Content-Type: multipart/alternative;
boundary="===============2767014682564602329=="
MIME-Version: 1.0
From: ProgrammableWeb Today
To: Andrew Van Tassel
Subject: Is It Time to Move On from .NET?
-Date: Mon, 15 Jun 2015 17:37:58 GMT
+Date: Thu, 4 Sep 2014 1:38:28 GMT
X-Kmail-Relay: [gmh6f2.krelaymail.com]:587
Message-Id: <20150615173758.E2343271B8@queue-worker-166.servers.clovesoftware.com>
X-SG-EID: z/7dFXGDBkx/fUP8kk8C2xB01JCsbu4AgSgValkgiTccFIBxI9L4Y1qe6fbe+Uf2YVSIXGTcm+Mu+n
diff --git a/test/mailhops.js b/test/mailhops.js
index 0b0c99d..2c103a5 100644
--- a/test/mailhops.js
+++ b/test/mailhops.js
@@ -14,6 +14,7 @@ describe("mailhops", function(){
it("required api methods exist", function(){
var required_keys = [
"configure",
+ "time_traveled",
"lookup",
"mapUrl",
"getIPsFromHeader",
@@ -23,21 +24,21 @@ describe("mailhops", function(){
assert.deepEqual(_.keys(mailhops.__proto__), required_keys);
});
- it("default config parameters are set correctly", function(){
- assert.equal(mailhops.api_version, "v1");
+ it("uses default config parameters", function(){
+ assert.equal(mailhops.api_version, "v2");
assert.equal(mailhops.api_key, undefined);
});
});
describe("configure()", function(){
- it("sets config parameters correctly", function(){
+ it("sets config parameters with configure method", function(){
mailhops.configure({
- api_version: 1,
+ api_version: 2,
api_key: "aWN8Pb27Xj6GfV8D6ARsjokonYwbWUNbz9rM",
app_name: "Node App"
});
- assert.equal(mailhops.api_version, "v1");
+ assert.equal(mailhops.api_version, "v2");
assert.equal(mailhops.api_key, "aWN8Pb27Xj6GfV8D6ARsjokonYwbWUNbz9rM");
assert.equal(mailhops.app_name, "Node App");
});
diff --git a/test/main.js b/test/main.js
index 28dc6f0..38a62af 100644
--- a/test/main.js
+++ b/test/main.js
@@ -3,14 +3,13 @@ var assert = require("assert");
var request = require("request");
var fs = require('fs');
var configuration = require([__dirname, "..", "config"].join("/"));
-var pkg = require([__dirname, "..", "package"].join("/"));
var MailHops = require([__dirname, "..", "main"].join("/"));
var mailhops = new MailHops(configuration);
console.log('Using %s', mailhops.base_uri);
describe("main", function(){
-
+
describe("new MailHops()", function(){
it("api_version parameter exists", function(){
assert.ok(_.has(mailhops, "api_version"));
@@ -28,26 +27,29 @@ describe("main", function(){
assert.ok(_.has(mailhops, "version"));
});
- it("version parameter equals that defined in package.json", function(){
- assert.equal(mailhops.version, pkg.version);
- });
-
});
describe("lookup endpoint", function(){
it('string route should return a 200 response with private ip', function(done){
- mailhops.lookup('127.0.0.1', function(err, response){
- assert.equal(response.meta['code'],200);
- assert.equal(response.response.route[0]['private'],true);
+ mailhops.lookup('127.0.0.1', function(err, res, body){
+ assert.equal(res.statusCode,200);
+ assert.equal(body.response.route[0]['private'],true);
done();
});
});
it('array route should return a 200 response with private ip', function(done){
- mailhops.lookup(['127.0.0.1','216.58.217.46','98.138.253.109'], function(err, response){
- assert.equal(response.meta['code'],200);
- assert.equal(response.response.route[0]['private'],true);
+ mailhops.lookup(['127.0.0.1','216.58.217.46','98.138.253.109'], function(err, res, body){
+ assert.equal(res.statusCode,200);
+ assert.equal(body.response.route[0]['private'],true);
+ done();
+ });
+ });
+
+ it('string route should return a 401 response for invalid api_key', function(done){
+ mailhops.lookup('127.0.0.1', {'api_key':'aWN8Pb27Xj6GfV8D6ARsjokonYwbWUNbz9rM'}, function(err, res, body){
+ assert.equal(res.statusCode,401);
done();
});
});
@@ -57,20 +59,64 @@ describe("main", function(){
describe("map endpoint", function(){
it('should return a 200 response', function(done){
- request(mailhops.mapUrl('127.0.0.1'), function (error, response, body) {
- assert.equal(response.statusCode,200);
+ request(mailhops.mapUrl('127.0.0.1'), function (err, res, body) {
+ assert.equal(res.statusCode,200);
done();
});
});
});
- describe("parse header", function(){
+ describe("parse header and lookup", function(){
+ //read header form file
+ var header = fs.readFileSync(__dirname+'/header-test.eml',{ encoding: 'utf8' });
+ var ips = mailhops.getIPsFromHeader(header);
+ it('should return an array of 9 Received IPs', function(done){
+ assert.equal(ips.length,9);
+ done();
+ });
- it('should return an array of 8 IP addresses', function(done){
- //read header form file
- var header = fs.readFileSync(__dirname+'/header-test.eml',{ encoding: 'utf8' });
- assert.equal(mailhops.getIPsFromHeader(header).length,8);
+ it('should return a time of 10000 milliseconds', function(done){
+ var ips = mailhops.getIPsFromHeader(header);
+ assert.equal(mailhops.time_traveled,10000);
+ done();
+ });
+
+ it('should find 9 Received IPs', function(done){
+ assert.deepEqual(ips,['130.235.227.36',
+ '130.235.56.196',
+ '130.236.48.25',
+ '130.235.59.236',
+ '130.235.56.196',
+ '127.0.0.1',
+ '54.157.138.253',
+ '198.21.5.108',
+ '2607:fb90:50f:5547:0:46:e46a:bd01']);
+ done();
+ });
+
+ it('should return a 200 response and route of 10 hops', function(done){
+ mailhops.lookup(mailhops.getIPsFromHeader(header), function(err, res, body){
+ assert.equal(res.statusCode,200);
+ assert.equal(body.response['route'].length,10);
+ done();
+ });
+ });
+
+ });
+
+ describe("parse header", function(){
+ //read header form file
+ var header = fs.readFileSync(__dirname+'/header-test-no-ips.eml',{ encoding: 'utf8' });
+ var ips = mailhops.getIPsFromHeader(header);
+
+ it('should return an array of 0 IP addresses', function(done){
+ assert.equal(ips.length,0);
+ done();
+ });
+
+ it('should return a time of 0 milliseconds', function(done){
+ assert.equal(mailhops.time,null);
done();
});