using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.Request; using Bit.Core.Models.Response; using Bit.Core.Models.View; using Newtonsoft.Json; namespace Bit.Core.Services { public class SendService : ISendService { private List _decryptedSendsCache; private readonly ICryptoService _cryptoService; private readonly IUserService _userService; private readonly IApiService _apiService; private readonly IStorageService _storageService; private readonly II18nService _i18nService; private readonly ICryptoFunctionService _cryptoFunctionService; private Task> _getAllDecryptedTask; private readonly IFileUploadService _fileUploadService; public SendService( ICryptoService cryptoService, IUserService userService, IApiService apiService, IFileUploadService fileUploadService, IStorageService storageService, II18nService i18nService, ICryptoFunctionService cryptoFunctionService) { _cryptoService = cryptoService; _userService = userService; _apiService = apiService; _fileUploadService = fileUploadService; _storageService = storageService; _i18nService = i18nService; _cryptoFunctionService = cryptoFunctionService; } public static string GetSendKey(string userId) => string.Format("sends_{0}", userId); public async Task ClearAsync(string userId) { await _storageService.RemoveAsync(GetSendKey(userId)); ClearCache(); } public void ClearCache() => _decryptedSendsCache = null; public async Task DeleteAsync(params string[] ids) { var userId = await _userService.GetUserIdAsync(); var sends = await _storageService.GetAsync>(GetSendKey(userId)); if (sends == null) { return; } foreach (var id in ids) { sends.Remove(id); } await _storageService.SaveAsync(GetSendKey(userId), sends); ClearCache(); } public async Task DeleteWithServerAsync(string id) { await _apiService.DeleteSendAsync(id); await DeleteAsync(id); } public async Task<(Send send, EncByteArray encryptedFileData)> EncryptAsync(SendView model, byte[] fileData, string password, SymmetricCryptoKey key = null) { if (model.Key == null) { model.Key = _cryptoFunctionService.RandomBytes(16); model.CryptoKey = await _cryptoService.MakeSendKeyAsync(model.Key); } var send = new Send { Id = model.Id, Type = model.Type, Disabled = model.Disabled, DeletionDate = model.DeletionDate, ExpirationDate = model.ExpirationDate, MaxAccessCount = model.MaxAccessCount, Key = await _cryptoService.EncryptAsync(model.Key, key), Name = await _cryptoService.EncryptAsync(model.Name, model.CryptoKey), Notes = await _cryptoService.EncryptAsync(model.Notes, model.CryptoKey), HideEmail = model.HideEmail }; EncByteArray encryptedFileData = null; if (password != null) { var passwordHash = await _cryptoFunctionService.Pbkdf2Async(password, model.Key, CryptoHashAlgorithm.Sha256, 100000); send.Password = Convert.ToBase64String(passwordHash); } switch (send.Type) { case SendType.Text: send.Text = new SendText { Text = await _cryptoService.EncryptAsync(model.Text.Text, model.CryptoKey), Hidden = model.Text.Hidden }; break; case SendType.File: send.File = new SendFile(); if (fileData != null) { send.File.FileName = await _cryptoService.EncryptAsync(model.File.FileName, model.CryptoKey); encryptedFileData = await _cryptoService.EncryptToBytesAsync(fileData, model.CryptoKey); } break; default: break; } return (send, encryptedFileData); } public async Task> GetAllAsync() { var userId = await _userService.GetUserIdAsync(); var sends = await _storageService.GetAsync>(GetSendKey(userId)); return sends?.Select(kvp => new Send(kvp.Value)).ToList() ?? new List(); } public async Task> GetAllDecryptedAsync() { if (_decryptedSendsCache != null) { return _decryptedSendsCache; } var hasKey = await _cryptoService.HasKeyAsync(); if (!hasKey) { throw new Exception("No Key."); } if (_getAllDecryptedTask != null && !_getAllDecryptedTask.IsCompleted && !_getAllDecryptedTask.IsFaulted) { return await _getAllDecryptedTask; } async Task> doTask() { var decSends = new List(); async Task decryptAndAddSendAsync(Send send) => decSends.Add(await send.DecryptAsync()); await Task.WhenAll((await GetAllAsync()).Select(s => decryptAndAddSendAsync(s))); decSends = decSends.OrderBy(s => s, new SendLocaleComparer(_i18nService)).ToList(); _decryptedSendsCache = decSends; return _decryptedSendsCache; } _getAllDecryptedTask = doTask(); return await _getAllDecryptedTask; } public async Task GetAsync(string id) { var userId = await _userService.GetUserIdAsync(); var sends = await _storageService.GetAsync>(GetSendKey(userId)); if (sends == null || !sends.ContainsKey(id)) { return null; } return new Send(sends[id]); } public async Task ReplaceAsync(Dictionary sends) { var userId = await _userService.GetUserIdAsync(); await _storageService.SaveAsync(GetSendKey(userId), sends); _decryptedSendsCache = null; } public async Task SaveWithServerAsync(Send send, EncByteArray encryptedFileData) { var request = new SendRequest(send, encryptedFileData?.Buffer?.LongLength); SendResponse response = default; if (send.Id == null) { switch (send.Type) { case SendType.Text: response = await _apiService.PostSendAsync(request); break; case SendType.File: try{ var uploadDataResponse = await _apiService.PostFileTypeSendAsync(request); response = uploadDataResponse.SendResponse; await _fileUploadService.UploadSendFileAsync(uploadDataResponse, send.File.FileName, encryptedFileData); } catch (ApiException e) when (e.Error.StatusCode == HttpStatusCode.NotFound) { response = await LegacyServerSendFileUpload(request, send, encryptedFileData); } catch { if (response != default){ await _apiService.DeleteSendAsync(response.Id); } throw; } break; default: throw new NotImplementedException($"Cannot save unknown Send type {send.Type}"); } send.Id = response.Id; } else { response = await _apiService.PutSendAsync(send.Id, request); } var userId = await _userService.GetUserIdAsync(); await UpsertAsync(new SendData(response, userId)); return response.Id; } [Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")] private async Task LegacyServerSendFileUpload(SendRequest request, Send send, EncByteArray encryptedFileData) { var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}") { { new StringContent(JsonConvert.SerializeObject(request)), "model" }, { new ByteArrayContent(encryptedFileData.Buffer), "data", send.File.FileName.EncryptedString } }; return await _apiService.PostSendFileAsync(fd); } public async Task UpsertAsync(params SendData[] sends) { var userId = await _userService.GetUserIdAsync(); var knownSends = await _storageService.GetAsync>(GetSendKey(userId)) ?? new Dictionary(); foreach (var send in sends) { knownSends[send.Id] = send; } await _storageService.SaveAsync(GetSendKey(userId), knownSends); _decryptedSendsCache = null; } public async Task RemovePasswordWithServerAsync(string id) { var response = await _apiService.PutSendRemovePasswordAsync(id); var userId = await _userService.GetUserIdAsync(); await UpsertAsync(new SendData(response, userId)); } private class SendLocaleComparer : IComparer { private readonly II18nService _i18nService; public SendLocaleComparer(II18nService i18nService) { _i18nService = i18nService; } public int Compare(SendView a, SendView b) { var aName = a?.Name; var bName = b?.Name; if (aName == null && bName != null) { return -1; } if (aName != null && bName == null) { return 1; } if (aName == null && bName == null) { return 0; } return _i18nService.StringComparer.Compare(aName, bName); } } } }