diff --git a/src/core/worker.js b/src/core/worker.js
index 0326bf4f4..12a75df53 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -103,6 +103,16 @@ IPDFStreamReader.prototype = {
     return null;
   },
 
+  /**
+   * Gets the Content-Disposition filename. It is defined after the headersReady
+   * promise is resolved.
+   * @returns {string|null} The filename, or `null` if the Content-Disposition
+   *                        header is missing/invalid.
+   */
+  get filename() {
+    return null;
+  },
+
   /**
    * Gets PDF binary data length. It is defined after the headersReady promise
    * is resolved.
diff --git a/src/display/api.js b/src/display/api.js
index 2c5b339cb..fb52474b5 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -1997,10 +1997,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
 
     getMetadata: function WorkerTransport_getMetadata() {
       return this.messageHandler.sendWithPromise('GetMetadata', null).
-        then(function transportMetadata(results) {
+          then((results) => {
         return {
           info: results[0],
           metadata: (results[1] ? new Metadata(results[1]) : null),
+          contentDispositionFilename: (this._fullReader ?
+                                       this._fullReader.filename : null),
         };
       });
     },
diff --git a/src/display/fetch_stream.js b/src/display/fetch_stream.js
index 9fb7e1c56..44d726c96 100644
--- a/src/display/fetch_stream.js
+++ b/src/display/fetch_stream.js
@@ -17,8 +17,8 @@ import {
   AbortException, assert, createPromiseCapability
 } from '../shared/util';
 import {
-  createResponseStatusError, validateRangeRequestCapabilities,
-  validateResponseStatus
+  createResponseStatusError, extractFilenameFromHeader,
+  validateRangeRequestCapabilities, validateResponseStatus
 } from './network_utils';
 
 function createFetchOptions(headers, withCredentials) {
@@ -69,6 +69,7 @@ class PDFFetchStreamReader {
     this._stream = stream;
     this._reader = null;
     this._loaded = 0;
+    this._filename = null;
     let source = stream.source;
     this._withCredentials = source.withCredentials;
     this._contentLength = source.length;
@@ -100,11 +101,12 @@ class PDFFetchStreamReader {
       this._reader = response.body.getReader();
       this._headersCapability.resolve();
 
+      const getResponseHeader = (name) => {
+        return response.headers.get(name);
+      };
       let { allowRangeRequests, suggestedLength, } =
         validateRangeRequestCapabilities({
-          getResponseHeader: (name) => {
-            return response.headers.get(name);
-          },
+          getResponseHeader,
           isHttp: this._stream.isHttp,
           rangeChunkSize: this._rangeChunkSize,
           disableRange: this._disableRange,
@@ -113,6 +115,8 @@ class PDFFetchStreamReader {
       this._contentLength = suggestedLength;
       this._isRangeSupported = allowRangeRequests;
 
+      this._filename = extractFilenameFromHeader(getResponseHeader);
+
       // We need to stop reading when range is supported and streaming is
       // disabled.
       if (!this._isStreamingSupported && this._isRangeSupported) {
@@ -127,6 +131,10 @@ class PDFFetchStreamReader {
     return this._headersCapability.promise;
   }
 
+  get filename() {
+    return this._filename;
+  }
+
   get contentLength() {
     return this._contentLength;
   }
diff --git a/src/display/network.js b/src/display/network.js
index d34843451..54a93eeed 100644
--- a/src/display/network.js
+++ b/src/display/network.js
@@ -15,7 +15,8 @@
 
 import { assert, createPromiseCapability, stringToBytes } from '../shared/util';
 import {
-  createResponseStatusError, validateRangeRequestCapabilities
+  createResponseStatusError, extractFilenameFromHeader,
+  validateRangeRequestCapabilities
 } from './network_utils';
 import globalScope from '../shared/global_scope';
 
@@ -340,6 +341,7 @@ function PDFNetworkStreamFullRequestReader(manager, source) {
   this._requests = [];
   this._done = false;
   this._storedError = undefined;
+  this._filename = null;
 
   this.onProgress = null;
 }
@@ -350,11 +352,13 @@ PDFNetworkStreamFullRequestReader.prototype = {
     var fullRequestXhrId = this._fullRequestId;
     var fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId);
 
+    const getResponseHeader = (name) => {
+      return fullRequestXhr.getResponseHeader(name);
+    };
+
     let { allowRangeRequests, suggestedLength, } =
       validateRangeRequestCapabilities({
-        getResponseHeader: (name) => {
-          return fullRequestXhr.getResponseHeader(name);
-        },
+        getResponseHeader,
         isHttp: this._manager.isHttp,
         rangeChunkSize: this._rangeChunkSize,
         disableRange: this._disableRange,
@@ -367,6 +371,8 @@ PDFNetworkStreamFullRequestReader.prototype = {
       this._isRangeSupported = true;
     }
 
+    this._filename = extractFilenameFromHeader(getResponseHeader);
+
     var networkManager = this._manager;
     if (networkManager.isStreamingRequest(fullRequestXhrId)) {
       // We can continue fetching when progressive loading is enabled,
@@ -429,6 +435,10 @@ PDFNetworkStreamFullRequestReader.prototype = {
     }
   },
 
+  get filename() {
+    return this._filename;
+  },
+
   get isRangeSupported() {
     return this._isRangeSupported;
   },
diff --git a/src/display/network_utils.js b/src/display/network_utils.js
index 1f4f15817..1b0eb0ee9 100644
--- a/src/display/network_utils.js
+++ b/src/display/network_utils.js
@@ -16,6 +16,7 @@
 import {
   assert, MissingPDFException, UnexpectedResponseException
 } from '../shared/util';
+import { getFilenameFromUrl } from './dom_utils';
 
 function validateRangeRequestCapabilities({ getResponseHeader, isHttp,
                                             rangeChunkSize, disableRange, }) {
@@ -52,6 +53,18 @@ function validateRangeRequestCapabilities({ getResponseHeader, isHttp,
   return returnValues;
 }
 
+function extractFilenameFromHeader(getResponseHeader) {
+  const contentDisposition = getResponseHeader('Content-Disposition');
+  if (contentDisposition) {
+    let parts =
+      /.+;\s*filename=(?:'|")(.+\.pdf)(?:'|")/gi.exec(contentDisposition);
+    if (parts !== null && parts.length > 1) {
+      return getFilenameFromUrl(parts[1]);
+    }
+  }
+  return null;
+}
+
 function createResponseStatusError(status, url) {
   if (status === 404 || status === 0 && /^file:/.test(url)) {
     return new MissingPDFException('Missing PDF "' + url + '".');
@@ -67,6 +80,7 @@ function validateResponseStatus(status) {
 
 export {
   createResponseStatusError,
+  extractFilenameFromHeader,
   validateRangeRequestCapabilities,
   validateResponseStatus,
 };
diff --git a/src/display/node_stream.js b/src/display/node_stream.js
index baa439e03..a8431cdb7 100644
--- a/src/display/node_stream.js
+++ b/src/display/node_stream.js
@@ -22,7 +22,9 @@ let url = __non_webpack_require__('url');
 import {
   AbortException, assert, createPromiseCapability
 } from '../shared/util';
-import { validateRangeRequestCapabilities } from './network_utils';
+import {
+  extractFilenameFromHeader, validateRangeRequestCapabilities
+} from './network_utils';
 
 const fileUriRegex = /^file:\/\/\/[a-zA-Z]:\//;
 
@@ -78,6 +80,7 @@ class BaseFullReader {
     let source = stream.source;
     this._contentLength = source.length; // optional
     this._loaded = 0;
+    this._filename = null;
 
     this._disableRange = source.disableRange || false;
     this._rangeChunkSize = source.rangeChunkSize;
@@ -97,6 +100,10 @@ class BaseFullReader {
     return this._headersCapability.promise;
   }
 
+  get filename() {
+    return this._filename;
+  }
+
   get contentLength() {
     return this._contentLength;
   }
@@ -284,23 +291,26 @@ class PDFNodeStreamFullReader extends BaseFullReader {
       this._headersCapability.resolve();
       this._setReadableStream(response);
 
+      const getResponseHeader = (name) => {
+        // Make sure that headers name are in lower case, as mentioned
+        // here: https://nodejs.org/api/http.html#http_message_headers.
+        return this._readableStream.headers[name.toLowerCase()];
+      };
       let { allowRangeRequests, suggestedLength, } =
-      validateRangeRequestCapabilities({
-        getResponseHeader: (name) => {
-          // Make sure that headers name are in lower case, as mentioned
-          // here: https://nodejs.org/api/http.html#http_message_headers.
-          return this._readableStream.headers[name.toLowerCase()];
-        },
-        isHttp: stream.isHttp,
-        rangeChunkSize: this._rangeChunkSize,
-        disableRange: this._disableRange,
-      });
+        validateRangeRequestCapabilities({
+          getResponseHeader,
+          isHttp: stream.isHttp,
+          rangeChunkSize: this._rangeChunkSize,
+          disableRange: this._disableRange,
+        });
 
       if (allowRangeRequests) {
         this._isRangeSupported = true;
       }
       // Setting right content length.
       this._contentLength = suggestedLength;
+
+      this._filename = extractFilenameFromHeader(getResponseHeader);
     };
 
     this._request = null;
diff --git a/src/display/transport_stream.js b/src/display/transport_stream.js
index a89920589..21525cfac 100644
--- a/src/display/transport_stream.js
+++ b/src/display/transport_stream.js
@@ -119,6 +119,7 @@ var PDFDataTransportStream = (function PDFDataTransportStreamClosure() {
   function PDFDataTransportStreamReader(stream, queuedChunks) {
     this._stream = stream;
     this._done = false;
+    this._filename = null;
     this._queuedChunks = queuedChunks || [];
     this._requests = [];
     this._headersReady = Promise.resolve();
@@ -143,6 +144,10 @@ var PDFDataTransportStream = (function PDFDataTransportStreamClosure() {
       return this._headersReady;
     },
 
+    get filename() {
+      return this._filename;
+    },
+
     get isRangeSupported() {
       return this._stream._isRangeSupported;
     },
diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js
index d67596a31..48dd07c91 100644
--- a/test/unit/api_spec.js
+++ b/test/unit/api_spec.js
@@ -794,6 +794,7 @@ describe('api', function() {
         expect(metadata.info['Title']).toEqual('Basic API Test');
         expect(metadata.info['PDFFormatVersion']).toEqual('1.7');
         expect(metadata.metadata.get('dc:title')).toEqual('Basic API Test');
+        expect(metadata.contentDispositionFilename).toEqual(null);
         done();
       }).catch(function (reason) {
         done.fail(reason);
diff --git a/test/unit/network_utils_spec.js b/test/unit/network_utils_spec.js
index 7cd1b848a..b98ac5d01 100644
--- a/test/unit/network_utils_spec.js
+++ b/test/unit/network_utils_spec.js
@@ -14,8 +14,8 @@
  */
 
 import {
-  createResponseStatusError, validateRangeRequestCapabilities,
-  validateResponseStatus
+  createResponseStatusError, extractFilenameFromHeader,
+  validateRangeRequestCapabilities, validateResponseStatus
 } from '../../src/display/network_utils';
 import {
   MissingPDFException, UnexpectedResponseException
@@ -134,6 +134,84 @@ describe('network_utils', function() {
     });
   });
 
