xref: /haiku/src/apps/haikudepot/server/AbstractServerProcess.cpp (revision 5e19679ea35a79a26477c6215c7abba9bb7c4d00)
1 /*
2  * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 #include "AbstractServerProcess.h"
6 
7 #include <errno.h>
8 #include <string.h>
9 
10 #include <AutoDeleter.h>
11 #include <FileIO.h>
12 #include <HttpRequest.h>
13 #include <HttpTime.h>
14 #include <UrlProtocolRoster.h>
15 
16 #include <support/ZlibCompressionAlgorithm.h>
17 
18 #include "Logger.h"
19 #include "ServerSettings.h"
20 #include "StandardMetaDataJsonEventListener.h"
21 #include "ToFileUrlProtocolListener.h"
22 
23 
24 #define MAX_REDIRECTS 3
25 #define MAX_FAILURES 2
26 
27 #define HTTP_STATUS_FOUND 302
28 #define HTTP_STATUS_NOT_MODIFIED 304
29 
30 // 30 seconds
31 #define TIMEOUT_MICROSECONDS 3e+7
32 
33 
34 status_t
35 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue) const
36 {
37 	BPath metaDataPath;
38 	BString jsonPath;
39 
40 	GetStandardMetaDataPath(metaDataPath);
41 	GetStandardMetaDataJsonPath(jsonPath);
42 
43 	return IfModifiedSinceHeaderValue(headerValue, metaDataPath, jsonPath);
44 }
45 
46 
47 status_t
48 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue,
49 	const BPath& metaDataPath, const BString& jsonPath) const
50 {
51 	headerValue.SetTo("");
52 	struct stat s;
53 
54 	if (-1 == stat(metaDataPath.Path(), &s)) {
55 		if (ENOENT != errno)
56 			 return B_ERROR;
57 
58 		return B_FILE_NOT_FOUND;
59 	}
60 
61 	if (s.st_size == 0)
62 		return B_BAD_VALUE;
63 
64 	StandardMetaData metaData;
65 	status_t result = PopulateMetaData(metaData, metaDataPath, jsonPath);
66 
67 	if (result == B_OK) {
68 
69 		// An example of this output would be; 'Fri, 24 Oct 2014 19:32:27 +0000'
70 
71 		BDateTime modifiedDateTime = metaData
72 			.GetDataModifiedTimestampAsDateTime();
73 		BPrivate::BHttpTime modifiedHttpTime(modifiedDateTime);
74 		headerValue.SetTo(modifiedHttpTime
75 			.ToString(BPrivate::B_HTTP_TIME_FORMAT_COOKIE));
76 	} else {
77 		fprintf(stderr, "unable to parse the meta-data date and time -"
78 			" cannot set the 'If-Modified-Since' header\n");
79 	}
80 
81 	return result;
82 }
83 
84 
85 status_t
86 AbstractServerProcess::PopulateMetaData(
87 	StandardMetaData& metaData, const BPath& path,
88 	const BString& jsonPath) const
89 {
90 	StandardMetaDataJsonEventListener listener(jsonPath, metaData);
91 	status_t result = ParseJsonFromFileWithListener(&listener, path);
92 
93 	if (result != B_OK)
94 		return result;
95 
96 	result = listener.ErrorStatus();
97 
98 	if (result != B_OK)
99 		return result;
100 
101 	if (!metaData.IsPopulated()) {
102 		fprintf(stderr, "the meta data was read from [%s], but no values "
103 			"were extracted\n", path.Path());
104 		return B_BAD_DATA;
105 	}
106 
107 	return B_OK;
108 }
109 
110 
111 bool
112 AbstractServerProcess::LooksLikeGzip(const char *pathStr) const
113 {
114 	int l = strlen(pathStr);
115 	return l > 4 && 0 == strncmp(&pathStr[l - 3], ".gz", 3);
116 }
117 
118 
119 /*!	Note that a B_OK return code from this method may not indicate that the
120 	listening process went well.  One has to see if there was an error in
121 	the listener.
122 */
123 
124 status_t
125 AbstractServerProcess::ParseJsonFromFileWithListener(
126 	BJsonEventListener *listener,
127 	const BPath& path) const
128 {
129 	const char* pathStr = path.Path();
130 	FILE* file = fopen(pathStr, "rb");
131 
132 	if (file == NULL) {
133 		fprintf(stderr, "unable to find the meta data file at [%s]\n",
134 			path.Path());
135 		return B_FILE_NOT_FOUND;
136 	}
137 
138 	BFileIO rawInput(file, true); // takes ownership
139 
140 		// if the file extension ends with '.gz' then the data will be
141 		// compressed and the algorithm needs to decompress the data as
142 		// it is parsed.
143 
144 	if (LooksLikeGzip(pathStr)) {
145 		BDataIO* gzDecompressedInput = NULL;
146 		BZlibDecompressionParameters* zlibDecompressionParameters
147 			= new BZlibDecompressionParameters();
148 
149 		status_t result = BZlibCompressionAlgorithm()
150 			.CreateDecompressingInputStream(&rawInput,
151 				zlibDecompressionParameters, gzDecompressedInput);
152 
153 		if (B_OK != result)
154 			return result;
155 
156 		ObjectDeleter<BDataIO> gzDecompressedInputDeleter(gzDecompressedInput);
157 		BPrivate::BJson::Parse(gzDecompressedInput, listener);
158 	} else {
159 		BPrivate::BJson::Parse(&rawInput, listener);
160 	}
161 
162 	return B_OK;
163 }
164 
165 
166 status_t
167 AbstractServerProcess::DownloadToLocalFile(const BPath& targetFilePath,
168 	const BUrl& url, uint32 redirects, uint32 failures)
169 {
170 	if (redirects > MAX_REDIRECTS) {
171 		fprintf(stdout, "exceeded %d redirects --> failure\n", MAX_REDIRECTS);
172 		return B_IO_ERROR;
173 	}
174 
175 	if (failures > MAX_FAILURES) {
176 		fprintf(stdout, "exceeded %d failures\n", MAX_FAILURES);
177 		return B_IO_ERROR;
178 	}
179 
180 	fprintf(stdout, "will stream '%s' to [%s]\n", url.UrlString().String(),
181 		targetFilePath.Path());
182 
183 	ToFileUrlProtocolListener listener(targetFilePath, LoggingName(),
184 		Logger::IsTraceEnabled());
185 
186 	BHttpHeaders headers;
187 	ServerSettings::AugmentHeaders(headers);
188 
189 	BString ifModifiedSinceHeader;
190 	status_t ifModifiedSinceHeaderStatus = IfModifiedSinceHeaderValue(
191 		ifModifiedSinceHeader);
192 
193 	if (ifModifiedSinceHeaderStatus == B_OK &&
194 		ifModifiedSinceHeader.Length() > 0) {
195 		headers.AddHeader("If-Modified-Since", ifModifiedSinceHeader);
196 	}
197 
198 	BHttpRequest *request = dynamic_cast<BHttpRequest *>(
199 		BUrlProtocolRoster::MakeRequest(url, &listener));
200 	ObjectDeleter<BHttpRequest> requestDeleter(request);
201 	request->SetHeaders(headers);
202 	request->SetMaxRedirections(0);
203 	request->SetTimeout(TIMEOUT_MICROSECONDS);
204 
205 	thread_id thread = request->Run();
206 	wait_for_thread(thread, NULL);
207 
208 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
209 		request->Result());
210 
211 	int32 statusCode = result.StatusCode();
212 
213 	if (BHttpRequest::IsSuccessStatusCode(statusCode)) {
214 		fprintf(stdout, "did complete streaming data\n");
215 		return B_OK;
216 	} else if (statusCode == HTTP_STATUS_NOT_MODIFIED) {
217 		fprintf(stdout, "remote data has not changed since [%s]\n",
218 			ifModifiedSinceHeader.String());
219 		return APP_ERR_NOT_MODIFIED;
220 	} else if (BHttpRequest::IsRedirectionStatusCode(statusCode)) {
221 		const BHttpHeaders responseHeaders = result.Headers();
222 		const char *locationValue = responseHeaders["Location"];
223 
224 		if (locationValue != NULL && strlen(locationValue) != 0) {
225 			BUrl location(result.Url(), locationValue);
226 			fprintf(stdout, "will redirect to; %s\n",
227 				location.UrlString().String());
228 				return DownloadToLocalFile(targetFilePath, location,
229 					redirects + 1, 0);
230 		}
231 
232 		fprintf(stdout, "unable to find 'Location' header for redirect\n");
233 		return B_IO_ERROR;
234 	} else {
235 		if (statusCode == 0 || (statusCode / 100) == 5) {
236 			fprintf(stdout, "error response from server; %" B_PRId32 " --> "
237 				"retry...\n", statusCode);
238 			return DownloadToLocalFile(targetFilePath, url, redirects,
239 				failures + 1);
240 		}
241 
242 		fprintf(stdout, "unexpected response from server; %" B_PRId32 "\n",
243 			statusCode);
244 		return B_IO_ERROR;
245 	}
246 }
247 
248 
249 /*!	When a file is damaged or corrupted in some way, the file should be 'moved
250     aside' so that it is not involved in the next update.  This method will
251     create such an alternative 'damaged' file location and move this file to
252     that location.
253 */
254 
255 status_t
256 AbstractServerProcess::MoveDamagedFileAside(const BPath& currentFilePath)
257 {
258 	BPath damagedFilePath;
259 	BString damagedLeaf;
260 
261 	damagedLeaf.SetToFormat("%s__damaged", currentFilePath.Leaf());
262 	currentFilePath.GetParent(&damagedFilePath);
263 	damagedFilePath.Append(damagedLeaf.String());
264 
265 	if (0 != rename(currentFilePath.Path(), damagedFilePath.Path())) {
266 		printf("unable to move damaged file [%s] aside to [%s]\n",
267 			currentFilePath.Path(), damagedFilePath.Path());
268 		return B_IO_ERROR;
269 	}
270 
271 	printf("did move damaged file [%s] aside to [%s]\n",
272 		currentFilePath.Path(), damagedFilePath.Path());
273 
274 	return B_OK;
275 }
276