seb-win-refactoring/SafeExamBrowser.Proctoring/ScreenProctoring/Cache.cs

242 lines
7.2 KiB
C#

/*
* Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Xml.Linq;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.ScreenProctoring.Data;
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
using SafeExamBrowser.Settings.Proctoring;
namespace SafeExamBrowser.Proctoring.ScreenProctoring
{
internal class Cache
{
private const string DATA_FILE_EXTENSION = "xml";
private readonly AppConfig appConfig;
private readonly ILogger logger;
private readonly Queue<(string fileName, int checksum, string hash)> queue;
private string Directory { get; set; }
public Cache(AppConfig appConfig, ILogger logger)
{
this.appConfig = appConfig;
this.logger = logger;
this.queue = new Queue<(string, int, string)>();
}
internal bool Any()
{
return false;
}
internal bool TryEnqueue(MetaData metaData, ScreenShot screenShot)
{
var fileName = $"{screenShot.CaptureTime:yyyy-MM-dd HH\\hmm\\mss\\sfff\\m\\s}";
var success = false;
try
{
InitializeDirectory();
SaveData(fileName, metaData, screenShot);
SaveImage(fileName, screenShot);
Enqueue(fileName, metaData, screenShot);
success = true;
logger.Debug($"Cached data for '{fileName}'.");
}
catch (Exception e)
{
logger.Error($"Failed to cache data for '{fileName}'!", e);
}
return success;
}
internal bool TryDequeue(out MetaData metaData, out ScreenShot screenShot)
{
var success = false;
metaData = default;
screenShot = default;
if (queue.Any())
{
var (fileName, checksum, hash) = queue.Peek();
try
{
LoadData(fileName, out metaData, out screenShot);
LoadImage(fileName, screenShot);
Dequeue(fileName, checksum, hash, metaData, screenShot);
success = true;
logger.Debug($"Uncached data for '{fileName}'.");
}
catch (Exception e)
{
logger.Error($"Failed to uncache data for '{fileName}'!", e);
}
}
return success;
}
private void Dequeue(string fileName, int checksum, string hash, MetaData metaData, ScreenShot screenShot)
{
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
var extension = screenShot.Format.ToString().ToLower();
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
if (checksum != GenerateChecksum(screenShot))
{
logger.Warn($"The checksum for '{fileName}' does not match, the image data may be manipulated!");
}
if (hash != GenerateHash(metaData, screenShot))
{
logger.Warn($"The hash for '{fileName}' does not match, the metadata may be manipulated!");
}
File.Delete(dataPath);
File.Delete(imagePath);
queue.Dequeue();
}
private void Enqueue(string fileName, MetaData metaData, ScreenShot screenShot)
{
var checksum = GenerateChecksum(screenShot);
var hash = GenerateHash(metaData, screenShot);
queue.Enqueue((fileName, checksum, hash));
}
private int GenerateChecksum(ScreenShot screenShot)
{
var checksum = default(int);
foreach (var data in screenShot.Data)
{
unchecked
{
checksum += data;
}
}
return checksum;
}
private string GenerateHash(MetaData metaData, ScreenShot screenShot)
{
var hash = default(string);
using (var algorithm = new SHA256Managed())
{
var input = metaData.ToJson() + screenShot.CaptureTime + screenShot.Format + screenShot.Height + screenShot.Width;
var bytes = Encoding.UTF8.GetBytes(input);
var result = algorithm.ComputeHash(bytes);
hash = string.Join(string.Empty, result.Select(b => $"{b:x2}"));
}
return hash;
}
private void InitializeDirectory()
{
if (Directory == default)
{
Directory = Path.Combine(appConfig.TemporaryDirectory, nameof(ScreenProctoring));
}
if (!System.IO.Directory.Exists(Directory))
{
System.IO.Directory.CreateDirectory(Directory);
logger.Debug($"Created caching directory '{Directory}'.");
}
}
private void LoadData(string fileName, out MetaData metaData, out ScreenShot screenShot)
{
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
var document = XDocument.Load(dataPath);
var xml = document.Descendants(nameof(MetaData)).First();
metaData = new MetaData();
screenShot = new ScreenShot();
metaData.ApplicationInfo = xml.Descendants(nameof(MetaData.ApplicationInfo)).First().Value;
metaData.BrowserInfo = xml.Descendants(nameof(MetaData.BrowserInfo)).First().Value;
metaData.Elapsed = TimeSpan.Parse(xml.Descendants(nameof(MetaData.Elapsed)).First().Value);
metaData.TriggerInfo = xml.Descendants(nameof(MetaData.TriggerInfo)).First().Value;
metaData.Urls = xml.Descendants(nameof(MetaData.Urls)).First().Value;
metaData.WindowTitle = xml.Descendants(nameof(MetaData.WindowTitle)).First().Value;
xml = document.Descendants(nameof(ScreenShot)).First();
screenShot.CaptureTime = DateTime.Parse(xml.Descendants(nameof(ScreenShot.CaptureTime)).First().Value);
screenShot.Format = (ImageFormat) Enum.Parse(typeof(ImageFormat), xml.Descendants(nameof(ScreenShot.Format)).First().Value);
screenShot.Height = int.Parse(xml.Descendants(nameof(ScreenShot.Height)).First().Value);
screenShot.Width = int.Parse(xml.Descendants(nameof(ScreenShot.Width)).First().Value);
}
private void LoadImage(string fileName, ScreenShot screenShot)
{
var extension = screenShot.Format.ToString().ToLower();
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
screenShot.Data = File.ReadAllBytes(imagePath);
}
private void SaveData(string fileName, MetaData metaData, ScreenShot screenShot)
{
var data = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
data.Add(
new XElement("Data",
new XElement(nameof(MetaData),
new XElement(nameof(MetaData.ApplicationInfo), metaData.ApplicationInfo),
new XElement(nameof(MetaData.BrowserInfo), metaData.BrowserInfo),
new XElement(nameof(MetaData.Elapsed), metaData.Elapsed.ToString()),
new XElement(nameof(MetaData.TriggerInfo), metaData.TriggerInfo),
new XElement(nameof(MetaData.Urls), metaData.Urls),
new XElement(nameof(MetaData.WindowTitle), metaData.WindowTitle)
),
new XElement(nameof(ScreenShot),
new XElement(nameof(ScreenShot.CaptureTime), screenShot.CaptureTime.ToString()),
new XElement(nameof(ScreenShot.Format), screenShot.Format),
new XElement(nameof(ScreenShot.Height), screenShot.Height),
new XElement(nameof(ScreenShot.Width), screenShot.Width)
)
)
);
data.Save(dataPath);
}
private void SaveImage(string fileName, ScreenShot screenShot)
{
var extension = screenShot.Format.ToString().ToLower();
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
File.WriteAllBytes(imagePath, screenShot.Data);
}
}
}