Skip to content

Commit

Permalink
Merge branch 'release/3.8.0' into customer
Browse files Browse the repository at this point in the history
  • Loading branch information
Vlad Oros committed Aug 13, 2019
2 parents d79df00 + 3da2ed7 commit 4c67072
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 43 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
CWD=`dirname $0:A`

alias mocli="${CWD}/mocli.sh"
alias mocli="${CWD}/mocli.sh"
mocli gen-cmp
19 changes: 13 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
FROM node:11-alpine

LABEL version="1.0"
LABEL version="1.2"
LABEL description="Linux alpine with node:11 and chromium browser"

RUN set -x \
&& apk update \
&& apk upgrade \
&& echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories \
&& echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories \
&& apk add --no-cache \
&& echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories

RUN apk add --no-cache \
bash \
python3

RUN apk add --no-cache \
chromium@edge \
harfbuzz@edge \
nss@edge \
freetype@edge \
ttf-freefont@edge \
&& rm -rf /var/cache/* \
&& mkdir /var/cache/apk
ttf-freefont@edge

RUN pip3 install awscli

RUN rm -rf /var/cache/* && mkdir /var/cache/apk

WORKDIR /opt/mo

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# MetaDefender browser extension

[![metadefender-browser-extension](https://david-dm.org/opswat/metadefender-browser-extension.svg)](https://david-dm.org/opswat/metadefender-browser-extension)

## Intro

OPSWAT File Security for Chrome, provides the ability to quickly scan a file for malware prior to download directly from the browser. At the moment this only works with google chrome, but can be extended to any other browser.
Expand Down Expand Up @@ -167,6 +169,8 @@ To rebuild the docker image run:
```bash
IMG=opswat/metadefender-browser-extension && VER=1.0
docker build -f Dockerfile . -t ${IMG}:${VER} -t ${IMG}:latest
docker push ${IMG}:${VER}
docker push ${IMG}:latest
```

## Contributing
Expand Down
7 changes: 7 additions & 0 deletions app/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
"message": "Save clean files<div class='text-secondary text-14'>Download clean files on my computer after they are scanned.</div>",
"description": ""
},
"safeUrl": {
"message": "Enable safe URL<div class='text-secondary text-14'>Browse the web in safety and alert me when accessing harmful websites.</div>",
"description": ""
},
"safeUrlSub": {
"message": "Read more about <a target='blank' href='https://onlinehelp.opswat.com/mdcloud/2.1_Safe_URL_redirect.html'>Safe URL Redirect</a>"
},
"searcHistory": {
"message": "Search history"
},
Expand Down
4 changes: 3 additions & 1 deletion app/config/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,7 @@
"g.files.metadefender.com"
],

"browserNotificationTimeout": 5000
"browserNotificationTimeout": 5000,

"maxCleanUrls": 1000
}
4 changes: 4 additions & 0 deletions app/html/extension/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
<li class="list-group-item">
<mcl-checkbox value="vm.settings.saveCleanFiles" label="{{vm.__MSG.getMessage('saveCleanFiles')}}" change="vm.settingsChanged('saveCleanFiles')"></mcl-checkbox>
</li>
<li class="list-group-item">
<mcl-checkbox value="vm.settings.safeUrl" label="{{vm.__MSG.getMessage('safeUrl')}}" change="vm.settingsChanged('safeUrl')"></mcl-checkbox>
<div><sub ng-bind-html="vm.__MSG.getMessage('safeUrlSub')"></sub></div>
</li>
</ul>

</div>
7 changes: 7 additions & 0 deletions app/scripts/background/background-task.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import FileProcessor from '../common/file-processor';
import cookieManager from './cookie-manager';
import DownloadsManager from './download-manager';
import { goToTab } from './navigation';
import SafeUrl from './safe-url';

const MCL_CONFIG = MCL.config;

Expand Down Expand Up @@ -54,6 +55,7 @@ class BackgroundTask {
chrome.notifications.onClosed.addListener(() => { });

browserMessage.addListener(this.messageListener.bind(this));

}

async init() {
Expand Down Expand Up @@ -82,6 +84,8 @@ class BackgroundTask {
this.setApikey(cookie.value);
}
});

SafeUrl.toggle(settings.safeUrl);
}

/**
Expand Down Expand Up @@ -222,6 +226,9 @@ class BackgroundTask {
if (settings.saveCleanFiles !== saveCleanFiles) {
this.updateContextMenu();
}

SafeUrl.toggle(settings.safeUrl);

break;
}
case BROWSER_EVENT.SCAN_FILES_UPDATED: {
Expand Down
131 changes: 131 additions & 0 deletions app/scripts/background/safe-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import '../common/config';

import xhr from 'xhr';

/**
* Metadefender Cloud Endpoint for url check
*/
const safeRedirectEndpoint = `${MCL.config.mclDomain}/safe-redirect/`;

