Restore SEBPatch

This commit is contained in:
2025-06-01 11:56:28 +02:00
parent 8c656e3137
commit 00707825b4
1009 changed files with 5005 additions and 6502 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -23,19 +23,27 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
{
internal class Cache
{
private const string DATA_FILE_EXTENSION = "xml";
private const string DATA_FILE_EXTENSION = "spsdat";
private const string IMAGE_FILE_EXTENSION = "spsimg";
private readonly AppConfig appConfig;
private readonly Lazy<string> directory;
private readonly Encryptor encryptor;
private readonly ILogger logger;
private readonly ScreenProctoringSettings settings;
private readonly ConcurrentQueue<(string fileName, int checksum, string hash)> queue;
internal int Count => queue.Count;
internal string Directory { get; private set; }
internal string Directory => directory.Value;
internal long Size { get; private set; }
public Cache(AppConfig appConfig, ILogger logger)
public Cache(AppConfig appConfig, ILogger logger, ScreenProctoringSettings settings)
{
this.appConfig = appConfig;
this.directory = new Lazy<string>(InitializeDirectory);
this.encryptor = new Encryptor(settings);
this.logger = logger;
this.settings = settings;
this.queue = new ConcurrentQueue<(string, int, string)>();
}
@@ -44,6 +52,24 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
return queue.Any();
}
internal void Clear()
{
while (queue.Any())
{
if (queue.TryDequeue(out var item))
{
var (dataPath, imagePath) = BuildPathsFor(item.fileName);
File.Delete(dataPath);
File.Delete(imagePath);
}
}
Size = 0;
logger.Debug("Cleared all data.");
}
internal bool TryEnqueue(MetaData metaData, ScreenShot screenShot)
{
var fileName = $"{screenShot.CaptureTime:yyyy-MM-dd HH\\hmm\\mss\\sfff\\m\\s}";
@@ -51,14 +77,22 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
try
{
InitializeDirectory();
SaveData(fileName, metaData, screenShot);
SaveImage(fileName, screenShot);
Enqueue(fileName, metaData, screenShot);
var (dataPath, imagePath) = BuildPathsFor(fileName);
if (Size <= settings.CacheSize * 1000000)
{
SaveData(dataPath, metaData, screenShot);
SaveImage(imagePath, screenShot);
Enqueue(fileName, metaData, screenShot);
logger.Debug($"Cached data for '{fileName}', now holding {Count} item(s) counting {Size / 1000000.0:N1} MB.");
}
else
{
logger.Debug($"The maximum cache size of {settings.CacheSize:N1} MB has been reached, dropping data for '{fileName}'!");
}
success = true;
logger.Debug($"Cached data for '{fileName}', now holding {Count} item(s).");
}
catch (Exception e)
{
@@ -79,13 +113,15 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
{
try
{
LoadData(item.fileName, out metaData, out screenShot);
LoadImage(item.fileName, screenShot);
Dequeue(item.fileName, item.checksum, item.hash, metaData, screenShot);
var (dataPath, imagePath) = BuildPathsFor(item.fileName);
LoadData(dataPath, out metaData, out screenShot);
LoadImage(imagePath, screenShot);
Dequeue(item.checksum, dataPath, item.fileName, item.hash, imagePath, metaData, screenShot);
success = true;
logger.Debug($"Removed data for '{item.fileName}', {Count} item(s) remaining.");
logger.Debug($"Removed data for '{item.fileName}', {Count} item(s) counting {Size / 1000000.0:N1} MB remaining.");
}
catch (Exception e)
{
@@ -96,12 +132,16 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
return success;
}
private void Dequeue(string fileName, int checksum, string hash, MetaData metaData, ScreenShot screenShot)
private (string dataPath, string imagePath) BuildPathsFor(string fileName)
{
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
var extension = screenShot.Format.ToString().ToLower();
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
var imagePath = Path.Combine(Directory, $"{fileName}.{IMAGE_FILE_EXTENSION}");
return (dataPath, imagePath);
}
private void Dequeue(int checksum, string dataPath, string fileName, string hash, string imagePath, MetaData metaData, ScreenShot screenShot)
{
if (checksum != GenerateChecksum(screenShot))
{
logger.Warn($"The checksum for '{fileName}' does not match, the image data may be manipulated!");
@@ -157,24 +197,25 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
return hash;
}
private void InitializeDirectory()
private string InitializeDirectory()
{
if (Directory == default)
var path = Path.Combine(appConfig.TemporaryDirectory, nameof(ScreenProctoring));
if (!System.IO.Directory.Exists(path))
{
Directory = Path.Combine(appConfig.TemporaryDirectory, nameof(ScreenProctoring));
System.IO.Directory.CreateDirectory(path);
logger.Debug($"Created caching directory '{path}'.");
}
if (!System.IO.Directory.Exists(Directory))
{
System.IO.Directory.CreateDirectory(Directory);
logger.Debug($"Created caching directory '{Directory}'.");
}
return path;
}
private void LoadData(string fileName, out MetaData metaData, out ScreenShot screenShot)
private void LoadData(string filePath, out MetaData metaData, out ScreenShot screenShot)
{
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
var document = XDocument.Load(dataPath);
var encrypted = File.ReadAllBytes(filePath);
var data = encryptor.Decrypt(encrypted);
var text = Encoding.UTF8.GetString(data);
var document = XDocument.Parse(text);
var xml = document.Descendants(nameof(MetaData)).First();
metaData = new MetaData();
@@ -193,22 +234,24 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
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);
Size -= encrypted.Length;
}
private void LoadImage(string fileName, ScreenShot screenShot)
private void LoadImage(string filePath, ScreenShot screenShot)
{
var extension = screenShot.Format.ToString().ToLower();
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
var encrypted = File.ReadAllBytes(filePath);
var image = encryptor.Decrypt(encrypted);
screenShot.Data = File.ReadAllBytes(imagePath);
screenShot.Data = image;
Size -= encrypted.Length;
}
private void SaveData(string fileName, MetaData metaData, ScreenShot screenShot)
private void SaveData(string filePath, MetaData metaData, ScreenShot screenShot)
{
var data = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
var xml = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
data.Add(
xml.Add(
new XElement("Data",
new XElement(nameof(MetaData),
new XElement(nameof(MetaData.ApplicationInfo), metaData.ApplicationInfo),
@@ -227,15 +270,25 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
)
);
data.Save(dataPath);
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream, new UTF8Encoding(false)))
{
xml.Save(writer);
var data = stream.ToArray();
var encrypted = encryptor.Encrypt(data);
File.WriteAllBytes(filePath, encrypted);
Size += encrypted.Length;
}
}
private void SaveImage(string fileName, ScreenShot screenShot)
private void SaveImage(string filePath, ScreenShot screenShot)
{
var extension = screenShot.Format.ToString().ToLower();
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
var encrypted = encryptor.Encrypt(screenShot.Data);
File.WriteAllBytes(imagePath, screenShot.Data);
File.WriteAllBytes(filePath, encrypted);
Size += encrypted.Length;
}
}
}