+  describe('extractFilenameFromHeader', function() {
+    it('returns null when content disposition header is blank', function() {
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return null;
+        }
+      })).toBeNull();
+
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return undefined;
+        }
+      })).toBeNull();
+
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return '';
+        }
+      })).toBeNull();
+    });
+
+    it('gets the filename from the response header', function() {
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'inline';
+        }
+      })).toBeNull();
+
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'attachment';
+        }
+      })).toBeNull();
+
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'attachment; filename="filename.pdf"';
+        }
+      })).toEqual('filename.pdf');
+    });
+
+    it('returns null when content disposition is form-data', function() {
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'form-data';
+        }
+      })).toBeNull();
+
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'form-data; name="filename.pdf"';
+        }
+      })).toBeNull();
+
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'form-data; name="filename.pdf"; filename="file.pdf"';
+        }
+      })).toEqual('file.pdf');
+    });
+
+    it('only extracts filename with pdf extension', function () {
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'attachment; filename="filename.png"';
+        }
+      })).toBeNull();
+    });
+
+    it('extension validation is case insensitive', function () {
+      expect(extractFilenameFromHeader((headerName) => {
+        if (headerName === 'Content-Disposition') {
+          return 'form-data; name="fieldName"; filename="file.PdF"';
+        }
+      })).toEqual('file.PdF');
+    });
+  });
+
   describe('createResponseStatusError', function() {
     it('handles missing PDF file responses', function() {
       expect(createResponseStatusError(404, 'https://foo.com/bar.pdf')).toEqual(
diff --git a/web/app.js b/web/app.js
index 9c561f22d..edeea6ec3 100644
--- a/web/app.js
+++ b/web/app.js
@@ -154,6 +154,7 @@ let PDFViewerApplication = {
   baseUrl: '',
   externalServices: DefaultExternalServices,
   _boundEvents: {},
+  contentDispositionFilename: null,
 
   // Called once when the document is loaded.
   initialize(appConfig) {
@@ -678,6 +679,7 @@ let PDFViewerApplication = {
     this.downloadComplete = false;
     this.url = '';
     this.baseUrl = '';
+    this.contentDispositionFilename = null;
 
     this.pdfSidebar.reset();
     this.pdfOutlineViewer.reset();
@@ -801,7 +803,8 @@ let PDFViewerApplication = {
     let url = this.baseUrl;
     // Use this.url instead of this.baseUrl to perform filename detection based
     // on the reference fragment as ultimate fallback if needed.
-    let filename = getPDFFileNameFromURL(this.url);
+    let filename = this.contentDispositionFilename ||
+      getPDFFileNameFromURL(this.url);
     let downloadManager = this.downloadManager;
     downloadManager.onerror = (err) => {
       // This error won't really be helpful because it's likely the
@@ -1153,9 +1156,11 @@ let PDFViewerApplication = {
       });
     });
 
-    pdfDocument.getMetadata().then(({ info, metadata, }) => {
+    pdfDocument.getMetadata().then(
+        ({ info, metadata, contentDispositionFilename, }) => {
       this.documentInfo = info;
       this.metadata = metadata;
+      this.contentDispositionFilename = contentDispositionFilename;
 
       // Provides some basic debug information
       console.log('PDF ' + pdfDocument.fingerprint + ' [' +
@@ -1178,7 +1183,10 @@ let PDFViewerApplication = {
       }
 
       if (pdfTitle) {
-        this.setTitle(pdfTitle + ' - ' + document.title);
+        this.setTitle(
+          `${pdfTitle} - ${contentDispositionFilename || document.title}`);
+      } else if (contentDispositionFilename) {
+        this.setTitle(contentDispositionFilename);
       }
 
       if (info.IsAcroFormPresent) {
diff --git a/web/pdf_document_properties.js b/web/pdf_document_properties.js
index 6727627b7..cb118920b 100644
--- a/web/pdf_document_properties.js
+++ b/web/pdf_document_properties.js
@@ -71,24 +71,26 @@ class PDFDocumentProperties {
         return;
       }
       // Get the document properties.
-      this.pdfDocument.getMetadata().then(({ info, metadata, }) => {
+      this.pdfDocument.getMetadata().then(
+          ({ info, metadata, contentDispositionFilename, }) => {
         return Promise.all([
           info,
           metadata,
+          contentDispositionFilename || getPDFFileNameFromURL(this.url),
           this._parseFileSize(this.maybeFileSize),
           this._parseDate(info.CreationDate),
           this._parseDate(info.ModDate)
         ]);
-      }).then(([info, metadata, fileSize, creationDate, modificationDate]) => {
+      }).then(([info, metadata, fileName, fileSize, creationDate, modDate]) => {
         freezeFieldData({
-          'fileName': getPDFFileNameFromURL(this.url),
+          'fileName': fileName,
           'fileSize': fileSize,
           'title': info.Title,
           'author': info.Author,
           'subject': info.Subject,
           'keywords': info.Keywords,
           'creationDate': creationDate,
-          'modificationDate': modificationDate,
+          'modificationDate': modDate,
           'creator': info.Creator,
           'producer': info.Producer,
           'version': info.PDFFormatVersion,