using Microsoft.Data.Sqlite; using System.Security.Cryptography; using System.Text; namespace SimPas2_Windows.Managers { public class QaManager { private readonly string mConnectionString; private readonly byte[] mEncryptionKey; private readonly string mCulture; private string mJpLang; public QaManager(string databasePath, byte[] encryptionKey, string culture) { mConnectionString = $"Data Source={databasePath}"; mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey)); mCulture = culture; mJpLang = "ja-JP"; } private bool AlreadyExists(string website, string question, int? excludeId = null) { using (SqliteConnection conn = new SqliteConnection(mConnectionString)) { conn.Open(); SqliteCommand com = conn.CreateCommand(); com.CommandText = @" SELECT COUNT(*) FROM Qa WHERE UPPER(Website) = UPPER(@website) AND UPPER(Question) = UPPER(@question)"; com.Parameters.AddWithValue("@website", website); com.Parameters.AddWithValue("@question", question); if (excludeId.HasValue) { com.CommandText += " AND Id != @excludeId"; com.Parameters.AddWithValue("@excludeId", excludeId.Value); } return Convert.ToInt32(com.ExecuteScalar()) > 0; } } public void AddQa(string website, string question, string answer, string note) { if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer)) { string err = mCulture == mJpLang ? "ウェブサイト、質問及び、回答を御入力下さい。" : "Please fill in the website, question, and answer."; throw new ArgumentException(err); } string encryptedAnswer = EncryptAnswer(answer); if (AlreadyExists(website, question)) { string err = mCulture == mJpLang ? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。" : $"An secret question with the website and question for '{website}/{question}' already exists."; throw new ArgumentException(err); } using (SqliteConnection conn = new SqliteConnection(mConnectionString)) { conn.Open(); SqliteCommand com = conn.CreateCommand(); com.CommandText = @" INSERT INTO Qa (Website, Question, Answer, Note) VALUES ($website, $question, $answer, $note)"; com.Parameters.AddWithValue("$website", website); com.Parameters.AddWithValue("$question", question); com.Parameters.AddWithValue("$answer", encryptedAnswer); com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note); com.ExecuteNonQuery(); } } public bool EditQa(int id, string website, string question, string answer, string note) { if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer)) { string err = mCulture == mJpLang ? "ウェブサイト、質問及び、回答を御入力下さい。" : "Please fill in the website, question, and answer."; throw new ArgumentException(err); } string encryptedAnswer = EncryptAnswer(answer); if (AlreadyExists(website, question, id)) { string err = mCulture == mJpLang ? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。" : $"An secret question with the website and question for '{website}/{question}' already exists."; throw new ArgumentException(err); } using (SqliteConnection conn = new SqliteConnection(mConnectionString)) { conn.Open(); SqliteCommand com = conn.CreateCommand(); com.CommandText = @" UPDATE Qa SET Website = $website, Question = $question, Answer = $answer, Note = $note WHERE Id = $id"; com.Parameters.AddWithValue("$id", id); com.Parameters.AddWithValue("$website", website); com.Parameters.AddWithValue("$question", question); com.Parameters.AddWithValue("$answer", encryptedAnswer); com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note); return com.ExecuteNonQuery() > 0; } } public bool DeleteQa(int id) { using (SqliteConnection conn = new SqliteConnection(mConnectionString)) { conn.Open(); SqliteCommand com = conn.CreateCommand(); com.CommandText = "DELETE FROM Qa WHERE Id = $id"; com.Parameters.AddWithValue("$id", id); return com.ExecuteNonQuery() > 0; } } public List<(int Id, string Website, string Question, string Answer, string Note)> GetAll(string keyword = "") { var qas = new List<(int, string, string, string, string)>(); using (SqliteConnection conn = new SqliteConnection(mConnectionString)) { conn.Open(); SqliteCommand com = conn.CreateCommand(); if (string.IsNullOrWhiteSpace(keyword)) { com.CommandText = @" SELECT Id, Website, Question, Answer, Note FROM Qa ORDER BY Website ASC"; } else { com.CommandText = @" SELECT Id, Website, Question, Answer, Note FROM Qa WHERE Website LIKE @keyword OR Question LIKE @keyword ORDER BY Website ASC"; com.Parameters.AddWithValue("@keyword", $"%{keyword}%"); } using (SqliteDataReader reader = com.ExecuteReader()) { while (reader.Read()) { string encryptedAnswer = reader.GetString(3); string decryptedAnswer = DecryptAnswer(encryptedAnswer); qas.Add(( reader.GetInt32(0), reader.GetString(1), reader.GetString(2), decryptedAnswer, reader.IsDBNull(4) ? string.Empty : reader.GetString(4) )); } } } return qas; } private string EncryptAnswer(string answer) { using (Aes aes = Aes.Create()) { aes.Key = mEncryptionKey; aes.GenerateIV(); byte[] iv = aes.IV; using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv)) { byte[] plainBytes = Encoding.UTF8.GetBytes(answer); byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); byte[] result = new byte[iv.Length + encryptedBytes.Length]; Buffer.BlockCopy(iv, 0, result, 0, iv.Length); Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length); return Convert.ToBase64String(result); } } } private string DecryptAnswer(string encryptedAnswer) { byte[] combined = Convert.FromBase64String(encryptedAnswer); byte[] iv = new byte[16]; byte[] encryptedBytes = new byte[combined.Length - iv.Length]; Buffer.BlockCopy(combined, 0, iv, 0, iv.Length); Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length); using (Aes aes = Aes.Create()) { aes.Key = mEncryptionKey; aes.IV = iv; using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) { byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length); return Encoding.UTF8.GetString(decryptedBytes); } } } } }