/*
* This code is based on /mozilla/source/toolkit/components/downloads/src/nsDownloadScanner.cpp
* and sample code at https://bugzilla.mozilla.org/show_bug.cgi?id=103487,
* created by Rob Arnold.
*/
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace NaGet.InteropServices
{
public enum DownloadScannerResult : uint {
OK = 0, // S_OK
InfectedAndCleaned = 1, // S_FALSE
InfectedButNotCleaned = 0x80004005, // E_FAIL
ErrorNotFound = 2, // ERROR_NOT_FOUND
ScannerNotFound = 0xFFFFFFFF,
}
///
/// ダウンロードしたファイルをスキャンする
///
public class DownloadScannerService : IDisposable
{
#region COMInterop
[Flags()]
private enum MSOAVINFOFLAG : uint {
fPath = 1,
fReadOnlyRequest = 2,
fInstalled = 4,
fHttpDownload = 8,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct MSOAVINFO {
public int cbsize;
[MarshalAs(UnmanagedType.U4)]
public MSOAVINFOFLAG uFlags;
public IntPtr hwnd;
[MarshalAs(UnmanagedType.LPWStr)]
public string pwzFullPath;
[MarshalAs(UnmanagedType.LPWStr)]
public string pwzHostName;
[MarshalAs(UnmanagedType.LPWStr)]
public string pwzOrigURL;
}
[ComImport()]
[Guid("56FFCC30-D398-11D0-B2AE-00A0C908FA49")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IOfficeAntiVirus {
[PreserveSig()]
uint Scan(ref MSOAVINFO pmsoavinfo);
}
#endregion
///
/// ウイルススキャナーに渡すプログラム・ホスト名。
///
public static string HostName {
get {
// 実行アセンブリ名を返す
return Assembly.GetExecutingAssembly().GetName().FullName;
}
}
private List scanners;
///
/// コンストラクタ。内部でCOM呼び出し初期化(CoInitialize)されます。
///
public DownloadScannerService()
{
int result = ComDirectAccess.CoInitialize(IntPtr.Zero);
if (result == 0) {
throw new System.ComponentModel.Win32Exception();
}
}
///
/// 内部でCOM開放(CoUninitialize)します。必ず呼び出す必要があります。
///
public void Dispose()
{
Release();
ComDirectAccess.CoUninitialize();
GC.SuppressFinalize(this);
}
///
/// ウイルススキャンがあるかないか
///
/// Init()呼出し後に使える
public bool HasScanner {
get { return scanners.Count > 0; }
}
///
/// 初期化処理としてウイルススキャンを探す。
///
public void Init()
{
scanners = new List();
Guid IID_MSOfficeAntiVirus = new Guid(((GuidAttribute) Attribute.GetCustomAttribute(typeof(IOfficeAntiVirus), typeof(GuidAttribute))).Value);
using (GuidEnumeratorForCategories guids = new GuidEnumeratorForCategories(IID_MSOfficeAntiVirus)) {
foreach (Guid guid in guids) {
IOfficeAntiVirus oav = ComDirectAccess.CreateInstance(guid, ComDirectAccess.CLSCTX.CLSCTX_INPROC_SERVER);
scanners.Add(oav);
}
}
}
///
/// ウイルススキャンのオブジェクトを開放しInitの前の状態に戻す。
///
public void Release()
{
if ((scanners != null) && (scanners.Count > 0)) {
foreach (IOfficeAntiVirus i in scanners) {
Marshal.ReleaseComObject(i);
}
scanners.Clear();
}
}
///
/// ファイルをスキャンする。ウイルススキャンが複数個見つかっている
/// ならばそれらすべてでスキャンする。
/// ウイルススキャンの実装によるが、ウイルス発見時にはダイアログが開く。
/// ウイルスの処理はユーザに委ねられるので、それの制御は一切できない。
///
/// ウイルスが見つかったか否かは取得できない。
/// 本メソッド呼び出し後にウイルスが退避されているかもしれないが、ファイルの存在確認でしかそれをチェックできない
/// ファイルのパス
/// ファイルをダウンロードしたURL。nullであってはならない
/// COMのエラー発生時。たとえば、AVGではウイルスと検出されたのにユーザが「無視」を指定したときにも投げられる。
/// ウイルススキャン結果。
/// Init()呼出し後に使える
public DownloadScannerResult Scan(string path, string origin)
{
MSOAVINFO info = new MSOAVINFO();
info.cbsize = Marshal.SizeOf(info);
info.uFlags = MSOAVINFOFLAG.fPath | MSOAVINFOFLAG.fHttpDownload;
info.hwnd = IntPtr.Zero;
info.pwzFullPath = path;
info.pwzHostName = HostName;
info.pwzOrigURL = origin;
DownloadScannerResult result = DownloadScannerResult.ScannerNotFound;
foreach (IOfficeAntiVirus i in scanners) {
if (System.IO.File.Exists(path)) {
result = (DownloadScannerResult) i.Scan(ref info);
if (result == DownloadScannerResult.OK && System.IO.File.Exists(path) == false) {
result = DownloadScannerResult.InfectedAndCleaned;
}
} else {
result = DownloadScannerResult.ErrorNotFound;
}
if (result != DownloadScannerResult.OK) {
break;
}
}
return result;
}
}
}