/* * Copyright 2013-2014 Haiku Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Adrien Destugues, pulkomandy@pulkomandy.tk */ #include #include #include #include #include #include #include using namespace BPrivate::Network; BFileRequest::BFileRequest(const BUrl& url, BDataIO* output, BUrlProtocolListener* listener, BUrlContext* context) : BUrlRequest(url, output, listener, context, "BUrlProtocol.File", "file"), fResult() { fUrl.UrlDecode(true); } BFileRequest::~BFileRequest() { status_t status = Stop(); if (status == B_OK) wait_for_thread(fThreadId, &status); } const BUrlResult& BFileRequest::Result() const { return fResult; } status_t BFileRequest::_ProtocolLoop() { BNode node(fUrl.Path().String()); if (node.IsSymLink()) { // Traverse the symlink and start over BEntry entry(fUrl.Path().String(), true); node = BNode(&entry); } ssize_t transferredSize = 0; if (node.IsFile()) { BFile file(fUrl.Path().String(), B_READ_ONLY); status_t error = file.InitCheck(); if (error != B_OK) return error; BNodeInfo info(&file); char mimeType[B_MIME_TYPE_LENGTH + 1]; if (info.GetType(mimeType) != B_OK) update_mime_info(fUrl.Path().String(), false, true, false); if (info.GetType(mimeType) == B_OK) fResult.SetContentType(mimeType); // Send all notifications to listener, if any if (fListener != NULL) fListener->ConnectionOpened(this); off_t size = 0; error = file.GetSize(&size); if (error != B_OK) return error; fResult.SetLength(size); if (fListener != NULL) fListener->HeadersReceived(this); if (fOutput != NULL) { ssize_t chunkSize = 0; char chunk[4096]; while (!fQuit) { chunkSize = file.Read(chunk, sizeof(chunk)); if (chunkSize > 0) { size_t written = 0; error = fOutput->WriteExactly(chunk, chunkSize, &written); if (fListener != NULL && written > 0) fListener->BytesWritten(this, written); if (error != B_OK) return error; transferredSize += chunkSize; if (fListener != NULL) fListener->DownloadProgress(this, transferredSize, size); } else break; } if (fQuit) return B_INTERRUPTED; // Return error if we didn't transfer everything if (transferredSize != size) { if (chunkSize < 0) return (status_t)chunkSize; else return B_IO_ERROR; } } return B_OK; } node_ref ref; status_t error = node.GetNodeRef(&ref); // Stop here, and don't hit the assert below, if the file doesn't exist. if (error != B_OK) return error; assert(node.IsDirectory()); BDirectory directory(&ref); fResult.SetContentType("application/x-ftp-directory; charset=utf-8"); // This tells WebKit to use its FTP directory rendering code. if (fListener != NULL) { fListener->ConnectionOpened(this); fListener->HeadersReceived(this); } if (fOutput != NULL) { // Add a parent directory entry. size_t written = 0; error = fOutput->WriteExactly("+/,\t..\r\n", 8, &written); if (fListener != NULL && written > 0) fListener->BytesWritten(this, written); if (error != B_OK) return error; transferredSize += written; if (fListener != NULL) fListener->DownloadProgress(this, transferredSize, 0); char name[B_FILE_NAME_LENGTH]; BEntry entry; while (!fQuit && directory.GetNextEntry(&entry) != B_ENTRY_NOT_FOUND) { // We read directories using the EPLF (Easily Parsed List Format) // This happens to be one of the formats that WebKit can understand, // and it is not too hard to parse or generate. // http://tools.ietf.org/html/draft-bernstein-eplf-02 BString eplf("+"); if (entry.IsFile() || entry.IsSymLink()) { eplf += "r,"; off_t fileSize; if (entry.GetSize(&fileSize) == B_OK) eplf << "s" << fileSize << ","; } else if (entry.IsDirectory()) eplf += "/,"; time_t modification; if (entry.GetModificationTime(&modification) == B_OK) eplf << "m" << modification << ","; mode_t permissions; if (entry.GetPermissions(&permissions) == B_OK) eplf << "up" << BString().SetToFormat("%03o", permissions) << ","; node_ref ref; if (entry.GetNodeRef(&ref) == B_OK) eplf << "i" << ref.device << "." << ref.node << ","; entry.GetName(name); eplf << "\t" << name << "\r\n"; size_t written = 0; error = fOutput->WriteExactly(eplf.String(), eplf.Length(), &written); if (fListener != NULL && written > 0) fListener->BytesWritten(this, written); if (error != B_OK) return error; transferredSize += written; if (fListener != NULL) fListener->DownloadProgress(this, transferredSize, 0); } if (!fQuit) fResult.SetLength(transferredSize); } return fQuit ? B_INTERRUPTED : B_OK; }