/**
* A list of urls that are currently redirecting.
*/
const activeRedirects = new Set();

/**
* A list of urls that are infected.
*/
const infectedUrls = new Set();

/**
* A list of urls that are not infected.
* Used to speed-up navigation after fist check.
*/
const cleanUrls = new Set();

/**
* Removes old urls that were marked as clean
*/
const removeOldUrls = () => {
if (cleanUrls.size > MCL.config.maxCleanUrls) {
const firstValue = cleanUrls.values().next().value;
cleanUrls.delete(firstValue);
}
};

/**
* Save the url as infected or not.
* @param {string} testUrl the url to be tested
* @param {string} urlValidator test endpoint
*/
const handleUrlValidatorResponse = (testUrl, err, res) => {
if (err) {
cleanUrls.add(testUrl);
return;
}

try {
if (res.headers.status === '400') {
infectedUrls.add(testUrl);
}
else {
cleanUrls.add(testUrl);
}
}
catch (e) {
cleanUrls.add(testUrl);
}
removeOldUrls();
};

const isSafeUrl = (testUrl, urlValidator) => {
xhr.get(urlValidator, {sync: true, headers: {noredirect: true}}, (err, res) => handleUrlValidatorResponse(testUrl, err, res));
};

/**
* Intercept web request before the request is made
* and redirect valid urls to safe-redirect endpoint.
*
* @param {string} safeRedirectEndpoint
* @param {*} details chrome.webRequest event details
* @returns a BlockingResponse https://developer.chrome.com/extensions/webRequest
*/
const doSafeRedirect = (details) => {
const tabUrl = details.url || '';

if (!tabUrl.startsWith(safeRedirectEndpoint)) {
const shortUrl = tabUrl.split('?')[0];
if (!activeRedirects.has(shortUrl) && details.initiator !== 'null') {
if (!cleanUrls.has(shortUrl)) {
const safeUrl = safeRedirectEndpoint + encodeURIComponent(tabUrl);
isSafeUrl(shortUrl, safeUrl);
if (infectedUrls.has(shortUrl)) {
activeRedirects.add(shortUrl);
return {
redirectUrl: safeUrl
};
}
}
}
activeRedirects.delete(shortUrl);
if (infectedUrls.has(shortUrl)) {
infectedUrls.delete(shortUrl);
}
}
};

/**
* Verifies urls using metadefender cloud safe-redirect feature.
*/
class SafeUrl {
constructor() {
this.enabled = false;
this.toggle = this.toggle.bind(this);

this._infectedUrls = infectedUrls;
this._cleanUrls = cleanUrls;
this._doSafeRedirect = doSafeRedirect;
this._handleUrlValidatorResponse = handleUrlValidatorResponse;
}

toggle(enable) {
if (this.enabled === enable) {
return;
}

this.enabled = enable;
if (this.enabled) {
chrome.webRequest.onBeforeRequest.addListener(doSafeRedirect, {
urls: ['http://*/*', 'https://*/*'],
types: ['main_frame']
}, ['blocking']);
}
else {
chrome.webRequest.onBeforeRequest.removeListener(doSafeRedirect);
}

return this.enabled;
}
}

export default new SafeUrl();
122 changes: 122 additions & 0 deletions app/scripts/background/safe-url.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import chrome from 'sinon-chrome';
import sinon from 'sinon';
import xhr from 'xhr';
import SafeUrl from './safe-url';

