diff --git a/src/client/download.ts b/src/client/download.ts
index 60525eb..5050025 100644
--- a/src/client/download.ts
+++ b/src/client/download.ts
@@ -1,55 +1,100 @@
-import * as fileType from 'file-type';
-import Toastify from 'toastify-js';
-
-export const FILE_BEGIN = '\u001b[5i';
-export const FILE_END = '\u001b[4i';
-export let fileBuffer = [];
-
-export function onCompleteFile() {
-  let bufferCharacters = fileBuffer.join('');
-  bufferCharacters = bufferCharacters.substring(
-    bufferCharacters.lastIndexOf(FILE_BEGIN) + FILE_BEGIN.length,
-    bufferCharacters.lastIndexOf(FILE_END)
-  );
-
-  // Try to decode it as base64, if it fails we assume it's not base64
-  try {
-    bufferCharacters = window.atob(bufferCharacters);
-  } catch (err) {
-    // Assuming it's not base64...
+const DEFAULT_FILE_BEGIN = '\u001b[5i';
+const DEFAULT_FILE_END = '\u001b[4i';
+
+export class FileDownloader {
+  constructor(
+    onCompleteFileCallback: (file: string) => any,
+    fileBegin: string = DEFAULT_FILE_BEGIN,
+    fileEnd: string = DEFAULT_FILE_END
+  ) {
+    this.fileBuffer = [];
+    this.onCompleteFileCallback = onCompleteFileCallback;
+    this.fileBegin = fileBegin;
+    this.fileEnd = fileEnd;
+    this.partialFileBegin = '';
+  }
+
+  bufferCharacter(character: string): string {
+    // If we are not currently buffering a file.
+    if (this.fileBuffer.length === 0) {
+      // If we are not currently expecting the rest of the fileBegin sequences.
+      if (this.partialFileBegin.length === 0) {
+        // If the character is the first character of fileBegin we know to start
+        // expecting the rest of the fileBegin sequence.
+        if (character === this.fileBegin[0]) {
+          this.partialFileBegin = character;
+          return '';
+        }
+        // Otherwise, we just return the character for printing to the terminal.
+
+        return character;
+      }
+      // We're currently in the state of buffering a beginner marker...
+
+      const nextExpectedCharacter = this.fileBegin[
+        this.partialFileBegin.length
+      ];
+      // If the next character *is* the next character in the fileBegin sequence.
+      if (character === nextExpectedCharacter) {
+        this.partialFileBegin += character;
+        // Do we now have the complete fileBegin sequence.
+        if (this.partialFileBegin === this.fileBegin) {
+          this.partialFileBegin = '';
+          this.fileBuffer = this.fileBuffer.concat(this.fileBegin.split(''));
+          return '';
+        }
+        // Otherwise, we just wait until the next character.
+
+        return '';
+      }
+      // If the next expected character wasn't found for the fileBegin sequence,
+      // we need to return all the data that was bufferd in the partialFileBegin
+      // back to the terminal.
+
+      const dataToReturn = this.partialFileBegin + character;
+      this.partialFileBegin = '';
+      return dataToReturn;
+    }
+    // If we are currently in the state of buffering a file.
+
+    this.fileBuffer.push(character);
+    // If we now have an entire fileEnd marker, we have a complete file!
+    if (
+      this.fileBuffer.length >= this.fileBegin.length + this.fileEnd.length &&
+      this.fileBuffer.slice(-this.fileEnd.length).join('') === this.fileEnd
+    ) {
+      this.onCompleteFile(
+        this.fileBuffer
+          .slice(
+            this.fileBegin.length,
+            this.fileBuffer.length - this.fileEnd.length
+          )
+          .join('')
+      );
+      this.fileBuffer = [];
+    }
+
+    return '';
   }
 
-  const bytes = new Uint8Array(bufferCharacters.length);
-  for (let i = 0; i < bufferCharacters.length; i += 1) {
-    bytes[i] = bufferCharacters.charCodeAt(i);
+  buffer(data: string): string {
+    // This is a optimization to quickly return if we know for
+    // sure we don't need to loop over each character.
+    if (
+      this.fileBuffer.length === 0 &&
+      this.partialFileBegin.length === 0 &&
+      data.indexOf(this.fileBegin[0]) === -1
+    ) {
+      return data;
+    }
+    let newData = '';
+    for (let i = 0; i < data.length; i += 1) {
+      newData += this.bufferCharacter(data[i]);
+    }
+    return newData;
   }
 
-  let mimeType = 'application/octet-stream';
-  let fileExt = '';
-  const typeData = fileType(bytes);
-  if (typeData) {
-    mimeType = typeData.mime;
-    fileExt = typeData.ext;
+  onCompleteFile(bufferCharacters: string) {
+    this.onCompleteFileCallback(bufferCharacters);
   }
-  const fileName = `file-${new Date()
-    .toISOString()
-    .split('.')[0]
-    .replace(/-/g, '')
-    .replace('T', '')
-    .replace(/:/g, '')}${fileExt ? `.${fileExt}` : ''}`;
-
-  const blob = new Blob([new Uint8Array(bytes.buffer)], { type: mimeType });
-  const blobUrl = URL.createObjectURL(blob);
-
-  fileBuffer = [];
-
-  Toastify({
-    text: `Download ready: <a href="${blobUrl}" target="_blank" download="${fileName}">${fileName}</a>`,
-    duration: 10000,
-    newWindow: true,
-    gravity: 'bottom',
-    position: 'right',
-    backgroundColor: '#fff',
-    stopOnFocus: true,
-  }).showToast();
 }
diff --git a/src/client/index.ts b/src/client/index.ts
index 0101d6c..eee0940 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -3,9 +3,11 @@ import { isNull } from 'lodash';
 
 import { dom, library } from "@fortawesome/fontawesome-svg-core";
 import { faCogs } from "@fortawesome/free-solid-svg-icons/faCogs";
+import Toastify from 'toastify-js';
+import * as fileType from 'file-type';
 import { socket } from './socket';
 import { overlay, terminal } from './elements';
-import { FILE_BEGIN, FILE_END, fileBuffer, onCompleteFile } from './download';
+import { FileDownloader } from './download';
 import verifyPrompt from './verify';
 import disconnect from './disconnect';
 import mobileKeyboard from './mobile';
@@ -76,6 +78,52 @@ socket.on('connect', () => {
   term.focus();
   mobileKeyboard();
 
+  const fileDownloader = new FileDownloader(function (
+    bufferCharacters: string
+  ) {
+    let fileCharacters = bufferCharacters;
+    // Try to decode it as base64, if it fails we assume it's not base64
+    try {
+      fileCharacters = window.atob(fileCharacters);
+    } catch (err) {
+      // Assuming it's not base64...
+    }
+
+    const bytes = new Uint8Array(fileCharacters.length);
+    for (let i = 0; i < fileCharacters.length; i += 1) {
+      bytes[i] = fileCharacters.charCodeAt(i);
+    }
+
+    let mimeType = 'application/octet-stream';
+    let fileExt = '';
+    const typeData = fileType(bytes);
+    if (typeData) {
+      mimeType = typeData.mime;
+      fileExt = typeData.ext;
+    }
+    const fileName = `file-${new Date()
+      .toISOString()
+      .split('.')[0]
+      .replace(/-/g, '')
+      .replace('T', '')
+      .replace(/:/g, '')}${fileExt ? `.${fileExt}` : ''}`;
+
+    const blob = new Blob([new Uint8Array(bytes.buffer)], {
+      type: mimeType,
+    });
+    const blobUrl = URL.createObjectURL(blob);
+
+    Toastify({
+      text: `Download ready: <a href="${blobUrl}" target="_blank" download="${fileName}">${fileName}</a>`,
+      duration: 10000,
+      newWindow: true,
+      gravity: 'bottom',
+      position: 'right',
+      backgroundColor: '#fff',
+      stopOnFocus: true,
+    }).showToast();
+  });
+
   term.on('data', data => {
     socket.emit('input', data);
   });
@@ -84,30 +132,9 @@ socket.on('connect', () => {
   });
   socket
     .on('data', (data: string) => {
-      const indexOfFileBegin = data.indexOf(FILE_BEGIN);
-      const indexOfFileEnd = data.indexOf(FILE_END);
-
-      // If we've got the entire file in one chunk
-      if (indexOfFileBegin !== -1 && indexOfFileEnd !== -1) {
-        fileBuffer.push(data);
-        onCompleteFile();
-      }
-      // If we've found a beginning marker
-      else if (indexOfFileBegin !== -1) {
-        fileBuffer.push(data);
-      }
-      // If we've found an ending marker
-      else if (indexOfFileEnd !== -1) {
-        fileBuffer.push(data);
-        onCompleteFile();
-      }
-      // If we've found the continuation of a file
-      else if (fileBuffer.length > 0) {
-        fileBuffer.push(data);
-      }
-      // Just treat it as normal data
-      else {
-        term.write(data);
+      const remainingData = fileDownloader.buffer(data);
+      if (remainingData) {
+        term.write(remainingData);
       }
     })
     .on('login', () => {