1/*
2 * Copyright 2013-2014 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * 		Adrien Destugues, pulkomandy@pulkomandy.tk
7 */
8
9
10#include <assert.h>
11#include <stdlib.h>
12
13#include <Directory.h>
14#include <File.h>
15#include <FileRequest.h>
16#include <NodeInfo.h>
17#include <Path.h>
18
19using namespace BPrivate::Network;
20
21
22BFileRequest::BFileRequest(const BUrl& url, BDataIO* output,
23	BUrlProtocolListener* listener, BUrlContext* context)
24	:
25	BUrlRequest(url, output, listener, context, "BUrlProtocol.File", "file"),
26	fResult()
27{
28	fUrl.UrlDecode(true);
29}
30
31
32BFileRequest::~BFileRequest()
33{
34	status_t status = Stop();
35	if (status == B_OK)
36		wait_for_thread(fThreadId, &status);
37}
38
39
40const BUrlResult&
41BFileRequest::Result() const
42{
43	return fResult;
44}
45
46
47status_t
48BFileRequest::_ProtocolLoop()
49{
50	BNode node(fUrl.Path().String());
51
52	if (node.IsSymLink()) {
53		// Traverse the symlink and start over
54		BEntry entry(fUrl.Path().String(), true);
55		node = BNode(&entry);
56	}
57
58	ssize_t transferredSize = 0;
59	if (node.IsFile()) {
60		BFile file(fUrl.Path().String(), B_READ_ONLY);
61		status_t error = file.InitCheck();
62		if (error != B_OK)
63			return error;
64
65		BNodeInfo info(&file);
66		char mimeType[B_MIME_TYPE_LENGTH + 1];
67		if (info.GetType(mimeType) != B_OK)
68			update_mime_info(fUrl.Path().String(), false, true, false);
69		if (info.GetType(mimeType) == B_OK)
70			fResult.SetContentType(mimeType);
71
72		// Send all notifications to listener, if any
73		if (fListener != NULL)
74			fListener->ConnectionOpened(this);
75
76		off_t size = 0;
77		error = file.GetSize(&size);
78		if (error != B_OK)
79			return error;
80		fResult.SetLength(size);
81
82		if (fListener != NULL)
83			fListener->HeadersReceived(this);
84
85		if (fOutput != NULL) {
86			ssize_t chunkSize = 0;
87			char chunk[4096];
88			while (!fQuit) {
89				chunkSize = file.Read(chunk, sizeof(chunk));
90				if (chunkSize > 0) {
91					size_t written = 0;
92					error = fOutput->WriteExactly(chunk, chunkSize, &written);
93					if (fListener != NULL && written > 0)
94						fListener->BytesWritten(this, written);
95					if (error != B_OK)
96						return error;
97					transferredSize += chunkSize;
98					if (fListener != NULL)
99						fListener->DownloadProgress(this, transferredSize,
100							size);
101				} else
102					break;
103			}
104			if (fQuit)
105				return B_INTERRUPTED;
106			// Return error if we didn't transfer everything
107			if (transferredSize != size) {
108				if (chunkSize < 0)
109					return (status_t)chunkSize;
110				else
111					return B_IO_ERROR;
112			}
113		}
114
115		return B_OK;
116	}
117
118	node_ref ref;
119	status_t error = node.GetNodeRef(&ref);
120
121	// Stop here, and don't hit the assert below, if the file doesn't exist.
122	if (error != B_OK)
123		return error;
124
125	assert(node.IsDirectory());
126	BDirectory directory(&ref);
127
128	fResult.SetContentType("application/x-ftp-directory; charset=utf-8");
129		// This tells WebKit to use its FTP directory rendering code.
130
131	if (fListener != NULL) {
132		fListener->ConnectionOpened(this);
133		fListener->HeadersReceived(this);
134	}
135
136	if (fOutput != NULL) {
137		// Add a parent directory entry.
138		size_t written = 0;
139		error = fOutput->WriteExactly("+/,\t..\r\n", 8, &written);
140		if (fListener != NULL && written > 0)
141			fListener->BytesWritten(this, written);
142		if (error != B_OK)
143			return error;
144		transferredSize += written;
145		if (fListener != NULL)
146			fListener->DownloadProgress(this, transferredSize, 0);
147
148		char name[B_FILE_NAME_LENGTH];
149		BEntry entry;
150		while (!fQuit && directory.GetNextEntry(&entry) != B_ENTRY_NOT_FOUND) {
151			// We read directories using the EPLF (Easily Parsed List Format)
152			// This happens to be one of the formats that WebKit can understand,
153			// and it is not too hard to parse or generate.
154			// http://tools.ietf.org/html/draft-bernstein-eplf-02
155			BString eplf("+");
156			if (entry.IsFile() || entry.IsSymLink()) {
157				eplf += "r,";
158				off_t fileSize;
159				if (entry.GetSize(&fileSize) == B_OK)
160					eplf << "s" << fileSize << ",";
161			} else if (entry.IsDirectory())
162				eplf += "/,";
163
164			time_t modification;
165			if (entry.GetModificationTime(&modification) == B_OK)
166				eplf << "m" << modification << ",";
167
168			mode_t permissions;
169			if (entry.GetPermissions(&permissions) == B_OK)
170				eplf << "up" << BString().SetToFormat("%03o", permissions) << ",";
171
172			node_ref ref;
173			if (entry.GetNodeRef(&ref) == B_OK)
174				eplf << "i" << ref.device << "." << ref.node << ",";
175
176			entry.GetName(name);
177			eplf << "\t" << name << "\r\n";
178			size_t written = 0;
179			error = fOutput->WriteExactly(eplf.String(), eplf.Length(),
180				&written);
181			if (fListener != NULL && written > 0)
182				fListener->BytesWritten(this, written);
183			if (error != B_OK)
184				return error;
185			transferredSize += written;
186			if (fListener != NULL)
187				fListener->DownloadProgress(this, transferredSize, 0);
188		}
189
190		if (!fQuit)
191			fResult.SetLength(transferredSize);
192	}
193
194	return fQuit ? B_INTERRUPTED : B_OK;
195}
196