1 /* 2 * Copyright 2017-2020, 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 HDERROR("unable to parse the meta-data date and time from [%s]" 126 " - cannot set the 'If-Modified-Since' header", 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 HDERROR("the meta data was read from [%s], but no values " 152 "were extracted", 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 HDERROR("[%s] unable to find the meta data file at [%s]", 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 HDINFO("[%s] did rename [%s] --> [%s]", 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 HDINFO("[%s] exceeded %d redirects --> failure", Name(), 263 MAX_REDIRECTS); 264 return B_IO_ERROR; 265 } 266 267 if (failures > MAX_FAILURES) { 268 HDINFO("[%s] exceeded %d failures", Name(), MAX_FAILURES); 269 return B_IO_ERROR; 270 } 271 272 HDINFO("[%s] will stream '%s' to [%s]", 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 HDINFO("[%s] did complete streaming data [%" 318 B_PRIdSSIZE " bytes]", Name(), listener.ContentLength()); 319 return B_OK; 320 } else if (statusCode == B_HTTP_STATUS_NOT_MODIFIED) { 321 HDINFO("[%s] remote data has not changed since [%s]", Name(), 322 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 HDINFO("[%s] will redirect to; %s", 331 Name(), redirectUrl.UrlString().String()); 332 return DownloadToLocalFile(targetFilePath, redirectUrl, 333 redirects + 1, 0); 334 } 335 336 HDERROR("[%s] unable to find 'Location' header for redirect", Name()); 337 return B_IO_ERROR; 338 } else { 339 if (statusCode == 0 || (statusCode / 100) == 5) { 340 HDERROR("error response from server [%" B_PRId32 "] --> retry...", 341 statusCode); 342 return DownloadToLocalFile(targetFilePath, url, redirects, 343 failures + 1); 344 } 345 346 HDERROR("[%s] unexpected response from server [%" B_PRId32 "]", 347 Name(), statusCode); 348 return B_IO_ERROR; 349 } 350 } 351 352 353 status_t 354 AbstractServerProcess::DeleteLocalFile(const BPath& currentFilePath) 355 { 356 if (0 == remove(currentFilePath.Path())) 357 return B_OK; 358 359 return B_IO_ERROR; 360 } 361 362 363 /*! When a file is damaged or corrupted in some way, the file should be 'moved 364 aside' so that it is not involved in the next update. This method will 365 create such an alternative 'damaged' file location and move this file to 366 that location. 367 */ 368 369 status_t 370 AbstractServerProcess::MoveDamagedFileAside(const BPath& currentFilePath) 371 { 372 BPath damagedFilePath; 373 BString damagedLeaf; 374 375 damagedLeaf.SetToFormat("%s__damaged", currentFilePath.Leaf()); 376 currentFilePath.GetParent(&damagedFilePath); 377 damagedFilePath.Append(damagedLeaf.String()); 378 379 if (0 != rename(currentFilePath.Path(), damagedFilePath.Path())) { 380 HDERROR("[%s] unable to move damaged file [%s] aside to [%s]", 381 Name(), currentFilePath.Path(), damagedFilePath.Path()); 382 return B_IO_ERROR; 383 } 384 385 HDINFO("[%s] did move damaged file [%s] aside to [%s]", 386 Name(), currentFilePath.Path(), damagedFilePath.Path()); 387 388 return B_OK; 389 } 390 391 392 bool 393 AbstractServerProcess::IsSuccess(status_t e) { 394 return e == B_OK || e == HD_ERR_NOT_MODIFIED; 395 } 396