describe('app/scripts/background/safe-url.js', () => {

beforeAll(() => {
global.chrome = chrome;
});

describe('enabled', () => {

it('should be disabled by default', () => {
expect(SafeUrl.enabled).toBeFalsy();
});
});

describe('toggle', () => {

it('should enable safe url', () => {
chrome.webRequest.onBeforeRequest.addListener.resetHistory();

SafeUrl.enabled = false;
const enabled = SafeUrl.toggle(true);
const callArgs = chrome.webRequest.onBeforeRequest.addListener.getCall(0).args;

expect(enabled).toBeTruthy();
expect(callArgs.length).toEqual(3);
expect(typeof callArgs[0]).toBe('function');
expect(callArgs[1]).toEqual({urls: ['http://*/*', 'https://*/*'], types: ['main_frame']});
expect(callArgs[2]).toEqual(['blocking']);
});

it('shold not change enabled state', () => {
SafeUrl.enabled = false;
expect(typeof SafeUrl.toggle(false)).toBe('undefined');
});

it('should disable safe url', () => {
chrome.webRequest.onBeforeRequest.removeListener.resetHistory();

SafeUrl.enabled = true;
const enabled = SafeUrl.toggle(false);
const callArgs = chrome.webRequest.onBeforeRequest.removeListener.getCall(0).args;

expect(enabled).toBeFalsy();
expect(callArgs.length).toEqual(1);
expect(typeof callArgs[0]).toBe('function');
});

});

describe('_handleUrlValidatorResponse', () => {

it('should mark url as infected', () => {
SafeUrl._handleUrlValidatorResponse('a1', 'error', {});
expect(SafeUrl._cleanUrls.has('a1')).toBeTruthy();

SafeUrl._handleUrlValidatorResponse('a2', '', {headers: {status: '200'}});
expect(SafeUrl._cleanUrls.has('a2')).toBeTruthy();

SafeUrl._handleUrlValidatorResponse('a3', '', {headers: {status: '404'}});
expect(SafeUrl._cleanUrls.has('a3')).toBeTruthy();
});

it('should mark the url as infected', () => {
SafeUrl._handleUrlValidatorResponse('b1', '', {headers: {status: '400'}});
expect(SafeUrl._infectedUrls.has('b1')).toBeTruthy();
});

});

describe('_doSafeRedirect', () => {

it('should not redirect metadefender safe redirect endpoint', () => {
expect(typeof SafeUrl._doSafeRedirect({type: 'main_frame', url: `${MCL.config.mclDomain}/safe-redirect/`})).toBe('undefined');
});

it('should not redirect clean URLs', () => {
const testUrl = 'http://metadefender.opswat.com';
const details = {
url: testUrl
};
sinon.stub(xhr, 'get').callsFake(() => SafeUrl._handleUrlValidatorResponse(testUrl, 'error', {}));

const redirect = SafeUrl._doSafeRedirect(details);
expect(typeof redirect).toBe('undefined');

xhr.get.restore();
});

it('should redirect infected URLs', () => {
const testUrl = 'http://infected.url';
const details = {
url: testUrl
};
sinon.stub(xhr, 'get').callsFake(() => SafeUrl._handleUrlValidatorResponse(testUrl, '', {headers: {status: '400'}}));

const redirect = SafeUrl._doSafeRedirect(details);
expect(typeof redirect.redirectUrl).toBe('string');
expect(redirect.redirectUrl.startsWith(`${MCL.config.mclDomain}/safe-redirect/`)).toBeTruthy();
expect(redirect.redirectUrl.endsWith(encodeURIComponent(testUrl))).toBeTruthy();

xhr.get.restore();
});

it('should not redirect infected URLs on second access', () => {
const testUrl = 'http://infected.url/access';
const details = {
url: testUrl
};
sinon.stub(xhr, 'get').callsFake(() => SafeUrl._handleUrlValidatorResponse(testUrl, '', {headers: {status: '400'}}));

SafeUrl._doSafeRedirect(details);
expect(typeof SafeUrl._doSafeRedirect(details)).toBe('undefined');

xhr.get.restore();
});
});

});
Loading

0 comments on commit 4c67072

Please sign in to comment.