/// <summary> /// Constructor /// </summary> /// <param name="parent">Parent view model</param> public Abort(TransferProgress parent) { Parent = parent; CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
/// <summary> /// Actually do stuff /// </summary> /// <param name="UseExistingProgress">If a new progress bar should be created</param> private void Work(bool UseExistingProgress = false) { if (!UseExistingProgress) { Progress = new TransferProgress { UserName = Receiver.Name, UserPicture = Receiver.PicBytes, FromOrTo = "to", Parent = this }; // Add new Progress Bar Application.Current.Dispatcher.Invoke(() => { ((ObservableCollection <TransferProgress>)Application.Current.Properties["TransferProgresses"]).Insert(0, Progress); }); } // Check the file if (!GetFileData()) { return; } // Update the progress bar with the new data just obtained Progress.FileName = FileName; Progress.Maximum = FileSize; // Don't even start transferring if user already cancelled if (!MainCancellationToken.IsCancellationRequested) { // Set up the cancellation token AbortTransfer = new CancellationTokenSource(); // Send File data Response response = SendHello(); if (response != Response.ACCEPTED) { if (response == Response.REJECTED) { StopSending = true; } Progress.CurrentActivity = "Request rejected"; Progress.IsError = true; return; } if (AbortTransfer.Token.IsCancellationRequested) { Progress.CurrentActivity = "Operation Cancelled."; Progress.IsError = true; // Useless, but just to keep things clean StopSending = true; return; } // If the main token (the one thrown by the main application) requests cancellation, // then cancel the trasfer too. using (CancellationTokenRegistration ctr = MainCancellationToken.Register(() => Cancel())) { //------------------------------------ // Load the file //------------------------------------ Loader = Task.Run(() => { Load(); }, AbortTransfer.Token); //------------------------------------ // Send the file //------------------------------------ Sender = Task.Run(() => { Transmit(); }, AbortTransfer.Token); Progress.CurrentActivity = "Transferring..."; // Wait for them to finish (Or that the user cancels them). // NOTE: I can't wrap the Task definition in using(), // because I need to wait for them to finish! I can't dispose them earlier! Sender.Wait(); Loader.Wait(); // Dispose the tasks Sender.Dispose(); Loader.Dispose(); if (!Progress.IsError) { Progress.CurrentActivity = "Transfer completed"; Progress.TextColor = "Green"; } } // Dispose it; AbortTransfer.Dispose(); // Clean up // If you're here, it means that the sender and loader have exited, so no need to lock. Buffer.Clear(); } }
/// <summary> /// Parse the connection request /// </summary> /// <returns>True if request is valid, false if not.</returns> /// <remarks>I should change this method, making it throw exceptions when something happens instead of /// return bool. Remember this.</remarks> public bool ParseHello() { //------------------------------------ // Init //------------------------------------ int length = 0, i = 0; byte[] buffer; try { // NOTE: I have no guarantees that I will read as much as I specified. // In the first implementation, I was just doing something like Stream.Read(buffer,0,n), // ignoring the fact that I could read less than n, trusting the fact that I was reading small data. // Now, I am doing it like this to prevent such cases in which I read less than how much specified Debug.WriteLine("------------------------"); Stream.ReadTimeout = 5 * 1000; //------------------------------------ // The type of message //------------------------------------ // Get the message buffer = new byte[5]; while (i < 5) { i += Stream.Read(buffer, 0, 5 - i); } Debug.WriteLine("Received {0}", Encoding.UTF8.GetString(buffer)); // Parse the message type string MessageType = Encoding.UTF8.GetString(buffer); // ENDOC means that I don't want to send you anything else (= End the connection), // so the return true is useless if (MessageType.Equals("ENDOC")) { // Did I receive a ENDOC as very first message? if (StopReceiving) { return(false); } StopReceiving = true; return(true); /** * SECURITY IMPLICATIONS * * If an attacker spoofs their IP addres and sends ENDOC as very first message, this will * stop the communication even before starting it. This is because this is not an authenticated protocol. * * POSSIBILE SOLUTIONS * * The ones listed in the HELLO below **/ } // If is not ENDOC and not even HELLO, then discard the packet. // NOTE: the first time, when this return false, the communication will be instantly ended. // But if you're sending a list of files, this will tell the sender to proceed with next file. if (!MessageType.Equals("HELLO")) { return(false); } int isprivate = Stream.ReadByte(); if (isprivate == -1) { StopReceiving = true; return(true); } // User is not private, but do I know who this is? if (((byte)isprivate).Equals(1)) { IPAddress ip = ((IPEndPoint)Client.Client.RemoteEndPoint).Address; bool found = false; ObservableCollection <User> users = (ObservableCollection <User>)Application.Current.Properties["Users"]; foreach (User u in users) { Debug.WriteLine("IP from remote endpoint: {0}, IP to check {1}", ip, u.IP); if (u.IP.Equals(ip)) { found = true; SenderUser = new User(u); break; } } // Don't accept transfer if I don't know who this is. // But what about private users? I don't know who they are either! // Well, software's requirements say that private users don't communicate their existence, // but can still send, so... if (found == false) { Progress.CurrentActivity = "User not found"; Progress.IsError = true; StopReceiving = true; return(false); } } else { if (((byte)isprivate).Equals(0)) { SenderUser = null; } else { Progress.CurrentActivity = "Invalid request"; Progress.IsError = true; // I don't understand your message... StopReceiving = true; return(false); } } //------------------------------------ // The file name //------------------------------------ // Get its length first // At first, I used the set a fixed length of 200 bytes for file name, // but wasn't sure if it would play nice with UNICODE. So I made it TLV-Style buffer = new byte[4]; i = 0; while (i < 4) { i += Stream.Read(buffer, 0, 4 - i); } length = BitConverter.ToInt32(buffer, 0); // Name of files can be like: "file", "folder1/file", "folder1/folder2/file" // So, it must be at least one byte long if (length < 2) { return(false); } // Now get the name buffer = new byte[length]; i = 0; while (i < length) { i += Stream.Read(buffer, 0, length - i); } FileName = Encoding.Unicode.GetString(buffer).Trim('/').Trim('\0'); if (FileName.Length - FileName.Replace("/", "").Length > 0) { IsFolder = true; } Debug.WriteLine("Filename: {0}", FileName); // Was wondering if there was a better method to count occurrences in a string than just looping it c-style. // So I found this: // https://stackoverflow.com/questions/541954/how-would-you-count-occurrences-of-a-string-within-a-string //------------------------------------ // The file's extension //------------------------------------ // Get its length buffer = new byte[4]; i = 0; while (i < 4) { i += Stream.Read(buffer, 0, 4 - i); } length = BitConverter.ToInt32(buffer, 0); // Now get the actual extension buffer = new byte[length]; i = 0; while (i < length) { i += Stream.Read(buffer, 0, length - i); } FileExtension = Encoding.Unicode.GetString(buffer).Trim('\0'); Debug.WriteLine("Extension: {0}", FileExtension); //------------------------------------ // Get the file size //------------------------------------ buffer = new byte[8]; i = 0; while (i < 8) { i += Stream.Read(buffer, 0, 8 - i); } FileSize = BitConverter.ToInt64(buffer, 0); // Return true and don't stop receiving, meaning that the request is valid StopReceiving = false; Progress = new TransferProgress { Maximum = FileSize, FileName = FileName, UserName = SenderUser != null ? SenderUser.Name : "Private User", UserPicture = SenderUser?.PicBytes, FromOrTo = "from", CurrentActivity = "Parsing Request...", Parent = this }; // Add new Progress Bar Application.Current.Dispatcher.InvokeAsync(() => { ((ObservableCollection <TransferProgress>)Application.Current.Properties["TransferProgresses"]).Insert(0, Progress); }); return(true); } catch (Exception) { return(false); } }
/// <summary> /// Start transferring /// </summary> public override void Start() { // Before getting here, are there any errors? if (SendType == PathType.UKNOWN) { return; } Worker = Task.Run(() => { Progress = new TransferProgress { UserName = Receiver.Name, UserPicture = Receiver.PicBytes, FromOrTo = "to", Parent = this }; // Add new Progress Bar Application.Current.Dispatcher.Invoke(() => { ((ObservableCollection <TransferProgress>)Application.Current.Properties["TransferProgresses"]).Insert(0, Progress); }); using (Client = new TcpClient(AddressFamily.InterNetworkV6)) { //------------------------------------ // Try To Connect //------------------------------------ if (!Connect()) { // Set Error Message as not responding return; } //------------------------------------ // Send the "Hello" message //------------------------------------ using (Stream = Client.GetStream()) { Stream.Flush(); // At this point, I have already notified the receiver that I'm goin to send a directory. // So from now on, I'm going to send files, not directories SendType = PathType.FILE; // Tells if you need to create a new progress bar // The first time, you won't have to, but for subsequent files yes you do. bool _progress = true; // Loop through the files foreach (string file in Files) { FilePath = file; Work(_progress); _progress = false; if (StopSending) { break; } } Stop(); } // Dispose the stream Client.Close(); } // Dispose the client }); Worker.Wait(); Worker.Dispose(); Worker = null; }