00001 #include "aa_config.h"
00002
00003 #include <errno.h>
00004 #include <stdio.h>
00005 #include <stdlib.h>
00006 #include <string.h>
00007 #include <sys/stat.h>
00008 #include <sys/types.h>
00009
00010 #include <libxml/nanohttp.h>
00011
00012 #include "tConfiguration.h"
00013 #include "tDirectories.h"
00014 #include "tResourceManager.h"
00015 #include "tString.h"
00016
00017 void tResourceManager::RegisterLoader()
00018 {
00019 }
00020
00021 tResource* tResourceManager::GetResource(const char *file, int typeID)
00022 {
00023
00024 return NULL;
00025 }
00026
00027 int tResourceManager::RegisterResourceType(tNewResourceFunc func) {
00028
00029 return 0;
00030 }
00031
00032
00033
00034
00035
00036
00037 tString & tResourceManager::AccessRepoServer()
00038 {
00039 static tString resRepoServer("http://resource.armagetronad.net/resource/");
00040 return resRepoServer;
00041 }
00042
00043
00044
00045 tString & tResourceManager::AccessRepoClient()
00046 {
00047 static tString resRepoClient("http://resource.armagetronad.net/resource/");
00048 return resRepoClient;
00049 }
00050
00051 static tSettingItem<tString> conf_res_repo("RESOURCE_REPOSITORY_CLIENT", tResourceManager::AccessRepoClient());
00052
00053 static int myHTTPFetch(const char *URI, const char *filename, const char *savepath)
00054 {
00055 void *ctxt = NULL;
00056 char *buf = NULL;
00057 FILE* fd;
00058 int len, rc;
00059
00060 con << tOutput( "$resource_downloading", URI );
00061
00062
00063 ctxt = xmlNanoHTTPOpen(URI, NULL);
00064 if (ctxt == NULL) {
00065 con << "ERROR: ctxt is NULL\n";
00066 return 1;
00067 }
00068
00069 if ( (rc = xmlNanoHTTPReturnCode(ctxt)) != 200 ) {
00070 con << tOutput( rc == 404 ? "$resource_fetcherror_404" : "$resource_fetcherror", rc );
00071 return 2;
00072 }
00073
00074 fd = fopen(savepath, "wb");
00075 if (fd < 0) {
00076 xmlNanoHTTPClose(ctxt);
00077 con << tOutput( "$resource_no_write", savepath );
00078 return 3;
00079 }
00080
00081
00082 int maxlen = 10000;
00083 buf = (char*)malloc(maxlen);
00084 while( (len = xmlNanoHTTPRead(ctxt, buf, maxlen)) > 0 ) {
00085 fwrite(buf, len, 1, fd);
00086 }
00087 free(buf);
00088
00089 xmlNanoHTTPClose(ctxt);
00090 fclose(fd);
00091
00092
00093 con << "OK\n";
00094
00095 return 0;
00096 }
00097
00098 static int myFetch(const char *URIs, const char *filename, const char *savepath) {
00099 const char *r = URIs, *p, *n;
00100 char *u;
00101 size_t len;
00102 int rv = -1;
00103
00104
00105
00106 while (r[0] != '\0') {
00107 while (r[0] == ' ') ++r;
00108 (p = strchr(r, ';')) ? 0 : (p = strchr(r, '\0'));
00109 n = (p[0] == '\0') ? p : (p + 1);
00110
00111 while (p[-1] == ' ') --p;
00112 len = (size_t)(p - r);
00113 if (len > 0) {
00114 u = (char*)malloc((len + 1) * sizeof(char));
00115 strncpy(u, r, len);
00116 u[len] = '\0';
00117 rv = myHTTPFetch(u, filename, savepath);
00118 free(u);
00119 if (rv == 0) return 0;
00120 }
00121 r = n;
00122 }
00123
00124 return rv;
00125 }
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140 tString tResourceManager::locateResource(const char *file, const char *uri) {
00141 tString filepath, a_uri = tString(), savepath;
00142 int rv;
00143
00144 char * to_free = NULL;
00145
00146 {
00147 char const *pos, *posb;
00148 char *nf;
00149 size_t l;
00150
00151
00152 if ( (pos = strchr(file, '(')) ) {
00153 l = (size_t)(pos - file);
00154 nf = (char*)malloc((l + 1) * sizeof(char));
00155 strncpy(nf, file, l);
00156 nf[l] = '\0';
00157 file = nf;
00158 to_free = nf;
00159
00160
00161 ++pos;
00162 if ( (posb = strchr(pos, ')')) ) {
00163 l = (size_t)(posb - pos);
00164 nf = (char*)malloc((l + 1) * sizeof(char));
00165 strncpy(nf, pos, l);
00166 nf[l] = '\0';
00167 a_uri << nf << ';';
00168 free( nf );
00169 }
00170 }
00171 }
00172
00173 if (!file || file[0] == '\0') {
00174 con << tOutput( "$resource_no_filename" );
00175 return (tString) NULL;
00176 }
00177 if (file[0] == '/' || file[0] == '\\') {
00178 con << tOutput( "$resource_abs_path" );
00179 return (tString) NULL;
00180 }
00181 savepath = tDirectories::Resource().GetWritePath(file);
00182 if (savepath == "") {
00183 con << tOutput( "$resource_no_writepath" );
00184 return (tString) NULL;
00185 }
00186
00187
00188 filepath = tDirectories::Resource().GetReadPath(file);
00189
00190 if (filepath != "")
00191 {
00192 if ( NULL != to_free )
00193 free( to_free );
00194 return filepath;
00195 }
00196
00197
00198 if (uri && strcmp("0", uri))
00199 a_uri << uri << ';';
00200
00201
00202 if ( AccessRepoServer().Len() > 2 )
00203 a_uri << AccessRepoServer() << file << ';';
00204
00205 if ( AccessRepoClient().Len() > 2 && AccessRepoClient() != AccessRepoServer() )
00206 a_uri << AccessRepoClient() << file << ';';
00207
00208 con << tOutput( "$resource_not_cached", file );
00209
00210 rv = myFetch((const char *)a_uri, file, (const char *)savepath);
00211
00212 if ( NULL != to_free )
00213 free( to_free );
00214
00215 if (rv)
00216 return (tString) NULL;
00217 return savepath;
00218 }
00219
00220 FILE* tResourceManager::openResource(const char *file, const char *uri) {
00221 tString filepath;
00222 filepath = locateResource(file, uri);
00223 if ( filepath.Len() <= 1 )
00224 return NULL;
00225 return fopen((const char *)filepath, "r");
00226 }
00227
00228 static void RInclude(std::istream& s)
00229 {
00230 tString file;
00231 s >> file;
00232
00233 tString rclcl = tResourceManager::locateResource(NULL, file);
00234 if ( rclcl ) {
00235 std::ifstream rc(rclcl);
00236 tConfItemBase::LoadAll(rc);
00237 return;
00238 }
00239
00240 con << tOutput( "$config_rinclude_not_found", file );
00241 }
00242
00243 static tConfItemFunc s_RInclude("RINCLUDE", &RInclude);
00244
00245 static bool st_checkAuthor(tString const &Author) {
00246 if(Author.empty() || Author[0] < 'A' || Author[0] > 'z' || Author[0] > 'Z' && Author[0] < 'a' || Author.find('/') != tString::npos) {
00247 tERR_WARN("Resource authors must start with a letter and may not contain slashes");
00248 return false;
00249 }
00250 return true;
00251 }
00252 static bool st_checkCategory(tString const &Category) {
00253 if(Category[0] == '/' || *Category.rbegin() == '/' || Category.find("/.") != tString::npos) {
00254 tERR_WARN("Resource categories must not start or end with a slash or dot or contain the sequence \"./\".");
00255 return false;
00256 }
00257 return true;
00258 }
00259 static bool st_checkName(tString const &Name) {
00260 if(Name.empty() || Name[0] == '.' || Name.find_first_of("-/") != tString::npos) {
00261 tERR_WARN("Resource names must not start with a dot or contain slashes or minus signs");
00262 return false;
00263 }
00264 return true;
00265 }
00266 static bool st_checkExtension(tString const &Extension) {
00267 if(Extension.empty() || Extension.find_first_of("/.") != tString::npos) {
00268 tERR_WARN("Resource extensions must not contain slashes or dots");
00269 return false;
00270 }
00271 return true;
00272 }
00273 static bool st_checkType(tString const &Type) {
00274 if(Type.empty() || Type.find_first_of("/.") != tString::npos) {
00275 tERR_WARN("Resource types must not contain slashes or dots");
00276 return false;
00277 }
00278 return true;
00279 }
00280 static bool st_checkVersion(tString const &Version) {
00281 if(Version.empty() || Version.find('/') != tString::npos) {
00282 tERR_WARN("Resource versions must not contain slashes");
00283 return false;
00284 }
00285 return true;
00286 }
00287
00288 tResourcePath::tResourcePath(tString const &Author,
00289 tString const &Category,
00290 tString const &Name,
00291 tString const &Version,
00292 tString const &Type,
00293 tString const &Extension,
00294 tString const &URI) :
00295 m_Author (Author ),
00296 m_Category (Category ),
00297 m_Name (Name ),
00298 m_Version (Version ),
00299 m_Type (Type ),
00300 m_Extension(Extension),
00301 m_URI (URI ),
00302 m_Valid(false) {
00303 m_Path << Author << '/';
00304 if(!st_checkAuthor(Author)) return;
00305 if(!Category.empty()) {
00306 if(!st_checkCategory(Category)) return;
00307 m_Path << Category << '/';
00308 }
00309 if(!st_checkName(Name)) return;
00310 if(!st_checkExtension(Extension)) return;
00311 if(!st_checkType(Extension)) return;
00312 if(!st_checkVersion(Version)) return;
00313 m_Path << Name << '-' << Version << '.' << Type << '.' << Extension;
00314 if(!URI.empty()) {
00315 m_Path << '(' << URI << ')';
00316 }
00317 m_Valid = true;
00318 }
00319
00320 tResourcePath::tResourcePath(tString const &Path) : m_Path(Path), m_Valid(false) {
00321
00322 tString::size_type uridelim = m_Path.find('(');
00323 if(uridelim != tString::npos) {
00324
00325 if(*m_Path.rbegin() != ')') {
00326 tERR_WARN("Incomplete URI specification");
00327 return;
00328 }
00329 m_URI = m_Path.substr(uridelim + 1, m_Path.size() - uridelim - 2);
00330 --uridelim;
00331 } else {
00332 uridelim = m_Path.size() - 1;
00333 }
00334
00335 tString::size_type authordelim = Path.find('/');
00336 if(authordelim == tString::npos || authordelim >= uridelim) {
00337 tERR_WARN("Resource paths need to contain at least one slash");
00338 return;
00339 }
00340 m_Author = Path.substr(0, authordelim);
00341 if(!st_checkAuthor(m_Author)) return;
00342 tString::size_type categorydelim = Path.rfind('/', uridelim);
00343 if(categorydelim != authordelim) {
00344 m_Category = Path.substr(authordelim + 1, categorydelim - authordelim - 1);
00345 if(!st_checkCategory(m_Category)) return;
00346 }
00347 tString::size_type namedelim = Path.find('-', categorydelim);
00348 if(namedelim == tString::npos || namedelim >= uridelim) {
00349 tERR_WARN("Resource path is missing the version delimiter ('-')");
00350 return;
00351 }
00352 m_Name = Path.substr(categorydelim+1, namedelim - categorydelim - 1);
00353 if(!st_checkName(m_Name)) return;
00354
00355
00356
00357 tString::size_type extensiondelim = Path.rfind('.', uridelim);
00358 if(extensiondelim == tString::npos || extensiondelim <= namedelim || extensiondelim >= Path.size() - 1) {
00359 tERR_WARN("Resource path is missing the extension delimiter ('.')");
00360 }
00361 m_Extension = Path.substr(extensiondelim + 1, uridelim - extensiondelim);
00362 if(!st_checkExtension(m_Extension)) return;
00363 tString::size_type typedelim = Path.rfind('.', extensiondelim - 1);
00364 if(typedelim == tString::npos || typedelim <= namedelim) {
00365 tERR_WARN("Resource path is missing the type delimiter ('.')");
00366 }
00367 m_Type = Path.substr(typedelim + 1, extensiondelim - typedelim - 1);
00368 if(!st_checkType(m_Type)) return;
00369
00370
00371 m_Version = Path.substr(namedelim + 1, typedelim - namedelim - 1);
00372 if(!st_checkVersion(m_Version)) return;
00373 m_Valid=true;
00374 }
00375
00376 bool tResourcePath::operator==(tResourcePath const &other) const {
00377 return m_Author == other.m_Author &&
00378 m_Category == other.m_Category &&
00379 m_Name == other.m_Name &&
00380 m_Version == other.m_Version &&
00381 m_Type == other.m_Type &&
00382 m_Extension == other.m_Extension;
00383 }
00384
00385
00386 bool tResourcePath::operator!=(tResourcePath const &other) const {
00387 return m_Author != other.m_Author ||
00388 m_Category != other.m_Category ||
00389 m_Name != other.m_Name ||
00390 m_Version != other.m_Version ||
00391 m_Type != other.m_Type ||
00392 m_Extension != other.m_Extension;
00393 }