diff --git a/Dockerfile b/Dockerfile
index 95cb345..4303bfb 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
FROM node:11-alpine
-LABEL version="1.2"
+LABEL version="1.3"
LABEL description="Linux alpine with node:11 and chromium browser"
RUN set -x \
@@ -9,9 +9,7 @@ RUN set -x \
&& echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories \
&& echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories
-RUN apk add --no-cache \
- bash \
- python3
+RUN apk add --no-cache bash python3 pkgconfig autoconf automake libtool nasm build-base zlib-dev
RUN apk add --no-cache \
chromium@edge \
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 299bc37..60d2d9d 100755
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -48,7 +48,7 @@
"description": ""
},
"aboutThankYou": {
- "message": "Thank you for installing OPSWAT File Security for Chrome ! This extension gives you the ability to scan downloads* for malware with 30+ anti-malware engines using OPSWAT's MetaDefender Cloud .* maximum file size is 140 MB ",
+ "message": "Thank you for installing OPSWAT File Security for Chrome ! This extension gives you the ability to scan downloads* for malware with 30+ anti-malware engines using OPSWAT's MetaDefender Cloud .",
"description": ""
},
"aboutApiKeyInfo": {
@@ -60,7 +60,7 @@
"description": ""
},
"aboutMoreAboutMCL": {
- "message": "Read more about MetaDefender Cloud Licensing options",
+ "message": "Read more about MetaDefender Cloud Licensing options",
"description": ""
},
"aboutHowToUse": {
@@ -79,17 +79,11 @@
"message": "To access the OPSWAT File Security for Chrome extension settings page, right click on the 'OPSWAT File Security for Chrome' button in the Chrome top bar and select 'Options' from the drop down menu",
"description": ""
},
- "howToScanAll": {
- "message": "If you would like to automatically scan all downloads, click on the 'OPSWAT File Security for Chrome button' in the Chrome top bar and select the box to 'Scan downloads'",
- "description": ""
- },
- "howToSaveClean": {
- "message": "If you would like to automatically download the scanned files with no threat detected, go to OPSWAT File Security for Chrome settings page and enable 'Save clean files'",
- "description": ""
+ "chromeExtensionHelp": {
+ "message": "Read more about OPSWAT File Security for Chrome extension settings on our online help page."
},
- "howToMoreInfo": {
- "message": "To view more information about the scan results, go to scan history page and click on the file result",
- "description": ""
+ "githubInfo": {
+ "message": "The chrome extension is an open-source tool developed by OPSWAT, the source code is published on github: metadefender-browser-extension "
},
"contactOpswat": {
"message": "Contact OPSWAT Sales to increase your limits",
@@ -129,6 +123,22 @@
"safeUrlSub": {
"message": "Read more about Safe URL Redirect "
},
+ "useCore": {
+ "message": "On-premise scanning using MetaDefender Core
Send your files to a private
MetaDefender Core server ",
+ "description": ""
+ },
+ "useCoreSub": {
+ "message": ""
+ },
+ "coreUrl": {
+ "message": "URL"
+ },
+ "coreApikey": {
+ "message": "Apikey"
+ },
+ "coreRule": {
+ "message": "Workflow"
+ },
"searcHistory": {
"message": "Search history"
},
@@ -140,6 +150,22 @@
"message": "Threats detected",
"description": ""
},
+ "coreSettingsSave": {
+ "message": "Validate & Save",
+ "description": "Save core settings button label"
+ },
+ "coreSettingsSavedNotification": {
+ "message": "Core settings saved!"
+ },
+ "coreSettingsInvalidNotification": {
+ "message": "Core settings are not valid!"
+ },
+ "coreSettingsInvalidApikey": {
+ "message": "Invalid MetaDefender Core apikey!"
+ },
+ "coreSettingsInvalidUrl": {
+ "message": "Could not connect to a MetaDefender Core server at the specified URL!"
+ },
"scanStatusScanning": {
"message": "Scan in progress...",
"description": ""
@@ -157,7 +183,7 @@
"description": ""
},
"unsupportedUrl": {
- "message": "Url not supported.\nYou may be able to scan the file by downloading it while 'Scan Downloads' is enabled"
+ "message": "URL not supported.\nYou may be able to scan the file by downloading it while 'Scan Downloads' is enabled"
},
"errorWhileDownloading": {
"message": "We encountered an error while downloading this file. Please try again",
@@ -167,6 +193,9 @@
"message": "Unable to access local file. Please make sure 'Allow access to file URLs' is enabled for OPSWAT extension",
"description": ""
},
+ "scanFileError": {
+ "message": "There was an error while scanning the file. Please try again later"
+ },
"fileAccessDisabled": {
"message": "To use this feature, 'Allow access to file URLs' must be enabled for OPSWAT extension"
},
diff --git a/app/fonts/fontello/LICENSE.txt b/app/fonts/fontello/LICENSE.txt
index aa4b284..d244c95 100644
--- a/app/fonts/fontello/LICENSE.txt
+++ b/app/fonts/fontello/LICENSE.txt
@@ -1,24 +1,6 @@
Font license info
-## Iconic
-
- Copyright (C) 2012 by P.J. Onori
-
- Author: P.J. Onori
- License: SIL (http://scripts.sil.org/OFL)
- Homepage: http://somerandomdude.com/work/iconic/
-
-
-## MFG Labs
-
- Copyright (C) 2012 by Daniel Bruce
-
- Author: MFG Labs
- License: SIL (http://scripts.sil.org/OFL)
- Homepage: http://www.mfglabs.com/
-
-
## Font Awesome
Copyright (C) 2016 by Dave Gandy
diff --git a/app/fonts/fontello/config.json b/app/fonts/fontello/config.json
index 820a3b0..4058474 100644
--- a/app/fonts/fontello/config.json
+++ b/app/fonts/fontello/config.json
@@ -6,12 +6,6 @@
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
- {
- "uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
- "css": "trash-empty",
- "code": 59394,
- "src": "fontawesome"
- },
{
"uid": "9bc2902722abb366a213a052ade360bc",
"css": "spin",
@@ -25,34 +19,52 @@
"src": "fontawesome"
},
{
- "uid": "1d35198f5190ec004dd4ec742fbe19ca",
- "css": "right",
- "code": 59393,
- "src": "mfglabs"
+ "uid": "2c413e78faf1d6631fd7b094d14c2253",
+ "css": "cloud",
+ "code": 59395,
+ "src": "fontawesome"
},
{
- "uid": "ec21fe3492bb04d9e29103c319556ed8",
- "css": "search",
- "code": 62733,
- "src": "mfglabs"
+ "uid": "bbfb51903f40597f0b70fd75bc7b5cac",
+ "css": "trash",
+ "code": 61944,
+ "src": "fontawesome"
},
{
- "uid": "ce50292e85eb5d6ee3be61b32bf2bdf3",
- "css": "ok",
- "code": 59399,
- "src": "mfglabs"
+ "uid": "e99461abfef3923546da8d745372c995",
+ "css": "cog",
+ "code": 59394,
+ "src": "fontawesome"
},
{
- "uid": "06301c50d89b5d3e651bd07ebd6d7de7",
+ "uid": "9dd9e835aebe1060ba7190ad2b2ed951",
+ "css": "search",
+ "code": 59392,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "5211af474d3a9848f67f945e2ccaf143",
"css": "cancel",
+ "code": 59396,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "12f4ece88e46abd864e40b35e05b11cd",
+ "css": "ok",
"code": 59400,
- "src": "mfglabs"
+ "src": "fontawesome"
},
{
- "uid": "fc94b92194752796654c96c7b7dccebb",
- "css": "cog",
- "code": 59392,
- "src": "iconic"
+ "uid": "ad6b3fbb5324abe71a9c0b6609cbb9f1",
+ "css": "right",
+ "code": 59397,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "d59ff824282fc6edaeca991deab522aa",
+ "css": "server",
+ "code": 62003,
+ "src": "fontawesome"
}
]
}
\ No newline at end of file
diff --git a/app/fonts/fontello/css/mcl-ext-icons-codes.css b/app/fonts/fontello/css/mcl-ext-icons-codes.css
index f0f794b..a5bd335 100644
--- a/app/fonts/fontello/css/mcl-ext-icons-codes.css
+++ b/app/fonts/fontello/css/mcl-ext-icons-codes.css
@@ -1,9 +1,11 @@
-.icon-cog:before { content: '\e800'; } /* '' */
-.icon-right:before { content: '\e801'; } /* '' */
-.icon-trash-empty:before { content: '\e802'; } /* '' */
-.icon-ok:before { content: '\e807'; } /* '' */
-.icon-cancel:before { content: '\e808'; } /* '' */
+.icon-search:before { content: '\e800'; } /* '' */
+.icon-cog:before { content: '\e802'; } /* '' */
+.icon-cloud:before { content: '\e803'; } /* '' */
+.icon-cancel:before { content: '\e804'; } /* '' */
+.icon-right:before { content: '\e805'; } /* '' */
+.icon-ok:before { content: '\e808'; } /* '' */
.icon-spin:before { content: '\e839'; } /* '' */
.icon-help:before { content: '\f128'; } /* '' */
-.icon-search:before { content: '\f50d'; } /* '' */
\ No newline at end of file
+.icon-trash:before { content: '\f1f8'; } /* '' */
+.icon-server:before { content: '\f233'; } /* '' */
\ No newline at end of file
diff --git a/app/fonts/fontello/css/mcl-ext-icons-embedded.css b/app/fonts/fontello/css/mcl-ext-icons-embedded.css
index 2d94694..f52f328 100644
--- a/app/fonts/fontello/css/mcl-ext-icons-embedded.css
+++ b/app/fonts/fontello/css/mcl-ext-icons-embedded.css
@@ -1,15 +1,15 @@
@font-face {
font-family: 'mcl-ext-icons';
- src: url('../font/mcl-ext-icons.eot?84545260');
- src: url('../font/mcl-ext-icons.eot?84545260#iefix') format('embedded-opentype'),
- url('../font/mcl-ext-icons.svg?84545260#mcl-ext-icons') format('svg');
+ src: url('../font/mcl-ext-icons.eot?66982893');
+ src: url('../font/mcl-ext-icons.eot?66982893#iefix') format('embedded-opentype'),
+ url('../font/mcl-ext-icons.svg?66982893#mcl-ext-icons') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'mcl-ext-icons';
- src: url('data:application/octet-stream;base64,d09GRgABAAAAABA0AA8AAAAAGkgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAAQwAAAFY+IFX2Y21hcAAAAdgAAACLAAAB8kxtpAxjdnQgAAACZAAAABMAAAAgBtX/BGZwZ20AAAJ4AAAFkAAAC3CKkZBZZ2FzcAAACAgAAAAIAAAACAAAABBnbHlmAAAIEAAABRQAAAZkkqV0bmhlYWQAAA0kAAAAMgAAADYQaySnaGhlYQAADVgAAAAfAAAAJAcwA1lobXR4AAANeAAAACQAAAAkHin/5GxvY2EAAA2cAAAAFAAAABQGrAfabWF4cAAADbAAAAAgAAAAIAEYDApuYW1lAAAN0AAAAZIAAAMJ4ly+snBvc3QAAA9kAAAAUQAAAGgCOEH7cHJlcAAAD7gAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgZI5inMDAysDAVMW0h4GBoQdCMz5gMGRkAooysDIzYAUBaa4pDA4vGL7yMgf9z2KIYg5imAYUZgTJAQDeTwuhAHic7ZHBCcMwDEWfHDc2JYHSKXLIAtmio3SAnjqtrjnknEqWaZeozDP8j5Hhf+ACDMZqZJA3gs/LXGn+wLX5mYfpiUoia9Kq274c83mCouWnviP2+sa9HVfJdmX7caTYFmTkP1O7n11VzzLw9LVjyaEdb0tL4I1pDbxJ3QJLmH0JLGuOOaB+AOchIV4AeJxjYEADEhDIHPQ/C4QBEmwD3QB4nK1WaXfTRhQdeUmchCwlCy1qYcTEabBGJmzBgAlBsmMgXZytlaCLFDvpvvGJ3+Bf82Tac+g3flrvGy8kkLTncJqTo3fnzdXM22USWpLYC+uRlJsvxdTWJo3sPAnphk3LUXwoO3shZYrJ3wVREK2W2rcdh0REIlC1rrBEEPseWZpkfOhRRsu2pFdNyi096S5b40G9Vd9+GjrKsTuhpGYzdGg9siVVGFWiSKY9UtKmZaj6K0krvL/CzFfNUMKITiJpvBnG0EjeG2e0ymg1tuMoimyy3ChSJJrhQRR5lNUS5+SKCQzKB82Q8sqnEeXD/Iis2KOcVrBLttP8vi95p3c5P7Ffb1G25EAfyI7s4Ox0JV+EW1th3LST7ShUEXbXd0Js2exU/2aP8ppGA7crMr3QjGCpfIUQKz+hzP4hWS2cT/mSR6NaspETQetlTuxLPoHW44gpcc0YWdDd0QkR1P2SMwz2mD4e/PHeKZYLEwJ4HMt6RyWcCBMpYXM0SdowcmAlZYsqqfWumDjldVrEW8J+7drRl85o41B3YjxbDx1bOVHJ8WhSp5lMndpJzaMpDaKUdCZ4zK8DKD+iSV5tYzWJlUfTOGbGhEQiAi3cS1NBLDuxpCkEzaMZvbkbprl2LVqkyQP13KP39OZWuLnTU9oO9LNGf1anYjrYC9PpaeQv8Wna5SJF6frpGX5M4kHWAjKRLTbDlIMHb/0O0svXlhyF1wbY7u3zK6h91kTwpAH7G9AeT9UpCUyFmFWIVkBirWtZlsnVrBapyNR3Q5pWvqzTBIpyHBfHvoxx/V8zM5aYEr7fidOzIy49c+1LCNMcfJt1PZrXqcVyAXFmeU6nWZbv6zTH8gOd5lme1+kIS1unoyw/1GmB5Uc6HWN5QQuadN/BkIsw5AIOkDCEpQNDWF6CISwVDGG5CENYFmEIyyUYwvJjGMJyGYawvKxl1dRTSePamVgGbEJgYo4eucxF5WoquVRCu2hUakOeEm6VVBTPqn9loF488oY5sBZIl8iaXzHOlY9G5fjWFS1vGjtXwLHqbx+O9jnxUtaLhT8F/9XWVCW9Ys3Dk6vwG4aebCeqNql4dE2Xz1U9uv5fVFRYC/QbSIVYKMqybHBnIoSPOp2GaqCVQ8xszDy063XLmp/D/TcxQhZQ/fg3FBoL3INOWUlZ7eCs1dfbstw7g3I4EyxJMTfz+lb4IiOz0n6RWcqej3wecAWMSmXYagOtFbzZJzEPmd4kzwRxW1E2SNrYzgSJDRzzgHnznQQmYeqqDeRO4YYN+AVhbsF5J1yieqMsh+5F7PMopPxbp+JE9qhojMCz2Rthr+9Cym9xDCQ0+aV+DFQVoakYNRXQNFJuqAZfxtm6bULGDvQjKnbDsqziw8cW95WSbRmEfKSI1aOjn9Zeok6q3H5mFJfvnb4FwSA1MX9733RxkMq7WskyR20DU7calVPXmkPjVYfq5lH1vePsEzlrmm66Jx56X9Oq28HFXCyw9m0O0lImF9T1YYUNosvFpVDqZTRJ77gHGBYY0O9Qio3/q/rYfJ4rVYXRcSTfTtS30edgDPwP2H9H9QPQ92Pocg0uz/eaE59u9OFsma6iF+un6Dcwa625WboG3NB0A+IhR62OuMoNfKcGcXqkuRzpIeBj3RXiAcAmgMXgE921jOZTAKP5jDk+wOfMYdBkDoMt5jDYZs4awA5zGOwyh8Eecxh8wZx1gC+ZwyBkDoOIOQyeMCcAeMocBl8xh8HXzGHwDXPuA3zLHAYxcxgkzGGwr+nWMMwtXtBdoLZBVaADU09Y3MPiUFNlyP6OF4b9vUHM/sEgpv6o6faQ+hMvDPVng5j6i0FM/VXTnSH1N14Y6u8GMfUPg5j6TL8Yy2UGv4x8lwoHlF1sPufvifcP28VAuQABAAH//wAPeJyFVFtvG0UUnjOzV9tx7Hi963USJ961d0lcHHe9tlOqJs7FThrSJkoCuZGQhqQIVVX6UlWUlqIkDhRV9AWkvvEEEqipaKWqKi9IOIInJP4BP6APQVWFVKQ6nE0qwQvqzmp25szZmTnnfN9HKCEHX7PbrJ10EYcMlwfSKbOtNS4w6LJ0TQ0xIlHWHGwSeE4glI0QYLBBGGUbHFBCNwgh5zPdPdluJ+N0dug8H82AKIiaaAvNYNqWLdolqwfwLfSDXdKK/ZDXVK2kiWoH8E7RzYKQAHa7JnGliU9ltXSp169uny0AwytsbQWLZ2uqH43BcG2yKAi1xkBldfXzNboCdq8q1yYKnLRT4+TSxA4TAgKPnsntiaLMbW/zEm4YaOn9Y+XWKj3/2RohgLH+zL6jz4hKust2ABgBDIhgQJTRS4QwRmYwIDZHGGHjKSVqcHwsE3aL+aRtuaWio8kgGFnoh6RhpXS6+TQSCTsR2GlcVXIpqjx98bGegsW48azFDUUijauND1tSbvhpihABz77PWpifNJMO0kNOkiHyPlkrn5utUEHqSuph3JoAHeEZxYEA5CJHKREFIl4gQSJLQXmluYlKAR8VQBKWiej3izNEFP1zxC/6x9fXzi0vzk9PTYyfHhnoV1KK5T1miG/PQFgRMmBYBQzkJDiq9op5JJwMKwnIJ50+gDyW0BREPur5oKOJkdth07BOgedd6oNS3sFSYgcJn5SSfIfd7X+HX/jEo6HoG20EJInC71SSGjf/buX4+wIHT3xS0U03cmkXCp7fXVs+pj7QumV7V/LBo8ZPnhEGvP5/xo01Gn6xH1B8PoWuD/AA/DSe+GI/OzyYpZHDSyxF2yChLPk8DGCB79BvSJi0l+NNQKiHAeph4CWclajCeC0TVjpAdUpQdC1DBNsyhNhfsdEYPNedqt4YeqxXHZg29X1dh/1YNac3zjyM5Y72p/foA9JGEuXWWNjHOIowAzyIbDCg9LwSUzQPVwUXGWG5RacDEFqq0gyaqghGD/LHsOL1etytxvfq+qij1+u6M6rX9/TR/EIq7n0dfW/Ps+3V40freTwWDv48uMGesAo5QZrLgWNAKl48EUBSpgVRsOzDVkIOFkvFBEWeYkM2YitiJW0Xl7MgFopHNk3FZTZbeXfh14Xl4aW0GY+/xUUCrYNZKSjKlbiuakNjH6z+MlQ4AZ1d05O/rV++cvmdlR6T0t5SSK6YSSFtDy/d+Oj6J+9xmhgWs32tTfLYyvLi8tCY0lI5c+rO5NT4ufIpw4CuSKR6+tLU/Oy3FfUwhciX5/QErSNfTpILDz2awsibPzRPzpUtwqEAcetYOiTLBcITxvFsxcs9zKAbmfO4Pt5ato8c6cVXeM6XZVvJJKyUwMczaYS6+BLgGsI6ogSZGIT/WDFVrmUbXvJMLFcWy5d3EhRuBqUdORiUd+TA/VDMike1BE6kwFh3ss01UjHlNdEniosS5ea/f31hNPsVOsLhPxCASsI1Olt8TbkmXwhUOZ6dioQ6HQNCQUfmqkJI+tJ4423My8Ez1OvrqNftxCZOuQdVjNMA9esQxShiGDPhVlCiKZtBRaNznriNR9R0W5Tn9UzE8u6PrE+A4GnwEeQsG0PxsKGoyHoEvODXsuXcta1rOdeROtui98zqmrmrStWJwWu58tmtx5u09mhoAK7M3i0fd5zj7s6tPim6a65VzXtKu+nkyrtLm5s/bg0OkH8AjewTWHicY2BkYGAAYv7KSdrx/DZfGbiZXwBFGK7tKKiH0f8//K9nfs3sCuRyMDCBRAFfTQ2EAAB4nGNgZGBgDvqfBSRf/P/w/y/zawagCArgBAC1WQeOAAPoAAADoAAAA6oAAAMRAAADmAAAAq4AAAPo//ACOwAAA3z/9AAAAAAAdgC0AXgBqAHmAkwC2AMyAAEAAAAJAGgABgAAAAAAAgAgADAAcwAAAHULcAAAAAB4nH2Ry2rCQBSG/3grVdpFC110NVAoSjFeoCBCQSoopTsX7mMck0jMyGQUpYs+RV+h2677Mn2W/sah1ILNEPKd75yZOTMBcIEvONg/93z37OCM0Z5zOMGj5Tz9k+UCeWy5iAp8yyX6xHIZd3ixXMEl3rmCUzhlNMenZQfXzo3lHM6dB8t5+mfLBbK0XMSV82q5RP9muYyx82G5gttcsa+WWx0FoRHVfk20m62OmGyFoooSLxbeyoRKp6InZioxMo6V66vFwo/rcmPqka+SdCSDVezpA3cQjKVOI5WIlts88EOZSO0ZOd3tmK6DtjEzMdNqIQZ2L7HUai5944bGLLuNxu8e0IfCEltoRAgQwkCgSlvjt40mWuiQJqwQrNxXRbx0DzGNhxVnhFkmZdzjO2OU0EpWxGSXv0thweEzrtNvmK1zFT+rTDGiC7hSzPX0P3XHM2O6XQdRFgt27bL34/VDuiSb42WdTn/OmGLNXtq0hifZnUZn3QsM/pxL8N52uTmNT+9mt2dou2hwHLmHb9B1klIAAHicbcE7DoAgEAXAffwUPIuHIhsCRAQCNN7ewtYZEvRx9M9CQEJBw2DDDgtHklvUI8e0jjX8TGe4+3pEuwz7yqGo2XNVKZRuZvCDE9EL22YRbAAAAHicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MTAyaIEYm7mYGDkgLD4GMIvNaRfTAaA0J5DN7rSLwQHCZmZw2ajC2BEYscGhI2Ijc4rLRjUQbxdHAwMji0NHckgESEkkEGzmYWLk0drB+L91A0vvRiYGFwAMdiP0AAA=') format('woff'),
- url('data:application/octet-stream;base64,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YFlmsAFjYCMgsAErI7AEQ2CwASuwBSVhsAUlsAJiILAAUFiwQGBZZrABY7AEJmEgsAQlYGQjsAMlYGRQWCEbIyFZIyAgsAQmI0ZhOFktsDcssAAWICAgsAUmIC5HI0cjYSM8OC2wOCywABYgsAgjQiAgIEYjR7ABKyNhOC2wOSywABawAyWwAiVHI0cjYbAAVFguIDwjIRuwAiWwAiVHI0cjYSCwBSWwBCVHI0cjYbAGJbAFJUmwAiVhuQgACABjYyMgWGIbIVljuAQAYiCwAFBYsEBgWWawAWNgIy4jICA8ijgjIVktsDossAAWILAIQyAuRyNHI2EgYLAgYGawAmIgsABQWLBAYFlmsAFjIyAgPIo4LbA7LCMgLkawAiVGUlggPFkusSsBFCstsDwsIyAuRrACJUZQWCA8WS6xKwEUKy2wPSwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xKwEUKy2wPiywNSsjIC5GsAIlRlJYIDxZLrErARQrLbA/LLA2K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrErARQrsARDLrArKy2wQCywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixKwEUKy2wQSyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKyAAEAKrEABUKzCgIBCCqxAAVCsw4AAQgqsQAGQroCwAABAAkqsQAHQroAQAABAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbMMAgEMKrgB/4WwBI2xAgBEAAA=') format('truetype');
+ src: url('data:application/octet-stream;base64,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') format('woff'),
+ url('data:application/octet-stream;base64,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') format('truetype');
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
@@ -17,7 +17,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'mcl-ext-icons';
- src: url('../font/mcl-ext-icons.svg?84545260#mcl-ext-icons') format('svg');
+ src: url('../font/mcl-ext-icons.svg?66982893#mcl-ext-icons') format('svg');
}
}
*/
@@ -52,11 +52,13 @@
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
-.icon-cog:before { content: '\e800'; } /* '' */
-.icon-right:before { content: '\e801'; } /* '' */
-.icon-trash-empty:before { content: '\e802'; } /* '' */
-.icon-ok:before { content: '\e807'; } /* '' */
-.icon-cancel:before { content: '\e808'; } /* '' */
+.icon-search:before { content: '\e800'; } /* '' */
+.icon-cog:before { content: '\e802'; } /* '' */
+.icon-cloud:before { content: '\e803'; } /* '' */
+.icon-cancel:before { content: '\e804'; } /* '' */
+.icon-right:before { content: '\e805'; } /* '' */
+.icon-ok:before { content: '\e808'; } /* '' */
.icon-spin:before { content: '\e839'; } /* '' */
.icon-help:before { content: '\f128'; } /* '' */
-.icon-search:before { content: '\f50d'; } /* '' */
\ No newline at end of file
+.icon-trash:before { content: '\f1f8'; } /* '' */
+.icon-server:before { content: '\f233'; } /* '' */
\ No newline at end of file
diff --git a/app/fonts/fontello/css/mcl-ext-icons-ie7-codes.css b/app/fonts/fontello/css/mcl-ext-icons-ie7-codes.css
index 88e07f6..68c5d33 100644
--- a/app/fonts/fontello/css/mcl-ext-icons-ie7-codes.css
+++ b/app/fonts/fontello/css/mcl-ext-icons-ie7-codes.css
@@ -1,9 +1,11 @@
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-trash-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-spin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-help { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
\ No newline at end of file
+.icon-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-server { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
\ No newline at end of file
diff --git a/app/fonts/fontello/css/mcl-ext-icons-ie7.css b/app/fonts/fontello/css/mcl-ext-icons-ie7.css
index 8832747..068576f 100644
--- a/app/fonts/fontello/css/mcl-ext-icons-ie7.css
+++ b/app/fonts/fontello/css/mcl-ext-icons-ie7.css
@@ -10,11 +10,13 @@
/* font-size: 120%; */
}
-.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-trash-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-spin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
.icon-help { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
-.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
\ No newline at end of file
+.icon-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
+.icon-server { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
\ No newline at end of file
diff --git a/app/fonts/fontello/css/mcl-ext-icons.css b/app/fonts/fontello/css/mcl-ext-icons.css
index 9e1995f..22e025b 100644
--- a/app/fonts/fontello/css/mcl-ext-icons.css
+++ b/app/fonts/fontello/css/mcl-ext-icons.css
@@ -1,11 +1,11 @@
@font-face {
font-family: 'mcl-ext-icons';
- src: url('../font/mcl-ext-icons.eot?20163023');
- src: url('../font/mcl-ext-icons.eot?20163023#iefix') format('embedded-opentype'),
- url('../font/mcl-ext-icons.woff2?20163023') format('woff2'),
- url('../font/mcl-ext-icons.woff?20163023') format('woff'),
- url('../font/mcl-ext-icons.ttf?20163023') format('truetype'),
- url('../font/mcl-ext-icons.svg?20163023#mcl-ext-icons') format('svg');
+ src: url('../font/mcl-ext-icons.eot?98126950');
+ src: url('../font/mcl-ext-icons.eot?98126950#iefix') format('embedded-opentype'),
+ url('../font/mcl-ext-icons.woff2?98126950') format('woff2'),
+ url('../font/mcl-ext-icons.woff?98126950') format('woff'),
+ url('../font/mcl-ext-icons.ttf?98126950') format('truetype'),
+ url('../font/mcl-ext-icons.svg?98126950#mcl-ext-icons') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'mcl-ext-icons';
- src: url('../font/mcl-ext-icons.svg?20163023#mcl-ext-icons') format('svg');
+ src: url('../font/mcl-ext-icons.svg?98126950#mcl-ext-icons') format('svg');
}
}
*/
@@ -55,11 +55,13 @@
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
-.icon-cog:before { content: '\e800'; } /* '' */
-.icon-right:before { content: '\e801'; } /* '' */
-.icon-trash-empty:before { content: '\e802'; } /* '' */
-.icon-ok:before { content: '\e807'; } /* '' */
-.icon-cancel:before { content: '\e808'; } /* '' */
+.icon-search:before { content: '\e800'; } /* '' */
+.icon-cog:before { content: '\e802'; } /* '' */
+.icon-cloud:before { content: '\e803'; } /* '' */
+.icon-cancel:before { content: '\e804'; } /* '' */
+.icon-right:before { content: '\e805'; } /* '' */
+.icon-ok:before { content: '\e808'; } /* '' */
.icon-spin:before { content: '\e839'; } /* '' */
.icon-help:before { content: '\f128'; } /* '' */
-.icon-search:before { content: '\f50d'; } /* '' */
\ No newline at end of file
+.icon-trash:before { content: '\f1f8'; } /* '' */
+.icon-server:before { content: '\f233'; } /* '' */
\ No newline at end of file
diff --git a/app/fonts/fontello/demo.html b/app/fonts/fontello/demo.html
index 0ec916f..8220bdc 100644
--- a/app/fonts/fontello/demo.html
+++ b/app/fonts/fontello/demo.html
@@ -229,11 +229,11 @@
}
@font-face {
font-family: 'mcl-ext-icons';
- src: url('./font/mcl-ext-icons.eot?50590932');
- src: url('./font/mcl-ext-icons.eot?50590932#iefix') format('embedded-opentype'),
- url('./font/mcl-ext-icons.woff?50590932') format('woff'),
- url('./font/mcl-ext-icons.ttf?50590932') format('truetype'),
- url('./font/mcl-ext-icons.svg?50590932#mcl-ext-icons') format('svg');
+ src: url('./font/mcl-ext-icons.eot?16275132');
+ src: url('./font/mcl-ext-icons.eot?16275132#iefix') format('embedded-opentype'),
+ url('./font/mcl-ext-icons.woff?16275132') format('woff'),
+ url('./font/mcl-ext-icons.ttf?16275132') format('truetype'),
+ url('./font/mcl-ext-icons.svg?16275132#mcl-ext-icons') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -298,16 +298,20 @@ mcl-ext-icons font demo
-
icon-cog 0xe800
-
icon-right 0xe801
-
icon-trash-empty 0xe802
-
icon-ok 0xe807
+
icon-search 0xe800
+
icon-cog 0xe802
+
icon-cloud 0xe803
+
icon-cancel 0xe804
-
icon-cancel 0xe808
+
icon-right 0xe805
+
icon-ok 0xe808
icon-spin 0xe839
icon-help 0xf128
-
icon-search 0xf50d
+
+
+
icon-trash 0xf1f8
+
icon-server 0xf233
diff --git a/app/fonts/fontello/font/mcl-ext-icons.eot b/app/fonts/fontello/font/mcl-ext-icons.eot
index dd34567..e02356a 100644
Binary files a/app/fonts/fontello/font/mcl-ext-icons.eot and b/app/fonts/fontello/font/mcl-ext-icons.eot differ
diff --git a/app/fonts/fontello/font/mcl-ext-icons.svg b/app/fonts/fontello/font/mcl-ext-icons.svg
index 2c79b8b..8d93d49 100644
--- a/app/fonts/fontello/font/mcl-ext-icons.svg
+++ b/app/fonts/fontello/font/mcl-ext-icons.svg
@@ -1,26 +1,30 @@
-Copyright (C) 2018 by original authors @ fontello.com
+Copyright (C) 2019 by original authors @ fontello.com
-
+
-
+
-
+
-
+
-
+
+
+
-
+
+
+
\ No newline at end of file
diff --git a/app/fonts/fontello/font/mcl-ext-icons.ttf b/app/fonts/fontello/font/mcl-ext-icons.ttf
index 8ff513a..7ebf2f2 100644
Binary files a/app/fonts/fontello/font/mcl-ext-icons.ttf and b/app/fonts/fontello/font/mcl-ext-icons.ttf differ
diff --git a/app/fonts/fontello/font/mcl-ext-icons.woff b/app/fonts/fontello/font/mcl-ext-icons.woff
index b5e7141..0a40148 100644
Binary files a/app/fonts/fontello/font/mcl-ext-icons.woff and b/app/fonts/fontello/font/mcl-ext-icons.woff differ
diff --git a/app/fonts/fontello/font/mcl-ext-icons.woff2 b/app/fonts/fontello/font/mcl-ext-icons.woff2
index 1cd6074..ab43bc4 100644
Binary files a/app/fonts/fontello/font/mcl-ext-icons.woff2 and b/app/fonts/fontello/font/mcl-ext-icons.woff2 differ
diff --git a/app/html/extension/about.html b/app/html/extension/about.html
index 3688305..80b1089 100755
--- a/app/html/extension/about.html
+++ b/app/html/extension/about.html
@@ -4,6 +4,10 @@
+
+
+
+
@@ -25,18 +29,22 @@
Feed API Limit
{{vm.apikeyInfo.feedLimit}}
+
+ Sandbox API Limit
+ {{vm.apikeyInfo.sandboxLimit}}
+
Paid User
{{vm.__MSG.getMessage((vm.apikeyInfo.paidUser) ? 'yes' : 'no')}}
- Limit Interval
- {{vm.apikeyInfo.limitInterval}}
+ Maximum file size
+ {{vm.apikeyInfo.maxUploadFileSize}} MB
{{vm.__MSG.getMessage('contactOpswat')}}
-
+
@@ -55,29 +63,4 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/html/extension/history.html b/app/html/extension/history.html
index 6484c04..12901ab 100755
--- a/app/html/extension/history.html
+++ b/app/html/extension/history.html
@@ -31,11 +31,14 @@
-
- {{file.fileName | decodeFileNameFilter}}
-
- {{file.sha256}}
-
+
+
{{vm.momentFrom(file.scanTime)}}
@@ -43,7 +46,7 @@
{{file.statusLabel}}
-
+
diff --git a/app/html/extension/settings.html b/app/html/extension/settings.html
index 8fe5635..baac468 100755
--- a/app/html/extension/settings.html
+++ b/app/html/extension/settings.html
@@ -1,24 +1,73 @@
\ No newline at end of file
diff --git a/app/images/how-to/more-info.png b/app/images/how-to/more-info.png
deleted file mode 100644
index 70e0bf0..0000000
Binary files a/app/images/how-to/more-info.png and /dev/null differ
diff --git a/app/images/how-to/popup.png b/app/images/how-to/popup.png
deleted file mode 100644
index 38d2d34..0000000
Binary files a/app/images/how-to/popup.png and /dev/null differ
diff --git a/app/images/how-to/save-clean.png b/app/images/how-to/save-clean.png
deleted file mode 100644
index 6f6f47b..0000000
Binary files a/app/images/how-to/save-clean.png and /dev/null differ
diff --git a/app/images/how-to/scan-all.png b/app/images/how-to/scan-all.png
deleted file mode 100644
index 69f3793..0000000
Binary files a/app/images/how-to/scan-all.png and /dev/null differ
diff --git a/app/manifest.json b/app/manifest.json
index ab51548..8a0018b 100755
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -2,7 +2,7 @@
"name": "__MSG_appName__",
"short_name": "__MSG_appShortName__",
"description": "__MSG_appDescription__",
- "version": "3.8.1",
+ "version": "3.9.0",
"manifest_version": 2,
"default_locale": "en",
"icons": {
diff --git a/app/scripts/background/background-task.js b/app/scripts/background/background-task.js
index d6d1155..22e76f6 100644
--- a/app/scripts/background/background-task.js
+++ b/app/scripts/background/background-task.js
@@ -9,11 +9,12 @@ import { BROWSER_EVENT } from '../common/browser/browser-message-event';
import { settings } from '../common/persistent/settings';
import { apikeyInfo } from '../common/persistent/apikey-info';
import { scanHistory } from '../common/persistent/scan-history';
+import CoreClient from '../common/core-client';
import MetascanClient from '../common/metascan-client';
import FileProcessor from '../common/file-processor';
import cookieManager from './cookie-manager';
-import DownloadsManager from './download-manager';
+import DownloadManager from './download-manager';
import { goToTab } from './navigation';
import SafeUrl from './safe-url';
@@ -22,21 +23,10 @@ const MCL_CONFIG = MCL.config;
class BackgroundTask {
constructor() {
-
this.apikeyInfo = apikeyInfo;
this.settings = settings;
this.scanHistory = scanHistory;
- MetascanClient
- .configure({
- pollingIncrementor: MCL_CONFIG.scanResults.incrementor,
- pollingMaxInterval: MCL_CONFIG.scanResults.maxInterval
- })
- .setHost(MCL_CONFIG.metadefenderDomain)
- .setVersion(MCL_CONFIG.metadefenderVersion);
-
- this.fileProcessor = new FileProcessor(MetascanClient);
-
cookieManager.onChange(info => {
const cookie = info.cookie;
@@ -55,22 +45,33 @@ class BackgroundTask {
chrome.notifications.onClosed.addListener(() => { });
browserMessage.addListener(this.messageListener.bind(this));
-
}
async init() {
const settings = this.settings;
const apiKeyInfo = this.apikeyInfo;
const scanHistory = this.scanHistory;
- const fileProcessor = this.fileProcessor;
await settings.init();
await apiKeyInfo.init();
await scanHistory.init();
await scanHistory.cleanPendingFiles();
- await fileProcessor.init();
+
+ MetascanClient.configure({
+ pollingIncrementor: MCL_CONFIG.scanResults.incrementor,
+ pollingMaxInterval: MCL_CONFIG.scanResults.maxInterval
+ })
+ .setHost(MCL_CONFIG.metadefenderDomain)
+ .setVersion(MCL_CONFIG.metadefenderVersion);
+
+ CoreClient.configure({
+ apikey: settings.coreApikey,
+ endpoint: settings.coreUrl,
+ pollingIncrementor: MCL_CONFIG.scanResults.incrementor,
+ pollingMaxInterval: MCL_CONFIG.scanResults.maxInterval,
+ });
- this.downloadsManager = new DownloadsManager(fileProcessor);
+ this.downloadsManager = new DownloadManager(FileProcessor);
const downloadsManager = this.downloadsManager;
chrome.downloads.onCreated.addListener(downloadsManager.trackInProgressDownloads.bind(downloadsManager));
@@ -113,7 +114,8 @@ class BackgroundTask {
try {
cookieData = JSON.parse(cookieData);
} catch (error) {
- console.log('setApikey failed', error);
+ browserNotification.create(error, 'info');
+ _gaq.push(['exception', {exDescription: 'background-task:setApikey' + JSON.stringify(error)}]);
}
if (apikeyInfo.apikey === cookieData.apikey && apikeyInfo.loggedIn === cookieData.loggedIn) {
@@ -193,7 +195,10 @@ class BackgroundTask {
/**
* Extension notifications click event handler
*/
- async handleNotificationClicks() {
+ async handleNotificationClicks(notificationId) {
+ if (notificationId == 'info') {
+ return;
+ }
goToTab('history');
}
@@ -223,6 +228,11 @@ class BackgroundTask {
await settings.load();
+ CoreClient.configure({
+ apikey: settings.coreApikey,
+ endpoint: settings.coreUrl
+ });
+
if (settings.saveCleanFiles !== saveCleanFiles) {
this.updateContextMenu();
}
diff --git a/app/scripts/common/browser/browser-notification.js b/app/scripts/common/browser/browser-notification.js
index eb36e55..858bf68 100755
--- a/app/scripts/common/browser/browser-notification.js
+++ b/app/scripts/common/browser/browser-notification.js
@@ -40,7 +40,7 @@ async function create(message, id, fileInfected) {
title: chrome.i18n.getMessage('appName'),
message: message,
priority: 1,
- isClickable: true
+ isClickable: (typeof fileInfected !== 'undefined')
};
if (typeof id === 'undefined') {
@@ -50,8 +50,8 @@ async function create(message, id, fileInfected) {
chrome.notifications.create(String(id), optionObject, clearNotification);
}
- } catch (e) {
- console.error(e);
+ } catch (error) {
+ _gaq.push(['exception', {exDescription: 'browser-notification:create' + JSON.stringify(error)}]);
}
}
diff --git a/app/scripts/common/core-client.js b/app/scripts/common/core-client.js
new file mode 100755
index 0000000..8845454
--- /dev/null
+++ b/app/scripts/common/core-client.js
@@ -0,0 +1,257 @@
+'use strict';
+
+import 'chromereload/devonly';
+
+import browserMessage from './browser/browser-message';
+import { BROWSER_EVENT } from './browser/browser-message-event';
+
+import xhr from 'xhr';
+
+/**
+ *
+ * @type {{configure: configure, setAuth: setAuth, hash: {lookup: hashLookup}, file: {upload: fileUpload, lookup: fileLookup, poolForResults: poolForResults}}}
+ */
+const CoreClient = {
+ configure: configure,
+ setAuth: setAuth,
+
+ // endpoints
+ file: {
+ upload: fileUpload,
+ lookup: fileLookup,
+ poolForResults: poolForResults,
+ checkSanitized: checkSanitized
+ },
+ hash: {
+ lookup: hashLookup
+ },
+ version: getVersion,
+ rules: getRules
+};
+
+export default CoreClient;
+
+const config = {
+ apikey: null,
+ endpoint: null,
+ pollingIncrementor: 1,
+ pollingMaxInterval: 10000
+};
+
+const authHeader = {
+ 'apikey': null
+};
+
+/**
+ * Overwrite default configuration.
+ *
+ * @param {*} conf
+ */
+function configure(conf){
+ for (let c in conf) {
+ if (Object.prototype.hasOwnProperty.call(config, c)) {
+ config[c] = conf[c];
+ }
+ }
+
+ setAuth(config.apikey);
+ return this;
+}
+
+/**
+ * Set client authentication.
+ *
+ * @param apikey
+ * @returns {setAuth}
+ */
+function setAuth(apikey) {
+ authHeader['apikey'] = apikey;
+ return this;
+}
+
+/**
+ * https://onlinehelp.opswat.com/corev4/8.1.3.1._Process_a_file.html
+ *
+ * @param {any} fileData file content
+ * @param {string} fileName file name
+ * @param {boolean} canBeSanitized a flag for sanitizable files
+ * @param {string} rule core scan workflow
+ * @returns {Promise}
+ */
+function fileUpload({fileData, fileName, rule}) {
+ let restEndpoint = `${config.endpoint}/file`;
+ const httpHeaders = {
+ 'Content-Type': 'application/octet-stream',
+ 'user_agent': 'chrome_extension',
+ 'filename': fileName,
+ };
+
+ if (rule) {
+ httpHeaders.rule = rule;
+ httpHeaders.workflow = rule;
+ }
+
+ const options = {
+ headers: Object.assign({}, authHeader, httpHeaders),
+ body: fileData
+ };
+ return callAPI(restEndpoint, options, 'post');
+}
+
+/**
+ * https://onlinehelp.opswat.com/corev4/8.1.3.2._Fetch_processing_result.html
+ *
+ * @param {string} dataId
+ * @returns {Promise}
+ */
+function fileLookup(dataId) {
+ const restEndpoint = `${config.endpoint}/file/${dataId}`;
+ const options = {
+ headers: Object.assign({}, authHeader, {})
+ };
+
+ return callAPI(restEndpoint, options);
+}
+
+/**
+ * https://onlinehelp.opswat.com/corev4/8.1.3.2._Fetch_processing_result.html
+ *
+ * @param {string} hash md5|sha1|sha256
+ * @returns {Promise}
+ */
+function hashLookup(hash) {
+ const restEndpoint = `${config.endpoint}/hash/${hash}`;
+ const options = {
+ headers: authHeader
+ };
+
+ return callAPI(restEndpoint, options);
+}
+
+/**
+ *
+ * @param {string} dataId
+ * @param {number} pollingInterval
+ * @returns {Promise}
+ */
+function poolForResults(dataId, pollingInterval) {
+ return new Promise((resolve) => {
+ recursiveLookup(dataId, pollingInterval, resolve);
+ });
+}
+
+/**
+ *
+ * @param dataId
+ * @param pollingInterval
+ * @param resolve
+ * @returns {Promise.}
+ */
+async function recursiveLookup(dataId, pollingInterval, resolve) {
+ let response = await fileLookup(dataId);
+
+ if (response.error) {
+ return;
+ }
+
+ if (response.sanitized && Object.prototype.hasOwnProperty.call(response.sanitized, 'file_path')) {
+ browserMessage.send({ event: BROWSER_EVENT.SANITIZED_FILE_READY, data: {
+ dataId,
+ sanitized: response.sanitized
+ } });
+ }
+
+ pollingInterval = Math.min(pollingInterval * config.pollingIncrementor, config.pollingMaxInterval);
+
+ if (response && response.scan_results && response.scan_results.progress_percentage < 100) {
+ setTimeout(() => { recursiveLookup(dataId, pollingInterval, resolve); }, pollingInterval);
+ }
+ else {
+ resolve(response);
+ }
+}
+
+/**
+ * https://onlinehelp.opswat.com/corev4/8.1.8.2._Get_Product_Version.html
+ *
+ * @returns {Promise}
+ */
+function getVersion() {
+ const restEndpoint = `${config.endpoint}/version`;
+ const options = {
+ headers: authHeader
+ };
+
+ return callAPI(restEndpoint, options);
+}
+
+/**
+ * /file/rules
+ */
+function getRules() {
+ const restEndpoint = `${config.endpoint}/file/rules`;
+ const options = {
+ headers: authHeader
+ };
+
+ return callAPI(restEndpoint, options);
+}
+
+/**
+ * Check if the user has access to the URL.
+ *
+ * @param {string} downloadUrl
+ */
+function checkSanitized(downloadUrl) {
+ return new Promise((resolve) => {
+ xhr({
+ method: 'get',
+ url: downloadUrl,
+ beforeSend: function(xhrObject){
+ xhrObject.onprogress = (event) => {
+ if (event.target.status === 200) {
+ xhrObject.abort();
+ return resolve(true);
+ }
+ resolve(false);
+ };
+ }
+ }, (error, response) => {
+ if (response.statusCode === 200) {
+ return resolve(true);
+ }
+ resolve(false);
+ });
+ });
+}
+
+/**
+ * Call core api and handle http response and errors.
+ *
+ * @param {string} endpoint endpoint URL
+ * @param {*} options http options
+ * @param {string} verb request type: 'get' | 'post'
+ * @returns {Promise}
+ */
+function callAPI(endpoint, options, verb='get') {
+ return new Promise((resolve, reject) => {
+ xhr[verb](endpoint, options, (err, resp, body) => {
+ if (err) {
+ reject(err);
+ }
+
+ if (resp.statusCode !== 200) {
+ return reject({
+ statusCode: resp.statusCode,
+ error: resp.err
+ });
+ }
+
+ try {
+ resolve(JSON.parse(body));
+ } catch (error) {
+ reject(error);
+ }
+ });
+ });
+}
diff --git a/app/scripts/common/file-processor.js b/app/scripts/common/file-processor.js
index 2b2ef99..aeb5178 100644
--- a/app/scripts/common/file-processor.js
+++ b/app/scripts/common/file-processor.js
@@ -12,266 +12,322 @@ import { apikeyInfo } from '../common/persistent/apikey-info';
import { scanHistory } from '../common/persistent/scan-history';
import BrowserNotification from '../common/browser/browser-notification';
import BrowserMessage from '../common/browser/browser-message';
-
-let MetascanClient;
+import CoreClient from '../common/core-client';
+import MetascanClient from '../common/metascan-client';
const ON_SCAN_COMPLETE_LISTENERS = [];
/**
*
- * @param metascanClient
* @constructor
*/
-function FileProcessor(metascanClient) {
-
- MetascanClient = metascanClient;
-
- this.browserMessage = BrowserMessage;
- this.processTarget = processTarget;
- this.getDownloadedFile = getDownloadedFile;
- this.handleFileScanResults = handleFileScanResults;
- this.startStatusPolling = startStatusPolling;
- this.init = init;
- this.addOnScanCompleteListener = addOnScanCompleteListener;
- this.removeOnScanCompleteListener = removeOnScanCompleteListener;
- this.callOnScanCompleteListeners = callOnScanCompleteListeners;
-}
+class FileProcessor {
+ /**
+ * Proccess a link to a file or a downloaded file.
+ *
+ * @param {string} linkUrl file url
+ * @param {*} downloadItem https://developer.chrome.com/extensions/downloads#type-DownloadItem
+ */
+ async processTarget(linkUrl, downloadItem) {
+
+ if (!apikeyInfo.apikey) {
+ BrowserNotification.create(chrome.i18n.getMessage('undefinedApiKey'));
+ return;
+ }
-async function init() {
- await settings.init();
- await apikeyInfo.init();
- await scanHistory.init();
-}
+ let file = new ScanFile();
-export default FileProcessor;
+ if (ScanFile.isSanitizedFile(linkUrl)) {
+ return;
+ }
-async function processTarget(linkUrl, downloadItem) {
+ if (downloadItem) {
+ file.fileName = downloadItem.filename.split('/').pop();
+ file.size = downloadItem.fileSize;
+ }
+ else {
+ file.fileName = linkUrl.split('/').pop();
+ file.fileName = file.fileName.split('?')[0];
+ try {
+ file.size = await ScanFile.getFileSize(linkUrl, file.fileName);
+ }
+ catch (errMsg) {
+ if (errMsg) {
+ BrowserNotification.create(errMsg);
+ }
+ return;
+ }
+ }
- if (!apikeyInfo.apikey) {
- BrowserNotification.create(chrome.i18n.getMessage('undefinedApiKey'));
- return;
- }
+ file.fileName = decodeFileName(file.fileName);
- let file = new ScanFile();
+ file.extension = file.fileName.split('.').pop();
+ file.canBeSanitized = file.extension && SANITIZATION_FILE_TYPES.indexOf(file.extension.toLowerCase()) > -1;
- if (ScanFile.isSanitizedFile(linkUrl)) {
- return;
- }
+ if (file.size === null ) {
+ BrowserNotification.create(chrome.i18n.getMessage('fileEmpty'));
+ return;
+ }
- if (downloadItem) {
- file.fileName = downloadItem.filename.split('/').pop();
- file.size = downloadItem.fileSize;
- }
- else {
- file.fileName = linkUrl.split('/').pop();
- file.fileName = file.fileName.split('?')[0];
- try {
- file.size = await ScanFile.getFileSize(linkUrl, file.fileName);
+ if (file.size > MCL.config.fileSizeLimit) {
+ BrowserNotification.create(chrome.i18n.getMessage('fileSizeLimitExceeded'));
+ return;
}
- catch (errMsg) {
- if (errMsg) {
- BrowserNotification.create(errMsg);
+
+ file.statusLabel = ScanFile.getScanStatusLabel();
+
+ await scanHistory.addFile(file);
+
+ let fileData = null;
+
+ if (downloadItem) {
+ try {
+ fileData = await this.getDownloadedFile(downloadItem.localPath || 'file://' + downloadItem.filename);
+ BrowserNotification.create(chrome.i18n.getMessage('scanStarted') + file.fileName, file.id);
+ }
+ catch (e) {
+ BrowserNotification.create(e, file.id);
+ scanHistory.removeFile(file);
+ return;
}
- return;
}
- }
+ else {
+ if (file.size === 0) {
+ BrowserNotification.create(chrome.i18n.getMessage('fileEmpty'));
+ return;
+ }
- file.fileName = decodeFileName(file.fileName);
+ BrowserNotification.create(chrome.i18n.getMessage('scanStarted') + file.fileName, file.id);
+ fileData = await ScanFile.getFileData(linkUrl, file.fileName);
+ }
- file.extension = file.fileName.split('.').pop();
- file.canBeSanitized = file.extension && SANITIZATION_FILE_TYPES.indexOf(file.extension.toLowerCase()) > -1;
+ file.md5 = ScanFile.getMd5Hash(fileData);
- if (file.size === null ) {
- BrowserNotification.create(chrome.i18n.getMessage('fileEmpty'));
- return;
- }
+ if (file.fileName === '') {
+ file.fileName = file.md5;
+ }
- if (file.size > MCL.config.fileSizeLimit) {
- BrowserNotification.create(chrome.i18n.getMessage('fileSizeLimitExceeded'));
- return;
+ this.scanFile(file, linkUrl, fileData, downloadItem, settings.useCore);
}
- file.statusLabel = ScanFile.getScanStatusLabel();
+ /**
+ * Load a local file content.
+ *
+ * @param {string} localPath local file path
+ * @returns {Promise}
+ */
+ async getDownloadedFile(localPath) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.responseType = 'arraybuffer';
+ xhr.onreadystatechange = function () {
+ if (this.readyState === XMLHttpRequest.DONE) {
+ if (this.status === 0 && this.response === null) {
+ reject(chrome.i18n.getMessage('errorCors'));
+ }
+
+ resolve(this.response);
+ }
+ };
+ xhr.open('GET', localPath);
+ xhr.send();
+ });
+ }
- await scanHistory.addFile(file);
+ /**
+ * Register callback that will run on scan complete with file data.
+ *
+ * @param {*} callback
+ */
+ addOnScanCompleteListener(callback) {
+ ON_SCAN_COMPLETE_LISTENERS.push(callback);
+ }
- let fileData = null;
+ /**
+ * Remove registered callback.
+ *
+ * @param {*} callback
+ */
+ removeOnScanCompleteListener(callback) {
+ const index = ON_SCAN_COMPLETE_LISTENERS.indexOf(callback);
- if (downloadItem) {
- try {
- fileData = await this.getDownloadedFile(downloadItem.localPath || 'file://' + downloadItem.filename);
- BrowserNotification.create(chrome.i18n.getMessage('scanStarted') + file.fileName, file.id);
- }
- catch (e) {
- BrowserNotification.create(e, file.id);
- scanHistory.removeFile(file);
- return;
+ if (index > -1) {
+ ON_SCAN_COMPLETE_LISTENERS.splice(index, 1);
}
}
- else {
- if (file.size === 0) {
- BrowserNotification.create(chrome.i18n.getMessage('fileEmpty'));
+
+ /**
+ * Call all registered listeners and pass the payload.
+ *
+ * @param {*} payload
+ */
+ callOnScanCompleteListeners(payload) {
+ if (!ON_SCAN_COMPLETE_LISTENERS.length) {
return;
}
- BrowserNotification.create(chrome.i18n.getMessage('scanStarted') + file.fileName, file.id);
- fileData = await ScanFile.getFileData(linkUrl, file.fileName);
- }
-
- file.md5 = ScanFile.getMd5Hash(fileData);
-
- if (file.fileName === '') {
- file.fileName = file.md5;
+ for (let i=0; i}
+ */
+ async handleFileScanResults(file, info, linkUrl, fileData, downloaded) {
+ if (info.scan_results) {
+ file.status = ScanFile.getScanStatus(info.scan_results.scan_all_result_i);
+ file.statusLabel = ScanFile.getScanStatusLabel(info.scan_results.scan_all_result_i);
+ }
+ file.sha256 = info.file_info.sha256;
+ file.dataId = info.data_id;
+
+ if (file.useCore) {
+ file.scanResults = `${settings.coreUrl}/#/user/dashboard/processinghistory/dataId/${file.dataId}`;
+ const postProcessing = info.process_info && info.process_info.post_processing;
+ const sanitizationSuccessfull = postProcessing && postProcessing.sanitization_details && postProcessing.sanitization_details.description === 'Sanitized successfully.';
+ const sanitized = postProcessing && postProcessing.actions_ran.indexOf('Sanitized') !== -1;
+ if (sanitizationSuccessfull || sanitized) {
+ const sanitizedFileURL = `${settings.coreUrl}/file/converted/${file.dataId}?apikey=${settings.coreApikey}`;
+ // verify if the user has access
+ if (await CoreClient.file.checkSanitized(sanitizedFileURL)) {
+ file.sanitizedFileURL = sanitizedFileURL;
}
-
- file.dataId = response.data_id;
- scanHistory.save();
- await this.startStatusPolling(file, linkUrl, fileData);
- return;
}
- throw response.error;
+ }
+ else {
+ file.scanResults = `${MCL.config.mclDomain}/results#!/file/${file.dataId}/regular/overview`;
+ if (info.sanitized && info.sanitized.file_path && !Object.prototype.hasOwnProperty.call(file, 'sanitizedFileURL')) {
+ file.sanitizedFileURL = info.sanitized.file_path;
+ }
}
- this.handleFileScanResults(file, response, linkUrl, fileData, !!downloadItem);
- }
- catch (reject){
- console.error(reject);
+ await scanHistory.save();
+ BrowserMessage.send({
+ event: BROWSER_EVENT.SCAN_COMPLETE
+ });
+
+ let notificationMessage = file.fileName + chrome.i18n.getMessage('fileScanComplete');
+ notificationMessage += (file.status === ScanFile.STATUS.INFECTED) ? chrome.i18n.getMessage('threatDetected') : chrome.i18n.getMessage('noThreatDetected');
+
+ BrowserNotification.create(notificationMessage, file.id, file.status === ScanFile.STATUS.INFECTED);
+
+ this.callOnScanCompleteListeners({
+ status: file.status,
+ downloaded,
+ fileData,
+ linkUrl,
+ name: file.fileName
+ });
}
-}
-/**
- *
- * @param localPath
- * @returns {Promise}
- */
-async function getDownloadedFile(localPath) {
- return new Promise((resolve, reject) => {
- let xhr = new XMLHttpRequest();
- xhr.responseType = 'arraybuffer';
- xhr.onreadystatechange = function () {
- if (this.readyState === XMLHttpRequest.DONE) {
- if (this.status === 0 && this.response === null) {
- reject(chrome.i18n.getMessage('errorCors'));
- }
+ /**
+ *
+ * @param {*} file file info
+ * @param {string} linkUrl file file url
+ * @param {*} fileData file content
+ * @param {boolean} downloaded flag for files that are already downloaded
+ * @returns {Promise.}
+ */
+ async startStatusPolling(file, linkUrl, fileData, downloaded) {
+ let response;
+
+ if (file.useCore) {
+ file.scanResults = `${settings.coreUrl}/#/user/dashboard/processinghistory/dataId/${file.dataId}`;
+ await scanHistory.save();
+ response = await CoreClient.file.poolForResults(file.dataId, 3000);
- resolve(this.response);
- }
- };
- xhr.open('GET', localPath);
- xhr.send();
- });
-}
-/**
- * Register callback that will run on scan complete with file data
- * @param {*} callback
- */
-function addOnScanCompleteListener(callback) {
- ON_SCAN_COMPLETE_LISTENERS.push(callback);
-}
+ }
+ else {
+ file.scanResults = `${MCL.config.mclDomain}/results#!/file/${file.dataId}/regular/overview`;
+ await scanHistory.save();
+ response = await MetascanClient.setAuth(apikeyInfo.apikey).file.poolForResults(file.dataId, 3000);
+ }
-/**
- * Remove registered callback
- * @param {*} callback
- */
-function removeOnScanCompleteListener(callback) {
- const index = ON_SCAN_COMPLETE_LISTENERS.indexOf(callback);
+ if (response.error) {
+ return;
+ }
- if (index > -1) {
- ON_SCAN_COMPLETE_LISTENERS.splice(index, 1);
- }
-}
-/**
- * Call all registered listeners and pass the payload
- * @param {*} payload
- */
-function callOnScanCompleteListeners(payload) {
- if (!ON_SCAN_COMPLETE_LISTENERS.length) {
- return;
+ this.handleFileScanResults(file, response, linkUrl, fileData, downloaded);
}
- for (let i=0; i}
- */
-async function handleFileScanResults(file, info, linkUrl, fileData, downloaded) {
- file.status = ScanFile.getScanStatus(info.scan_results.scan_all_result_i);
- file.statusLabel = ScanFile.getScanStatusLabel(info.scan_results.scan_all_result_i);
- file.sha256 = info.file_info.sha256;
- file.dataId = info.data_id;
- file.scanResults = `${MCL.config.mclDomain}/results#!/file/${file.dataId}/regular/overview`;
-
- if (info.sanitized && info.sanitized.file_path && !Object.prototype.hasOwnProperty.call(file, 'sanitizedFileURL')) {
- file.sanitizedFileURL = info.sanitized.file_path;
- }
+ /**
+ *
+ * @param {*} file file information
+ * @param {string} linkUrl file url
+ * @param {*} fileData file content
+ * @param {*} downloadItem https://developer.chrome.com/extensions/downloads#type-DownloadItem
+ * @param {boolean} useCore use core API instead of cloud
+ */
+ async scanFile(file, linkUrl, fileData, downloadItem, useCore) {
+ try {
+ file.useCore = useCore;
+ scanHistory.save();
- await scanHistory.save();
- this.browserMessage.send({
- event: BROWSER_EVENT.SCAN_COMPLETE
- });
+ let response = useCore
+ ? await this.scanWithCore(file, fileData)
+ : await this.scanWithCloud(file, fileData);
- let notificationMessage = file.fileName + chrome.i18n.getMessage('fileScanComplete');
- notificationMessage += (file.status === ScanFile.STATUS.INFECTED) ? chrome.i18n.getMessage('threatDetected') : chrome.i18n.getMessage('noThreatDetected');
+ if (!response.data_id) {
+ throw response;
+ }
- BrowserNotification.create(notificationMessage, file.id, file.status === ScanFile.STATUS.INFECTED);
+ file.dataId = response.data_id;
+ scanHistory.save();
+ await this.startStatusPolling(file, linkUrl, fileData, !!downloadItem);
- this.callOnScanCompleteListeners({
- status: file.status,
- downloaded,
- fileData,
- linkUrl,
- name: file.fileName
- });
-}
+ } catch (error) {
+ BrowserNotification.create(chrome.i18n.getMessage('scanFileError'));
+ _gaq.push(['exception', {exDescription: 'file-processor:scanFile' + JSON.stringify(error)}]);
+ }
+ }
-/**
- *
- * @param file
- * @param linkUrl
- * @param fileData
- * @returns {Promise.}
- */
-async function startStatusPolling(file, linkUrl, fileData) {
- if (!file.dataId){
- scanHistory.files.splice(scanHistory.files.indexOf(file), 1);
- scanHistory.save();
- return;
+ /**
+ * Scan a file using Metadefender Core
+ *
+ * @param {*} file file information
+ * @param {*} fileData file content
+ */
+ async scanWithCore(file, fileData) {
+ let response = await CoreClient.hash.lookup(file.md5);
+
+ if (response[file.md5] === 'Not Found') {
+ response = await CoreClient.file.upload({
+ fileData: fileData,
+ fileName: file.fileName,
+ rule: settings.coreRule
+ });
+ }
+
+ return response;
}
- file.scanResults = `${MCL.config.mclDomain}/results#!/file/${file.dataId}/regular/overview`;
- await scanHistory.save();
+ /**
+ * Scan a file using Metadefender Cloud
+ *
+ * @param {*} file file information
+ * @param {*} fileData file content
+ */
+ async scanWithCloud(file, fileData) {
+ let response = await MetascanClient.setAuth(apikeyInfo.apikey).hash.lookup(file.md5);
- let response = await MetascanClient.setAuth(apikeyInfo.apikey).file.poolForResults(file.dataId, 3000);
+ if (response.error && response.error.code === MetascanClient.ERROR_CODE.HASH_NOT_FOUND) {
+ response = await MetascanClient.setAuth(apikeyInfo.apikey).file.upload({
+ fileName: file.fileName,
+ fileData,
+ sampleSharing: settings.shareResults,
+ canBeSanitized: file.canBeSanitized
+ });
+ }
- if (response.error) {
- return;
+ return response;
}
-
- this.handleFileScanResults(file, response, linkUrl, fileData);
}
+
+export default new FileProcessor();
\ No newline at end of file
diff --git a/app/scripts/common/persistent/apikey-info.js b/app/scripts/common/persistent/apikey-info.js
index caaf313..e5f68f1 100644
--- a/app/scripts/common/persistent/apikey-info.js
+++ b/app/scripts/common/persistent/apikey-info.js
@@ -17,6 +17,8 @@ function ApikeyInfo() {
feedLimit: null,
paidUser: null,
limitInterval: 'Daily',
+ maxUploadFileSize: null,
+ sandboxLimit: null,
loggedIn: true,
// methods
@@ -64,7 +66,9 @@ async function save() {
feedLimit: this.feedLimit,
paidUser: this.paidUser,
limitInterval: this.limitInterval,
- loggedIn: this.loggedIn
+ maxUploadFileSize: this.maxUploadFileSize,
+ sandboxLimit: this.sandboxLimit,
+ loggedIn: this.loggedIn,
}});
}
@@ -78,6 +82,8 @@ function parseMclInfo(info) {
this.feedLimit = info.limit_feed;
this.paidUser = info.paid_user;
this.limitInterval = info.time_interval;
+ this.maxUploadFileSize = info.max_upload_file_size;
+ this.sandboxLimit = info.limit_sandbox;
}
/**
diff --git a/app/scripts/common/persistent/settings.js b/app/scripts/common/persistent/settings.js
index 8be04b7..0048cdb 100644
--- a/app/scripts/common/persistent/settings.js
+++ b/app/scripts/common/persistent/settings.js
@@ -12,24 +12,25 @@ import BrowserMessage from './../browser/browser-message';
* @returns {{scanDownloads: boolean, shareResults: boolean, showNotifications: boolean, saveCleanFiles: boolean, init: init, merge: merge, save: save, load: load}}
* @constructor
*/
-function Settings() {
+const Settings = {
+ scanDownloads: true,
+ shareResults: true,
+ showNotifications: true,
+ saveCleanFiles: false,
+ safeUrl: false,
+ useCore: false,
+ coreUrl: '',
+ coreApikey: '',
+ coreRule: '',
- return {
- scanDownloads: true,
- shareResults: true,
- showNotifications: true,
- saveCleanFiles: false,
- safeUrl: false,
+ // methods
+ init: init,
+ merge: merge,
+ save: save,
+ load: load,
+};
- // methods
- init: init,
- merge: merge,
- save: save,
- load: load,
- };
-}
-
-export const settings = Settings();
+export const settings = Settings;
/**
*
@@ -58,13 +59,12 @@ function merge(newData) {
* @returns {Promise.}
*/
async function save(){
- await BrowserStorage.set({[MCL.config.storageKey.settings]: {
- scanDownloads: this.scanDownloads,
- shareResults: this.shareResults,
- showNotifications: this.showNotifications,
- saveCleanFiles: this.saveCleanFiles,
- safeUrl: this.safeUrl
- }});
+ const settingKeys = ['scanDownloads', 'shareResults', 'showNotifications', 'saveCleanFiles', 'safeUrl', 'useCore', 'coreUrl', 'coreApikey', 'coreRule'];
+ const data = {};
+ for (const key of settingKeys) {
+ data[key] = this[key];
+ }
+ await BrowserStorage.set({[MCL.config.storageKey.settings]: data});
await BrowserMessage.send({event: BROWSER_EVENT.SETTINGS_UPDATED});
}
diff --git a/app/scripts/common/scan-file.js b/app/scripts/common/scan-file.js
index 00248f3..19ef10f 100755
--- a/app/scripts/common/scan-file.js
+++ b/app/scripts/common/scan-file.js
@@ -60,14 +60,26 @@ async function download(link, fileData, fileName) {
});
}
+/**
+ * Checks if an URL points to a sanitized file.
+ *
+ * @param {string} url a file url
+ * @returns {boolean} `true` if the url provided is of a sanitized file
+ */
function isSanitizedFile(url) {
const urlLow = url.toLowerCase();
+ // metadefender cloud sanitized files
for (let bucket of MCL.config.sanitizationBuckets) {
if (urlLow.indexOf(bucket) > -1 ) {
return true;
}
}
+
+ // metadefender core sanitized files
+ if (urlLow.indexOf('/file/converted/') > -1 && urlLow.indexOf('?apikey=') > -1) {
+ return true;
+ }
}
function getFileSize(url, filename){
diff --git a/app/scripts/extension/settings.controller.js b/app/scripts/extension/settings.controller.js
index 664126b..c36100f 100755
--- a/app/scripts/extension/settings.controller.js
+++ b/app/scripts/extension/settings.controller.js
@@ -1,11 +1,12 @@
'use strict';
import 'chromereload/devonly';
+import CoreClient from './../common/core-client';
-settingsController.$inject = ['$scope', '$timeout', 'browserTranslate', 'browserExtension', 'settings', 'apikeyInfo', 'CONFIG', 'EVENT'];
+settingsController.$inject = ['$scope', '$timeout', 'browserTranslate', 'browserExtension', 'browserNotification', 'settings', 'apikeyInfo', 'CONFIG', 'EVENT'];
/* @ngInject */
-function settingsController($scope, $timeout, browserTranslate, browserExtension, settings, apikeyInfo, CONFIG, EVENT){
+function settingsController($scope, $timeout, browserTranslate, browserExtension, browserNotification, settings, apikeyInfo, CONFIG, EVENT){
let vm = this; // use vm instead of $scope
vm.title = 'settingsController';
@@ -16,9 +17,34 @@ function settingsController($scope, $timeout, browserTranslate, browserExtension
vm.apikeyInfo = apikeyInfo;
vm.settings = settings;
-
+ vm.coreSettings = {
+ useCore: false,
+ apikey: {
+ value: '',
+ valid: undefined,
+ groupClass: {},
+ iconClass: {},
+ },
+ url: {
+ value: '',
+ valid: undefined,
+ groupClass: {},
+ iconClass: {},
+ },
+ rule: {
+ value: ''
+ },
+ scanRules: [],
+ };
+
vm.settingsChanged = settingsChanged;
vm.openExtensionSettings = openExtensionSettings;
+ vm.validateCoreSettings = validateCoreSettings;
+
+ CoreClient.configure({
+ pollingIncrementor: CONFIG.scanResults.incrementor,
+ pollingMaxInterval: CONFIG.scanResults.maxInterval,
+ });
activate();
@@ -34,27 +60,67 @@ function settingsController($scope, $timeout, browserTranslate, browserExtension
await vm.apikeyInfo.init();
await vm.settings.init();
+ vm.coreSettings.useCore = vm.settings.useCore;
+ vm.coreSettings.apikey.value = vm.settings.coreApikey || '';
+ vm.coreSettings.url.value = vm.settings.coreUrl || '';
+ vm.coreSettings.rule.value = vm.settings.coreRule || '';
+
vm.isAllowedFileAccess = await browserExtension.isAllowedFileSchemeAccess();
if (!vm.isAllowedFileAccess) {
settingsChanged('scanDownloads');
}
- $scope.$apply();
+ if (vm.coreSettings.useCore) {
+ vm.validateCoreSettings();
+ }
+
+ $timeout(() => {
+ initDropdowns();
+ $scope.$apply();
+ });
+ }
+
+ function initDropdowns() {
+ vm.coreSettings.rule.value = vm.settings.coreRule || vm.coreSettings.scanRules[0];
}
- async function settingsChanged(property) {
- if (property === 'scanDownloads' && !vm.settings[property]) {
+ async function settingsChanged(key) {
+ if (key === 'coreSettings') {
+ $scope.coreSettingsForm.$setPristine();
+ vm.settings.coreApikey = vm.coreSettings.apikey.value;
+ vm.settings.coreUrl = vm.coreSettings.url.value;
+ if (await vm.validateCoreSettings()) {
+ vm.settings.useCore = vm.coreSettings.useCore;
+ vm.settings.coreRule = vm.coreSettings.rule.value;
+ browserNotification.create(browserTranslate.getMessage('coreSettingsSavedNotification'), 'info');
+ }
+ else {
+ vm.settings.useCore = false;
+ browserNotification.create(browserTranslate.getMessage('coreSettingsInvalidNotification'), 'info');
+ }
+ await vm.settings.save();
+ $timeout(() => { $scope.$apply(); });
+ return;
+ }
+
+ if (key === 'useCore') {
+ vm.coreSettings.useCore = !vm.coreSettings.useCore;
+ if (!vm.coreSettings.useCore || await vm.validateCoreSettings()) {
+ vm.settings.useCore = vm.coreSettings.useCore;
+ }
+ }
+ else if (key === 'scanDownloads' && !vm.settings[key]) {
vm.isAllowedFileAccess = await browserExtension.isAllowedFileSchemeAccess();
- vm.settings[property] = vm.isAllowedFileAccess;
+ vm.settings[key] = vm.isAllowedFileAccess;
}
else {
- vm.settings[property] = !vm.settings[property];
+ vm.settings[key] = !vm.settings[key];
}
- vm.settings.save();
-
- _gaq.push(['_trackEvent', MCL.config.gaEventCategory.name, MCL.config.gaEventCategory.action.settingsChanged, property, (vm.settings[property] ? 'enabled' : 'disabled')]);
+ await vm.settings.save();
$timeout(() => { $scope.$apply(); });
+
+ _gaq.push(['_trackEvent', MCL.config.gaEventCategory.name, MCL.config.gaEventCategory.action.settingsChanged, key, (vm.settings[key] ? 'enabled' : 'disabled')]);
}
function refreshSettings() {
@@ -74,6 +140,73 @@ function settingsController($scope, $timeout, browserTranslate, browserExtension
}
});
}
+
+ /**
+ * // settings.coreApikey;
+ * // settings.coreUrl;
+ * // settings.coreWorkflow;
+ *
+ * vm.settings.coreApikey
+ */
+ async function validateCoreSettings() {
+ if (!vm.coreSettings.apikey.value || !vm.coreSettings.url.value) {
+ return;
+ }
+
+ CoreClient.configure({
+ apikey: vm.coreSettings.apikey.value,
+ endpoint: vm.coreSettings.url.value,
+ });
+
+ try {
+ let result = await CoreClient.version();
+ result = await CoreClient.rules('');
+ vm.coreSettings.scanRules = result.map(r => r.name);
+
+ setInputState(vm.coreSettings.apikey, 'success');
+ setInputState(vm.coreSettings.url, 'success');
+
+ $timeout(() => { $scope.$apply(); });
+ return true;
+ } catch (error) {
+ if (error.statusCode === 403) {
+ setInputState(vm.coreSettings.apikey, 'error');
+ $timeout(() => { $scope.$apply(); });
+ return false;
+ }
+ setInputState(vm.coreSettings.url, 'error');
+ $timeout(() => { $scope.$apply(); });
+ return false;
+ }
+ }
}
export default settingsController;
+
+/**
+ *
+ * @param {*} element
+ * @param {string} state input state: 'success' | 'error' | undefined
+ */
+function setInputState(element, state) {
+ switch (state) {
+ case 'success': {
+ element.valid = true;
+ element.groupClass = {'has-success': true};
+ element.iconClass = {'icon-ok': true};
+ break;
+ }
+ case 'error': {
+ element.valid = false;
+ element.groupClass = {'has-error': true};
+ element.iconClass = {'icon-cancel': true};
+ break;
+ }
+ default: {
+ element.valid = undefined;
+ element.groupClass = {};
+ element.iconClass = {};
+ }
+ }
+}
+
diff --git a/app/styles/extension.scss b/app/styles/extension.scss
index 7ba378a..3fdfca7 100755
--- a/app/styles/extension.scss
+++ b/app/styles/extension.scss
@@ -94,7 +94,7 @@ body {
}
&:after {
- content: '\e801';
+ content: '\e805';
font-family: mcl-ext-icons;
position: absolute;
top: 9px;
@@ -195,6 +195,27 @@ body {
.list-group-item {
padding: $padding $padding $padding + 10;
+ &.useCore {
+
+ .form-horizontal {
+ padding-top: 15px;
+ padding-left: 60px;
+
+ label.control-label {
+ font-weight: 400;
+ }
+ }
+
+ .btn {
+ line-height: 1px;
+ height: 38px;
+ }
+
+ p.rule-label {
+ padding: 7px 12px;
+ }
+ }
+
sub {
padding-left: 60px;
@@ -246,7 +267,7 @@ body {
.icon-ok {
display: none;
- color: #fff;
+ color: $colorWhite;
&:before {
margin-top: 3px;
@@ -303,18 +324,28 @@ body {
vertical-align: middle;
position: relative;
+ &:first-child {
+ display: flex;
+
+ .scan-type {
+ padding: 4px 10px 0 0;
+ font-size: 2rem;
+ color: $colorGreyDarker;
+ }
+ }
+
&.action-column {
padding: 0;
text-align: center;
}
- .icon-trash-empty {
+ .icon-trash {
display:none;
}
}
&:hover {
- .icon-trash-empty {
+ .icon-trash {
display: inline-block;
}
}
diff --git a/git_hooks/pre-flow-hotfix-start b/git_hooks/pre-flow-hotfix-start
index dde0a46..ac9a520 100755
--- a/git_hooks/pre-flow-hotfix-start
+++ b/git_hooks/pre-flow-hotfix-start
@@ -3,7 +3,10 @@
VERSION=$1
echo "Incrementing version to $VERSION"
-sed -i "s/\"version\": \".*\"/version\": \"${VERSION}\"/g" package.json
-sed -i "s/\"version\": \".*\"/version\": \"${VERSION}\"/g" app/manifest.json
+git checkout customer
+git pull
+
+sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/g" package.json
+sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/g" app/manifest.json
git commit -a -m "Hook updating version"
\ No newline at end of file
diff --git a/git_hooks/pre-flow-release-start b/git_hooks/pre-flow-release-start
index dde0a46..ddf73b4 100755
--- a/git_hooks/pre-flow-release-start
+++ b/git_hooks/pre-flow-release-start
@@ -3,7 +3,10 @@
VERSION=$1
echo "Incrementing version to $VERSION"
-sed -i "s/\"version\": \".*\"/version\": \"${VERSION}\"/g" package.json
-sed -i "s/\"version\": \".*\"/version\": \"${VERSION}\"/g" app/manifest.json
+git checkout master
+git pull
+
+sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/g" package.json
+sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/g" app/manifest.json
git commit -a -m "Hook updating version"
\ No newline at end of file
diff --git a/package.json b/package.json
index 4ad4b9c..92a6577 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "opswat-file-security-chrome",
"private": true,
- "version": "3.8.1",
+ "version": "3.9.0",
"description": "Scan files and downloads with OPSWAT File Security for Chrome",
"repository": {
"type": "git",