/* * XPilot NG, a multiplayer space war game. * * Copyright (C) 2001 Juha Lindström * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "xpclient.h" /* kps - you should be able to change this without a recompile */ #define DATADIR ".xpilot_data" #define COPY_BUF_SIZE 8192 typedef struct { char *protocol; char *host; int port; const char *path; char *query; } URL; static int Mapdata_extract(const char *name); static int Mapdata_download(const URL *url, const char *filePath); static int Url_parse(const char *urlstr, URL *url); static void Url_free_parsed(URL *url); static bool setup_done = false; int Mapdata_setup(const char *urlstr) { URL url; const char *name, *dir = NULL; char path[1024], buf[1024], *ptr; int rv = false; if (setup_done) return true; memset(path, 0, sizeof(path)); memset(buf, 0, sizeof(buf)); if (!Url_parse(urlstr, &url)) { warn("malformed URL: %s", urlstr); return false; } for (name = url.path + strlen(url.path) - 1; name > url.path; name--) { if (*(name - 1) == '/') break; } if (*name == '\0') { warn("no file name in URL: %s", urlstr); goto end; } if (realTexturePath != NULL) { for (dir = strtok(realTexturePath, ":"); dir; dir = strtok(NULL, ":")) if (access(dir, R_OK | W_OK | X_OK) == 0) break; } if (dir == NULL) { /* realTexturePath hasn't got a directory with proper access rights */ /* so lets create one into users home dir */ char *home = getenv("HOME"); if (home == NULL) { error("couldn't access any dir in %s and HOME is unset", path); goto end; } if (strlen(home) == 0) sprintf(buf, "%s", DATADIR); else if (home[strlen(home) - 1] == PATHNAME_SEP) sprintf(buf, "%s%s", home, DATADIR); else sprintf(buf, "%s%c%s", home, PATHNAME_SEP, DATADIR); if (access(buf, F_OK) != 0) { if (mkdir(buf, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { error("failed to create directory %s", dir); goto end; } } dir = buf; } if (strlen(dir) == 0) sprintf(path, "%s", name); else if (dir[strlen(dir) - 1] == PATHNAME_SEP) sprintf(path, "%s%s", dir, name); else sprintf(path, "%s%c%s", dir, PATHNAME_SEP, name); if (strrchr(path, '.') == NULL) { error("no extension in file name %s.", name); goto end; } /* temporarily make path point to the directory name */ ptr = strrchr(path, '.'); *ptr = '\0'; /* add this new texture directory to texturePath */ if (realTexturePath == NULL) { realTexturePath = strdup(path); } else { char *temp = XMALLOC(char, strlen(realTexturePath) + strlen(path) + 2); if (temp == NULL) { error("not enough memory to new realTexturePath"); goto end; } sprintf(temp, "%s:%s", realTexturePath, path); free(realTexturePath); realTexturePath = temp; } if (access(path, F_OK) == 0) { warn("Required bitmaps have already been downloaded."); rv = true; goto end; } /* reset path so that it points to the package file name */ *ptr = '.'; warn("Downloading map data from %s to %s.", urlstr, path); if (!Mapdata_download(&url, path)) { warn("downloading map data failed"); goto end; } if (!Mapdata_extract(path)) { warn("extracting map data failed"); goto end; } rv = true; setup_done = true; end: Url_free_parsed(&url); return rv; } static int Mapdata_extract(const char *name) { gzFile in; FILE *out; int retval; size_t rlen, wlen; char dir[256], buf[COPY_BUF_SIZE], fname[256], *ptr; long int size; int count, i; strlcpy(dir, name, sizeof dir); ptr = strrchr(dir, '.'); if (ptr == NULL) { error("file name has no extension %s", dir); return 0; } *ptr = '\0'; if (mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) { error("failed to create directory %s", dir); return 0; } if ((in = gzopen(name, "rb")) == NULL) { error("failed to open %s for reading", name); return 0; } if (gzgets(in, buf, COPY_BUF_SIZE) == Z_NULL) { error("failed to read header from %s", name); gzclose(in); return 0; } if (sscanf(buf, "XPD %d\n", &count) != 1) { error("invalid header in %s", name); gzclose(in); return 0; } for (i = 0; i < count; i++) { if (gzgets(in, buf, COPY_BUF_SIZE) == Z_NULL) { error("failed to read file info from %s", name); gzclose(in); return 0; } sprintf(fname, "%s%c", dir, PATHNAME_SEP); if (sscanf(buf, "%s\n%ld\n", fname + strlen(dir) + 1, &size) != 2) { error("failed to parse file info %s", buf); gzclose(in); return 0; } /* security check */ if (strchr(fname + strlen(dir) + 1, PATHNAME_SEP) != NULL) { error("file name %s is illegal", fname); gzclose(in); return 0; } warn("Extracting %s (%ld)", fname, size); if ((out = fopen(fname, "wb")) == NULL) { error("failed to open %s for writing", buf); gzclose(in); return 0; } while (size > 0) { retval = gzread(in, buf, MIN(COPY_BUF_SIZE, (unsigned)size)); if (retval == -1) { error("error when reading %s", name); gzclose(in); fclose(out); return 0; } if (retval == 0) { error("unexpected end of file %s", name); gzclose(in); fclose(out); return 0; } rlen = retval; wlen = fwrite(buf, 1, rlen, out); if (wlen != rlen) { error("failed to write to %s", fname); gzclose(in); fclose(out); return 0; } size -= rlen; } fclose(out); } gzclose(in); return 1; } static int Mapdata_download(const URL *url, const char *filePath) { char buf[1024]; int rv, header, c, len, i; sock_t s; FILE *f = NULL; size_t n; if (strncmp("http", url->protocol, 4) != 0) { error("unsupported protocol %s", url->protocol); return false; } if (sock_open_tcp(&s) == SOCK_IS_ERROR) { error("failed to create a socket"); return false; } if (sock_connect(&s, url->host, url->port) == SOCK_IS_ERROR) { error("couldn't connect to download address"); sock_close(&s); return false; } if (url->query) { if (snprintf(buf, sizeof buf, "GET %s?%s HTTP/1.1\r\nHost: %s:%d\r\nConnection: close\r\n\r\n", url->path, url->query, url->host, url->port) == -1) { error("too long URL"); sock_close(&s); return false; } } else { if (snprintf(buf, sizeof buf, "GET %s HTTP/1.1\r\nHost: %s:%d\r\nConnection: close\r\n\r\n", url->path, url->host, url->port) == -1) { error("too long URL"); sock_close(&s); return false; } } if (sock_write(&s, buf, (int)strlen(buf)) == -1) { error("socket write failed"); sock_close(&s); return false; } header = 2; c = 0; for(;;) { len = 0; while (len < 100) { if ((i = sock_read(&s, buf + len, sizeof(buf) - len)) == -1) { error("socket read failed"); rv = false; goto done; } if (i == 0) break; len += i; } if (len == 0) { rv = !header; break; } if (header == 2) { if (strncmp(buf, "HTTP", 4)) { rv = false; break; } i = 0; while (buf[i] != ' ') { i++; if (i >= len - 1) { rv = false; goto done; } } i++; if (buf[i] != '2') { /* HTTP status code starts with 2 */ rv = false; break; } header = 1; } printf("#"); fflush(stdout); if (header) { for (i = 0; i < len; i++) { if (c % 2 == 0 && buf[i] == '\r') c++; else if (c % 2 == 1 && buf[i] == '\n') c++; else c = 0; if (c == 4) { header = 0; if ((f = fopen(filePath, "wb")) == NULL) { error("failed to open %s", filePath); rv = false; goto done; } if (i < len - 1) { n = len - i - 1; memmove(buf, buf + i + 1, n); len = len - i - 1; } else if (i == len - 1) { len = 0; } } } } if (!header && len) { n = len; if (fwrite(buf, 1, n, f) < n) { error("file write failed"); rv = false; break; } } } done: printf("\n"); if (f) if (fclose(f) != 0) error("Error closing texture file %s", filePath); sock_close(&s); return rv; } static int Url_parse(const char *urlstr, URL *url) { int len, i, beg, doPort; char *buf; memset(url, 0, sizeof(URL)); url->port = 80; url->path = "/"; len = strlen(urlstr); buf = strdup(urlstr); if (buf == NULL) { error("no memory for URL"); return false; } for (i = 0; i < len; i++) { if (buf[i] == ':') { buf[i] = '\0'; url->protocol = buf; break; } } beg = i + 3; if (beg >= len || buf[i + 1] != '/' || buf[i + 2] != '/') { free(buf); return false; } doPort = 0; for (i = beg; i < len; i++) { if (buf[i] == ':' || buf[i] == '/') { if (buf[i] == ':') doPort = 1; buf[i] = '\0'; break; } } url->host = buf + beg; beg = i + 1; if (beg >= len) return true; if (doPort) { for (i = beg; i < len; i++) { if (buf[i] == '/') { buf[i] = '\0'; break; } } url->port = atoi(buf + beg); /* error detection should be added */ beg = i + 1; if (beg >= len) return true; } /* make space for / in the beginning of path */ memmove(url->host - 1, url->host, strlen(url->host) + 1); url->host--; buf[beg - 1] = '/'; for (i = beg; i < len; i++) { if (buf[i] == '?') { buf[i] = '\0'; break; } } url->path = buf + beg - 1; beg = i + 1; if (beg >= len) return true; url->query = buf + beg; return true; } static void Url_free_parsed(URL *url) { free(url->protocol); }