Restore SEBPatch
This commit is contained in:
207
SebWindowsConfig/Utilities/FileCompressor.cs
Normal file
207
SebWindowsConfig/Utilities/FileCompressor.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Ionic.Zip;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public class FileCompressor : IFileCompressor
|
||||
{
|
||||
private static readonly string TempDirectory = SEBClientInfo.SebClientSettingsAppDataDirectory + "temp\\";
|
||||
private static readonly string TempIconFilename = SEBClientInfo.SebClientSettingsAppDataDirectory + "temp\\tempIcon.png";
|
||||
|
||||
public static void CleanupTempDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(TempDirectory))
|
||||
{
|
||||
DeleteDirectory(TempDirectory);
|
||||
Logger.AddInformation("Successfully deleted temporary directory.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.AddError("Error when trying to delete temporary directory: ", null, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to fix the issue happening when deleting the TempDirectory (see SEBWIN-49).
|
||||
/// Source: https://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true/1703799#1703799
|
||||
/// </summary>
|
||||
private static void DeleteDirectory(string path)
|
||||
{
|
||||
foreach (string directory in Directory.GetDirectories(path))
|
||||
{
|
||||
DeleteDirectory(directory);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.AddWarning(String.Format("Failed to delete {0} with IOException: {1}", path, e.Message), null);
|
||||
Thread.Sleep(100);
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
Logger.AddWarning(String.Format("Failed to delete {0} with UnauthorizedAccessException: {1}", path, e.Message), null);
|
||||
Thread.Sleep(100);
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
}
|
||||
|
||||
public string CompressAndEncodeFile(string filename)
|
||||
{
|
||||
var zip = new ZipFile();
|
||||
zip.AddFile(filename, "");
|
||||
var stream = new MemoryStream();
|
||||
zip.Save(stream);
|
||||
return base64_encode(stream.ToArray());
|
||||
}
|
||||
|
||||
public string CompressAndEncodeIcon(Icon icon)
|
||||
{
|
||||
//Save the file first locally
|
||||
icon.ToBitmap().Save(TempIconFilename, ImageFormat.Png);
|
||||
|
||||
return CompressAndEncodeFile(TempIconFilename);
|
||||
}
|
||||
|
||||
public string CompressAndEncodeFavicon(Uri uri)
|
||||
{
|
||||
if (File.Exists(TempIconFilename))
|
||||
{
|
||||
File.Delete(TempIconFilename);
|
||||
}
|
||||
if (!Directory.Exists(TempDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(TempDirectory);
|
||||
}
|
||||
|
||||
var client = new System.Net.WebClient();
|
||||
client.DownloadFile(
|
||||
string.Format(@"https://www.google.com/s2/favicons?domain_url={0}", uri.Host),
|
||||
TempIconFilename);
|
||||
return CompressAndEncodeFile(TempIconFilename);
|
||||
}
|
||||
|
||||
public string CompressAndEncodeDirectory(string path, out List<string> containingFilenames)
|
||||
{
|
||||
var zip = new ZipFile();
|
||||
zip.AddDirectory(path, "");
|
||||
var stream = new MemoryStream();
|
||||
zip.Save(stream);
|
||||
containingFilenames = zip.Entries.Select(x => x.FileName.Replace(path, "")).ToList();
|
||||
return base64_encode(stream.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses the entire specified directory (preserving its relative structure) and returns the data as Base64-encoded string.
|
||||
/// </summary>
|
||||
public string CompressAndEncodeEntireDirectory(string path)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
using (var zip = new ZipFile())
|
||||
{
|
||||
var data = default(string);
|
||||
var directory = new DirectoryInfo(path);
|
||||
|
||||
zip.AddDirectory(path, directory.Name);
|
||||
zip.Save(stream);
|
||||
data = base64_encode(stream.ToArray());
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the given Base64-encoded archive into the specified target directory, overwrites existing files if the overwrite flag
|
||||
/// is set and returns the absolute paths of all extracted elements.
|
||||
/// </summary>
|
||||
public IEnumerable<string> DecodeAndDecompressDirectory(string base64, string targetDirectory, bool overwrite = true)
|
||||
{
|
||||
var data = base64_decode(base64);
|
||||
var paths = new List<string>();
|
||||
var policy = overwrite ? ExtractExistingFileAction.OverwriteSilently : ExtractExistingFileAction.DoNotOverwrite;
|
||||
|
||||
using (var zipStream = new MemoryStream(data))
|
||||
using (var zip = ZipFile.Read(zipStream))
|
||||
{
|
||||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
var path = Path.Combine(targetDirectory, entry.FileName.Replace('/', '\\'));
|
||||
|
||||
entry.ExtractExistingFile = policy;
|
||||
entry.Extract(targetDirectory);
|
||||
paths.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to a temporary directory and returns the path to the file (without filename)
|
||||
/// </summary>
|
||||
/// <param name="base64">the encoded and compressed file content</param>
|
||||
/// <param name="filename">the filename of the file to save</param>
|
||||
/// <param name="directoryName">the subdirectory of the tempdir (usually the id of the additional resource</param>
|
||||
/// <returns></returns>
|
||||
public string DecompressDecodeAndSaveFile(string base64, string filename, string directoryName)
|
||||
{
|
||||
string tempPath = TempDirectory + directoryName + "\\";
|
||||
if (Directory.Exists(tempPath))
|
||||
{
|
||||
return tempPath;
|
||||
}
|
||||
Directory.CreateDirectory(tempPath);
|
||||
|
||||
var data = base64_decode(base64);
|
||||
var stream = new MemoryStream(data);
|
||||
var zip = ZipFile.Read(stream);
|
||||
zip.ExtractAll(tempPath);
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
public MemoryStream DeCompressAndDecode(string base64)
|
||||
{
|
||||
var data = base64_decode(base64);
|
||||
var zipStream = new MemoryStream(data);
|
||||
var zip = ZipFile.Read(zipStream);
|
||||
var stream = new MemoryStream();
|
||||
zip.Entries.First().Extract(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFileList(string base64)
|
||||
{
|
||||
var data = base64_decode(base64);
|
||||
var zipStream = new MemoryStream(data);
|
||||
var zip = ZipFile.Read(zipStream);
|
||||
|
||||
return zip.EntryFileNames;
|
||||
}
|
||||
|
||||
private string base64_encode(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException("data");
|
||||
return Convert.ToBase64String(data);
|
||||
}
|
||||
private byte[] base64_decode(string encodedData)
|
||||
{
|
||||
byte[] encodedDataAsBytes = Convert.FromBase64String(encodedData);
|
||||
return encodedDataAsBytes;
|
||||
}
|
||||
}
|
||||
}
|
21
SebWindowsConfig/Utilities/IFileCompressor.cs
Normal file
21
SebWindowsConfig/Utilities/IFileCompressor.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public interface IFileCompressor
|
||||
{
|
||||
string CompressAndEncodeFile(string filename);
|
||||
|
||||
string CompressAndEncodeIcon(Icon icon);
|
||||
string CompressAndEncodeFavicon(Uri uri);
|
||||
|
||||
string CompressAndEncodeDirectory(string path, out List<string> containingFileNames);
|
||||
|
||||
string DecompressDecodeAndSaveFile(string base64, string filename, string subdirectory);
|
||||
|
||||
MemoryStream DeCompressAndDecode(string base64);
|
||||
}
|
||||
}
|
118
SebWindowsConfig/Utilities/LogCollector.cs
Normal file
118
SebWindowsConfig/Utilities/LogCollector.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using Ionic.Zip;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
internal class LogCollector
|
||||
{
|
||||
private readonly IWin32Window owner;
|
||||
|
||||
internal LogCollector(IWin32Window owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
internal void Run()
|
||||
{
|
||||
var description = "Please select the location where you would like to save the collected log files:";
|
||||
var password = default(string);
|
||||
var path = default(string);
|
||||
|
||||
using (var dialog = new FolderBrowserDialog { Description = description })
|
||||
{
|
||||
if (dialog.ShowDialog(owner) == DialogResult.OK)
|
||||
{
|
||||
path = dialog.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (path != default(string))
|
||||
{
|
||||
var encrypt = AskUserForDataEncrpytion();
|
||||
|
||||
if (!encrypt || (encrypt && TryAskForPassword(out password)))
|
||||
{
|
||||
CollectLogFiles(path, password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool AskUserForDataEncrpytion()
|
||||
{
|
||||
var message = "Log files can contain sensitive information about you and your computer. Would you like to protect the data with a password?";
|
||||
var result = MessageBox.Show(owner, message, "Data Protection", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
||||
|
||||
return result == DialogResult.Yes;
|
||||
}
|
||||
|
||||
private bool TryAskForPassword(out string password)
|
||||
{
|
||||
password = default(string);
|
||||
|
||||
using (var dialog = new SebPasswordDialogForm())
|
||||
{
|
||||
dialog.Text = "Data Protection";
|
||||
dialog.LabelText = "Please enter the password to be used to encrypt the data:";
|
||||
|
||||
if (dialog.ShowDialog(owner) == DialogResult.OK)
|
||||
{
|
||||
password = dialog.txtSEBPassword.Text;
|
||||
}
|
||||
}
|
||||
|
||||
return password != default(string);
|
||||
}
|
||||
|
||||
private void CollectLogFiles(string outputPath, string password = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var zipPath = Path.Combine(outputPath, $"SEB_Logs_{DateTime.Today.ToString("yyyy-MM-dd")}.zip");
|
||||
var logFiles = new[]
|
||||
{
|
||||
SEBClientInfo.SebClientLogFile,
|
||||
Path.Combine(SEBClientInfo.SebClientSettingsAppDataDirectory, SebWindowsConfigForm.SEB_CONFIG_LOG),
|
||||
Path.Combine(SEBClientInfo.SebClientSettingsAppDataDirectory, "seb.log"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"SafeExamBrowser\SebWindowsServiceWCF\sebwindowsservice.log")
|
||||
};
|
||||
var existingFiles = logFiles.Where(f => File.Exists(f));
|
||||
var missingFiles = logFiles.Except(existingFiles);
|
||||
|
||||
using (var stream = new FileStream(zipPath, FileMode.Create))
|
||||
using (var zip = new ZipFile())
|
||||
{
|
||||
if (password != default(string))
|
||||
{
|
||||
zip.Password = password;
|
||||
}
|
||||
|
||||
foreach (var file in existingFiles)
|
||||
{
|
||||
zip.AddFile(file, string.Empty);
|
||||
}
|
||||
|
||||
zip.Save(stream);
|
||||
}
|
||||
|
||||
if (missingFiles.Any())
|
||||
{
|
||||
var count = $"{existingFiles.Count()} of {logFiles.Count()}";
|
||||
var missing = $"The following file(s) could not be found:\n\n{String.Join("\n\n", missingFiles)}";
|
||||
|
||||
MessageBox.Show(owner, $"{count} log files were collected and saved as '{zipPath}'.\n\n{missing}", "Status", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(owner, $"The log files were successfully collected and saved as '{zipPath}'.", "Sucess", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MessageBox.Show(owner, $"Failed to collect the log files. Reason: {e.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
113
SebWindowsConfig/Utilities/Logger.cs
Normal file
113
SebWindowsConfig/Utilities/Logger.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public class Logger
|
||||
{
|
||||
private static readonly object Lock = new object();
|
||||
private static string LogFilePath { get; set; }
|
||||
|
||||
public static void AddError(string message, object eventSource, Exception exception, string details = null)
|
||||
{
|
||||
Log(Severity.Error, message, eventSource, exception, details);
|
||||
}
|
||||
|
||||
public static void AddWarning(string message, object eventSource = null, Exception exception = null, string details = null)
|
||||
{
|
||||
Log(Severity.Warning, message, eventSource, exception, details);
|
||||
}
|
||||
|
||||
public static void AddInformation(string message, object eventSource = null, Exception exception = null, string details = null)
|
||||
{
|
||||
Log(Severity.Information, message, eventSource, exception, details);
|
||||
}
|
||||
|
||||
public static void InitLogger(string logFileDirectory = null, string logFilePath = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(logFileDirectory))
|
||||
{
|
||||
logFileDirectory = SEBClientInfo.SebClientLogFileDirectory;
|
||||
|
||||
if (String.IsNullOrEmpty(logFileDirectory))
|
||||
{
|
||||
throw new DirectoryNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
if (!Directory.Exists(logFileDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(logFileDirectory);
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(logFilePath))
|
||||
{
|
||||
logFilePath = SEBClientInfo.SebClientLogFile;
|
||||
|
||||
if (String.IsNullOrEmpty(logFilePath))
|
||||
{
|
||||
logFilePath = String.Format(@"{0}\{1}", logFileDirectory, SEBClientInfo.SEB_CLIENT_LOG);
|
||||
}
|
||||
}
|
||||
|
||||
LogFilePath = logFilePath;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LogFilePath = String.Format(@"{0}\{1}\{2}", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), SEBClientInfo.MANUFACTURER_LOCAL, SEBClientInfo.SEB_CLIENT_LOG);
|
||||
}
|
||||
|
||||
HandleFileSize();
|
||||
AddInformation(String.Format("SEB version: {0}", Assembly.GetExecutingAssembly().GetName().Version));
|
||||
}
|
||||
|
||||
private static void Log(Severity severity, string message, object eventSource, Exception exception, string details = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
using (var file = new StreamWriter(LogFilePath, true))
|
||||
{
|
||||
string eventSourceString = eventSource == null ? "" : string.Format(" ({0})", eventSource);
|
||||
string exceptionString = exception == null ? "" : string.Format("\n\n Exception: {0} - {1}\n{2}", exception, exception.Message, exception.StackTrace);
|
||||
string detailsString = details == null || (exception != null && details == exception.Message) ? "" : string.Format("\n\n{0}", details);
|
||||
|
||||
file.WriteLine("{0} [{1}]: {2}{3}{4}{5}\n", DateTime.Now.ToLocalTime(), severity, message, eventSourceString, exceptionString, detailsString);
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("{0} [{1}]: {2}{3}{4}{5}\n", DateTime.Now.ToLocalTime(), severity, message, eventSourceString, exceptionString, detailsString);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleFileSize()
|
||||
{
|
||||
const int TEN_MB = 10000000;
|
||||
var logFile = new FileInfo(LogFilePath);
|
||||
var timestamp = DateTime.Now.Ticks;
|
||||
var fileName = Path.GetFileNameWithoutExtension(logFile.Name);
|
||||
var backupFilePath = Path.Combine(logFile.DirectoryName, String.Format("{0}_Backup_{1}.log", fileName, timestamp));
|
||||
|
||||
if (logFile.Exists && logFile.Length > TEN_MB)
|
||||
{
|
||||
File.Move(LogFilePath, backupFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
enum Severity
|
||||
{
|
||||
Error,
|
||||
Warning,
|
||||
Information
|
||||
}
|
||||
}
|
||||
}
|
964
SebWindowsConfig/Utilities/Plist.cs
Normal file
964
SebWindowsConfig/Utilities/Plist.cs
Normal file
@@ -0,0 +1,964 @@
|
||||
//
|
||||
// PlistCS Property List (plist) serialization and parsing library.
|
||||
//
|
||||
// https://github.com/animetrics/PlistCS
|
||||
//
|
||||
// Copyright (c) 2011 Animetrics Inc. (marc@animetrics.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public static class Plist
|
||||
{
|
||||
private static readonly List<int> offsetTable = new List<int>();
|
||||
private static List<byte> objectTable = new List<byte>();
|
||||
private static int refCount;
|
||||
private static int objRefSize;
|
||||
private static int offsetByteSize;
|
||||
private static long offsetTableOffset;
|
||||
|
||||
#region Public Functions
|
||||
|
||||
public static object readPlist(string path)
|
||||
{
|
||||
using (FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
return readPlist(f, plistType.Auto);
|
||||
}
|
||||
}
|
||||
|
||||
public static object readPlistSource(string source)
|
||||
{
|
||||
return readPlist(System.Text.Encoding.UTF8.GetBytes(source));
|
||||
}
|
||||
|
||||
public static object readPlist(byte[] data)
|
||||
{
|
||||
return readPlist(new MemoryStream(data), plistType.Auto);
|
||||
}
|
||||
|
||||
public static plistType getPlistType(Stream stream)
|
||||
{
|
||||
byte[] magicHeader = new byte[8];
|
||||
stream.Read(magicHeader, 0, 8);
|
||||
|
||||
if (BitConverter.ToInt64(magicHeader, 0) == 3472403351741427810)
|
||||
{
|
||||
return plistType.Binary;
|
||||
}
|
||||
else
|
||||
{
|
||||
return plistType.Xml;
|
||||
}
|
||||
}
|
||||
|
||||
public static object readPlist(Stream stream, plistType type)
|
||||
{
|
||||
if (type == plistType.Auto)
|
||||
{
|
||||
type = getPlistType(stream);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
if (type == plistType.Binary)
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
byte[] data = reader.ReadBytes((int) reader.BaseStream.Length);
|
||||
return readBinary(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
XmlDocument xml = new XmlDocument();
|
||||
xml.XmlResolver = null;
|
||||
xml.Load(stream);
|
||||
return readXml(xml);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeXml(object value, string path)
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(path))
|
||||
{
|
||||
writer.Write(writeXml(value));
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeXml(object value, Stream stream)
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(stream))
|
||||
{
|
||||
writer.Write(writeXml(value));
|
||||
}
|
||||
}
|
||||
|
||||
public static string writeXml(object value)
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
|
||||
xmlWriterSettings.Encoding = new System.Text.UTF8Encoding(false);
|
||||
xmlWriterSettings.ConformanceLevel = ConformanceLevel.Document;
|
||||
xmlWriterSettings.Indent = true;
|
||||
|
||||
using (XmlWriter xmlWriter = XmlWriter.Create(ms, xmlWriterSettings))
|
||||
{
|
||||
xmlWriter.WriteStartDocument();
|
||||
xmlWriter.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", "https://www.apple.com/DTDs/PropertyList-1.0.dtd", null);
|
||||
xmlWriter.WriteStartElement("plist");
|
||||
xmlWriter.WriteAttributeString("version", "1.0");
|
||||
compose(value, xmlWriter);
|
||||
xmlWriter.WriteEndElement();
|
||||
xmlWriter.WriteEndDocument();
|
||||
xmlWriter.Flush();
|
||||
xmlWriter.Close();
|
||||
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeBinary(object value, string path)
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(new FileStream(path, FileMode.Create)))
|
||||
{
|
||||
writer.Write(writeBinary(value));
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeBinary(object value, Stream stream)
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(writeBinary(value));
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] writeBinary(object value)
|
||||
{
|
||||
offsetTable.Clear();
|
||||
objectTable.Clear();
|
||||
refCount = 0;
|
||||
objRefSize = 0;
|
||||
offsetByteSize = 0;
|
||||
offsetTableOffset = 0;
|
||||
|
||||
//Do not count the root node, subtract by 1
|
||||
int totalRefs = countObject(value) - 1;
|
||||
|
||||
refCount = totalRefs;
|
||||
|
||||
objRefSize = RegulateNullBytes(BitConverter.GetBytes(refCount)).Length;
|
||||
|
||||
composeBinary(value);
|
||||
|
||||
writeBinaryString("bplist00", false);
|
||||
|
||||
offsetTableOffset = (long) objectTable.Count;
|
||||
|
||||
offsetTable.Add(objectTable.Count - 8);
|
||||
|
||||
offsetByteSize = RegulateNullBytes(BitConverter.GetBytes(offsetTable[offsetTable.Count - 1])).Length;
|
||||
|
||||
List<byte> offsetBytes = new List<byte>();
|
||||
|
||||
offsetTable.Reverse();
|
||||
|
||||
for (int i = 0; i < offsetTable.Count; i++)
|
||||
{
|
||||
offsetTable[i] = objectTable.Count - offsetTable[i];
|
||||
byte[] buffer = RegulateNullBytes(BitConverter.GetBytes(offsetTable[i]), offsetByteSize);
|
||||
Array.Reverse(buffer);
|
||||
offsetBytes.AddRange(buffer);
|
||||
}
|
||||
|
||||
objectTable.AddRange(offsetBytes);
|
||||
|
||||
objectTable.AddRange(new byte[6]);
|
||||
objectTable.Add(Convert.ToByte(offsetByteSize));
|
||||
objectTable.Add(Convert.ToByte(objRefSize));
|
||||
|
||||
var a = BitConverter.GetBytes((long) totalRefs + 1);
|
||||
Array.Reverse(a);
|
||||
objectTable.AddRange(a);
|
||||
|
||||
objectTable.AddRange(BitConverter.GetBytes((long) 0));
|
||||
a = BitConverter.GetBytes(offsetTableOffset);
|
||||
Array.Reverse(a);
|
||||
objectTable.AddRange(a);
|
||||
|
||||
return objectTable.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
private static object readXml(XmlDocument xml)
|
||||
{
|
||||
XmlNode rootNode = xml.DocumentElement.ChildNodes[0];
|
||||
return (Dictionary<string, object>) parse(rootNode);
|
||||
}
|
||||
|
||||
private static object readBinary(byte[] data)
|
||||
{
|
||||
offsetTable.Clear();
|
||||
List<byte> offsetTableBytes = new List<byte>();
|
||||
objectTable.Clear();
|
||||
refCount = 0;
|
||||
objRefSize = 0;
|
||||
offsetByteSize = 0;
|
||||
offsetTableOffset = 0;
|
||||
|
||||
List<byte> bList = new List<byte>(data);
|
||||
|
||||
List<byte> trailer = bList.GetRange(bList.Count - 32, 32);
|
||||
|
||||
parseTrailer(trailer);
|
||||
|
||||
objectTable = bList.GetRange(0, (int) offsetTableOffset);
|
||||
|
||||
offsetTableBytes = bList.GetRange((int) offsetTableOffset, bList.Count - (int) offsetTableOffset - 32);
|
||||
|
||||
parseOffsetTable(offsetTableBytes);
|
||||
|
||||
return parseBinary(0);
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> parseDictionary(XmlNode node)
|
||||
{
|
||||
XmlNodeList children = node.ChildNodes;
|
||||
if (children.Count % 2 != 0)
|
||||
{
|
||||
throw new DataMisalignedException("Dictionary elements must have an even number of child nodes");
|
||||
}
|
||||
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
|
||||
for (int i = 0; i < children.Count; i += 2)
|
||||
{
|
||||
XmlNode keynode = children[i];
|
||||
XmlNode valnode = children[i + 1];
|
||||
|
||||
if (keynode.Name != "key")
|
||||
{
|
||||
throw new ApplicationException("expected a key node");
|
||||
}
|
||||
|
||||
object result = parse(valnode);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
dict.Add(keynode.InnerText, result);
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private static List<object> parseArray(XmlNode node)
|
||||
{
|
||||
List<object> array = new List<object>();
|
||||
|
||||
foreach (XmlNode child in node.ChildNodes)
|
||||
{
|
||||
object result = parse(child);
|
||||
if (result != null)
|
||||
{
|
||||
array.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private static void composeArray(List<object> value, XmlWriter writer)
|
||||
{
|
||||
writer.WriteStartElement("array");
|
||||
foreach (object obj in value)
|
||||
{
|
||||
compose(obj, writer);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
private static object parse(XmlNode node)
|
||||
{
|
||||
switch (node.Name)
|
||||
{
|
||||
case "dict":
|
||||
return parseDictionary(node);
|
||||
case "array":
|
||||
return parseArray(node);
|
||||
case "string":
|
||||
return node.InnerText;
|
||||
case "integer":
|
||||
// int result;
|
||||
//int.TryParse(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo, out result);
|
||||
return Convert.ToInt32(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||
case "real":
|
||||
return Convert.ToDouble(node.InnerText, System.Globalization.NumberFormatInfo.InvariantInfo);
|
||||
case "false":
|
||||
return false;
|
||||
case "true":
|
||||
return true;
|
||||
case "null":
|
||||
return null;
|
||||
case "date":
|
||||
return XmlConvert.ToDateTime(node.InnerText, XmlDateTimeSerializationMode.Utc);
|
||||
case "data":
|
||||
return Convert.FromBase64String(node.InnerText);
|
||||
}
|
||||
|
||||
throw new ApplicationException(String.Format("Plist Node `{0}' is not supported", node.Name));
|
||||
}
|
||||
|
||||
private static void compose(object value, XmlWriter writer)
|
||||
{
|
||||
|
||||
if (value == null || value is string)
|
||||
{
|
||||
writer.WriteElementString("string", value as string);
|
||||
}
|
||||
else if (value is int || value is long)
|
||||
{
|
||||
writer.WriteElementString("integer", ((int) value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
else if (value is System.Collections.Generic.Dictionary<string, object> ||
|
||||
value.GetType().ToString().StartsWith("System.Collections.Generic.Dictionary`2[System.String"))
|
||||
{
|
||||
//Convert to Dictionary<string, object>
|
||||
Dictionary<string, object> dic = value as Dictionary<string, object>;
|
||||
if (dic == null)
|
||||
{
|
||||
dic = new Dictionary<string, object>();
|
||||
IDictionary idic = (IDictionary) value;
|
||||
foreach (var key in idic.Keys)
|
||||
{
|
||||
dic.Add(key.ToString(), idic[key]);
|
||||
}
|
||||
}
|
||||
writeDictionaryValues(dic, writer);
|
||||
}
|
||||
else if (value is List<object>)
|
||||
{
|
||||
composeArray((List<object>) value, writer);
|
||||
}
|
||||
else if (value is byte[])
|
||||
{
|
||||
writer.WriteElementString("data", Convert.ToBase64String((Byte[]) value));
|
||||
}
|
||||
else if (value is float || value is double)
|
||||
{
|
||||
writer.WriteElementString("real", ((double) value).ToString(System.Globalization.NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
else if (value is DateTime)
|
||||
{
|
||||
DateTime time = (DateTime) value;
|
||||
string theString = XmlConvert.ToString(time, XmlDateTimeSerializationMode.Utc);
|
||||
writer.WriteElementString("date", theString);//, "yyyy-MM-ddTHH:mm:ssZ"));
|
||||
}
|
||||
else if (value is bool)
|
||||
{
|
||||
writer.WriteElementString(value.ToString().ToLower(), "");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(String.Format("Value type '{0}' is unhandled", value.GetType().ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeDictionaryValues(Dictionary<string, object> dictionary, XmlWriter writer)
|
||||
{
|
||||
writer.WriteStartElement("dict");
|
||||
foreach (string key in dictionary.Keys)
|
||||
{
|
||||
object value = dictionary[key];
|
||||
writer.WriteElementString("key", key);
|
||||
compose(value, writer);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
private static int countObject(object value)
|
||||
{
|
||||
int count = 0;
|
||||
switch (value.GetType().ToString())
|
||||
{
|
||||
case "System.Collections.Generic.Dictionary`2[System.String,System.Object]":
|
||||
Dictionary<string, object> dict = (Dictionary<string, object>) value;
|
||||
foreach (string key in dict.Keys)
|
||||
{
|
||||
count += countObject(dict[key]);
|
||||
}
|
||||
count += dict.Keys.Count;
|
||||
count++;
|
||||
break;
|
||||
case "System.Collections.Generic.List`1[System.Object]":
|
||||
List<object> list = (List<object>) value;
|
||||
foreach (object obj in list)
|
||||
{
|
||||
count += countObject(obj);
|
||||
}
|
||||
count++;
|
||||
break;
|
||||
default:
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static byte[] writeBinaryDictionary(Dictionary<string, object> dictionary)
|
||||
{
|
||||
List<byte> buffer = new List<byte>();
|
||||
List<byte> header = new List<byte>();
|
||||
List<int> refs = new List<int>();
|
||||
for (int i = dictionary.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var o = new object[dictionary.Count];
|
||||
dictionary.Values.CopyTo(o, 0);
|
||||
composeBinary(o[i]);
|
||||
offsetTable.Add(objectTable.Count);
|
||||
refs.Add(refCount);
|
||||
refCount--;
|
||||
}
|
||||
for (int i = dictionary.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var o = new string[dictionary.Count];
|
||||
dictionary.Keys.CopyTo(o, 0);
|
||||
composeBinary(o[i]);//);
|
||||
offsetTable.Add(objectTable.Count);
|
||||
refs.Add(refCount);
|
||||
refCount--;
|
||||
}
|
||||
|
||||
if (dictionary.Count < 15)
|
||||
{
|
||||
header.Add(Convert.ToByte(0xD0 | Convert.ToByte(dictionary.Count)));
|
||||
}
|
||||
else
|
||||
{
|
||||
header.Add(0xD0 | 0xf);
|
||||
header.AddRange(writeBinaryInteger(dictionary.Count, false));
|
||||
}
|
||||
|
||||
|
||||
foreach (int val in refs)
|
||||
{
|
||||
byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize);
|
||||
Array.Reverse(refBuffer);
|
||||
buffer.InsertRange(0, refBuffer);
|
||||
}
|
||||
|
||||
buffer.InsertRange(0, header);
|
||||
|
||||
|
||||
objectTable.InsertRange(0, buffer);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] composeBinaryArray(List<object> objects)
|
||||
{
|
||||
List<byte> buffer = new List<byte>();
|
||||
List<byte> header = new List<byte>();
|
||||
List<int> refs = new List<int>();
|
||||
|
||||
for (int i = objects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
composeBinary(objects[i]);
|
||||
offsetTable.Add(objectTable.Count);
|
||||
refs.Add(refCount);
|
||||
refCount--;
|
||||
}
|
||||
|
||||
if (objects.Count < 15)
|
||||
{
|
||||
header.Add(Convert.ToByte(0xA0 | Convert.ToByte(objects.Count)));
|
||||
}
|
||||
else
|
||||
{
|
||||
header.Add(0xA0 | 0xf);
|
||||
header.AddRange(writeBinaryInteger(objects.Count, false));
|
||||
}
|
||||
|
||||
foreach (int val in refs)
|
||||
{
|
||||
byte[] refBuffer = RegulateNullBytes(BitConverter.GetBytes(val), objRefSize);
|
||||
Array.Reverse(refBuffer);
|
||||
buffer.InsertRange(0, refBuffer);
|
||||
}
|
||||
|
||||
buffer.InsertRange(0, header);
|
||||
|
||||
objectTable.InsertRange(0, buffer);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] composeBinary(object obj)
|
||||
{
|
||||
byte[] value;
|
||||
switch (obj.GetType().ToString())
|
||||
{
|
||||
case "System.Collections.Generic.Dictionary`2[System.String,System.Object]":
|
||||
value = writeBinaryDictionary((Dictionary<string, object>) obj);
|
||||
return value;
|
||||
|
||||
case "System.Collections.Generic.List`1[System.Object]":
|
||||
value = composeBinaryArray((List<object>) obj);
|
||||
return value;
|
||||
|
||||
case "System.Byte[]":
|
||||
value = writeBinaryByteArray((byte[]) obj);
|
||||
return value;
|
||||
|
||||
case "System.Double":
|
||||
value = writeBinaryDouble((double) obj);
|
||||
return value;
|
||||
|
||||
case "System.Int32":
|
||||
value = writeBinaryInteger((int) obj, true);
|
||||
return value;
|
||||
|
||||
case "System.String":
|
||||
value = writeBinaryString((string) obj, true);
|
||||
return value;
|
||||
|
||||
case "System.DateTime":
|
||||
value = writeBinaryDate((DateTime) obj);
|
||||
return value;
|
||||
|
||||
case "System.Boolean":
|
||||
value = writeBinaryBool((bool) obj);
|
||||
return value;
|
||||
|
||||
default:
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] writeBinaryDate(DateTime obj)
|
||||
{
|
||||
List<byte> buffer = new List<byte>(RegulateNullBytes(BitConverter.GetBytes(PlistDateConverter.ConvertToAppleTimeStamp(obj)), 8));
|
||||
buffer.Reverse();
|
||||
buffer.Insert(0, 0x33);
|
||||
objectTable.InsertRange(0, buffer);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] writeBinaryBool(bool obj)
|
||||
{
|
||||
List<byte> buffer = new List<byte>(new byte[1] { (bool) obj ? (byte) 9 : (byte) 8 });
|
||||
objectTable.InsertRange(0, buffer);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] writeBinaryInteger(int value, bool write)
|
||||
{
|
||||
List<byte> buffer = new List<byte>(BitConverter.GetBytes((long) value));
|
||||
buffer = new List<byte>(RegulateNullBytes(buffer.ToArray()));
|
||||
while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2)))
|
||||
buffer.Add(0);
|
||||
int header = 0x10 | (int) (Math.Log(buffer.Count) / Math.Log(2));
|
||||
|
||||
buffer.Reverse();
|
||||
|
||||
buffer.Insert(0, Convert.ToByte(header));
|
||||
|
||||
if (write)
|
||||
objectTable.InsertRange(0, buffer);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] writeBinaryDouble(double value)
|
||||
{
|
||||
List<byte> buffer = new List<byte>(RegulateNullBytes(BitConverter.GetBytes(value), 4));
|
||||
while (buffer.Count != Math.Pow(2, Math.Log(buffer.Count) / Math.Log(2)))
|
||||
buffer.Add(0);
|
||||
int header = 0x20 | (int) (Math.Log(buffer.Count) / Math.Log(2));
|
||||
|
||||
buffer.Reverse();
|
||||
|
||||
buffer.Insert(0, Convert.ToByte(header));
|
||||
|
||||
objectTable.InsertRange(0, buffer);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] writeBinaryByteArray(byte[] value)
|
||||
{
|
||||
List<byte> buffer = new List<byte>(value);
|
||||
List<byte> header = new List<byte>();
|
||||
if (value.Length < 15)
|
||||
{
|
||||
header.Add(Convert.ToByte(0x40 | Convert.ToByte(value.Length)));
|
||||
}
|
||||
else
|
||||
{
|
||||
header.Add(0x40 | 0xf);
|
||||
header.AddRange(writeBinaryInteger(buffer.Count, false));
|
||||
}
|
||||
|
||||
buffer.InsertRange(0, header);
|
||||
|
||||
objectTable.InsertRange(0, buffer);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] writeBinaryString(string value, bool head)
|
||||
{
|
||||
List<byte> buffer = new List<byte>();
|
||||
List<byte> header = new List<byte>();
|
||||
foreach (char chr in value.ToCharArray())
|
||||
buffer.Add(Convert.ToByte(chr));
|
||||
|
||||
if (head)
|
||||
{
|
||||
if (value.Length < 15)
|
||||
{
|
||||
header.Add(Convert.ToByte(0x50 | Convert.ToByte(value.Length)));
|
||||
}
|
||||
else
|
||||
{
|
||||
header.Add(0x50 | 0xf);
|
||||
header.AddRange(writeBinaryInteger(buffer.Count, false));
|
||||
}
|
||||
}
|
||||
|
||||
buffer.InsertRange(0, header);
|
||||
|
||||
objectTable.InsertRange(0, buffer);
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] RegulateNullBytes(byte[] value)
|
||||
{
|
||||
return RegulateNullBytes(value, 1);
|
||||
}
|
||||
|
||||
private static byte[] RegulateNullBytes(byte[] value, int minBytes)
|
||||
{
|
||||
Array.Reverse(value);
|
||||
List<byte> bytes = new List<byte>(value);
|
||||
for (int i = 0; i < bytes.Count; i++)
|
||||
{
|
||||
if (bytes[i] == 0 && bytes.Count > minBytes)
|
||||
{
|
||||
bytes.Remove(bytes[i]);
|
||||
i--;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytes.Count < minBytes)
|
||||
{
|
||||
int dist = minBytes - bytes.Count;
|
||||
for (int i = 0; i < dist; i++)
|
||||
bytes.Insert(0, 0);
|
||||
}
|
||||
|
||||
value = bytes.ToArray();
|
||||
Array.Reverse(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void parseTrailer(List<byte> trailer)
|
||||
{
|
||||
offsetByteSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(6, 1).ToArray(), 4), 0);
|
||||
objRefSize = BitConverter.ToInt32(RegulateNullBytes(trailer.GetRange(7, 1).ToArray(), 4), 0);
|
||||
byte[] refCountBytes = trailer.GetRange(12, 4).ToArray();
|
||||
Array.Reverse(refCountBytes);
|
||||
refCount = BitConverter.ToInt32(refCountBytes, 0);
|
||||
byte[] offsetTableOffsetBytes = trailer.GetRange(24, 8).ToArray();
|
||||
Array.Reverse(offsetTableOffsetBytes);
|
||||
offsetTableOffset = BitConverter.ToInt64(offsetTableOffsetBytes, 0);
|
||||
}
|
||||
|
||||
private static void parseOffsetTable(List<byte> offsetTableBytes)
|
||||
{
|
||||
for (int i = 0; i < offsetTableBytes.Count; i += offsetByteSize)
|
||||
{
|
||||
byte[] buffer = offsetTableBytes.GetRange(i, offsetByteSize).ToArray();
|
||||
Array.Reverse(buffer);
|
||||
offsetTable.Add(BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0));
|
||||
}
|
||||
}
|
||||
|
||||
private static object parseBinaryDictionary(int objRef)
|
||||
{
|
||||
Dictionary<string, object> buffer = new Dictionary<string, object>();
|
||||
List<int> refs = new List<int>();
|
||||
int refCount = 0;
|
||||
|
||||
byte dictByte = objectTable[offsetTable[objRef]];
|
||||
|
||||
int refStartPosition;
|
||||
refCount = getCount(offsetTable[objRef], out refStartPosition);
|
||||
|
||||
|
||||
if (refCount < 15)
|
||||
refStartPosition = offsetTable[objRef] + 1;
|
||||
else
|
||||
refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length;
|
||||
|
||||
for (int i = refStartPosition; i < refStartPosition + refCount * 2 * objRefSize; i += objRefSize)
|
||||
{
|
||||
byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray();
|
||||
Array.Reverse(refBuffer);
|
||||
refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0));
|
||||
}
|
||||
|
||||
for (int i = 0; i < refCount; i++)
|
||||
{
|
||||
buffer.Add((string) parseBinary(refs[i]), parseBinary(refs[i + refCount]));
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static object parseBinaryArray(int objRef)
|
||||
{
|
||||
List<object> buffer = new List<object>();
|
||||
List<int> refs = new List<int>();
|
||||
int refCount = 0;
|
||||
|
||||
byte arrayByte = objectTable[offsetTable[objRef]];
|
||||
|
||||
int refStartPosition;
|
||||
refCount = getCount(offsetTable[objRef], out refStartPosition);
|
||||
|
||||
|
||||
if (refCount < 15)
|
||||
refStartPosition = offsetTable[objRef] + 1;
|
||||
else
|
||||
//The following integer has a header aswell so we increase the refStartPosition by two to account for that.
|
||||
refStartPosition = offsetTable[objRef] + 2 + RegulateNullBytes(BitConverter.GetBytes(refCount), 1).Length;
|
||||
|
||||
for (int i = refStartPosition; i < refStartPosition + refCount * objRefSize; i += objRefSize)
|
||||
{
|
||||
byte[] refBuffer = objectTable.GetRange(i, objRefSize).ToArray();
|
||||
Array.Reverse(refBuffer);
|
||||
refs.Add(BitConverter.ToInt32(RegulateNullBytes(refBuffer, 4), 0));
|
||||
}
|
||||
|
||||
for (int i = 0; i < refCount; i++)
|
||||
{
|
||||
buffer.Add(parseBinary(refs[i]));
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static int getCount(int bytePosition, out int newBytePosition)
|
||||
{
|
||||
byte headerByte = objectTable[bytePosition];
|
||||
byte headerByteTrail = Convert.ToByte(headerByte & 0xf);
|
||||
int count;
|
||||
if (headerByteTrail < 15)
|
||||
{
|
||||
count = headerByteTrail;
|
||||
newBytePosition = bytePosition + 1;
|
||||
}
|
||||
else
|
||||
count = (int) parseBinaryInt(bytePosition + 1, out newBytePosition);
|
||||
return count;
|
||||
}
|
||||
|
||||
private static object parseBinary(int objRef)
|
||||
{
|
||||
byte header = objectTable[offsetTable[objRef]];
|
||||
switch (header & 0xF0)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
//If the byte is
|
||||
//0 return null
|
||||
//9 return true
|
||||
//8 return false
|
||||
return (objectTable[offsetTable[objRef]] == 0) ? (object) null : ((objectTable[offsetTable[objRef]] == 9) ? true : false);
|
||||
}
|
||||
case 0x10:
|
||||
{
|
||||
return parseBinaryInt(offsetTable[objRef]);
|
||||
}
|
||||
case 0x20:
|
||||
{
|
||||
return parseBinaryReal(offsetTable[objRef]);
|
||||
}
|
||||
case 0x30:
|
||||
{
|
||||
return parseBinaryDate(offsetTable[objRef]);
|
||||
}
|
||||
case 0x40:
|
||||
{
|
||||
return parseBinaryByteArray(offsetTable[objRef]);
|
||||
}
|
||||
case 0x50://String ASCII
|
||||
{
|
||||
return parseBinaryAsciiString(offsetTable[objRef]);
|
||||
}
|
||||
case 0x60://String Unicode
|
||||
{
|
||||
return parseBinaryUnicodeString(offsetTable[objRef]);
|
||||
}
|
||||
case 0xD0:
|
||||
{
|
||||
return parseBinaryDictionary(objRef);
|
||||
}
|
||||
case 0xA0:
|
||||
{
|
||||
return parseBinaryArray(objRef);
|
||||
}
|
||||
}
|
||||
throw new Exception("This type is not supported");
|
||||
}
|
||||
|
||||
public static object parseBinaryDate(int headerPosition)
|
||||
{
|
||||
byte[] buffer = objectTable.GetRange(headerPosition + 1, 8).ToArray();
|
||||
Array.Reverse(buffer);
|
||||
double appleTime = BitConverter.ToDouble(buffer, 0);
|
||||
DateTime result = PlistDateConverter.ConvertFromAppleTimeStamp(appleTime);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object parseBinaryInt(int headerPosition)
|
||||
{
|
||||
int output;
|
||||
return parseBinaryInt(headerPosition, out output);
|
||||
}
|
||||
|
||||
private static object parseBinaryInt(int headerPosition, out int newHeaderPosition)
|
||||
{
|
||||
byte header = objectTable[headerPosition];
|
||||
int byteCount = (int) Math.Pow(2, header & 0xf);
|
||||
byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray();
|
||||
Array.Reverse(buffer);
|
||||
//Add one to account for the header byte
|
||||
newHeaderPosition = headerPosition + byteCount + 1;
|
||||
return BitConverter.ToInt32(RegulateNullBytes(buffer, 4), 0);
|
||||
}
|
||||
|
||||
private static object parseBinaryReal(int headerPosition)
|
||||
{
|
||||
byte header = objectTable[headerPosition];
|
||||
int byteCount = (int) Math.Pow(2, header & 0xf);
|
||||
byte[] buffer = objectTable.GetRange(headerPosition + 1, byteCount).ToArray();
|
||||
Array.Reverse(buffer);
|
||||
|
||||
return BitConverter.ToDouble(RegulateNullBytes(buffer, 8), 0);
|
||||
}
|
||||
|
||||
private static object parseBinaryAsciiString(int headerPosition)
|
||||
{
|
||||
int charStartPosition;
|
||||
int charCount = getCount(headerPosition, out charStartPosition);
|
||||
|
||||
var buffer = objectTable.GetRange(charStartPosition, charCount);
|
||||
return buffer.Count > 0 ? Encoding.ASCII.GetString(buffer.ToArray()) : string.Empty;
|
||||
}
|
||||
|
||||
private static object parseBinaryUnicodeString(int headerPosition)
|
||||
{
|
||||
int charStartPosition;
|
||||
int charCount = getCount(headerPosition, out charStartPosition);
|
||||
charCount = charCount * 2;
|
||||
|
||||
byte[] buffer = new byte[charCount];
|
||||
byte one, two;
|
||||
|
||||
for (int i = 0; i < charCount; i += 2)
|
||||
{
|
||||
one = objectTable.GetRange(charStartPosition + i, 1)[0];
|
||||
two = objectTable.GetRange(charStartPosition + i + 1, 1)[0];
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
buffer[i] = two;
|
||||
buffer[i + 1] = one;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[i] = one;
|
||||
buffer[i + 1] = two;
|
||||
}
|
||||
}
|
||||
|
||||
return Encoding.Unicode.GetString(buffer);
|
||||
}
|
||||
|
||||
private static object parseBinaryByteArray(int headerPosition)
|
||||
{
|
||||
int byteStartPosition;
|
||||
int byteCount = getCount(headerPosition, out byteStartPosition);
|
||||
return objectTable.GetRange(byteStartPosition, byteCount).ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum plistType
|
||||
{
|
||||
Auto, Binary, Xml
|
||||
}
|
||||
|
||||
public static class PlistDateConverter
|
||||
{
|
||||
public static long timeDifference = 978307200;
|
||||
|
||||
public static long GetAppleTime(long unixTime)
|
||||
{
|
||||
return unixTime - timeDifference;
|
||||
}
|
||||
|
||||
public static long GetUnixTime(long appleTime)
|
||||
{
|
||||
return appleTime + timeDifference;
|
||||
}
|
||||
|
||||
public static DateTime ConvertFromAppleTimeStamp(double timestamp)
|
||||
{
|
||||
DateTime origin = new DateTime(2001, 1, 1, 0, 0, 0, 0);
|
||||
return origin.AddSeconds(timestamp);
|
||||
}
|
||||
|
||||
public static double ConvertToAppleTimeStamp(DateTime date)
|
||||
{
|
||||
DateTime begin = new DateTime(2001, 1, 1, 0, 0, 0, 0);
|
||||
TimeSpan diff = date - begin;
|
||||
return Math.Floor(diff.TotalSeconds);
|
||||
}
|
||||
}
|
||||
}
|
461
SebWindowsConfig/Utilities/SEBClientInfo.cs
Normal file
461
SebWindowsConfig/Utilities/SEBClientInfo.cs
Normal file
@@ -0,0 +1,461 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
//
|
||||
// SEBClientInfo.cs
|
||||
// SafeExamBrowser
|
||||
//
|
||||
// Copyright (c) 2010-2020 Viktor Tomas, Dirk Bauer, Daniel R. Schneider, Pascal Wyss,
|
||||
// ETH Zurich, IT Services,
|
||||
// based on the original idea of Safe Exam Browser
|
||||
// by Stefan Schneider, University of Giessen
|
||||
// Project concept: Thomas Piendl, Daniel R. Schneider,
|
||||
// Dirk Bauer, Kai Reuter, Tobias Halbherr, Karsten Burger, Marco Lehre,
|
||||
// Brigitte Schmucki, Oliver Rahs. French localization: Nicolas Dunand
|
||||
//
|
||||
// ``The contents of this file are subject to the Mozilla Public License
|
||||
// Version 1.1 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.mozilla.org/MPL/
|
||||
//
|
||||
// Software distributed under the License is distributed on an "AS IS"
|
||||
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing rights and limitations
|
||||
// under the License.
|
||||
//
|
||||
// The Original Code is Safe Exam Browser for Windows.
|
||||
//
|
||||
// The Initial Developers of the Original Code are Viktor Tomas,
|
||||
// Dirk Bauer, Daniel R. Schneider, Pascal Wyss.
|
||||
// Portions created by Viktor Tomas, Dirk Bauer, Daniel R. Schneider, Pascal Wyss
|
||||
// are Copyright (c) 2010-2020 Viktor Tomas, Dirk Bauer, Daniel R. Schneider,
|
||||
// Pascal Wyss, ETH Zurich, IT Services,
|
||||
// based on the original idea of Safe Exam Browser
|
||||
// by Stefan Schneider, University of Giessen. All Rights Reserved.
|
||||
//
|
||||
// Contributor(s): ______________________________________.
|
||||
//
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public enum chooseFileToUploadPolicies
|
||||
{
|
||||
manuallyWithFileRequester = 0,
|
||||
attemptUploadSameFileDownloadedBefore = 1,
|
||||
onlyAllowUploadSameFileDownloadedBefore = 2
|
||||
};
|
||||
|
||||
public enum newBrowserWindowPolicies
|
||||
{
|
||||
getGenerallyBlocked = 0,
|
||||
openInSameWindow = 1,
|
||||
openInNewWindow = 2
|
||||
};
|
||||
|
||||
public enum sebServicePolicies
|
||||
{
|
||||
ignoreService = 0,
|
||||
indicateMissingService = 1,
|
||||
forceSebService = 2
|
||||
};
|
||||
|
||||
public enum browserViewModes
|
||||
{
|
||||
browserViewModeWindow = 0,
|
||||
browserViewModeFullscreen = 1
|
||||
};
|
||||
|
||||
// MAC
|
||||
public enum sebPurposePolicies
|
||||
{
|
||||
sebPurposePolicyStartingExam = 0,
|
||||
sebPurposePolicyConfiguringClient = 1
|
||||
};
|
||||
|
||||
public enum URLFilterRuleActions
|
||||
{
|
||||
block = 0,
|
||||
allow = 1,
|
||||
ignore = 2,
|
||||
unknown = 3
|
||||
};
|
||||
|
||||
public enum SEBMinMacOSVersion
|
||||
{
|
||||
SEBMinOSX10_7 = 0,
|
||||
SEBMinOSX10_8 = 1,
|
||||
SEBMinOSX10_9 = 2,
|
||||
SEBMinOSX10_10 = 3,
|
||||
SEBMinOSX10_11 = 4,
|
||||
SEBMinMacOS10_12 = 5,
|
||||
SEBMinMacOS10_13 = 6,
|
||||
SEBMinMacOS10_14 = 7
|
||||
};
|
||||
|
||||
public class SEBClientInfo
|
||||
{
|
||||
#region Imports
|
||||
[DllImport("kernel32.Dll")]
|
||||
public static extern short GetVersionEx(ref OSVERSIONINFO o);
|
||||
#endregion
|
||||
|
||||
// Socket protocol
|
||||
//static int ai_family = AF_INET;
|
||||
//static int ai_socktype = SOCK_STREAM;
|
||||
//static int ai_protocol = IPPROTO_TCP;
|
||||
|
||||
#region Constants
|
||||
|
||||
// Name and location of SEB configuration files and logfiles
|
||||
public const string SEB_CLIENT_CONFIG = "SebClientSettings.seb";
|
||||
public const string SEB_CLIENT_LOG = "SebClient.log";
|
||||
private const string XUL_RUNNER_CONFIG = "config.json";
|
||||
public const string SEB_SHORTNAME = "SEB";
|
||||
public const string XUL_RUNNER = "firefox.exe";
|
||||
private const string XUL_RUNNER_INI = "seb.ini";
|
||||
|
||||
// Application path contains [MANUFACTURER]\[PRODUCT_NAME]
|
||||
// (see also "SebWindowsPackageSetup" Project in MS Visual Studio 10)
|
||||
public const string MANUFACTURER_LOCAL = "SafeExamBrowser";
|
||||
//private const string MANUFACTURER = "ETH Zuerich";
|
||||
public const string PRODUCT_NAME = "SafeExamBrowser";
|
||||
public const string SEB_SERVICE_DIRECTORY = "SebWindowsServiceWCF";
|
||||
public const string SEB_BROWSER_DIRECTORY = "SebWindowsBrowser";
|
||||
private const string XUL_RUNNER_DIRECTORY = "xulrunner";
|
||||
private const string XUL_SEB_DIRECTORY = "xul_seb";
|
||||
public const string FILENAME_SEB = "SafeExamBrowser.exe";
|
||||
public const string FILENAME_SEBCONFIGTOOL = "SEBConfigTool.exe";
|
||||
public const string FILENAME_SEBSERVICE = "SebWindowsServiceWCF.exe";
|
||||
public const string FILENAME_DLL_FLECK = "Fleck.dll";
|
||||
public const string FILENAME_DLL_ICONLIB = "IconLib.dll";
|
||||
public const string FILENAME_DLL_IONICZIP = "Ionic.Zip.dll";
|
||||
public const string FILENAME_DLL_METRO = "MetroFramework.dll";
|
||||
public const string FILENAME_DLL_NAUDIO = "NAudio.dll";
|
||||
public const string FILENAME_DLL_NEWTONSOFTJSON = "Newtonsoft.Json.dll";
|
||||
public const string FILENAME_DLL_SERVICECONTRACTS = "SEBWindowsServiceContracts.dll";
|
||||
public const string BROWSER_USERAGENT_DESKTOP = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0";
|
||||
public const string BROWSER_USERAGENT_TOUCH = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0; Touch) Gecko/20100101 Firefox/52.0";
|
||||
public const string BROWSER_USERAGENT_TOUCH_IPAD = "Mozilla/5.0 (iPad; CPU OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.3 Mobile/15E216 Safari/605.1.15";
|
||||
public const string BROWSER_USERAGENT_SEB = "SEB";
|
||||
|
||||
public const string END_OF_STRING_KEYWORD = "---SEB---";
|
||||
private const string DEFAULT_USERNAME = "";
|
||||
private const string DEFAULT_HOSTNAME = "localhost";
|
||||
private const string DEFAULT_HOST_IP_ADDRESS = "127.0.0.1";
|
||||
private const int DEFAULT_PORTNUMBER = 57016;
|
||||
public const string DEFAULT_KEY = "Di𝈭l𝈖Ch𝈒aht𝈁aHai1972";
|
||||
private const int DEFAULT_SEND_INTERVAL = 100;
|
||||
private const int DEFAULT_RECV_TIMEOUT = 100;
|
||||
private const int DEFAULT_NUM_MESSAGES = 3;
|
||||
|
||||
public const string SEB_NEW_DESKTOP_NAME = "SEBDesktop";
|
||||
public const string SEB_WINDOWS_SERVICE_NAME = "SebWindowsService";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public static bool ExplorerShellWasKilled { get; set; }
|
||||
public static bool IsNewOS { get; set; }
|
||||
public static bool examMode = false;
|
||||
|
||||
// SEB Client Socket properties
|
||||
public static char[] UserNameRegistryFlags { get; set; }
|
||||
public static char[] RegistryFlags { get; set; }
|
||||
public static string HostName { get; set; }
|
||||
public static string HostIpAddress { get; set; }
|
||||
public static string UserName { get; set; }
|
||||
public static char[] UserSid { get; set; }
|
||||
public static int PortNumber { get; set; }
|
||||
public static int SendInterval { get; set; }
|
||||
public static int RecvTimeout { get; set; }
|
||||
public static int NumMessages { get; set; }
|
||||
public static int MessageNr { get; set; }
|
||||
public static string DesktopName { get; set; }
|
||||
|
||||
// SEB Client Directories properties
|
||||
public static string ApplicationExecutableDirectory { get; set; }
|
||||
public static string ProgramFilesX86Directory { get; set; }
|
||||
public static bool LogFileDesiredMsgHook { get; set; }
|
||||
public static bool LogFileDesiredSebClient { get; set; }
|
||||
public static string SebClientLogFileDirectory { get; set; }
|
||||
public static string SebClientDirectory { get; set; }
|
||||
public static string SebClientLogFile { get; set; }
|
||||
public static string SebClientSettingsProgramDataDirectory { get; set; }
|
||||
public static string SebClientSettingsAppDataDirectory { get; set; }
|
||||
public static string XulRunnerAdditionalDictionariesDirectory { get; set; }
|
||||
public static string XulRunnerDirectory { get; set; }
|
||||
public static string XulSebDirectory { get; set; }
|
||||
public static string SebClientSettingsProgramDataFile;
|
||||
public static string SebClientSettingsAppDataFile;
|
||||
public static string XulRunnerConfigFileDirectory { get; set; }
|
||||
public static string XulRunnerConfigFile;
|
||||
public static string XulRunnerExePath;
|
||||
public static string XulRunnerSebIniPath;
|
||||
public static string XulRunnerParameter;
|
||||
//public static string XulRunnerFlashContainerState { get; set; }
|
||||
|
||||
public static string ExamUrl { get; set; }
|
||||
public static string QuitPassword { get; set; }
|
||||
public static string QuitHashcode { get; set; }
|
||||
|
||||
//public static Dictionary<string, object> sebSettings = new Dictionary<string, object>();
|
||||
|
||||
public static string LoadingSettingsFileName = "";
|
||||
|
||||
public static float scaleFactor = 1;
|
||||
public static int appChooserHeight = 132;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Structures
|
||||
/// <summary>
|
||||
/// Stores windows version info.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct OSVERSIONINFO
|
||||
{
|
||||
public int dwOSVersionInfoSize;
|
||||
public int dwMajorVersion;
|
||||
public int dwMinorVersion;
|
||||
public int dwBuildNumber;
|
||||
public int dwPlatformId;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string szCSDVersion;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//public static SEBClientConfig sebClientConfig;
|
||||
|
||||
public static Dictionary<string, object> getSebSetting(string key)
|
||||
{
|
||||
object sebSetting = null;
|
||||
try
|
||||
{
|
||||
sebSetting = SEBSettings.settingsCurrent[key];
|
||||
}
|
||||
catch
|
||||
{
|
||||
sebSetting = null;
|
||||
}
|
||||
|
||||
if (sebSetting != null)
|
||||
return SEBSettings.settingsCurrent;
|
||||
else
|
||||
return SEBSettings.settingsDefault;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets user, host info, send-recv interval, recv timeout, Logger and read SebClient configuration.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool SetSebClientConfiguration()
|
||||
{
|
||||
|
||||
bool setSebClientConfiguration = false;
|
||||
|
||||
// Initialise socket properties
|
||||
IsNewOS = false;
|
||||
ExplorerShellWasKilled = false;
|
||||
UserNameRegistryFlags = new char[100];
|
||||
RegistryFlags = new char[50];
|
||||
UserSid = new char[512];
|
||||
UserName = DEFAULT_USERNAME;
|
||||
HostName = DEFAULT_HOSTNAME;
|
||||
HostIpAddress = DEFAULT_HOST_IP_ADDRESS;
|
||||
PortNumber = DEFAULT_PORTNUMBER;
|
||||
SendInterval = DEFAULT_SEND_INTERVAL;
|
||||
RecvTimeout = DEFAULT_RECV_TIMEOUT;
|
||||
NumMessages = DEFAULT_NUM_MESSAGES;
|
||||
|
||||
//Sets paths to files SEB has to save or read from the file system
|
||||
SetSebPaths();
|
||||
|
||||
byte[] sebClientSettings = null;
|
||||
|
||||
// Create a string builder for a temporary log (until we can write it with the Logger)
|
||||
StringBuilder tempLogStringBuilder = new StringBuilder();
|
||||
|
||||
// Try to read the SebClientSettigs.seb file from the program data directory
|
||||
try
|
||||
{
|
||||
sebClientSettings = File.ReadAllBytes(SebClientSettingsProgramDataFile);
|
||||
}
|
||||
catch (Exception streamReadException)
|
||||
{
|
||||
// Write error into string with temporary log string builder
|
||||
tempLogStringBuilder.Append("Could not load SebClientSettigs.seb from the Program Data directory").Append(streamReadException == null ? null : streamReadException.GetType().ToString()).Append(streamReadException.Message);
|
||||
}
|
||||
if (sebClientSettings == null)
|
||||
{
|
||||
// Try to read the SebClientSettigs.seb file from the local application data directory
|
||||
try
|
||||
{
|
||||
sebClientSettings = File.ReadAllBytes(SebClientSettingsAppDataFile);
|
||||
}
|
||||
catch (Exception streamReadException)
|
||||
{
|
||||
// Write error into string with temporary log string builder
|
||||
tempLogStringBuilder.Append("Could not load SebClientSettigs.seb from the Roaming Application Data directory. ").Append(streamReadException == null ? null : streamReadException.GetType().ToString()).Append(streamReadException.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the decrypted configuration settings.
|
||||
if (!SEBSettings.StoreDecryptedSebClientSettings(sebClientSettings))
|
||||
return false;
|
||||
|
||||
// Initialise Logger, if enabled
|
||||
InitializeLogger();
|
||||
|
||||
// Save the temporary log string into the log
|
||||
Logger.AddError(tempLogStringBuilder.ToString(), null, null);
|
||||
|
||||
// Set username
|
||||
UserName = Environment.UserName;
|
||||
|
||||
setSebClientConfiguration = true;
|
||||
|
||||
// Write settings into log
|
||||
StringBuilder userInfo =
|
||||
new StringBuilder ("User Name: " ).Append(UserName)
|
||||
.Append(" Host Name: " ).Append(HostName)
|
||||
.Append(" Port Number: " ).Append(PortNumber)
|
||||
.Append(" Send Interval: " ).Append(SendInterval)
|
||||
.Append(" Recv Timeout: " ).Append(RecvTimeout)
|
||||
.Append(" Num Messages: " ).Append(NumMessages)
|
||||
.Append(" SebClientConfigFileDirectory: ").Append(SebClientSettingsAppDataDirectory)
|
||||
.Append(" SebClientConfigFile: " ).Append(SebClientSettingsAppDataFile);
|
||||
Logger.AddInformation(userInfo.ToString(), null, null);
|
||||
|
||||
return setSebClientConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise Logger if it's enabled.
|
||||
/// </summary>
|
||||
public static void InitializeLogger()
|
||||
{
|
||||
if ((Boolean)getSebSetting(SEBSettings.KeyEnableLogging)[SEBSettings.KeyEnableLogging])
|
||||
{
|
||||
string logDirectory = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyLogDirectoryWin);
|
||||
if (!String.IsNullOrEmpty(logDirectory))
|
||||
{
|
||||
// Expand environment variables in log file path
|
||||
SebClientLogFileDirectory = Environment.ExpandEnvironmentVariables(logDirectory);
|
||||
|
||||
SebClientLogFile = String.Format(@"{0}\{1}", SebClientLogFileDirectory, SEB_CLIENT_LOG);
|
||||
}
|
||||
else
|
||||
{
|
||||
SEBClientInfo.SetDefaultClientLogFile();
|
||||
}
|
||||
Logger.InitLogger(SEBClientInfo.SebClientLogFileDirectory, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets paths to files SEB has to save or read from the file system.
|
||||
/// </summary>
|
||||
public static void SetSebPaths()
|
||||
{
|
||||
// Get the path of the directory the application executable lies in
|
||||
ApplicationExecutableDirectory = Path.GetDirectoryName(Application.ExecutablePath);
|
||||
|
||||
// Get the path of the "Program Files X86" directory.
|
||||
ProgramFilesX86Directory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
||||
|
||||
// Get the path of the "Program Data" and "Local Application Data" directory.
|
||||
string programDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); //GetEnvironmentVariable("PROGRAMMDATA");
|
||||
string appDataDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
|
||||
/// Get paths for the two possible locations of the SebClientSettings.seb file
|
||||
///
|
||||
// In the program data directory (for managed systems, only an administrator can write in this directory):
|
||||
// If there is a SebClientSettigs.seb file, then this has priority and is used by the SEB client, another
|
||||
// SebClientSettigs.seb file in the local app data folder is ignored then and the SEB client cannot be
|
||||
// reconfigured by opening a .seb file saved for configuring a client
|
||||
StringBuilder sebClientSettingsProgramDataDirectoryBuilder = new StringBuilder(programDataDirectory).Append("\\").Append(MANUFACTURER_LOCAL).Append("\\"); //.Append(PRODUCT_NAME).Append("\\");
|
||||
SebClientSettingsProgramDataDirectory = sebClientSettingsProgramDataDirectoryBuilder.ToString();
|
||||
|
||||
// In the local application data directory (for unmanaged systems like student computers, user can write in this directory):
|
||||
// A SebClientSettigs.seb file in this directory can be created or replaced by opening a .seb file saved for configuring a client
|
||||
StringBuilder sebClientSettingsAppDataDirectoryBuilder = new StringBuilder(appDataDirectory).Append("\\").Append(MANUFACTURER_LOCAL).Append("\\"); //.Append(PRODUCT_NAME).Append("\\");
|
||||
SebClientSettingsAppDataDirectory = sebClientSettingsAppDataDirectoryBuilder.ToString();
|
||||
|
||||
// Set the location of the SebWindowsClientDirectory
|
||||
StringBuilder sebClientDirectoryBuilder = new StringBuilder(ProgramFilesX86Directory).Append("\\").Append(PRODUCT_NAME).Append("\\");
|
||||
SebClientDirectory = sebClientDirectoryBuilder.ToString();
|
||||
|
||||
// The directory into which additional dictionaries are extracted.
|
||||
XulRunnerAdditionalDictionariesDirectory = Path.Combine(SebClientSettingsAppDataDirectory, "Dictionaries");
|
||||
|
||||
// Set the location of the XulRunnerDirectory
|
||||
//StringBuilder xulRunnerDirectoryBuilder = new StringBuilder(SebClientDirectory).Append(XUL_RUNNER_DIRECTORY).Append("\\");
|
||||
//XulRunnerDirectory = xulRunnerDirectoryBuilder.ToString();
|
||||
StringBuilder xulRunnerDirectoryBuilder = new StringBuilder(SEB_BROWSER_DIRECTORY).Append("\\").Append(XUL_RUNNER_DIRECTORY).Append("\\");
|
||||
XulRunnerDirectory = xulRunnerDirectoryBuilder.ToString();
|
||||
|
||||
// Set the location of the XulSebDirectory
|
||||
//StringBuilder xulSebDirectoryBuilder = new StringBuilder(SebClientDirectory).Append(XUL_SEB_DIRECTORY).Append("\\");
|
||||
//XulSebDirectory = xulSebDirectoryBuilder.ToString();
|
||||
StringBuilder xulSebDirectoryBuilder = new StringBuilder(SEB_BROWSER_DIRECTORY).Append("\\").Append(XUL_SEB_DIRECTORY).Append("\\");
|
||||
XulSebDirectory = xulSebDirectoryBuilder.ToString();
|
||||
|
||||
// Set the location of the XulRunnerExePath
|
||||
//StringBuilder xulRunnerExePathBuilder = new StringBuilder("\"").Append(XulRunnerDirectory).Append(XUL_RUNNER).Append("\"");
|
||||
//XulRunnerExePath = xulRunnerExePathBuilder.ToString();
|
||||
StringBuilder xulRunnerExePathBuilder = new StringBuilder(XulRunnerDirectory).Append(XUL_RUNNER); //.Append("\"");
|
||||
XulRunnerExePath = xulRunnerExePathBuilder.ToString();
|
||||
|
||||
// Set the location of the seb.ini
|
||||
StringBuilder xulRunnerSebIniPathBuilder = new StringBuilder(XulSebDirectory).Append(XUL_RUNNER_INI); //.Append("\"");
|
||||
XulRunnerSebIniPath = xulRunnerSebIniPathBuilder.ToString();
|
||||
|
||||
// Get the two possible paths of the SebClientSettings.seb file
|
||||
StringBuilder sebClientSettingsProgramDataBuilder = new StringBuilder(SebClientSettingsProgramDataDirectory).Append(SEB_CLIENT_CONFIG);
|
||||
SebClientSettingsProgramDataFile = sebClientSettingsProgramDataBuilder.ToString();
|
||||
|
||||
StringBuilder sebClientSettingsAppDataBuilder = new StringBuilder(SebClientSettingsAppDataDirectory).Append(SEB_CLIENT_CONFIG);
|
||||
SebClientSettingsAppDataFile = sebClientSettingsAppDataBuilder.ToString();
|
||||
|
||||
// Set the default location of the SebClientLogFileDirectory
|
||||
SetDefaultClientLogFile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the default location of the SebClientLogFileDirectory.
|
||||
/// </summary>
|
||||
public static void SetDefaultClientLogFile()
|
||||
{
|
||||
StringBuilder SebClientLogFileDirectoryBuilder = new StringBuilder(SebClientSettingsAppDataDirectory); //.Append("\\").Append(MANUFACTURER_LOCAL).Append("\\");
|
||||
SebClientLogFileDirectory = SebClientLogFileDirectoryBuilder.ToString();
|
||||
|
||||
// Set the path of the SebClient.log file
|
||||
StringBuilder sebClientLogFileBuilder = new StringBuilder(SebClientLogFileDirectory).Append(SEB_CLIENT_LOG);
|
||||
SebClientLogFile = sebClientLogFileBuilder.ToString();
|
||||
}
|
||||
|
||||
public static bool CreateNewDesktopOldValue { get; set; }
|
||||
|
||||
public static string ContractEnvironmentVariables(string path)
|
||||
{
|
||||
path = Path.GetFullPath(path);
|
||||
DictionaryEntry currentEntry = new DictionaryEntry("", "");
|
||||
foreach (object key in Environment.GetEnvironmentVariables().Keys)
|
||||
{
|
||||
string value = (string)Environment.GetEnvironmentVariables()[key];
|
||||
if (path.ToUpperInvariant().Contains(value.ToUpperInvariant()) && value.Length > ((string)currentEntry.Value).Length)
|
||||
{
|
||||
currentEntry.Key = (string)key;
|
||||
currentEntry.Value = value;
|
||||
}
|
||||
}
|
||||
return path.Replace((string)currentEntry.Value, "%" + (string)currentEntry.Key + "%");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
908
SebWindowsConfig/Utilities/SEBConfigFileManager.cs
Normal file
908
SebWindowsConfig/Utilities/SEBConfigFileManager.cs
Normal file
@@ -0,0 +1,908 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using DictObj = System.Collections.Generic.Dictionary<string, object>;
|
||||
|
||||
//
|
||||
// SEBConfigFileManager.cs
|
||||
// SafeExamBrowser
|
||||
//
|
||||
// Copyright (c) 2010-2020 Daniel R. Schneider,
|
||||
// ETH Zurich, IT Services,
|
||||
// based on the original idea of Safe Exam Browser
|
||||
// by Stefan Schneider, University of Giessen
|
||||
// Project concept: Thomas Piendl, Daniel R. Schneider,
|
||||
// Dirk Bauer, Kai Reuter, Tobias Halbherr, Karsten Burger, Marco Lehre,
|
||||
// Brigitte Schmucki, Oliver Rahs. French localization: Nicolas Dunand
|
||||
//
|
||||
// ``The contents of this file are subject to the Mozilla Public License
|
||||
// Version 1.1 (the "License"); you may not use this file except in
|
||||
// compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.mozilla.org/MPL/
|
||||
//
|
||||
// Software distributed under the License is distributed on an "AS IS"
|
||||
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing rights and limitations
|
||||
// under the License.
|
||||
//
|
||||
// The Original Code is Safe Exam Browser for Windows.
|
||||
//
|
||||
// The Initial Developer of the Original Code is Daniel R. Schneider.
|
||||
// Portions created by Daniel R. Schneider
|
||||
// are Copyright (c) 2010-2020 Daniel R. Schneider,
|
||||
// ETH Zurich, IT Services,
|
||||
// based on the original idea of Safe Exam Browser
|
||||
// by Stefan Schneider, University of Giessen. All Rights Reserved.
|
||||
//
|
||||
// Contributor(s): ______________________________________.
|
||||
//
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public class SEBConfigFileManager
|
||||
{
|
||||
public static SebPasswordDialogForm sebPasswordDialogForm;
|
||||
|
||||
// Prefixes
|
||||
private const int PREFIX_LENGTH = 4;
|
||||
private const int MULTIPART_LENGTH = 8;
|
||||
private const int CUSTOMHEADER_LENGTH = 4;
|
||||
private const string PUBLIC_KEY_HASH_MODE = "pkhs";
|
||||
private const string PUBLIC_SYMMETRIC_KEY_MODE = "phsk";
|
||||
private const string PASSWORD_MODE = "pswd";
|
||||
private const string PLAIN_DATA_MODE = "plnd";
|
||||
private const string PASSWORD_CONFIGURING_CLIENT_MODE = "pwcc";
|
||||
private const string UNENCRYPTED_MODE = "<?xm";
|
||||
private const string MULTIPART_MODE = "mphd";
|
||||
private const string CUSTOM_HEADER_MODE = "cmhd";
|
||||
|
||||
// Public key hash identifier length
|
||||
private const int PUBLIC_KEY_HASH_LENGTH = 20;
|
||||
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Decrypt and deserialize SEB settings
|
||||
/// When forEditing = true, then the decrypting password the user entered and/or
|
||||
/// certificate reference found in the .seb file is returned
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
public static DictObj DecryptSEBSettings(byte[] sebData, bool forEditing, ref string sebFilePassword, ref bool passwordIsHash, ref X509Certificate2 sebFileCertificateRef, bool suppressFileFormatError = false)
|
||||
{
|
||||
// Ungzip the .seb (according to specification >= v14) source data
|
||||
byte[] unzippedSebData = GZipByte.Decompress(sebData);
|
||||
|
||||
// if unzipped data is not null, then unzipping worked, we use unzipped data
|
||||
// if unzipped data is null, then the source data may be an uncompressed .seb file, we proceed with it
|
||||
if (unzippedSebData != null) sebData = unzippedSebData;
|
||||
|
||||
string prefixString;
|
||||
|
||||
// save the data including the first 4 bytes for the case that it's acutally an unencrypted XML plist
|
||||
byte[] sebDataUnencrypted = sebData.Clone() as byte[];
|
||||
|
||||
// Get 4-char prefix
|
||||
prefixString = GetPrefixStringFromData(ref sebData);
|
||||
|
||||
//// Check prefix identifying encryption modes
|
||||
|
||||
/// Check for new Multipart and Custom headers
|
||||
|
||||
// Multipart Config File: The first part containts the regular SEB key/value settings
|
||||
// following parts can contain additional resources. An updated SEB version will be
|
||||
// able to read and process those parts sequentially as a stream.
|
||||
// Therefore potentially large additional resources won't have to be loaded into memory at once
|
||||
if (prefixString.CompareTo(MULTIPART_MODE) == 0)
|
||||
{
|
||||
// Skip the Multipart Config File header
|
||||
byte[] multipartConfigLengthData = GetPrefixDataFromData(ref sebData, MULTIPART_LENGTH);
|
||||
long multipartConfigLength = BitConverter.ToInt64(multipartConfigLengthData, 0);
|
||||
Logger.AddInformation("Multipart Config File, first part (settings) length: " + multipartConfigLength);
|
||||
|
||||
try
|
||||
{
|
||||
Logger.AddInformation("Cropping config file, as this SEB version cannot process additional parts of multipart config files.");
|
||||
|
||||
byte[] dataFirstPart = new byte[sebData.Length - multipartConfigLength];
|
||||
Buffer.BlockCopy(sebData, 0, dataFirstPart, 0, dataFirstPart.Length);
|
||||
sebData = dataFirstPart;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.AddError("Error while cropping config file", null, ex, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Header: Containts a 32 bit value for the length of the header
|
||||
// followed by the custom header information. After the header, regular
|
||||
// SEB config file data follows
|
||||
if (prefixString.CompareTo(CUSTOM_HEADER_MODE) == 0)
|
||||
{
|
||||
// Skip the Custom Header
|
||||
byte[] customHeaderLengthData = GetPrefixDataFromData(ref sebData, CUSTOMHEADER_LENGTH);
|
||||
int customHeaderLength = BitConverter.ToInt32(customHeaderLengthData, 0);
|
||||
Logger.AddInformation("Custom Config File Header length: " + customHeaderLength);
|
||||
try
|
||||
{
|
||||
Logger.AddInformation("Removing custom header from config file data. This SEB version cannot process this header type and will ignore it.");
|
||||
|
||||
byte[] customHeaderData = GetPrefixDataFromData(ref sebData, customHeaderLength);
|
||||
|
||||
Logger.AddInformation("Custom header data: " + customHeaderData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.AddError("Error while removing custom header from config file data", null, ex, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix = pksh ("Public-Symmetric Key Hash") ?
|
||||
|
||||
if (prefixString.CompareTo(PUBLIC_SYMMETRIC_KEY_MODE) == 0)
|
||||
{
|
||||
|
||||
// Decrypt with cryptographic identity/private and symmetric key
|
||||
sebData = DecryptDataWithPublicKeyHashPrefix(sebData, true, forEditing, ref sebFileCertificateRef);
|
||||
if (sebData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get 4-char prefix again
|
||||
// and remaining data without prefix, which is either plain or still encoded with password
|
||||
prefixString = GetPrefixStringFromData(ref sebData);
|
||||
}
|
||||
|
||||
// Prefix = pkhs ("Public Key Hash") ?
|
||||
|
||||
if (prefixString.CompareTo(PUBLIC_KEY_HASH_MODE) == 0)
|
||||
{
|
||||
|
||||
// Decrypt with cryptographic identity/private key
|
||||
sebData = DecryptDataWithPublicKeyHashPrefix(sebData, false, forEditing, ref sebFileCertificateRef);
|
||||
if (sebData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get 4-char prefix again
|
||||
// and remaining data without prefix, which is either plain or still encoded with password
|
||||
prefixString = GetPrefixStringFromData(ref sebData);
|
||||
}
|
||||
|
||||
// Prefix = pswd ("Password") ?
|
||||
|
||||
if (prefixString.CompareTo(PASSWORD_MODE) == 0)
|
||||
{
|
||||
|
||||
// Decrypt with password
|
||||
// if the user enters the right one
|
||||
byte[] sebDataDecrypted = null;
|
||||
string password;
|
||||
// Allow up to 5 attempts for entering decoding password
|
||||
string enterPasswordString = SEBUIStrings.enterPassword;
|
||||
int i = 5;
|
||||
do
|
||||
{
|
||||
i--;
|
||||
// Prompt for password
|
||||
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.loadingSettings, enterPasswordString);
|
||||
if (password == null) return null;
|
||||
//error = nil;
|
||||
sebDataDecrypted = SEBProtectionController.DecryptDataWithPassword(sebData, password);
|
||||
enterPasswordString = SEBUIStrings.enterPasswordAgain;
|
||||
// in case we get an error we allow the user to try it again
|
||||
} while ((sebDataDecrypted == null) && i > 0);
|
||||
if (sebDataDecrypted == null)
|
||||
{
|
||||
//wrong password entered in 5th try: stop reading .seb file
|
||||
MessageBox.Show(SEBUIStrings.decryptingSettingsFailedReason, SEBUIStrings.decryptingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return null;
|
||||
}
|
||||
sebData = sebDataDecrypted;
|
||||
// If these settings are being decrypted for editing, we return the decryption password
|
||||
if (forEditing) sebFilePassword = password;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Prefix = pwcc ("Password Configuring Client") ?
|
||||
|
||||
if (prefixString.CompareTo(PASSWORD_CONFIGURING_CLIENT_MODE) == 0)
|
||||
{
|
||||
|
||||
// Decrypt with password and configure local client settings
|
||||
// and quit afterwards, returning if reading the .seb file was successfull
|
||||
DictObj sebSettings = DecryptDataWithPasswordForConfiguringClient(sebData, forEditing, ref sebFilePassword, ref passwordIsHash);
|
||||
return sebSettings;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Prefix = plnd ("Plain Data") ?
|
||||
|
||||
if (prefixString.CompareTo(PLAIN_DATA_MODE) != 0)
|
||||
{
|
||||
// No valid 4-char prefix was found in the .seb file
|
||||
// Check if .seb file is unencrypted
|
||||
if (prefixString.CompareTo(UNENCRYPTED_MODE) == 0)
|
||||
{
|
||||
// .seb file seems to be an unencrypted XML plist
|
||||
// get the original data including the first 4 bytes
|
||||
sebData = sebDataUnencrypted;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No valid prefix and no unencrypted file with valid header
|
||||
// cancel reading .seb file
|
||||
if (!suppressFileFormatError)
|
||||
{
|
||||
MessageBox.Show(SEBUIStrings.settingsNotUsableReason, SEBUIStrings.settingsNotUsable, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we don't deal with an unencrypted seb file
|
||||
// ungzip the .seb (according to specification >= v14) decrypted serialized XML plist data
|
||||
if (prefixString.CompareTo(UNENCRYPTED_MODE) != 0)
|
||||
{
|
||||
sebData = GZipByte.Decompress(sebData);
|
||||
}
|
||||
|
||||
// Get preferences dictionary from decrypted data
|
||||
DictObj sebPreferencesDict = GetPreferencesDictFromConfigData(sebData, forEditing);
|
||||
DictObj sebPreferencesDictOriginal = GetPreferencesDictFromConfigData(sebData, false);
|
||||
// If we didn't get a preferences dict back, we abort reading settings
|
||||
if (sebPreferencesDict == null) return null;
|
||||
|
||||
// We need to set the right value for the key sebConfigPurpose to know later where to store the new settings
|
||||
sebPreferencesDict[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam;
|
||||
sebPreferencesDictOriginal[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam;
|
||||
|
||||
SEBSettings.settingsCurrentOriginal = sebPreferencesDictOriginal;
|
||||
|
||||
// Reading preferences was successful!
|
||||
return sebPreferencesDict;
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Helper method which decrypts the byte array using an empty password,
|
||||
/// or the administrator password currently set in SEB
|
||||
/// or asks for the password used for encrypting this SEB file
|
||||
/// for configuring the client
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
private static DictObj DecryptDataWithPasswordForConfiguringClient(byte[] sebData, bool forEditing, ref string sebFilePassword, ref bool passwordIsHash)
|
||||
{
|
||||
passwordIsHash = false;
|
||||
string password;
|
||||
// First try to decrypt with the current admin password
|
||||
// get admin password hash
|
||||
string hashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyHashedAdminPassword);
|
||||
if (hashedAdminPassword == null)
|
||||
{
|
||||
hashedAdminPassword = "";
|
||||
}
|
||||
// We use always uppercase letters in the base16 hashed admin password used for encrypting
|
||||
hashedAdminPassword = hashedAdminPassword.ToUpper();
|
||||
DictObj sebPreferencesDict = null;
|
||||
DictObj sebPreferencesDictOriginal = null;
|
||||
byte[] decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, hashedAdminPassword);
|
||||
if (decryptedSebData == null)
|
||||
{
|
||||
// If decryption with admin password didn't work, try it with an empty password
|
||||
decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, "");
|
||||
if (decryptedSebData == null)
|
||||
{
|
||||
// If decryption with empty and admin password didn't work, ask for the password the .seb file was encrypted with
|
||||
// Allow up to 5 attempts for entering decoding password
|
||||
int i = 5;
|
||||
password = null;
|
||||
string enterPasswordString = SEBUIStrings.enterEncryptionPassword;
|
||||
do
|
||||
{
|
||||
i--;
|
||||
// Prompt for password
|
||||
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.reconfiguringLocalSettings, enterPasswordString);
|
||||
// If cancel was pressed, abort
|
||||
if (password == null) return null;
|
||||
string hashedPassword = SEBProtectionController.ComputePasswordHash(password);
|
||||
// we try to decrypt with the hashed password
|
||||
decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, hashedPassword);
|
||||
// in case we get an error we allow the user to try it again
|
||||
enterPasswordString = SEBUIStrings.enterEncryptionPasswordAgain;
|
||||
} while (decryptedSebData == null && i > 0);
|
||||
if (decryptedSebData == null)
|
||||
{
|
||||
//wrong password entered in 5th try: stop reading .seb file
|
||||
MessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailedWrongPassword, SEBUIStrings.reconfiguringLocalSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Decrypting with entered password worked: We save it for returning it later
|
||||
if (forEditing) sebFilePassword = password;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//decrypting with hashedAdminPassword worked: we save it for returning as decryption password
|
||||
sebFilePassword = hashedAdminPassword;
|
||||
// identify that password as hash
|
||||
passwordIsHash = true;
|
||||
}
|
||||
/// Decryption worked
|
||||
|
||||
// Ungzip the .seb (according to specification >= v14) decrypted serialized XML plist data
|
||||
decryptedSebData = GZipByte.Decompress(decryptedSebData);
|
||||
|
||||
// Check if the openend reconfiguring seb file has the same admin password inside like the current one
|
||||
|
||||
try
|
||||
{
|
||||
sebPreferencesDict = (DictObj)Plist.readPlist(decryptedSebData);
|
||||
sebPreferencesDictOriginal = (DictObj)Plist.readPlist(decryptedSebData);
|
||||
}
|
||||
catch (Exception readPlistException)
|
||||
{
|
||||
// Error when deserializing the decrypted configuration data
|
||||
// We abort reading the new settings here
|
||||
MessageBox.Show(SEBUIStrings.loadingSettingsFailedReason, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
Console.WriteLine(readPlistException.Message);
|
||||
return null;
|
||||
}
|
||||
// Get the admin password set in these settings
|
||||
string sebFileHashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(sebPreferencesDict, SEBSettings.KeyHashedAdminPassword);
|
||||
if (sebFileHashedAdminPassword == null)
|
||||
{
|
||||
sebFileHashedAdminPassword = "";
|
||||
}
|
||||
// Has the SEB config file the same admin password inside as the current settings have?
|
||||
if (String.Compare(hashedAdminPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
//No: The admin password inside the .seb file wasn't the same as the current one
|
||||
if (forEditing)
|
||||
{
|
||||
// If the file is openend for editing (and not to reconfigure SEB)
|
||||
// we have to ask the user for the admin password inside the file
|
||||
if (!askForPasswordAndCompareToHashedPassword(sebFileHashedAdminPassword, forEditing))
|
||||
{
|
||||
// If the user didn't enter the right password we abort
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The file was actually opened for reconfiguring the SEB client:
|
||||
// we have to ask for the current admin password and
|
||||
// allow reconfiguring only if the user enters the right one
|
||||
// We don't check this for the case the current admin password was used to encrypt the new settings
|
||||
// In this case there can be a new admin pw defined in the new settings and users don't need to enter the old one
|
||||
if (passwordIsHash == false && hashedAdminPassword.Length > 0)
|
||||
{
|
||||
// Allow up to 5 attempts for entering current admin password
|
||||
int i = 5;
|
||||
password = null;
|
||||
string hashedPassword;
|
||||
string enterPasswordString = SEBUIStrings.enterCurrentAdminPwdForReconfiguring;
|
||||
bool passwordsMatch;
|
||||
do
|
||||
{
|
||||
i--;
|
||||
// Prompt for password
|
||||
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.reconfiguringLocalSettings, enterPasswordString);
|
||||
// If cancel was pressed, abort
|
||||
if (password == null) return null;
|
||||
if (password.Length == 0)
|
||||
{
|
||||
hashedPassword = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
hashedPassword = SEBProtectionController.ComputePasswordHash(password);
|
||||
}
|
||||
passwordsMatch = (String.Compare(hashedPassword, hashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0);
|
||||
// in case we get an error we allow the user to try it again
|
||||
enterPasswordString = SEBUIStrings.enterCurrentAdminPwdForReconfiguringAgain;
|
||||
} while (!passwordsMatch && i > 0);
|
||||
if (!passwordsMatch)
|
||||
{
|
||||
//wrong password entered in 5th try: stop reading .seb file
|
||||
MessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailedWrongCurrentAdminPwd, SEBUIStrings.reconfiguringLocalSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to set the right value for the key sebConfigPurpose to know later where to store the new settings
|
||||
sebPreferencesDict[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient;
|
||||
sebPreferencesDictOriginal[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient;
|
||||
|
||||
SEBSettings.settingsCurrentOriginal = sebPreferencesDictOriginal;
|
||||
|
||||
// Reading preferences was successful!
|
||||
return sebPreferencesDict;
|
||||
}
|
||||
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Helper method: Get preferences dictionary from decrypted data.
|
||||
/// In editing mode, users have to enter the right SEB administrator password
|
||||
/// before they can access the settings contents
|
||||
/// and returns the decrypted bytes
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
private static DictObj GetPreferencesDictFromConfigData(byte[] sebData, bool forEditing)
|
||||
{
|
||||
DictObj sebPreferencesDict = null;
|
||||
try
|
||||
{
|
||||
// Get preferences dictionary from decrypted data
|
||||
sebPreferencesDict = (DictObj)Plist.readPlist(sebData);
|
||||
}
|
||||
catch (Exception readPlistException)
|
||||
{
|
||||
MessageBox.Show(SEBUIStrings.loadingSettingsFailedReason, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
Console.WriteLine(readPlistException.Message);
|
||||
return null;
|
||||
}
|
||||
// In editing mode, the user has to enter the right SEB administrator password used in those settings before he can access their contents
|
||||
if (forEditing)
|
||||
{
|
||||
// Get the admin password set in these settings
|
||||
string sebFileHashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(sebPreferencesDict, SEBSettings.KeyHashedAdminPassword);
|
||||
// If there was no or empty admin password set in these settings, the user can access them anyways
|
||||
if (!String.IsNullOrEmpty(sebFileHashedAdminPassword))
|
||||
{
|
||||
// Get the current hashed admin password
|
||||
string hashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyHashedAdminPassword);
|
||||
if (hashedAdminPassword == null)
|
||||
{
|
||||
hashedAdminPassword = "";
|
||||
}
|
||||
// If the current hashed admin password is same as the hashed admin password from the settings file
|
||||
// then the user is allowed to access the settings
|
||||
if (String.Compare(hashedAdminPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
// otherwise we have to ask for the SEB administrator password used in those settings and
|
||||
// allow opening settings only if the user enters the right one
|
||||
|
||||
if (!askForPasswordAndCompareToHashedPassword(sebFileHashedAdminPassword, forEditing))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reading preferences was successful!
|
||||
return sebPreferencesDict;
|
||||
}
|
||||
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Ask user to enter password and compare it to the passed (hashed) password string
|
||||
/// Returns true if correct password was entered
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
private static bool askForPasswordAndCompareToHashedPassword(string sebFileHashedAdminPassword, bool forEditing)
|
||||
{
|
||||
// Check if there wasn't a hashed password (= empty password)
|
||||
if (sebFileHashedAdminPassword.Length == 0) return true;
|
||||
// We have to ask for the SEB administrator password used in the settings
|
||||
// and allow opening settings only if the user enters the right one
|
||||
// Allow up to 5 attempts for entering admin password
|
||||
int i = 5;
|
||||
string password = null;
|
||||
string hashedPassword;
|
||||
string enterPasswordString = SEBUIStrings.enterAdminPasswordRequired;
|
||||
bool passwordsMatch;
|
||||
do
|
||||
{
|
||||
i--;
|
||||
// Prompt for password
|
||||
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.loadingSettings + (String.IsNullOrEmpty(SEBClientInfo.LoadingSettingsFileName) ? "" : ": " + SEBClientInfo.LoadingSettingsFileName), enterPasswordString);
|
||||
// If cancel was pressed, abort
|
||||
if (password == null) return false;
|
||||
if (password.Length == 0)
|
||||
{
|
||||
hashedPassword = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
hashedPassword = SEBProtectionController.ComputePasswordHash(password);
|
||||
}
|
||||
passwordsMatch = (String.Compare(hashedPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0);
|
||||
// in case we get an error we allow the user to try it again
|
||||
enterPasswordString = SEBUIStrings.enterAdminPasswordRequiredAgain;
|
||||
} while ((password == null || !passwordsMatch) && i > 0);
|
||||
if (!passwordsMatch)
|
||||
{
|
||||
//wrong password entered in 5th try: stop reading .seb file
|
||||
MessageBox.Show(SEBUIStrings.loadingSettingsFailedWrongAdminPwd, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return false;
|
||||
}
|
||||
// Right password entered
|
||||
return passwordsMatch;
|
||||
}
|
||||
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Helper method which fetches the public key hash from a byte array,
|
||||
/// retrieves the according cryptographic identity from the certificate store
|
||||
/// and returns the decrypted bytes
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
private static byte[] DecryptDataWithPublicKeyHashPrefix(byte[] sebData, bool usingSymmetricKey, bool forEditing, ref X509Certificate2 sebFileCertificateRef)
|
||||
{
|
||||
// Get 20 bytes public key hash prefix
|
||||
// and remaining data with the prefix stripped
|
||||
byte[] publicKeyHash = GetPrefixDataFromData(ref sebData, PUBLIC_KEY_HASH_LENGTH);
|
||||
|
||||
X509Certificate2 certificateRef = SEBProtectionController.GetCertificateFromStore(publicKeyHash);
|
||||
if (certificateRef == null)
|
||||
{
|
||||
MessageBox.Show(SEBUIStrings.certificateNotFoundInStore, SEBUIStrings.errorDecryptingSettings, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return null;
|
||||
}
|
||||
// If these settings are being decrypted for editing, we will return the decryption certificate reference
|
||||
// in the variable which was passed as reference when calling this method
|
||||
if (forEditing) sebFileCertificateRef = certificateRef;
|
||||
|
||||
// Are we using the new identity certificate decryption with a symmetric key?
|
||||
if (usingSymmetricKey)
|
||||
{
|
||||
// Get length of the encrypted symmetric key
|
||||
Int32 encryptedSymmetricKeyLength = BitConverter.ToInt32(GetPrefixDataFromData(ref sebData, sizeof(Int32)), 0);
|
||||
// Get encrypted symmetric key
|
||||
byte[] encryptedSymmetricKey = GetPrefixDataFromData(ref sebData, encryptedSymmetricKeyLength);
|
||||
// Decrypt symmetric key
|
||||
byte[] symmetricKey = SEBProtectionController.DecryptDataWithCertificate(encryptedSymmetricKey, certificateRef);
|
||||
if (symmetricKey == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string symmetricKeyString = Convert.ToBase64String(symmetricKey);
|
||||
// Decrypt config file data using the symmetric key as password
|
||||
sebData = SEBProtectionController.DecryptDataWithPassword(sebData, symmetricKeyString);
|
||||
}
|
||||
else
|
||||
{
|
||||
sebData = SEBProtectionController.DecryptDataWithCertificate(sebData, certificateRef);
|
||||
}
|
||||
|
||||
return sebData;
|
||||
}
|
||||
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Helper method for returning a prefix string (of PREFIX_LENGTH, currently 4 chars)
|
||||
/// from a data byte array which is returned without the stripped prefix
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
|
||||
public static string GetPrefixStringFromData(ref byte[] data)
|
||||
{
|
||||
string decryptedDataString = Encoding.UTF8.GetString(GetPrefixDataFromData(ref data, PREFIX_LENGTH));
|
||||
return decryptedDataString;
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Helper method for stripping (and returning) a prefix byte array of prefixLength
|
||||
/// from a data byte array which is returned without the stripped prefix
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
public static byte[] GetPrefixDataFromData(ref byte[] data, int prefixLength)
|
||||
{
|
||||
// Get prefix with indicated length
|
||||
byte[] prefixData = new byte[prefixLength];
|
||||
Buffer.BlockCopy(data, 0, prefixData, 0, prefixLength);
|
||||
|
||||
// Get data without the stripped prefix
|
||||
byte[] dataStrippedKey = new byte[data.Length - prefixLength];
|
||||
Buffer.BlockCopy(data, prefixLength, dataStrippedKey, 0, data.Length - prefixLength);
|
||||
data = dataStrippedKey;
|
||||
|
||||
return prefixData;
|
||||
}
|
||||
|
||||
|
||||
///// ----------------------------------------------------------------------------------------
|
||||
///// <summary>
|
||||
///// Show SEB Password Dialog Form.
|
||||
///// </summary>
|
||||
///// ----------------------------------------------------------------------------------------
|
||||
//public static string ShowPasswordDialogForm(string title, string passwordRequestText)
|
||||
//{
|
||||
// // Set the title of the dialog window
|
||||
// sebPasswordDialogForm.Text = title;
|
||||
// // Set the text of the dialog
|
||||
// sebPasswordDialogForm.LabelText = passwordRequestText;
|
||||
// sebPasswordDialogForm.txtSEBPassword.Focus();
|
||||
// // If we are running in SebWindowsClient we need to activate it before showing the password dialog
|
||||
// if (SEBClientInfo.SebWindowsClientForm != null) SebWindowsClientForm.SEBToForeground(); //SEBClientInfo.SebWindowsClientForm.Activate();
|
||||
// // Show password dialog as a modal dialog and determine if DialogResult = OK.
|
||||
// if (sebPasswordDialogForm.ShowDialog() == DialogResult.OK)
|
||||
// {
|
||||
// // Read the contents of testDialog's TextBox.
|
||||
// string password = sebPasswordDialogForm.txtSEBPassword.Text;
|
||||
// sebPasswordDialogForm.txtSEBPassword.Text = "";
|
||||
// //sebPasswordDialogForm.txtSEBPassword.Focus();
|
||||
// return password;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
//}
|
||||
|
||||
/// Generate Encrypted .seb Settings Data
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Read SEB settings from UserDefaults and encrypt them using provided security credentials
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
|
||||
public static byte[] EncryptSEBSettingsWithCredentials(string settingsPassword, bool passwordIsHash, X509Certificate2 certificateRef, bool useAsymmetricOnlyEncryption, SEBSettings.sebConfigPurposes configPurpose, bool forEditing)
|
||||
{
|
||||
// Get current settings dictionary and clean it from empty arrays and dictionaries
|
||||
//DictObj cleanedCurrentSettings = SEBSettings.CleanSettingsDictionary();
|
||||
|
||||
SEBSettings.settingsCurrentOriginal = SEBSettings.settingsCurrent;
|
||||
|
||||
// Serialize preferences dictionary to an XML string
|
||||
string sebXML = Plist.writeXml(SEBSettings.settingsCurrent);
|
||||
string cleanedSebXML = sebXML.Replace("<array />", "<array></array>");
|
||||
cleanedSebXML = cleanedSebXML.Replace("<dict />", "<dict></dict>");
|
||||
cleanedSebXML = cleanedSebXML.Replace("<data />", "<data></data>");
|
||||
|
||||
byte[] encryptedSebData = Encoding.UTF8.GetBytes(cleanedSebXML);
|
||||
|
||||
string encryptingPassword = null;
|
||||
|
||||
// Check for special case: .seb configures client, empty password
|
||||
if (String.IsNullOrEmpty(settingsPassword) && configPurpose == SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient)
|
||||
{
|
||||
encryptingPassword = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
// in all other cases:
|
||||
// Check if no password entered and no identity selected
|
||||
if (String.IsNullOrEmpty(settingsPassword) && certificateRef == null)
|
||||
{
|
||||
if (MessageBox.Show(SEBUIStrings.noEncryptionChosenSaveUnencrypted, SEBUIStrings.noEncryptionChosen, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
|
||||
{
|
||||
// OK: save .seb config data unencrypted
|
||||
return encryptedSebData;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// gzip the serialized XML data
|
||||
encryptedSebData = GZipByte.Compress(encryptedSebData);
|
||||
|
||||
// Check if password for encryption is provided and use it then
|
||||
if (!String.IsNullOrEmpty(settingsPassword))
|
||||
{
|
||||
encryptingPassword = settingsPassword;
|
||||
}
|
||||
// So if password is empty (special case) or provided
|
||||
if (!(encryptingPassword == null))
|
||||
{
|
||||
// encrypt with password
|
||||
encryptedSebData = EncryptDataUsingPassword(encryptedSebData, encryptingPassword, passwordIsHash, configPurpose);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create byte array large enough to hold prefix and data
|
||||
byte[] encryptedData = new byte[encryptedSebData.Length + PREFIX_LENGTH];
|
||||
|
||||
// if no encryption with password: Add a 4-char prefix identifying plain data
|
||||
string prefixString = PLAIN_DATA_MODE;
|
||||
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedData, 0, PREFIX_LENGTH);
|
||||
// append plain data
|
||||
Buffer.BlockCopy(encryptedSebData, 0, encryptedData, PREFIX_LENGTH, encryptedSebData.Length);
|
||||
encryptedSebData = (byte[])encryptedData.Clone();
|
||||
}
|
||||
// Check if cryptographic identity for encryption is selected
|
||||
if (certificateRef != null)
|
||||
{
|
||||
// Encrypt preferences using a cryptographic identity
|
||||
encryptedSebData = EncryptDataUsingIdentity(encryptedSebData, certificateRef, useAsymmetricOnlyEncryption);
|
||||
}
|
||||
|
||||
// gzip the encrypted data
|
||||
encryptedSebData = GZipByte.Compress(encryptedSebData);
|
||||
|
||||
return encryptedSebData;
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Encrypt preferences using a certificate
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
|
||||
public static byte[] EncryptDataUsingIdentity(byte[] data, X509Certificate2 certificateRef, bool useAsymmetricOnlyEncryption)
|
||||
{
|
||||
// Get public key hash from selected identity's certificate
|
||||
string prefixString;
|
||||
byte[] publicKeyHash = SEBProtectionController.GetPublicKeyHashFromCertificate(certificateRef);
|
||||
byte[] encryptedData;
|
||||
byte[] encryptedKeyLengthBytes = new byte[0];
|
||||
byte[] encryptedKey = new byte[0];
|
||||
byte[] encryptedSEBConfigData;
|
||||
|
||||
if (!useAsymmetricOnlyEncryption)
|
||||
{
|
||||
prefixString = PUBLIC_SYMMETRIC_KEY_MODE;
|
||||
|
||||
// For new asymmetric/symmetric encryption create a random symmetric key
|
||||
byte[] symmetricKey = AESThenHMAC.NewKey();
|
||||
string symmetricKeyString = Convert.ToBase64String(symmetricKey);
|
||||
|
||||
// Encrypt the symmetric key using the identity certificate
|
||||
encryptedKey = SEBProtectionController.EncryptDataWithCertificate(symmetricKey, certificateRef);
|
||||
|
||||
// Get length of the encrypted key
|
||||
encryptedKeyLengthBytes = BitConverter.GetBytes(encryptedKey.Length);
|
||||
|
||||
//encrypt data using symmetric key
|
||||
encryptedData = SEBProtectionController.EncryptDataWithPassword(data, symmetricKeyString);
|
||||
}
|
||||
else
|
||||
{
|
||||
prefixString = PUBLIC_KEY_HASH_MODE;
|
||||
|
||||
//encrypt data using public key
|
||||
encryptedData = SEBProtectionController.EncryptDataWithCertificate(data, certificateRef);
|
||||
}
|
||||
|
||||
// Create byte array large enough to hold prefix, public key hash, length of and encrypted symmetric key plus encrypted data
|
||||
encryptedSEBConfigData = new byte[PREFIX_LENGTH + publicKeyHash.Length + encryptedKeyLengthBytes.Length + encryptedKey.Length + encryptedData.Length];
|
||||
int destinationOffset = 0;
|
||||
|
||||
// Copy prefix indicating data has been encrypted with a public key identified by hash into out data
|
||||
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedSEBConfigData, destinationOffset, PREFIX_LENGTH);
|
||||
destinationOffset += PREFIX_LENGTH;
|
||||
|
||||
// Copy public key hash to out data
|
||||
Buffer.BlockCopy(publicKeyHash, 0, encryptedSEBConfigData, destinationOffset, publicKeyHash.Length);
|
||||
destinationOffset += publicKeyHash.Length;
|
||||
|
||||
// Copy length of encrypted symmetric key to out data
|
||||
Buffer.BlockCopy(encryptedKeyLengthBytes, 0, encryptedSEBConfigData, destinationOffset, encryptedKeyLengthBytes.Length);
|
||||
destinationOffset += encryptedKeyLengthBytes.Length;
|
||||
|
||||
// Copy encrypted symmetric key to out data
|
||||
Buffer.BlockCopy(encryptedKey, 0, encryptedSEBConfigData, destinationOffset, encryptedKey.Length);
|
||||
destinationOffset += encryptedKey.Length;
|
||||
|
||||
// Copy encrypted data to out data
|
||||
Buffer.BlockCopy(encryptedData, 0, encryptedSEBConfigData, destinationOffset, encryptedData.Length);
|
||||
|
||||
return encryptedSEBConfigData;
|
||||
}
|
||||
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Encrypt preferences using a password
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
// Encrypt preferences using a password
|
||||
public static byte[] EncryptDataUsingPassword(byte[] data, string password, bool passwordIsHash, SEBSettings.sebConfigPurposes configPurpose)
|
||||
{
|
||||
string prefixString;
|
||||
// Check if .seb file should start exam or configure client
|
||||
if (configPurpose == SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam)
|
||||
{
|
||||
// prefix string for starting exam: normal password will be prompted
|
||||
prefixString = PASSWORD_MODE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// prefix string for configuring client: configuring password will either be hashed admin pw on client
|
||||
// or if no admin pw on client set: empty pw
|
||||
prefixString = PASSWORD_CONFIGURING_CLIENT_MODE;
|
||||
if (!String.IsNullOrEmpty(password) && !passwordIsHash)
|
||||
{
|
||||
//empty password means no admin pw on clients and should not be hashed
|
||||
//or we got already a hashed admin pw as settings pw, then we don't hash again
|
||||
password = SEBProtectionController.ComputePasswordHash(password);
|
||||
}
|
||||
}
|
||||
byte[] encryptedData = SEBProtectionController.EncryptDataWithPassword(data, password);
|
||||
// Create byte array large enough to hold prefix and data
|
||||
byte[] encryptedSebData = new byte[encryptedData.Length + PREFIX_LENGTH];
|
||||
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedSebData, 0, PREFIX_LENGTH);
|
||||
Buffer.BlockCopy(encryptedData, 0, encryptedSebData, PREFIX_LENGTH, encryptedData.Length);
|
||||
|
||||
return encryptedSebData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Compressing and decompressing byte arrays using gzip
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
public static class GZipByte
|
||||
{
|
||||
public static byte[] Compress(byte[] input)
|
||||
{
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
using (GZipStream zip = new GZipStream(output, CompressionMode.Compress))
|
||||
{
|
||||
zip.Write(input, 0, input.Length);
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Decompress(byte[] input)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (GZipStream stream = new GZipStream(new MemoryStream(input),
|
||||
CompressionMode.Decompress))
|
||||
{
|
||||
const int size = 4096;
|
||||
byte[] buffer = new byte[size];
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
count = stream.Read(buffer, 0, size);
|
||||
if (count > 0)
|
||||
{
|
||||
output.Write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
while (count > 0);
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Show SEB Password Dialog Form.
|
||||
/// </summary>
|
||||
/// ----------------------------------------------------------------------------------------
|
||||
//public static string ShowPasswordDialogForm(string title, string passwordRequestText)
|
||||
//{
|
||||
// Thread sf= new Thread(new ThreadStart(SebPasswordDialogForm.ShowPasswordDialogForm);
|
||||
// sf.Start();
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
1123
SebWindowsConfig/Utilities/SEBProtectionController.cs
Normal file
1123
SebWindowsConfig/Utilities/SEBProtectionController.cs
Normal file
File diff suppressed because it is too large
Load Diff
344
SebWindowsConfig/Utilities/SEBURLFilter.cs
Normal file
344
SebWindowsConfig/Utilities/SEBURLFilter.cs
Normal file
@@ -0,0 +1,344 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using DictObj = System.Collections.Generic.Dictionary<string, object>;
|
||||
using ListObj = System.Collections.Generic.List<object>;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public class SEBURLFilter
|
||||
{
|
||||
public bool enableURLFilter;
|
||||
public bool enableContentFilter;
|
||||
public ListObj permittedList = new ListObj();
|
||||
public ListObj prohibitedList = new ListObj();
|
||||
|
||||
// Updates filter rule arrays with current settings
|
||||
public void UpdateFilterRules()
|
||||
{
|
||||
if (prohibitedList.Count != 0)
|
||||
{
|
||||
prohibitedList.Clear();
|
||||
}
|
||||
|
||||
if (permittedList.Count != 0)
|
||||
{
|
||||
permittedList.Clear();
|
||||
}
|
||||
|
||||
enableURLFilter = (bool)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterEnable];
|
||||
enableContentFilter = (bool)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterEnableContentFilter];
|
||||
|
||||
// Add global URLFilterRules
|
||||
ListObj URLFilterRules = (ListObj)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterRules];
|
||||
ReadURLFilterRules(URLFilterRules);
|
||||
|
||||
// Add URLFilterRules from additional resources
|
||||
ListObj additionalResources = (ListObj)SEBSettings.settingsCurrent[SEBSettings.KeyAdditionalResources];
|
||||
ReadFilterRulesFromAdditionalResources(additionalResources);
|
||||
|
||||
// If URL filtering is enabled, then
|
||||
// check if Start URL gets allowed by current filter rules and if not add a rule for the Start URL
|
||||
string startURLString = (string)SEBSettings.settingsCurrent[SEBSettings.KeyStartURL];
|
||||
|
||||
if (enableURLFilter && Uri.TryCreate(startURLString, UriKind.Absolute, out Uri startURL))
|
||||
{
|
||||
if (TestURLAllowed(startURL) != URLFilterRuleActions.allow)
|
||||
{
|
||||
SEBURLFilterRegexExpression expression;
|
||||
// If Start URL is not allowed: Create one using the full Start URL
|
||||
try
|
||||
{
|
||||
expression = new SEBURLFilterRegexExpression(startURLString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.AddError("Could not create SEBURLFilterRegexExpression: ", this, ex, ex.Message);
|
||||
prohibitedList.Clear();
|
||||
permittedList.Clear();
|
||||
// Convert these rules and add them to the XULRunner seb keys
|
||||
CreateSebRuleLists();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add this Start URL filter expression to the permitted filter list
|
||||
permittedList.Add(expression);
|
||||
|
||||
}
|
||||
}
|
||||
// Convert these rules and add them to the XULRunner seb keys
|
||||
CreateSebRuleLists();
|
||||
}
|
||||
|
||||
|
||||
public void ReadURLFilterRules(ListObj URLFilterRules)
|
||||
{
|
||||
foreach (DictObj URLFilterRule in URLFilterRules)
|
||||
{
|
||||
|
||||
if (URLFilterRule.ContainsKey(SEBSettings.KeyURLFilterRuleRegex) && (bool)URLFilterRule[SEBSettings.KeyURLFilterRuleActive] == true)
|
||||
{
|
||||
|
||||
string expressionString = (string)URLFilterRule[SEBSettings.KeyURLFilterRuleExpression];
|
||||
if (!String.IsNullOrEmpty(expressionString))
|
||||
{
|
||||
Object expression;
|
||||
|
||||
bool regex = (bool)URLFilterRule[SEBSettings.KeyURLFilterRuleRegex];
|
||||
try
|
||||
{
|
||||
if (regex)
|
||||
{
|
||||
expression = new Regex(expressionString, RegexOptions.IgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
expression = new SEBURLFilterRegexExpression(expressionString);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.AddError("Could not create SEBURLFilterRegexExpression: ", this, ex, ex.Message);
|
||||
prohibitedList.Clear();
|
||||
permittedList.Clear();
|
||||
throw;
|
||||
}
|
||||
|
||||
int action = (int)URLFilterRule[SEBSettings.KeyURLFilterRuleAction];
|
||||
switch (action)
|
||||
{
|
||||
|
||||
case (int)URLFilterRuleActions.block:
|
||||
|
||||
prohibitedList.Add(expression);
|
||||
break;
|
||||
|
||||
|
||||
case (int)URLFilterRuleActions.allow:
|
||||
|
||||
permittedList.Add(expression);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read URLFilterRules from additionalResources
|
||||
public void ReadFilterRulesFromAdditionalResources(ListObj additionalResources)
|
||||
{
|
||||
foreach (DictObj additionalResource in additionalResources)
|
||||
{
|
||||
if ((bool)additionalResource[SEBSettings.KeyAdditionalResourcesActive])
|
||||
{
|
||||
object URLFilterRules;
|
||||
if (additionalResource.TryGetValue(SEBSettings.KeyURLFilterRules, out URLFilterRules))
|
||||
{
|
||||
ReadURLFilterRules((ListObj)URLFilterRules);
|
||||
}
|
||||
|
||||
// Are there further additional resources in this additional resource?
|
||||
if (additionalResource.TryGetValue(SEBSettings.KeyAdditionalResources, out object additionalSubResources))
|
||||
{
|
||||
if (((ListObj)additionalSubResources).Count != 0)
|
||||
{
|
||||
ReadFilterRulesFromAdditionalResources((ListObj)additionalSubResources);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert these rules and add them to the XULRunner seb keys
|
||||
public void CreateSebRuleLists()
|
||||
{
|
||||
// Set prohibited rules
|
||||
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterBlacklist] = SebRuleStringForSEBURLFilterRuleList(prohibitedList);
|
||||
|
||||
// Set permitted rules
|
||||
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterWhitelist] = SebRuleStringForSEBURLFilterRuleList(permittedList);
|
||||
|
||||
// All rules are regex
|
||||
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterRulesAsRegex] = true;
|
||||
|
||||
// Set if content filter is enabled
|
||||
SEBSettings.settingsCurrent[SEBSettings.KeyUrlFilterTrustedContent] = !(bool)SEBSettings.settingsCurrent[SEBSettings.KeyURLFilterEnableContentFilter];
|
||||
}
|
||||
|
||||
|
||||
public string SebRuleStringForSEBURLFilterRuleList(ListObj filterRuleList)
|
||||
{
|
||||
if (filterRuleList.Count == 0)
|
||||
{
|
||||
// No rules defined
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sebRuleString = new StringBuilder();
|
||||
foreach (object expression in filterRuleList)
|
||||
{
|
||||
if (expression != null)
|
||||
{
|
||||
if (sebRuleString.Length == 0)
|
||||
{
|
||||
sebRuleString.Append(expression.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
sebRuleString.AppendFormat(";{0}", expression.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sebRuleString.ToString();
|
||||
}
|
||||
|
||||
|
||||
// Filter URL and return if it is allowed or blocked
|
||||
public URLFilterRuleActions TestURLAllowed(Uri URLToFilter)
|
||||
{
|
||||
string URLToFilterString = URLToFilter.ToString();
|
||||
// By default URLs are blocked
|
||||
bool allowURL = false;
|
||||
bool blockURL = false;
|
||||
|
||||
/// Apply current filter rules (expressions/actions) to URL
|
||||
/// Apply prohibited filter expressions
|
||||
|
||||
foreach (object expression in prohibitedList)
|
||||
{
|
||||
|
||||
if (expression.GetType().Equals(typeof(Regex)))
|
||||
{
|
||||
if (Regex.IsMatch(URLToFilterString, expression.ToString()))
|
||||
{
|
||||
blockURL = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (expression.GetType().Equals(typeof(SEBURLFilterRegexExpression)))
|
||||
{
|
||||
if (URLMatchesFilterExpression(URLToFilter, (SEBURLFilterRegexExpression)expression))
|
||||
{
|
||||
blockURL = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blockURL == true)
|
||||
{
|
||||
return URLFilterRuleActions.block;
|
||||
}
|
||||
|
||||
/// Apply permitted filter expressions
|
||||
|
||||
foreach (object expression in permittedList)
|
||||
{
|
||||
|
||||
if (expression.GetType().Equals(typeof(Regex)))
|
||||
{
|
||||
if (Regex.IsMatch(URLToFilterString, expression.ToString()))
|
||||
{
|
||||
allowURL = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (expression.GetType().Equals(typeof(SEBURLFilterRegexExpression)))
|
||||
{
|
||||
if (URLMatchesFilterExpression(URLToFilter, (SEBURLFilterRegexExpression)expression))
|
||||
{
|
||||
allowURL = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Return URLFilterActionAllow if URL is allowed or
|
||||
// URLFilterActionUnknown if it's unknown (= it will anyways be blocked)
|
||||
return allowURL ? URLFilterRuleActions.allow : URLFilterRuleActions.unknown;
|
||||
}
|
||||
|
||||
// Method comparing all components of a passed URL with the filter expression
|
||||
// and returning YES (= allow or block) if it matches
|
||||
public bool URLMatchesFilterExpression(Uri URLToFilter, SEBURLFilterRegexExpression filterExpression)
|
||||
{
|
||||
Regex filterComponent;
|
||||
|
||||
// If a scheme is indicated in the filter expression, it has to match
|
||||
filterComponent = filterExpression.scheme;
|
||||
UriBuilder urlToFilterParts = new UriBuilder(URLToFilter);
|
||||
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(URLToFilter.Scheme, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
// Scheme of the URL to filter doesn't match the one from the filter expression: Exit with matching = NO
|
||||
return false;
|
||||
}
|
||||
|
||||
string userInfo = URLToFilter.UserInfo;
|
||||
filterComponent = filterExpression.user;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(urlToFilterParts.UserName, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
filterComponent = filterExpression.password;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(urlToFilterParts.Password, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
filterComponent = filterExpression.host;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(URLToFilter.Host, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filterExpression.port != null && URLToFilter.Port != filterExpression.port)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
filterComponent = filterExpression.path;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(URLToFilter.AbsolutePath.TrimEnd(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string urlQuery = URLToFilter.GetComponents(UriComponents.Query, UriFormat.Unescaped);
|
||||
filterComponent = filterExpression.query;
|
||||
if (filterComponent != null)
|
||||
{
|
||||
// If there's a query filter component, then we need to even filter empty URL query strings
|
||||
// as the filter might either allow some specific queries or no query at all ("?." query filter)
|
||||
if (urlQuery == null)
|
||||
{
|
||||
urlQuery = "";
|
||||
}
|
||||
if (!Regex.IsMatch(urlQuery, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string urlFragment = URLToFilter.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
filterComponent = filterExpression.fragment;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(urlFragment, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// URL matches the filter expression
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
169
SebWindowsConfig/Utilities/SEBURLFilterExpression.cs
Normal file
169
SebWindowsConfig/Utilities/SEBURLFilterExpression.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public class SEBURLFilterExpression
|
||||
{
|
||||
public string scheme;
|
||||
public string user;
|
||||
public string password;
|
||||
public string host;
|
||||
public int? port;
|
||||
public string path;
|
||||
public string query;
|
||||
public string fragment;
|
||||
|
||||
|
||||
public SEBURLFilterExpression(string filterExpressionString)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filterExpressionString))
|
||||
{
|
||||
/// Convert Uri to a SEBURLFilterExpression
|
||||
string splitURLRegexPattern = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
|
||||
Regex splitURLRegex = new Regex(splitURLRegexPattern);
|
||||
Match regexMatch = splitURLRegex.Match(filterExpressionString);
|
||||
if (regexMatch.Success == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.scheme = regexMatch.Groups[1].Value;
|
||||
this.user = regexMatch.Groups[2].Value;
|
||||
this.password = regexMatch.Groups[3].Value;
|
||||
this.host = regexMatch.Groups[4].Value;
|
||||
|
||||
// Treat a special case when a query or fragment is interpreted as part of the host address
|
||||
if (this.host.Contains("?") || this.host.Contains("#"))
|
||||
{
|
||||
string splitURLRegexPattern2 = @"([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
|
||||
Regex splitURLRegex2 = new Regex(splitURLRegexPattern2);
|
||||
Match regexMatch2 = splitURLRegex2.Match(this.host);
|
||||
if (regexMatch.Success == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.host = regexMatch2.Groups[1].Value;
|
||||
this.port = null;
|
||||
this.path = "";
|
||||
this.query = regexMatch2.Groups[2].Value;
|
||||
this.fragment = regexMatch2.Groups[3].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
string portNumber = regexMatch.Groups[5].Value;
|
||||
|
||||
// We only want a port if the filter expression string explicitely defines one!
|
||||
if (portNumber.Length == 0 || portNumber == "*")
|
||||
{
|
||||
this.port = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.port = UInt16.Parse(portNumber);
|
||||
}
|
||||
|
||||
this.path = regexMatch.Groups[6].Value.TrimEnd(new char[] { '/' });
|
||||
this.query = regexMatch.Groups[7].Value;
|
||||
this.fragment = regexMatch.Groups[8].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string User(string userInfo)
|
||||
{
|
||||
string user = "";
|
||||
if (!string.IsNullOrEmpty(userInfo))
|
||||
{
|
||||
int userPasswordSeparator = userInfo.IndexOf(":");
|
||||
if (userPasswordSeparator == -1)
|
||||
{
|
||||
user = userInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (userPasswordSeparator != 0)
|
||||
{
|
||||
user = userInfo.Substring(0, userPasswordSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public static string Password(string userInfo)
|
||||
{
|
||||
string password = "";
|
||||
if (!string.IsNullOrEmpty(userInfo))
|
||||
{
|
||||
int userPasswordSeparator = userInfo.IndexOf(":");
|
||||
if (userPasswordSeparator != -1)
|
||||
{
|
||||
if (userPasswordSeparator < userInfo.Length - 1)
|
||||
{
|
||||
password = userInfo.Substring(userPasswordSeparator + 1, userInfo.Length - 1 - userPasswordSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
public SEBURLFilterExpression(string scheme, string user, string password, string host, int port, string path, string query, string fragment)
|
||||
{
|
||||
this.scheme = scheme;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.path = path;
|
||||
this.query = query;
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder expressionString = new StringBuilder();
|
||||
if (!string.IsNullOrEmpty(this.scheme)) {
|
||||
if (!string.IsNullOrEmpty(this.host)) {
|
||||
expressionString.AppendFormat("{0}://", this.scheme);
|
||||
} else {
|
||||
expressionString.AppendFormat("{0}:", this.scheme);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.user)) {
|
||||
expressionString.Append(this.user);
|
||||
|
||||
if (!string.IsNullOrEmpty(this.password)) {
|
||||
expressionString.AppendFormat(":{0}@", this.password);
|
||||
} else {
|
||||
expressionString.Append("@");
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.host)) {
|
||||
expressionString.Append(this.host);
|
||||
}
|
||||
if (this.port != null && this.port > 0 && this.port <= 65535) {
|
||||
expressionString.AppendFormat(":{0}", this.port);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.path)) {
|
||||
if (this.path.StartsWith("/")) {
|
||||
expressionString.Append(this.path);
|
||||
} else {
|
||||
expressionString.AppendFormat("/{0}", this.path);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.query)) {
|
||||
expressionString.AppendFormat("?{0}", this.query);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.fragment)) {
|
||||
expressionString.AppendFormat("#{0}", this.fragment);
|
||||
}
|
||||
|
||||
return expressionString.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
298
SebWindowsConfig/Utilities/SEBURLFilterRegexExpression.cs
Normal file
298
SebWindowsConfig/Utilities/SEBURLFilterRegexExpression.cs
Normal file
@@ -0,0 +1,298 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SebWindowsConfig.Utilities
|
||||
{
|
||||
public class SEBURLFilterRegexExpression
|
||||
{
|
||||
public Regex scheme;
|
||||
public Regex user;
|
||||
public Regex password;
|
||||
public Regex host;
|
||||
public int? port;
|
||||
public Regex path;
|
||||
public Regex query;
|
||||
public Regex fragment;
|
||||
|
||||
|
||||
public SEBURLFilterRegexExpression(string filterExpressionString)
|
||||
{
|
||||
SEBURLFilterExpression URLFromString = new SEBURLFilterExpression(filterExpressionString);
|
||||
try
|
||||
{
|
||||
this.scheme = RegexForFilterString(URLFromString.scheme);
|
||||
this.user = RegexForFilterString(URLFromString.user);
|
||||
this.password = RegexForFilterString(URLFromString.password);
|
||||
this.host = RegexForHostFilterString(URLFromString.host);
|
||||
this.port = URLFromString.port;
|
||||
this.path = RegexForPathFilterString(URLFromString.path);
|
||||
this.query = RegexForQueryFilterString(URLFromString.query);
|
||||
this.fragment = RegexForFilterString(URLFromString.fragment);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Regex RegexForFilterString(string filterString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
string regexString = Regex.Escape(filterString);
|
||||
regexString = regexString.Replace("\\*", ".*?");
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
regexString = string.Format("^{0}$", regexString);
|
||||
|
||||
try
|
||||
{
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Regex RegexForHostFilterString(string filterString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if host string has a dot "." prefix to disable subdomain matching
|
||||
if (filterString.Length > 1 && filterString.StartsWith("."))
|
||||
{
|
||||
// Get host string without the "." prefix
|
||||
filterString = filterString.Substring(1);
|
||||
// Get regex for host <*://example.com> (without possible subdomains)
|
||||
return RegexForFilterString(filterString);
|
||||
}
|
||||
// Allow subdomain matching: Create combined regex for <example.com> and <*.example.com>
|
||||
string regexString = Regex.Escape(filterString);
|
||||
regexString = regexString.Replace("\\*", ".*?");
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
regexString = string.Format("^(({0})|(.*?\\.{0}))$", regexString);
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Regex RegexForPathFilterString(string filterString)
|
||||
{
|
||||
// Trim a possible trailing slash "/", we will instead add a rule to also match paths to directories without trailing slash
|
||||
filterString = filterString.TrimEnd(new char[] { '/' });
|
||||
;
|
||||
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if path string ends with a "/*" for matching contents of a directory
|
||||
if (filterString.EndsWith("/*"))
|
||||
{
|
||||
// As the path filter string matches for a directory, we need to add a string to match directories without trailing slash
|
||||
|
||||
// Get path string without the "/*" suffix
|
||||
string filterStringDirectory = filterString.Substring(0, filterString.Length - 2);
|
||||
|
||||
string regexString = Regex.Escape(filterString);
|
||||
regexString = regexString.Replace("\\*", ".*?");
|
||||
|
||||
string regexStringDir = Regex.Escape(filterString);
|
||||
regexStringDir = regexStringDir.Replace("\\*", ".*?");
|
||||
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
regexString = string.Format("^(({0})|({1}))$", regexString, regexStringDir);
|
||||
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RegexForFilterString(filterString);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Regex RegexForQueryFilterString(string filterString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (filterString.Equals("."))
|
||||
{
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
// and regex for no string allowed
|
||||
string regexString = @"^$";
|
||||
try
|
||||
{
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return RegexForFilterString(filterString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder expressionString = new StringBuilder();
|
||||
string part;
|
||||
expressionString.Append("^");
|
||||
|
||||
/// Scheme
|
||||
if (this.scheme != null)
|
||||
{
|
||||
// If there is a regex filter for scheme
|
||||
// get stripped regex pattern
|
||||
part = StringForRegexFilter(this.scheme);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise use the regex wildcard pattern for scheme
|
||||
part = @".*?";
|
||||
}
|
||||
|
||||
expressionString.AppendFormat("{0}:\\/\\/", part);
|
||||
|
||||
/// User/Password
|
||||
if (this.user != null)
|
||||
{
|
||||
part = StringForRegexFilter(this.user);
|
||||
|
||||
expressionString.Append(part);
|
||||
|
||||
if (this.password != null)
|
||||
{
|
||||
expressionString.AppendFormat(":{0}@", StringForRegexFilter(this.password));
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.Append("@");
|
||||
}
|
||||
}
|
||||
|
||||
/// Host
|
||||
string hostPort = "";
|
||||
if (this.host != null)
|
||||
{
|
||||
hostPort = StringForRegexFilter(this.host);
|
||||
} else
|
||||
{
|
||||
hostPort = ".*?";
|
||||
}
|
||||
|
||||
/// Port
|
||||
if (this.port != null && this.port > 0 && this.port <= 65535)
|
||||
{
|
||||
hostPort = string.Format("{0}:{1}", hostPort, this.port);
|
||||
}
|
||||
|
||||
// When there is a host, but no path
|
||||
if (this.host != null && this.path == null)
|
||||
{
|
||||
hostPort = string.Format("(({0})|({0}\\/.*?))", hostPort);
|
||||
}
|
||||
|
||||
expressionString.Append(hostPort);
|
||||
|
||||
/// Path
|
||||
if (this.path != null)
|
||||
{
|
||||
string path = StringForRegexFilter(this.path);
|
||||
if (path.StartsWith("\\/"))
|
||||
{
|
||||
expressionString.Append(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.AppendFormat("\\/{0}", path);
|
||||
}
|
||||
}
|
||||
|
||||
/// Query
|
||||
if (this.query != null)
|
||||
{
|
||||
// Check for special case Query = "?." which means no query string is allowed
|
||||
if (StringForRegexFilter(this.query).Equals("."))
|
||||
{
|
||||
expressionString.AppendFormat("[^\\?]");
|
||||
} else
|
||||
{
|
||||
expressionString.AppendFormat("\\?{0}", StringForRegexFilter(this.query));
|
||||
}
|
||||
} else
|
||||
{
|
||||
expressionString.AppendFormat("(()|(\\?.*?))");
|
||||
}
|
||||
|
||||
/// Fragment
|
||||
if (this.fragment != null)
|
||||
{
|
||||
expressionString.AppendFormat("#{0}", StringForRegexFilter(this.fragment));
|
||||
}
|
||||
|
||||
expressionString.Append("$");
|
||||
|
||||
return expressionString.ToString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public string StringForRegexFilter(Regex regexFilter)
|
||||
{
|
||||
// Get pattern string from regular expression
|
||||
string regexPattern = regexFilter.ToString();
|
||||
if (regexPattern.Length <= 2)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
// Remove the regex command characters for matching at start and end of a line
|
||||
regexPattern = regexPattern.Substring(1, regexPattern.Length - 2);
|
||||
return regexPattern;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user