| /* |
| * Copyright (C) 2008 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/fileapi/File.h" |
| |
| #include <memory> |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/ScriptState.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/fileapi/FilePropertyBag.h" |
| #include "core/frame/UseCounter.h" |
| #include "platform/FileMetadata.h" |
| #include "platform/blob/BlobData.h" |
| #include "platform/network/mime/MIMETypeRegistry.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebFileUtilities.h" |
| #include "wtf/CurrentTime.h" |
| #include "wtf/DateMath.h" |
| |
| namespace blink { |
| |
| static String getContentTypeFromFileName(const String& name, |
| File::ContentTypeLookupPolicy policy) { |
| String type; |
| int index = name.reverseFind('.'); |
| if (index != -1) { |
| if (policy == File::WellKnownContentTypes) { |
| type = MIMETypeRegistry::getWellKnownMIMETypeForExtension( |
| name.substring(index + 1)); |
| } else { |
| ASSERT(policy == File::AllContentTypes); |
| type = |
| MIMETypeRegistry::getMIMETypeForExtension(name.substring(index + 1)); |
| } |
| } |
| return type; |
| } |
| |
| static std::unique_ptr<BlobData> createBlobDataForFileWithType( |
| const String& path, |
| const String& contentType) { |
| std::unique_ptr<BlobData> blobData = |
| BlobData::createForFileWithUnknownSize(path); |
| blobData->setContentType(contentType); |
| return blobData; |
| } |
| |
| static std::unique_ptr<BlobData> createBlobDataForFile( |
| const String& path, |
| File::ContentTypeLookupPolicy policy) { |
| return createBlobDataForFileWithType( |
| path, getContentTypeFromFileName(path, policy)); |
| } |
| |
| static std::unique_ptr<BlobData> createBlobDataForFileWithName( |
| const String& path, |
| const String& fileSystemName, |
| File::ContentTypeLookupPolicy policy) { |
| return createBlobDataForFileWithType( |
| path, getContentTypeFromFileName(fileSystemName, policy)); |
| } |
| |
| static std::unique_ptr<BlobData> createBlobDataForFileWithMetadata( |
| const String& fileSystemName, |
| const FileMetadata& metadata) { |
| std::unique_ptr<BlobData> blobData = BlobData::create(); |
| blobData->setContentType( |
| getContentTypeFromFileName(fileSystemName, File::WellKnownContentTypes)); |
| blobData->appendFile(metadata.platformPath, 0, metadata.length, |
| metadata.modificationTime / msPerSecond); |
| return blobData; |
| } |
| |
| static std::unique_ptr<BlobData> createBlobDataForFileSystemURL( |
| const KURL& fileSystemURL, |
| const FileMetadata& metadata) { |
| std::unique_ptr<BlobData> blobData = BlobData::create(); |
| blobData->setContentType(getContentTypeFromFileName( |
| fileSystemURL.path(), File::WellKnownContentTypes)); |
| blobData->appendFileSystemURL(fileSystemURL, 0, metadata.length, |
| metadata.modificationTime / msPerSecond); |
| return blobData; |
| } |
| |
| // static |
| File* File::create( |
| ExecutionContext* context, |
| const HeapVector<ArrayBufferOrArrayBufferViewOrBlobOrUSVString>& fileBits, |
| const String& fileName, |
| const FilePropertyBag& options, |
| ExceptionState& exceptionState) { |
| ASSERT(options.hasType()); |
| |
| double lastModified; |
| if (options.hasLastModified()) |
| lastModified = static_cast<double>(options.lastModified()); |
| else |
| lastModified = currentTimeMS(); |
| ASSERT(options.hasEndings()); |
| bool normalizeLineEndingsToNative = options.endings() == "native"; |
| if (normalizeLineEndingsToNative) |
| UseCounter::count(context, UseCounter::FileAPINativeLineEndings); |
| |
| std::unique_ptr<BlobData> blobData = BlobData::create(); |
| blobData->setContentType(normalizeType(options.type())); |
| populateBlobData(blobData.get(), fileBits, normalizeLineEndingsToNative); |
| |
| long long fileSize = blobData->length(); |
| return File::create(fileName, lastModified, |
| BlobDataHandle::create(std::move(blobData), fileSize)); |
| } |
| |
| File* File::createWithRelativePath(const String& path, |
| const String& relativePath) { |
| File* file = new File(path, File::AllContentTypes, File::IsUserVisible); |
| file->m_relativePath = relativePath; |
| return file; |
| } |
| |
| File::File(const String& path, |
| ContentTypeLookupPolicy policy, |
| UserVisibility userVisibility) |
| : Blob(BlobDataHandle::create(createBlobDataForFile(path, policy), -1)), |
| m_hasBackingFile(true), |
| m_userVisibility(userVisibility), |
| m_path(path), |
| m_name(Platform::current()->fileUtilities()->baseName(path)), |
| m_snapshotSize(-1), |
| m_snapshotModificationTimeMS(invalidFileTime()) {} |
| |
| File::File(const String& path, |
| const String& name, |
| ContentTypeLookupPolicy policy, |
| UserVisibility userVisibility) |
| : Blob(BlobDataHandle::create( |
| createBlobDataForFileWithName(path, name, policy), |
| -1)), |
| m_hasBackingFile(true), |
| m_userVisibility(userVisibility), |
| m_path(path), |
| m_name(name), |
| m_snapshotSize(-1), |
| m_snapshotModificationTimeMS(invalidFileTime()) {} |
| |
| File::File(const String& path, |
| const String& name, |
| const String& relativePath, |
| UserVisibility userVisibility, |
| bool hasSnapshotData, |
| uint64_t size, |
| double lastModified, |
| PassRefPtr<BlobDataHandle> blobDataHandle) |
| : Blob(std::move(blobDataHandle)), |
| m_hasBackingFile(!path.isEmpty() || !relativePath.isEmpty()), |
| m_userVisibility(userVisibility), |
| m_path(path), |
| m_name(name), |
| m_snapshotSize(hasSnapshotData ? static_cast<long long>(size) : -1), |
| m_snapshotModificationTimeMS(hasSnapshotData ? lastModified |
| : invalidFileTime()), |
| m_relativePath(relativePath) {} |
| |
| File::File(const String& name, |
| double modificationTimeMS, |
| PassRefPtr<BlobDataHandle> blobDataHandle) |
| : Blob(std::move(blobDataHandle)), |
| m_hasBackingFile(false), |
| m_userVisibility(File::IsNotUserVisible), |
| m_name(name), |
| m_snapshotSize(Blob::size()), |
| m_snapshotModificationTimeMS(modificationTimeMS) {} |
| |
| File::File(const String& name, |
| const FileMetadata& metadata, |
| UserVisibility userVisibility) |
| : Blob(BlobDataHandle::create( |
| createBlobDataForFileWithMetadata(name, metadata), |
| metadata.length)), |
| m_hasBackingFile(true), |
| m_userVisibility(userVisibility), |
| m_path(metadata.platformPath), |
| m_name(name), |
| m_snapshotSize(metadata.length), |
| m_snapshotModificationTimeMS(metadata.modificationTime) {} |
| |
| File::File(const KURL& fileSystemURL, |
| const FileMetadata& metadata, |
| UserVisibility userVisibility) |
| : Blob(BlobDataHandle::create( |
| createBlobDataForFileSystemURL(fileSystemURL, metadata), |
| metadata.length)), |
| m_hasBackingFile(false), |
| m_userVisibility(userVisibility), |
| m_name(decodeURLEscapeSequences(fileSystemURL.lastPathComponent())), |
| m_fileSystemURL(fileSystemURL), |
| m_snapshotSize(metadata.length), |
| m_snapshotModificationTimeMS(metadata.modificationTime) {} |
| |
| File::File(const File& other) |
| : Blob(other.blobDataHandle()), |
| m_hasBackingFile(other.m_hasBackingFile), |
| m_userVisibility(other.m_userVisibility), |
| m_path(other.m_path), |
| m_name(other.m_name), |
| m_fileSystemURL(other.m_fileSystemURL), |
| m_snapshotSize(other.m_snapshotSize), |
| m_snapshotModificationTimeMS(other.m_snapshotModificationTimeMS), |
| m_relativePath(other.m_relativePath) {} |
| |
| File* File::clone(const String& name) const { |
| File* file = new File(*this); |
| if (!name.isNull()) |
| file->m_name = name; |
| return file; |
| } |
| |
| double File::lastModifiedMS() const { |
| if (hasValidSnapshotMetadata() && |
| isValidFileTime(m_snapshotModificationTimeMS)) |
| return m_snapshotModificationTimeMS; |
| |
| double modificationTimeMS; |
| if (hasBackingFile() && getFileModificationTime(m_path, modificationTimeMS) && |
| isValidFileTime(modificationTimeMS)) |
| return modificationTimeMS; |
| |
| return currentTimeMS(); |
| } |
| |
| long long File::lastModified() const { |
| double modifiedDate = lastModifiedMS(); |
| |
| // The getter should return the current time when the last modification time |
| // isn't known. |
| if (!isValidFileTime(modifiedDate)) |
| modifiedDate = currentTimeMS(); |
| |
| // lastModified returns a number, not a Date instance, |
| // http://dev.w3.org/2006/webapi/FileAPI/#file-attrs |
| return floor(modifiedDate); |
| } |
| |
| double File::lastModifiedDate() const { |
| double modifiedDate = lastModifiedMS(); |
| |
| // The getter should return the current time when the last modification time |
| // isn't known. |
| if (!isValidFileTime(modifiedDate)) |
| modifiedDate = currentTimeMS(); |
| |
| // lastModifiedDate returns a Date instance, |
| // http://www.w3.org/TR/FileAPI/#dfn-lastModifiedDate |
| return modifiedDate; |
| } |
| |
| unsigned long long File::size() const { |
| if (hasValidSnapshotMetadata()) |
| return m_snapshotSize; |
| |
| // FIXME: JavaScript cannot represent sizes as large as unsigned long long, we |
| // need to come up with an exception to throw if file size is not |
| // representable. |
| long long size; |
| if (!hasBackingFile() || !getFileSize(m_path, size)) |
| return 0; |
| return static_cast<unsigned long long>(size); |
| } |
| |
| Blob* File::slice(long long start, |
| long long end, |
| const String& contentType, |
| ExceptionState& exceptionState) const { |
| if (isClosed()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "File has been closed."); |
| return nullptr; |
| } |
| |
| if (!m_hasBackingFile) |
| return Blob::slice(start, end, contentType, exceptionState); |
| |
| // FIXME: This involves synchronous file operation. We need to figure out how |
| // to make it asynchronous. |
| long long size; |
| double modificationTimeMS; |
| captureSnapshot(size, modificationTimeMS); |
| clampSliceOffsets(size, start, end); |
| |
| long long length = end - start; |
| std::unique_ptr<BlobData> blobData = BlobData::create(); |
| blobData->setContentType(normalizeType(contentType)); |
| if (!m_fileSystemURL.isEmpty()) { |
| blobData->appendFileSystemURL(m_fileSystemURL, start, length, |
| modificationTimeMS / msPerSecond); |
| } else { |
| ASSERT(!m_path.isEmpty()); |
| blobData->appendFile(m_path, start, length, |
| modificationTimeMS / msPerSecond); |
| } |
| return Blob::create(BlobDataHandle::create(std::move(blobData), length)); |
| } |
| |
| void File::captureSnapshot(long long& snapshotSize, |
| double& snapshotModificationTimeMS) const { |
| if (hasValidSnapshotMetadata()) { |
| snapshotSize = m_snapshotSize; |
| snapshotModificationTimeMS = m_snapshotModificationTimeMS; |
| return; |
| } |
| |
| // Obtains a snapshot of the file by capturing its current size and |
| // modification time. This is used when we slice a file for the first time. |
| // If we fail to retrieve the size or modification time, probably due to that |
| // the file has been deleted, 0 size is returned. |
| FileMetadata metadata; |
| if (!hasBackingFile() || !getFileMetadata(m_path, metadata)) { |
| snapshotSize = 0; |
| snapshotModificationTimeMS = invalidFileTime(); |
| return; |
| } |
| |
| snapshotSize = metadata.length; |
| snapshotModificationTimeMS = metadata.modificationTime; |
| } |
| |
| void File::close(ScriptState* scriptState, ExceptionState& exceptionState) { |
| if (isClosed()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "Blob has been closed."); |
| return; |
| } |
| |
| // Reset the File to its closed representation, an empty |
| // Blob. The name isn't cleared, as it should still be |
| // available. |
| m_hasBackingFile = false; |
| m_path = String(); |
| m_fileSystemURL = KURL(); |
| invalidateSnapshotMetadata(); |
| m_relativePath = String(); |
| Blob::close(scriptState, exceptionState); |
| } |
| |
| void File::appendTo(BlobData& blobData) const { |
| if (!m_hasBackingFile) { |
| Blob::appendTo(blobData); |
| return; |
| } |
| |
| // FIXME: This involves synchronous file operation. We need to figure out how |
| // to make it asynchronous. |
| long long size; |
| double modificationTimeMS; |
| captureSnapshot(size, modificationTimeMS); |
| if (!m_fileSystemURL.isEmpty()) { |
| blobData.appendFileSystemURL(m_fileSystemURL, 0, size, |
| modificationTimeMS / msPerSecond); |
| return; |
| } |
| ASSERT(!m_path.isEmpty()); |
| blobData.appendFile(m_path, 0, size, modificationTimeMS / msPerSecond); |
| } |
| |
| bool File::hasSameSource(const File& other) const { |
| if (m_hasBackingFile != other.m_hasBackingFile) |
| return false; |
| |
| if (m_hasBackingFile) |
| return m_path == other.m_path; |
| |
| if (m_fileSystemURL.isEmpty() != other.m_fileSystemURL.isEmpty()) |
| return false; |
| |
| if (!m_fileSystemURL.isEmpty()) |
| return m_fileSystemURL == other.m_fileSystemURL; |
| |
| return uuid() == other.uuid(); |
| } |
| |
| } // namespace blink |