xref: /haiku/src/kits/network/libnetservices/FileRequest.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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 
19 using namespace BPrivate::Network;
20 
21 
22 BFileRequest::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 
32 BFileRequest::~BFileRequest()
33 {
34 	status_t status = Stop();
35 	if (status == B_OK)
36 		wait_for_thread(fThreadId, &status);
37 }
38 
39 
40 const BUrlResult&
41 BFileRequest::Result() const
42 {
43 	return fResult;
44 }
45 
46 
47 status_t
48 BFileRequest::_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