1 /* 2 * Copyright 2017-2018, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "AbstractServerProcess.h" 8 9 #include <unistd.h> 10 #include <errno.h> 11 #include <string.h> 12 13 #include <AutoDeleter.h> 14 #include <FileIO.h> 15 #include <HttpTime.h> 16 #include <UrlProtocolRoster.h> 17 18 #include <support/ZlibCompressionAlgorithm.h> 19 20 #include "HaikuDepotConstants.h" 21 #include "Logger.h" 22 #include "ServerHelper.h" 23 #include "ServerSettings.h" 24 #include "StandardMetaDataJsonEventListener.h" 25 #include "StorageUtils.h" 26 #include "ToFileUrlProtocolListener.h" 27 28 29 #define MAX_REDIRECTS 3 30 #define MAX_FAILURES 2 31 32 33 // 30 seconds 34 #define TIMEOUT_MICROSECONDS 3e+7 35 36 37 AbstractServerProcess::AbstractServerProcess(uint32 options) 38 : 39 AbstractProcess(), 40 fOptions(options), 41 fRequest(NULL) 42 { 43 } 44 45 46 AbstractServerProcess::~AbstractServerProcess() 47 { 48 } 49 50 51 bool 52 AbstractServerProcess::HasOption(uint32 flag) 53 { 54 return (fOptions & flag) == flag; 55 } 56 57 58 bool 59 AbstractServerProcess::ShouldAttemptNetworkDownload(bool hasDataAlready) 60 { 61 return 62 !HasOption(SERVER_PROCESS_NO_NETWORKING) 63 && !(HasOption(SERVER_PROCESS_PREFER_CACHE) && hasDataAlready); 64 } 65 66 67 status_t 68 AbstractServerProcess::StopInternal() 69 { 70 if (fRequest != NULL) { 71 return fRequest->Stop(); 72 } 73 74 return AbstractProcess::StopInternal(); 75 } 76 77 78 status_t 79 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue) const 80 { 81 BPath metaDataPath; 82 BString jsonPath; 83 84 status_t result = GetStandardMetaDataPath(metaDataPath); 85 86 if (result != B_OK) 87 return result; 88 89 GetStandardMetaDataJsonPath(jsonPath); 90 91 return IfModifiedSinceHeaderValue(headerValue, metaDataPath, jsonPath); 92 } 93 94 95 status_t 96 AbstractServerProcess::IfModifiedSinceHeaderValue(BString& headerValue, 97 const BPath& metaDataPath, const BString& jsonPath) const 98 { 99 headerValue.SetTo(""); 100 struct stat s; 101 102 if (-1 == stat(metaDataPath.Path(), &s)) { 103 if (ENOENT != errno) 104 return B_ERROR; 105 106 return B_FILE_NOT_FOUND; 107 } 108 109 if (s.st_size == 0) 110 return B_BAD_VALUE; 111 112 StandardMetaData metaData; 113 status_t result = PopulateMetaData(metaData, metaDataPath, jsonPath); 114 115 if (result == B_OK) { 116 117 // An example of this output would be; 'Fri, 24 Oct 2014 19:32:27 +0000' 118 119 BDateTime modifiedDateTime = metaData 120 .GetDataModifiedTimestampAsDateTime(); 121 BPrivate::BHttpTime modifiedHttpTime(modifiedDateTime); 122 headerValue.SetTo(modifiedHttpTime 123 .ToString(BPrivate::B_HTTP_TIME_FORMAT_COOKIE)); 124 } else { 125 fprintf(stderr, "unable to parse the meta-data date and time from [%s]" 126 " - cannot set the 'If-Modified-Since' header\n", 127 metaDataPath.Path()); 128 } 129 130 return result; 131 } 132 133 134 status_t 135 AbstractServerProcess::PopulateMetaData( 136 StandardMetaData& metaData, const BPath& path, 137 const BString& jsonPath) const 138 { 139 StandardMetaDataJsonEventListener listener(jsonPath, metaData); 140 status_t result = ParseJsonFromFileWithListener(&listener, path); 141 142 if (result != B_OK) 143 return result; 144 145 result = listener.ErrorStatus(); 146 147 if (result != B_OK) 148 return result; 149 150 if (!metaData.IsPopulated()) { 151 fprintf(stderr, "the meta data was read from [%s], but no values " 152 "were extracted\n", path.Path()); 153 return B_BAD_DATA; 154 } 155 156 return B_OK; 157 } 158 159 160 /* static */ bool 161 AbstractServerProcess::LooksLikeGzip(const char *pathStr) 162 { 163 int l = strlen(pathStr); 164 return l > 4 && 0 == strncmp(&pathStr[l - 3], ".gz", 3); 165 } 166 167 168 /*! Note that a B_OK return code from this method may not indicate that the 169 listening process went well. One has to see if there was an error in 170 the listener. 171 */ 172 173 status_t 174 AbstractServerProcess::ParseJsonFromFileWithListener( 175 BJsonEventListener *listener, 176 const BPath& path) const 177 { 178 const char* pathStr = path.Path(); 179 FILE* file = fopen(pathStr, "rb"); 180 181 if (file == NULL) { 182 printf("[%s] unable to find the meta data file at [%s]\n", Name(), 183 path.Path()); 184 return B_FILE_NOT_FOUND; 185 } 186 187 BFileIO rawInput(file, true); // takes ownership 188 189 // if the file extension ends with '.gz' then the data will be 190 // compressed and the algorithm needs to decompress the data as 191 // it is parsed. 192 193 if (LooksLikeGzip(pathStr)) { 194 BDataIO* gzDecompressedInput = NULL; 195 BZlibDecompressionParameters* zlibDecompressionParameters 196 = new BZlibDecompressionParameters(); 197 198 status_t result = BZlibCompressionAlgorithm() 199 .CreateDecompressingInputStream(&rawInput, 200 zlibDecompressionParameters, gzDecompressedInput); 201 202 if (B_OK != result) 203 return result; 204 205 ObjectDeleter<BDataIO> gzDecompressedInputDeleter(gzDecompressedInput); 206 BPrivate::BJson::Parse(gzDecompressedInput, listener); 207 } else { 208 BPrivate::BJson::Parse(&rawInput, listener); 209 } 210 211 return B_OK; 212 } 213 214 215 /*! In order to reduce the chance of failure half way through downloading a 216 large file, this method will download the file to a temporary file and 217 then it can rename the file to the final target file. 218 */ 219 220 status_t 221 AbstractServerProcess::DownloadToLocalFileAtomically( 222 const BPath& targetFilePath, 223 const BUrl& url) 224 { 225 BPath temporaryFilePath(tmpnam(NULL), NULL, true); 226 status_t result = DownloadToLocalFile( 227 temporaryFilePath, url, 0, 0); 228 229 // not copying if the data has not changed because the data will be 230 // zero length. This is if the result is APP_ERR_NOT_MODIFIED. 231 if (result == B_OK) { 232 233 // if the file is zero length then assume that something has 234 // gone wrong. 235 off_t size; 236 bool hasFile; 237 238 result = StorageUtils::ExistsObject(temporaryFilePath, &hasFile, NULL, 239 &size); 240 241 if (result == B_OK && hasFile && size > 0) { 242 if (rename(temporaryFilePath.Path(), targetFilePath.Path()) != 0) { 243 printf("[%s] did rename [%s] --> [%s]\n", 244 Name(), temporaryFilePath.Path(), targetFilePath.Path()); 245 result = B_IO_ERROR; 246 } 247 } 248 } 249 250 return result; 251 } 252 253 254 status_t 255 AbstractServerProcess::DownloadToLocalFile(const BPath& targetFilePath, 256 const BUrl& url, uint32 redirects, uint32 failures) 257 { 258 if (WasStopped()) 259 return B_CANCELED; 260 261 if (redirects > MAX_REDIRECTS) { 262 printf("[%s] exceeded %d redirects --> failure\n", Name(), 263 MAX_REDIRECTS); 264 return B_IO_ERROR; 265 } 266 267 if (failures > MAX_FAILURES) { 268 printf("[%s] exceeded %d failures\n", Name(), MAX_FAILURES); 269 return B_IO_ERROR; 270 } 271 272 printf("[%s] will stream '%s' to [%s]\n", Name(), url.UrlString().String(), 273 targetFilePath.Path()); 274 275 ToFileUrlProtocolListener listener(targetFilePath, Name(), 276 Logger::IsTraceEnabled()); 277 278 BHttpHeaders headers; 279 ServerSettings::AugmentHeaders(headers); 280 281 BString ifModifiedSinceHeader; 282 status_t ifModifiedSinceHeaderStatus = IfModifiedSinceHeaderValue( 283 ifModifiedSinceHeader); 284 285 if (ifModifiedSinceHeaderStatus == B_OK && 286 ifModifiedSinceHeader.Length() > 0) { 287 headers.AddHeader("If-Modified-Since", ifModifiedSinceHeader); 288 } 289 290 thread_id thread; 291 292 { 293 fRequest = dynamic_cast<BHttpRequest *>( 294 BUrlProtocolRoster::MakeRequest(url, &listener)); 295 fRequest->SetHeaders(headers); 296 fRequest->SetMaxRedirections(0); 297 fRequest->SetTimeout(TIMEOUT_MICROSECONDS); 298 thread = fRequest->Run(); 299 } 300 301 wait_for_thread(thread, NULL); 302 303 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 304 fRequest->Result()); 305 int32 statusCode = result.StatusCode(); 306 const BHttpHeaders responseHeaders = result.Headers(); 307 const char *locationC = responseHeaders["Location"]; 308 BString location; 309 310 if (locationC != NULL) 311 location.SetTo(locationC); 312 313 delete fRequest; 314 fRequest = NULL; 315 316 if (BHttpRequest::IsSuccessStatusCode(statusCode)) { 317 fprintf(stdout, "[%s] did complete streaming data [%" 318 B_PRIdSSIZE " bytes]\n", Name(), listener.ContentLength()); 319 return B_OK; 320 } else if (statusCode == B_HTTP_STATUS_NOT_MODIFIED) { 321 fprintf(stdout, "[%s] remote data has not changed since [%s]\n", 322 Name(), ifModifiedSinceHeader.String()); 323 return HD_ERR_NOT_MODIFIED; 324 } else if (statusCode == B_HTTP_STATUS_PRECONDITION_FAILED) { 325 ServerHelper::NotifyClientTooOld(responseHeaders); 326 return HD_CLIENT_TOO_OLD; 327 } else if (BHttpRequest::IsRedirectionStatusCode(statusCode)) { 328 if (location.Length() != 0) { 329 BUrl redirectUrl(result.Url(), location); 330 fprintf(stdout, "[%s] will redirect to; %s\n", 331 Name(), redirectUrl.UrlString().String()); 332 return DownloadToLocalFile(targetFilePath, redirectUrl, 333 redirects + 1, 0); 334 } 335 336 fprintf(stdout, "[%s] unable to find 'Location' header for redirect\n", 337 Name()); 338 return B_IO_ERROR; 339 } else { 340 if (statusCode == 0 || (statusCode / 100) == 5) { 341 fprintf(stdout, "error response from server [%" B_PRId32 "] --> " 342 "retry...\n", statusCode); 343 return DownloadToLocalFile(targetFilePath, url, redirects, 344 failures + 1); 345 } 346 347 fprintf(stdout, "[%s] unexpected response from server [%" B_PRId32 "]\n", 348 Name(), statusCode); 349 return B_IO_ERROR; 350 } 351 } 352 353 354 status_t 355 AbstractServerProcess::DeleteLocalFile(const BPath& currentFilePath) 356 { 357 if (0 == remove(currentFilePath.Path())) 358 return B_OK; 359 360 return B_IO_ERROR; 361 } 362 363 364 /*! When a file is damaged or corrupted in some way, the file should be 'moved 365 aside' so that it is not involved in the next update. This method will 366 create such an alternative 'damaged' file location and move this file to 367 that location. 368 */ 369 370 status_t 371 AbstractServerProcess::MoveDamagedFileAside(const BPath& currentFilePath) 372 { 373 BPath damagedFilePath; 374 BString damagedLeaf; 375 376 damagedLeaf.SetToFormat("%s__damaged", currentFilePath.Leaf()); 377 currentFilePath.GetParent(&damagedFilePath); 378 damagedFilePath.Append(damagedLeaf.String()); 379 380 if (0 != rename(currentFilePath.Path(), damagedFilePath.Path())) { 381 printf("[%s] unable to move damaged file [%s] aside to [%s]\n", 382 Name(), currentFilePath.Path(), damagedFilePath.Path()); 383 return B_IO_ERROR; 384 } 385 386 printf("[%s] did move damaged file [%s] aside to [%s]\n", 387 Name(), currentFilePath.Path(), damagedFilePath.Path()); 388 389 return B_OK; 390 } 391 392 393 bool 394 AbstractServerProcess::IsSuccess(status_t e) { 395 return e == B_OK || e == HD_ERR_NOT_MODIFIED; 396 } 397