1 /* 2 * Copyright 2017, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include "ServerIconExportUpdateProcess.h" 7 8 #include <errno.h> 9 #include <stdio.h> 10 #include <sys/stat.h> 11 #include <time.h> 12 13 #include <HttpRequest.h> 14 #include <Json.h> 15 #include <Url.h> 16 #include <support/ZlibCompressionAlgorithm.h> 17 18 #include "ServerSettings.h" 19 #include "StorageUtils.h" 20 #include "TarArchiveService.h" 21 #include "ToFileUrlProtocolListener.h" 22 23 24 #define MAX_REDIRECTS 3 25 #define MAX_FAILURES 2 26 27 #define HTTP_STATUS_OK 200 28 #define HTTP_STATUS_FOUND 302 29 #define HTTP_STATUS_NOT_MODIFIED 304 30 31 #define APP_ERR_NOT_MODIFIED (B_APP_ERROR_BASE + 452) 32 33 // 30 seconds 34 #define TIMEOUT_MICROSECONDS 3e+7 35 36 37 /*! This constructor will locate the cached data in a standardized location */ 38 39 ServerIconExportUpdateProcess::ServerIconExportUpdateProcess( 40 const BPath& localStorageDirectoryPath) 41 { 42 fLocalStorageDirectoryPath = localStorageDirectoryPath; 43 } 44 45 46 status_t 47 ServerIconExportUpdateProcess::Run() 48 { 49 BPath tarGzFilePath(tmpnam(NULL)); 50 status_t result = B_OK; 51 52 fprintf(stdout, "will start fetching icons\n"); 53 54 result = _Download(tarGzFilePath); 55 56 if (result != APP_ERR_NOT_MODIFIED) { 57 if (result != B_OK) 58 return result; 59 60 fprintf(stdout, "delete any existing stored data\n"); 61 StorageUtils::RemoveDirectoryContents(fLocalStorageDirectoryPath); 62 63 BFile *tarGzFile = new BFile(tarGzFilePath.Path(), O_RDONLY); 64 BDataIO* tarIn; 65 66 BZlibDecompressionParameters* zlibDecompressionParameters 67 = new BZlibDecompressionParameters(); 68 69 result = BZlibCompressionAlgorithm() 70 .CreateDecompressingInputStream(tarGzFile, 71 zlibDecompressionParameters, tarIn); 72 73 if (result == B_OK) { 74 result = TarArchiveService::Unpack(*tarIn, 75 fLocalStorageDirectoryPath); 76 77 if (result == B_OK) { 78 if (0 != remove(tarGzFilePath.Path())) { 79 fprintf(stdout, "unable to delete the temporary tgz path; " 80 "%s", tarGzFilePath.Path()); 81 } 82 } 83 } 84 85 delete tarGzFile; 86 } 87 88 fprintf(stdout, "did complete fetching icons\n"); 89 90 return result; 91 } 92 93 94 status_t 95 ServerIconExportUpdateProcess::_IfModifiedSinceHeaderValue(BString& headerValue, 96 BPath& iconMetaDataPath) const 97 { 98 headerValue.SetTo(""); 99 struct stat s; 100 101 if (-1 == stat(iconMetaDataPath.Path(), &s)) { 102 if (ENOENT != errno) 103 return B_ERROR; 104 105 return B_FILE_NOT_FOUND; 106 } 107 108 IconMetaData iconMetaData; 109 status_t result = _PopulateIconMetaData(iconMetaData, iconMetaDataPath); 110 111 if (result == B_OK) { 112 _TimestampToRfc2822String(iconMetaData.GetDataModifiedTimestamp(), 113 headerValue); 114 } 115 116 return result; 117 } 118 119 120 status_t 121 ServerIconExportUpdateProcess::_IfModifiedSinceHeaderValue(BString& headerValue) 122 const 123 { 124 BPath iconMetaDataPath(fLocalStorageDirectoryPath); 125 iconMetaDataPath.Append("hicn/info.json"); 126 return _IfModifiedSinceHeaderValue(headerValue, iconMetaDataPath); 127 } 128 129 130 status_t 131 ServerIconExportUpdateProcess::_Download(BPath& tarGzFilePath) 132 { 133 BString urlString = ServerSettings::CreateFullUrl("/__pkgicon/all.tar.gz"); 134 return _Download(tarGzFilePath, BUrl(urlString), 0, 0); 135 } 136 137 138 status_t 139 ServerIconExportUpdateProcess::_Download(BPath& tarGzFilePath, const BUrl& url, 140 uint32 redirects, uint32 failures) 141 { 142 if (redirects > MAX_REDIRECTS) { 143 fprintf(stdout, "exceeded %d redirects --> failure\n", MAX_REDIRECTS); 144 return B_IO_ERROR; 145 } 146 147 if (failures > MAX_FAILURES) { 148 fprintf(stdout, "exceeded %d failures\n", MAX_FAILURES); 149 return B_IO_ERROR; 150 } 151 152 fprintf(stdout, "will stream '%s' to [%s]\n", url.UrlString().String(), 153 tarGzFilePath.Path()); 154 155 bool isSecure = url.Protocol() == BString("https"); 156 ToFileUrlProtocolListener listener(tarGzFilePath, "icon-export", 157 ServerSettings::UrlConnectionTraceLoggingEnabled()); 158 BUrlContext context; 159 160 BHttpHeaders headers; 161 ServerSettings::AugmentHeaders(headers); 162 163 BString ifModifiedSinceHeader; 164 status_t ifModifiedSinceHeaderStatus = _IfModifiedSinceHeaderValue( 165 ifModifiedSinceHeader); 166 167 if (ifModifiedSinceHeaderStatus == B_OK && 168 ifModifiedSinceHeader.Length() > 0) { 169 headers.AddHeader("If-Modified-Since", ifModifiedSinceHeader); 170 } 171 172 BHttpRequest request(url, isSecure, "HTTP", &listener, &context); 173 request.SetMethod(B_HTTP_GET); 174 request.SetHeaders(headers); 175 request.SetTimeout(TIMEOUT_MICROSECONDS); 176 177 thread_id thread = request.Run(); 178 wait_for_thread(thread, NULL); 179 180 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 181 request.Result()); 182 183 int32 statusCode = result.StatusCode(); 184 185 switch (statusCode) { 186 case HTTP_STATUS_OK: 187 fprintf(stdout, "did complete streaming data\n"); 188 return B_OK; 189 190 case HTTP_STATUS_NOT_MODIFIED: 191 fprintf(stdout, "remote data has not changed since [%s]\n", 192 ifModifiedSinceHeader.String()); 193 return APP_ERR_NOT_MODIFIED; 194 195 case HTTP_STATUS_FOUND: // redirect 196 { 197 const BHttpHeaders responseHeaders = result.Headers(); 198 const char *locationValue = responseHeaders["Location"]; 199 200 if (NULL != locationValue && 0 != strlen(locationValue)) { 201 BUrl location(locationValue); 202 fprintf(stdout, "will redirect to; %s\n", 203 location.UrlString().String()); 204 return _Download(tarGzFilePath, location, redirects + 1, 0); 205 } 206 207 fprintf(stdout, "unable to find 'Location' header for redirect\n"); 208 return B_IO_ERROR; 209 } 210 211 default: 212 if (0 == statusCode || 5 == (statusCode / 100)) { 213 fprintf(stdout, "error response from server; %zu --> " 214 "retry...\n", statusCode); 215 return _Download(tarGzFilePath, url, redirects, failures + 1); 216 } 217 218 fprintf(stdout, "unexpected response from server; %zu\n", 219 statusCode); 220 return B_IO_ERROR; 221 } 222 } 223 224 225 status_t 226 ServerIconExportUpdateProcess::_PopulateIconMetaData(IconMetaData& iconMetaData, 227 BMessage& message) const 228 { 229 status_t result = B_OK; 230 double value; // numeric resolution issue? 231 232 if (result == B_OK) 233 result = message.FindDouble("createTimestamp", &value); 234 235 if (result == B_OK) 236 iconMetaData.SetCreateTimestamp((uint64) value); 237 238 if (result == B_OK) 239 result = message.FindDouble("dataModifiedTimestamp", &value); 240 241 if (result == B_OK) 242 iconMetaData.SetDataModifiedTimestamp((uint64) value); 243 244 return result; 245 } 246 247 248 status_t 249 ServerIconExportUpdateProcess::_PopulateIconMetaData(IconMetaData& iconMetaData, 250 BString& jsonString) const 251 { 252 BJson parser; 253 BMessage infoMetaDataMessage; 254 status_t result = parser.Parse(infoMetaDataMessage, jsonString); 255 256 if (result == B_OK) 257 return _PopulateIconMetaData(iconMetaData, infoMetaDataMessage); 258 259 return result; 260 } 261 262 263 status_t 264 ServerIconExportUpdateProcess::_PopulateIconMetaData(IconMetaData& iconMetaData, 265 BPath& path) const 266 { 267 268 BString infoMetaDataStr; 269 status_t result = StorageUtils::AppendToString(path, infoMetaDataStr); 270 271 if (result == B_OK) 272 return _PopulateIconMetaData(iconMetaData, infoMetaDataStr); 273 274 return result; 275 } 276 277 278 /* The output format for this is suitable for use in the "If-Modified-Since" 279 * header. An example of this output would be; 280 * 'Fri, 24 Oct 2014 19:32:27 +0000' 281 */ 282 283 void 284 ServerIconExportUpdateProcess::_TimestampToRfc2822String( 285 uint64_t timestampMillis, 286 BString& rfc2822String) const 287 { 288 char output[256]; 289 output[0] = 0; 290 time_t t = (timestampMillis / 1000); 291 strftime(output, 256, "%a, %d %b %Y %T %z", gmtime(&t)); 292 rfc2822String.SetTo(output); 293 } 294 295