mirror of
https://github.com/FlipsideCrypto/eth-phishing-detect.git
synced 2026-02-06 03:06:43 +00:00
Add interactive page for checking why a page is blocked.
This commit is contained in:
parent
0029277b48
commit
9400d824cd
@ -4,6 +4,8 @@
|
||||
|
||||
Utility for detecting phishing domains targeting Ethereum users.
|
||||
|
||||
For checking why a given domain was blocked, try our interactive page [here](https://metamask.github.io/eth-phishing-detect)
|
||||
|
||||
### basic usage
|
||||
|
||||
```js
|
||||
@ -27,4 +29,4 @@ console.log(value)
|
||||
result: true,
|
||||
}
|
||||
*/
|
||||
```
|
||||
```
|
||||
|
||||
831
example/bundle.js
Normal file
831
example/bundle.js
Normal file
@ -0,0 +1,831 @@
|
||||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
const why = require('./why')
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
document.querySelector('form').addEventListener('submit', (ev) => {
|
||||
ev.preventDefault()
|
||||
result.innerText = why(input.value)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
},{"./why":2}],2:[function(require,module,exports){
|
||||
const PhishingDetector = require('eth-phishing-detect/src/detector')
|
||||
const fetch = require('isomorphic-fetch')
|
||||
let phishing
|
||||
|
||||
updatePhishingList()
|
||||
.then(() => {
|
||||
detector = new PhishingDetector(phishing)
|
||||
})
|
||||
|
||||
async function updatePhishingList () {
|
||||
// make request
|
||||
let response
|
||||
try {
|
||||
response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return
|
||||
}
|
||||
// parse response
|
||||
let rawResponse
|
||||
try {
|
||||
const rawResponse = await response.text()
|
||||
phishing = JSON.parse(rawResponse)
|
||||
} catch (err) {
|
||||
console.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A function for checking why a given domain is blocked.
|
||||
* @param { string } domain - The domain that is blocked.
|
||||
* @returns { string } reason - A string describing the reason for the block.
|
||||
*/
|
||||
function why (domain) {
|
||||
if (!detector) {
|
||||
return 'Cannot answer, still loading list data...'
|
||||
}
|
||||
|
||||
const reason = detector.check(domain)
|
||||
|
||||
if (!reason.result) {
|
||||
return 'This domain is not blocked! No problem here.'
|
||||
}
|
||||
|
||||
if (reason.type === 'fuzzy') {
|
||||
return `This domain was blocked for its similarity to ${reason.match}, a historical phishing target.`
|
||||
}
|
||||
|
||||
if (reason.type === 'blacklist') {
|
||||
return `This domain was blocked because it has been explicitly identified as a malicious site.`
|
||||
}
|
||||
|
||||
return `There was an issue identifying the reason for the block. The data is ${JSON.stringify(reason)}`
|
||||
}
|
||||
|
||||
module.exports = why
|
||||
|
||||
|
||||
},{"eth-phishing-detect/src/detector":6,"isomorphic-fetch":4}],3:[function(require,module,exports){
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var collator;
|
||||
try {
|
||||
collator = (typeof Intl !== "undefined" && typeof Intl.Collator !== "undefined") ? Intl.Collator("generic", { sensitivity: "base" }) : null;
|
||||
} catch (err){
|
||||
console.log("Collator could not be initialized and wouldn't be used");
|
||||
}
|
||||
// arrays to re-use
|
||||
var prevRow = [],
|
||||
str2Char = [];
|
||||
|
||||
/**
|
||||
* Based on the algorithm at http://en.wikipedia.org/wiki/Levenshtein_distance.
|
||||
*/
|
||||
var Levenshtein = {
|
||||
/**
|
||||
* Calculate levenshtein distance of the two strings.
|
||||
*
|
||||
* @param str1 String the first string.
|
||||
* @param str2 String the second string.
|
||||
* @param [options] Additional options.
|
||||
* @param [options.useCollator] Use `Intl.Collator` for locale-sensitive string comparison.
|
||||
* @return Integer the levenshtein distance (0 and above).
|
||||
*/
|
||||
get: function(str1, str2, options) {
|
||||
var useCollator = (options && collator && options.useCollator);
|
||||
|
||||
var str1Len = str1.length,
|
||||
str2Len = str2.length;
|
||||
|
||||
// base cases
|
||||
if (str1Len === 0) return str2Len;
|
||||
if (str2Len === 0) return str1Len;
|
||||
|
||||
// two rows
|
||||
var curCol, nextCol, i, j, tmp;
|
||||
|
||||
// initialise previous row
|
||||
for (i=0; i<str2Len; ++i) {
|
||||
prevRow[i] = i;
|
||||
str2Char[i] = str2.charCodeAt(i);
|
||||
}
|
||||
prevRow[str2Len] = str2Len;
|
||||
|
||||
var strCmp;
|
||||
if (useCollator) {
|
||||
// calculate current row distance from previous row using collator
|
||||
for (i = 0; i < str1Len; ++i) {
|
||||
nextCol = i + 1;
|
||||
|
||||
for (j = 0; j < str2Len; ++j) {
|
||||
curCol = nextCol;
|
||||
|
||||
// substution
|
||||
strCmp = 0 === collator.compare(str1.charAt(i), String.fromCharCode(str2Char[j]));
|
||||
|
||||
nextCol = prevRow[j] + (strCmp ? 0 : 1);
|
||||
|
||||
// insertion
|
||||
tmp = curCol + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
// deletion
|
||||
tmp = prevRow[j + 1] + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
|
||||
// copy current col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = curCol;
|
||||
}
|
||||
|
||||
// copy last col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = nextCol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// calculate current row distance from previous row without collator
|
||||
for (i = 0; i < str1Len; ++i) {
|
||||
nextCol = i + 1;
|
||||
|
||||
for (j = 0; j < str2Len; ++j) {
|
||||
curCol = nextCol;
|
||||
|
||||
// substution
|
||||
strCmp = str1.charCodeAt(i) === str2Char[j];
|
||||
|
||||
nextCol = prevRow[j] + (strCmp ? 0 : 1);
|
||||
|
||||
// insertion
|
||||
tmp = curCol + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
// deletion
|
||||
tmp = prevRow[j + 1] + 1;
|
||||
if (nextCol > tmp) {
|
||||
nextCol = tmp;
|
||||
}
|
||||
|
||||
// copy current col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = curCol;
|
||||
}
|
||||
|
||||
// copy last col value into previous (in preparation for next iteration)
|
||||
prevRow[j] = nextCol;
|
||||
}
|
||||
}
|
||||
return nextCol;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// amd
|
||||
if (typeof define !== "undefined" && define !== null && define.amd) {
|
||||
define(function() {
|
||||
return Levenshtein;
|
||||
});
|
||||
}
|
||||
// commonjs
|
||||
else if (typeof module !== "undefined" && module !== null && typeof exports !== "undefined" && module.exports === exports) {
|
||||
module.exports = Levenshtein;
|
||||
}
|
||||
// web worker
|
||||
else if (typeof self !== "undefined" && typeof self.postMessage === 'function' && typeof self.importScripts === 'function') {
|
||||
self.Levenshtein = Levenshtein;
|
||||
}
|
||||
// browser main thread
|
||||
else if (typeof window !== "undefined" && window !== null) {
|
||||
window.Levenshtein = Levenshtein;
|
||||
}
|
||||
}());
|
||||
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
// the whatwg-fetch polyfill installs the fetch() function
|
||||
// on the global object (window or self)
|
||||
//
|
||||
// Return that as the export for use in Webpack, Browserify etc.
|
||||
require('whatwg-fetch');
|
||||
module.exports = self.fetch.bind(self);
|
||||
|
||||
},{"whatwg-fetch":5}],5:[function(require,module,exports){
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(factory((global.WHATWGFetch = {})));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
|
||||
var support = {
|
||||
searchParams: 'URLSearchParams' in self,
|
||||
iterable: 'Symbol' in self && 'iterator' in Symbol,
|
||||
blob:
|
||||
'FileReader' in self &&
|
||||
'Blob' in self &&
|
||||
(function() {
|
||||
try {
|
||||
new Blob();
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
})(),
|
||||
formData: 'FormData' in self,
|
||||
arrayBuffer: 'ArrayBuffer' in self
|
||||
};
|
||||
|
||||
function isDataView(obj) {
|
||||
return obj && DataView.prototype.isPrototypeOf(obj)
|
||||
}
|
||||
|
||||
if (support.arrayBuffer) {
|
||||
var viewClasses = [
|
||||
'[object Int8Array]',
|
||||
'[object Uint8Array]',
|
||||
'[object Uint8ClampedArray]',
|
||||
'[object Int16Array]',
|
||||
'[object Uint16Array]',
|
||||
'[object Int32Array]',
|
||||
'[object Uint32Array]',
|
||||
'[object Float32Array]',
|
||||
'[object Float64Array]'
|
||||
];
|
||||
|
||||
var isArrayBufferView =
|
||||
ArrayBuffer.isView ||
|
||||
function(obj) {
|
||||
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeName(name) {
|
||||
if (typeof name !== 'string') {
|
||||
name = String(name);
|
||||
}
|
||||
if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) {
|
||||
throw new TypeError('Invalid character in header field name')
|
||||
}
|
||||
return name.toLowerCase()
|
||||
}
|
||||
|
||||
function normalizeValue(value) {
|
||||
if (typeof value !== 'string') {
|
||||
value = String(value);
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Build a destructive iterator for the value list
|
||||
function iteratorFor(items) {
|
||||
var iterator = {
|
||||
next: function() {
|
||||
var value = items.shift();
|
||||
return {done: value === undefined, value: value}
|
||||
}
|
||||
};
|
||||
|
||||
if (support.iterable) {
|
||||
iterator[Symbol.iterator] = function() {
|
||||
return iterator
|
||||
};
|
||||
}
|
||||
|
||||
return iterator
|
||||
}
|
||||
|
||||
function Headers(headers) {
|
||||
this.map = {};
|
||||
|
||||
if (headers instanceof Headers) {
|
||||
headers.forEach(function(value, name) {
|
||||
this.append(name, value);
|
||||
}, this);
|
||||
} else if (Array.isArray(headers)) {
|
||||
headers.forEach(function(header) {
|
||||
this.append(header[0], header[1]);
|
||||
}, this);
|
||||
} else if (headers) {
|
||||
Object.getOwnPropertyNames(headers).forEach(function(name) {
|
||||
this.append(name, headers[name]);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
Headers.prototype.append = function(name, value) {
|
||||
name = normalizeName(name);
|
||||
value = normalizeValue(value);
|
||||
var oldValue = this.map[name];
|
||||
this.map[name] = oldValue ? oldValue + ', ' + value : value;
|
||||
};
|
||||
|
||||
Headers.prototype['delete'] = function(name) {
|
||||
delete this.map[normalizeName(name)];
|
||||
};
|
||||
|
||||
Headers.prototype.get = function(name) {
|
||||
name = normalizeName(name);
|
||||
return this.has(name) ? this.map[name] : null
|
||||
};
|
||||
|
||||
Headers.prototype.has = function(name) {
|
||||
return this.map.hasOwnProperty(normalizeName(name))
|
||||
};
|
||||
|
||||
Headers.prototype.set = function(name, value) {
|
||||
this.map[normalizeName(name)] = normalizeValue(value);
|
||||
};
|
||||
|
||||
Headers.prototype.forEach = function(callback, thisArg) {
|
||||
for (var name in this.map) {
|
||||
if (this.map.hasOwnProperty(name)) {
|
||||
callback.call(thisArg, this.map[name], name, this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Headers.prototype.keys = function() {
|
||||
var items = [];
|
||||
this.forEach(function(value, name) {
|
||||
items.push(name);
|
||||
});
|
||||
return iteratorFor(items)
|
||||
};
|
||||
|
||||
Headers.prototype.values = function() {
|
||||
var items = [];
|
||||
this.forEach(function(value) {
|
||||
items.push(value);
|
||||
});
|
||||
return iteratorFor(items)
|
||||
};
|
||||
|
||||
Headers.prototype.entries = function() {
|
||||
var items = [];
|
||||
this.forEach(function(value, name) {
|
||||
items.push([name, value]);
|
||||
});
|
||||
return iteratorFor(items)
|
||||
};
|
||||
|
||||
if (support.iterable) {
|
||||
Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
|
||||
}
|
||||
|
||||
function consumed(body) {
|
||||
if (body.bodyUsed) {
|
||||
return Promise.reject(new TypeError('Already read'))
|
||||
}
|
||||
body.bodyUsed = true;
|
||||
}
|
||||
|
||||
function fileReaderReady(reader) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
reader.onload = function() {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = function() {
|
||||
reject(reader.error);
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
function readBlobAsArrayBuffer(blob) {
|
||||
var reader = new FileReader();
|
||||
var promise = fileReaderReady(reader);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
return promise
|
||||
}
|
||||
|
||||
function readBlobAsText(blob) {
|
||||
var reader = new FileReader();
|
||||
var promise = fileReaderReady(reader);
|
||||
reader.readAsText(blob);
|
||||
return promise
|
||||
}
|
||||
|
||||
function readArrayBufferAsText(buf) {
|
||||
var view = new Uint8Array(buf);
|
||||
var chars = new Array(view.length);
|
||||
|
||||
for (var i = 0; i < view.length; i++) {
|
||||
chars[i] = String.fromCharCode(view[i]);
|
||||
}
|
||||
return chars.join('')
|
||||
}
|
||||
|
||||
function bufferClone(buf) {
|
||||
if (buf.slice) {
|
||||
return buf.slice(0)
|
||||
} else {
|
||||
var view = new Uint8Array(buf.byteLength);
|
||||
view.set(new Uint8Array(buf));
|
||||
return view.buffer
|
||||
}
|
||||
}
|
||||
|
||||
function Body() {
|
||||
this.bodyUsed = false;
|
||||
|
||||
this._initBody = function(body) {
|
||||
this._bodyInit = body;
|
||||
if (!body) {
|
||||
this._bodyText = '';
|
||||
} else if (typeof body === 'string') {
|
||||
this._bodyText = body;
|
||||
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
|
||||
this._bodyBlob = body;
|
||||
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
|
||||
this._bodyFormData = body;
|
||||
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
||||
this._bodyText = body.toString();
|
||||
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
|
||||
this._bodyArrayBuffer = bufferClone(body.buffer);
|
||||
// IE 10-11 can't handle a DataView body.
|
||||
this._bodyInit = new Blob([this._bodyArrayBuffer]);
|
||||
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
|
||||
this._bodyArrayBuffer = bufferClone(body);
|
||||
} else {
|
||||
this._bodyText = body = Object.prototype.toString.call(body);
|
||||
}
|
||||
|
||||
if (!this.headers.get('content-type')) {
|
||||
if (typeof body === 'string') {
|
||||
this.headers.set('content-type', 'text/plain;charset=UTF-8');
|
||||
} else if (this._bodyBlob && this._bodyBlob.type) {
|
||||
this.headers.set('content-type', this._bodyBlob.type);
|
||||
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
||||
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (support.blob) {
|
||||
this.blob = function() {
|
||||
var rejected = consumed(this);
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return Promise.resolve(this._bodyBlob)
|
||||
} else if (this._bodyArrayBuffer) {
|
||||
return Promise.resolve(new Blob([this._bodyArrayBuffer]))
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as blob')
|
||||
} else {
|
||||
return Promise.resolve(new Blob([this._bodyText]))
|
||||
}
|
||||
};
|
||||
|
||||
this.arrayBuffer = function() {
|
||||
if (this._bodyArrayBuffer) {
|
||||
return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
|
||||
} else {
|
||||
return this.blob().then(readBlobAsArrayBuffer)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.text = function() {
|
||||
var rejected = consumed(this);
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return readBlobAsText(this._bodyBlob)
|
||||
} else if (this._bodyArrayBuffer) {
|
||||
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as text')
|
||||
} else {
|
||||
return Promise.resolve(this._bodyText)
|
||||
}
|
||||
};
|
||||
|
||||
if (support.formData) {
|
||||
this.formData = function() {
|
||||
return this.text().then(decode)
|
||||
};
|
||||
}
|
||||
|
||||
this.json = function() {
|
||||
return this.text().then(JSON.parse)
|
||||
};
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// HTTP methods whose capitalization should be normalized
|
||||
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
|
||||
|
||||
function normalizeMethod(method) {
|
||||
var upcased = method.toUpperCase();
|
||||
return methods.indexOf(upcased) > -1 ? upcased : method
|
||||
}
|
||||
|
||||
function Request(input, options) {
|
||||
options = options || {};
|
||||
var body = options.body;
|
||||
|
||||
if (input instanceof Request) {
|
||||
if (input.bodyUsed) {
|
||||
throw new TypeError('Already read')
|
||||
}
|
||||
this.url = input.url;
|
||||
this.credentials = input.credentials;
|
||||
if (!options.headers) {
|
||||
this.headers = new Headers(input.headers);
|
||||
}
|
||||
this.method = input.method;
|
||||
this.mode = input.mode;
|
||||
this.signal = input.signal;
|
||||
if (!body && input._bodyInit != null) {
|
||||
body = input._bodyInit;
|
||||
input.bodyUsed = true;
|
||||
}
|
||||
} else {
|
||||
this.url = String(input);
|
||||
}
|
||||
|
||||
this.credentials = options.credentials || this.credentials || 'same-origin';
|
||||
if (options.headers || !this.headers) {
|
||||
this.headers = new Headers(options.headers);
|
||||
}
|
||||
this.method = normalizeMethod(options.method || this.method || 'GET');
|
||||
this.mode = options.mode || this.mode || null;
|
||||
this.signal = options.signal || this.signal;
|
||||
this.referrer = null;
|
||||
|
||||
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
|
||||
throw new TypeError('Body not allowed for GET or HEAD requests')
|
||||
}
|
||||
this._initBody(body);
|
||||
}
|
||||
|
||||
Request.prototype.clone = function() {
|
||||
return new Request(this, {body: this._bodyInit})
|
||||
};
|
||||
|
||||
function decode(body) {
|
||||
var form = new FormData();
|
||||
body
|
||||
.trim()
|
||||
.split('&')
|
||||
.forEach(function(bytes) {
|
||||
if (bytes) {
|
||||
var split = bytes.split('=');
|
||||
var name = split.shift().replace(/\+/g, ' ');
|
||||
var value = split.join('=').replace(/\+/g, ' ');
|
||||
form.append(decodeURIComponent(name), decodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
return form
|
||||
}
|
||||
|
||||
function parseHeaders(rawHeaders) {
|
||||
var headers = new Headers();
|
||||
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.2
|
||||
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
|
||||
preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
|
||||
var parts = line.split(':');
|
||||
var key = parts.shift().trim();
|
||||
if (key) {
|
||||
var value = parts.join(':').trim();
|
||||
headers.append(key, value);
|
||||
}
|
||||
});
|
||||
return headers
|
||||
}
|
||||
|
||||
Body.call(Request.prototype);
|
||||
|
||||
function Response(bodyInit, options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
this.type = 'default';
|
||||
this.status = options.status === undefined ? 200 : options.status;
|
||||
this.ok = this.status >= 200 && this.status < 300;
|
||||
this.statusText = 'statusText' in options ? options.statusText : 'OK';
|
||||
this.headers = new Headers(options.headers);
|
||||
this.url = options.url || '';
|
||||
this._initBody(bodyInit);
|
||||
}
|
||||
|
||||
Body.call(Response.prototype);
|
||||
|
||||
Response.prototype.clone = function() {
|
||||
return new Response(this._bodyInit, {
|
||||
status: this.status,
|
||||
statusText: this.statusText,
|
||||
headers: new Headers(this.headers),
|
||||
url: this.url
|
||||
})
|
||||
};
|
||||
|
||||
Response.error = function() {
|
||||
var response = new Response(null, {status: 0, statusText: ''});
|
||||
response.type = 'error';
|
||||
return response
|
||||
};
|
||||
|
||||
var redirectStatuses = [301, 302, 303, 307, 308];
|
||||
|
||||
Response.redirect = function(url, status) {
|
||||
if (redirectStatuses.indexOf(status) === -1) {
|
||||
throw new RangeError('Invalid status code')
|
||||
}
|
||||
|
||||
return new Response(null, {status: status, headers: {location: url}})
|
||||
};
|
||||
|
||||
exports.DOMException = self.DOMException;
|
||||
try {
|
||||
new exports.DOMException();
|
||||
} catch (err) {
|
||||
exports.DOMException = function(message, name) {
|
||||
this.message = message;
|
||||
this.name = name;
|
||||
var error = Error(message);
|
||||
this.stack = error.stack;
|
||||
};
|
||||
exports.DOMException.prototype = Object.create(Error.prototype);
|
||||
exports.DOMException.prototype.constructor = exports.DOMException;
|
||||
}
|
||||
|
||||
function fetch(input, init) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var request = new Request(input, init);
|
||||
|
||||
if (request.signal && request.signal.aborted) {
|
||||
return reject(new exports.DOMException('Aborted', 'AbortError'))
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
function abortXhr() {
|
||||
xhr.abort();
|
||||
}
|
||||
|
||||
xhr.onload = function() {
|
||||
var options = {
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
|
||||
};
|
||||
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
|
||||
var body = 'response' in xhr ? xhr.response : xhr.responseText;
|
||||
resolve(new Response(body, options));
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
reject(new TypeError('Network request failed'));
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
reject(new TypeError('Network request failed'));
|
||||
};
|
||||
|
||||
xhr.onabort = function() {
|
||||
reject(new exports.DOMException('Aborted', 'AbortError'));
|
||||
};
|
||||
|
||||
xhr.open(request.method, request.url, true);
|
||||
|
||||
if (request.credentials === 'include') {
|
||||
xhr.withCredentials = true;
|
||||
} else if (request.credentials === 'omit') {
|
||||
xhr.withCredentials = false;
|
||||
}
|
||||
|
||||
if ('responseType' in xhr && support.blob) {
|
||||
xhr.responseType = 'blob';
|
||||
}
|
||||
|
||||
request.headers.forEach(function(value, name) {
|
||||
xhr.setRequestHeader(name, value);
|
||||
});
|
||||
|
||||
if (request.signal) {
|
||||
request.signal.addEventListener('abort', abortXhr);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
// DONE (success or failure)
|
||||
if (xhr.readyState === 4) {
|
||||
request.signal.removeEventListener('abort', abortXhr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
|
||||
})
|
||||
}
|
||||
|
||||
fetch.polyfill = true;
|
||||
|
||||
if (!self.fetch) {
|
||||
self.fetch = fetch;
|
||||
self.Headers = Headers;
|
||||
self.Request = Request;
|
||||
self.Response = Response;
|
||||
}
|
||||
|
||||
exports.Headers = Headers;
|
||||
exports.Request = Request;
|
||||
exports.Response = Response;
|
||||
exports.fetch = fetch;
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
})));
|
||||
|
||||
},{}],6:[function(require,module,exports){
|
||||
const levenshtein = require('fast-levenshtein')
|
||||
const DEFAULT_TOLERANCE = 3
|
||||
|
||||
class PhishingDetector {
|
||||
|
||||
constructor (opts) {
|
||||
this.whitelist = processDomainList(opts.whitelist || [])
|
||||
this.blacklist = processDomainList(opts.blacklist || [])
|
||||
this.fuzzylist = processDomainList(opts.fuzzylist || [])
|
||||
this.tolerance = ('tolerance' in opts) ? opts.tolerance : DEFAULT_TOLERANCE
|
||||
}
|
||||
|
||||
check (domain) {
|
||||
const source = domainToParts(domain)
|
||||
|
||||
// if source matches whitelist domain (or subdomain thereof), PASS
|
||||
const whitelistMatch = matchPartsAgainstList(source, this.whitelist)
|
||||
if (whitelistMatch) return { type: 'whitelist', result: false }
|
||||
|
||||
// if source matches blacklist domain (or subdomain thereof), FAIL
|
||||
const blacklistMatch = matchPartsAgainstList(source, this.blacklist)
|
||||
if (blacklistMatch) return { type: 'blacklist', result: true }
|
||||
|
||||
if (this.tolerance > 0) {
|
||||
// check if near-match of whitelist domain, FAIL
|
||||
let fuzzyForm = domainPartsToFuzzyForm(source)
|
||||
// strip www
|
||||
fuzzyForm = fuzzyForm.replace('www.', '')
|
||||
// check against fuzzylist
|
||||
const levenshteinMatched = this.fuzzylist.find((targetParts) => {
|
||||
const fuzzyTarget = domainPartsToFuzzyForm(targetParts)
|
||||
const distance = levenshtein.get(fuzzyForm, fuzzyTarget)
|
||||
return distance <= this.tolerance
|
||||
})
|
||||
if (levenshteinMatched) {
|
||||
const match = domainPartsToDomain(levenshteinMatched)
|
||||
return { type: 'fuzzy', result: true, match }
|
||||
}
|
||||
}
|
||||
|
||||
// matched nothing, PASS
|
||||
return { type: 'all', result: false }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PhishingDetector
|
||||
|
||||
// util
|
||||
|
||||
function processDomainList (list) {
|
||||
return list.map(domainToParts)
|
||||
}
|
||||
|
||||
function domainToParts (domain) {
|
||||
return domain.split('.').reverse()
|
||||
}
|
||||
|
||||
function domainPartsToDomain(domainParts) {
|
||||
return domainParts.slice().reverse().join('.')
|
||||
}
|
||||
|
||||
// for fuzzy search, drop TLD and re-stringify
|
||||
function domainPartsToFuzzyForm(domainParts) {
|
||||
return domainParts.slice(1).reverse().join('.')
|
||||
}
|
||||
|
||||
// match the target parts, ignoring extra subdomains on source
|
||||
// source: [io, metamask, xyz]
|
||||
// target: [io, metamask]
|
||||
// result: PASS
|
||||
function matchPartsAgainstList(source, list) {
|
||||
return list.some((target) => {
|
||||
// target domain has more parts than source, fail
|
||||
if (target.length > source.length) return false
|
||||
// source matches target or (is deeper subdomain)
|
||||
return target.every((part, index) => source[index] === part)
|
||||
})
|
||||
}
|
||||
},{"fast-levenshtein":3}]},{},[1]);
|
||||
9
example/index.js
Normal file
9
example/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
const why = require('./why')
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
document.querySelector('form').addEventListener('submit', (ev) => {
|
||||
ev.preventDefault()
|
||||
result.innerText = why(input.value)
|
||||
})
|
||||
})
|
||||
|
||||
58
example/why.js
Normal file
58
example/why.js
Normal file
@ -0,0 +1,58 @@
|
||||
const PhishingDetector = require('eth-phishing-detect/src/detector')
|
||||
const fetch = require('isomorphic-fetch')
|
||||
let phishing
|
||||
|
||||
updatePhishingList()
|
||||
.then(() => {
|
||||
detector = new PhishingDetector(phishing)
|
||||
})
|
||||
|
||||
async function updatePhishingList () {
|
||||
// make request
|
||||
let response
|
||||
try {
|
||||
response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return
|
||||
}
|
||||
// parse response
|
||||
let rawResponse
|
||||
try {
|
||||
const rawResponse = await response.text()
|
||||
phishing = JSON.parse(rawResponse)
|
||||
} catch (err) {
|
||||
console.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A function for checking why a given domain is blocked.
|
||||
* @param { string } domain - The domain that is blocked.
|
||||
* @returns { string } reason - A string describing the reason for the block.
|
||||
*/
|
||||
function why (domain) {
|
||||
if (!detector) {
|
||||
return 'Cannot answer, still loading list data...'
|
||||
}
|
||||
|
||||
const reason = detector.check(domain)
|
||||
|
||||
if (!reason.result) {
|
||||
return 'This domain is not blocked! No problem here.'
|
||||
}
|
||||
|
||||
if (reason.type === 'fuzzy') {
|
||||
return `This domain was blocked for its similarity to ${reason.match}, a historical phishing target.`
|
||||
}
|
||||
|
||||
if (reason.type === 'blacklist') {
|
||||
return `This domain was blocked because it has been explicitly identified as a malicious site.`
|
||||
}
|
||||
|
||||
return `There was an issue identifying the reason for the block. The data is ${JSON.stringify(reason)}`
|
||||
}
|
||||
|
||||
module.exports = why
|
||||
|
||||
16
index.html
Normal file
16
index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Why Blocked?</title>
|
||||
<script src="./example/bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Why Was This Site Blocked?</h1>
|
||||
<form>
|
||||
<input id="input"></input>
|
||||
<button class="why" type="submit">Check</button>
|
||||
</form>
|
||||
<br>
|
||||
<p id="result"></p>
|
||||
</body>
|
||||
</html>
|
||||
549
package-lock.json
generated
Normal file
549
package-lock.json
generated
Normal file
@ -0,0 +1,549 @@
|
||||
{
|
||||
"name": "eth-phishing-detect",
|
||||
"version": "1.1.13",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ansi-escape": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escape/-/ansi-escape-1.1.0.tgz",
|
||||
"integrity": "sha1-ithZ6Epp4P+Rd5aUeTqS5OjAXpk=",
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
|
||||
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.10"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"buffer-shims": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
|
||||
"integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"dev": true
|
||||
},
|
||||
"csv-parse": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-2.5.0.tgz",
|
||||
"integrity": "sha512-4OcjOJQByI0YDU5COYw9HAqjo8/MOLLmT9EKyMCXUzgvh30vS1SlMK+Ho84IH5exN44cSnrYecw/7Zpu2m4lkA==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"deep-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
||||
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
|
||||
"dev": true
|
||||
},
|
||||
"define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"object-keys": "^1.0.12"
|
||||
}
|
||||
},
|
||||
"defined": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
|
||||
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
|
||||
"dev": true
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"iconv-lite": "~0.4.13"
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
|
||||
"integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es-to-primitive": "^1.1.1",
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.1",
|
||||
"is-callable": "^1.1.3",
|
||||
"is-regex": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"es-to-primitive": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
|
||||
"integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-callable": "^1.1.4",
|
||||
"is-date-object": "^1.0.1",
|
||||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
|
||||
},
|
||||
"figures": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
|
||||
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
|
||||
"integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
|
||||
"dev": true
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
|
||||
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
|
||||
"dev": true
|
||||
},
|
||||
"is-date-object": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
|
||||
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
|
||||
"dev": true
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
|
||||
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||
"dev": true
|
||||
},
|
||||
"is-symbol": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
|
||||
"integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-fetch": "^1.0.1",
|
||||
"whatwg-fetch": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz",
|
||||
"integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^2.1.2",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"encoding": "^0.1.11",
|
||||
"is-stream": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
|
||||
"integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"object-keys": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
|
||||
"integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"parse-ms": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz",
|
||||
"integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=",
|
||||
"dev": true
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-ms": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.2.0.tgz",
|
||||
"integrity": "sha512-ZypexbfVUGTFxb0v+m1bUyy92DHe5SyYlnyY0msyms5zd3RwyvNgyxZZsXXgoyzlxjx5MiqtXUdhUfvQbe0A2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"parse-ms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
|
||||
"dev": true
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"dev": true
|
||||
},
|
||||
"re-emitter": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.3.tgz",
|
||||
"integrity": "sha1-+p4xn/3u6zWycpbvDz03TawvUqc=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.2.9",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz",
|
||||
"integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-shims": "~1.0.0",
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~1.0.6",
|
||||
"string_decoder": "~1.0.0",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
|
||||
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-parse": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"resumer": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz",
|
||||
"integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"through": "~2.3.4"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
|
||||
"dev": true
|
||||
},
|
||||
"split": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npmjs.org/split/-/split-1.0.0.tgz",
|
||||
"integrity": "sha1-xDlc5oOrzSVLwo/h2rtuXCfc/64=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"through": "2"
|
||||
}
|
||||
},
|
||||
"string.prototype.trim": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz",
|
||||
"integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"define-properties": "^1.1.2",
|
||||
"es-abstract": "^1.5.0",
|
||||
"function-bind": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"tap-out": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tap-out/-/tap-out-2.1.0.tgz",
|
||||
"integrity": "sha512-LJE+TBoVbOWhwdz4+FQk40nmbIuxJLqaGvj3WauQw3NYYU5TdjoV3C0x/yq37YAvVyi+oeBXmWnxWSjJ7IEyUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"re-emitter": "1.1.3",
|
||||
"readable-stream": "2.2.9",
|
||||
"split": "1.0.0",
|
||||
"trim": "0.0.1"
|
||||
}
|
||||
},
|
||||
"tap-summary": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tap-summary/-/tap-summary-4.0.0.tgz",
|
||||
"integrity": "sha1-kyFIYrpGfFCgnzeOoOXtxd0EQz0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-escape": "^1.0.1",
|
||||
"commander": "^2.9.0",
|
||||
"figures": "^2.0.0",
|
||||
"pretty-ms": "^3.0.0",
|
||||
"tap-out": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"tape": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/tape/-/tape-4.9.1.tgz",
|
||||
"integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"deep-equal": "~1.0.1",
|
||||
"defined": "~1.0.0",
|
||||
"for-each": "~0.3.3",
|
||||
"function-bind": "~1.1.1",
|
||||
"glob": "~7.1.2",
|
||||
"has": "~1.0.3",
|
||||
"inherits": "~2.0.3",
|
||||
"minimist": "~1.2.0",
|
||||
"object-inspect": "~1.6.0",
|
||||
"resolve": "~1.7.1",
|
||||
"resumer": "~0.0.0",
|
||||
"string.prototype.trim": "~1.1.2",
|
||||
"through": "~2.3.8"
|
||||
}
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
|
||||
"dev": true
|
||||
},
|
||||
"trim": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
||||
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@
|
||||
"devDependencies": {
|
||||
"async": "^2.5.0",
|
||||
"csv-parse": "^2.0.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"needle": "^2.0.0",
|
||||
"punycode": "^2.1.0",
|
||||
"tap-summary": "^4.0.0",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user