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