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