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