3 #include <simgear/package/Install.hxx>
5 #include <boost/foreach.hpp>
8 #include <simgear/package/unzip.h>
9 #include <simgear/package/md5.h>
11 #include <simgear/structure/exception.hxx>
12 #include <simgear/package/Catalog.hxx>
13 #include <simgear/package/Package.hxx>
14 #include <simgear/package/Root.hxx>
15 #include <simgear/io/HTTPRequest.hxx>
16 #include <simgear/io/HTTPClient.hxx>
17 #include <simgear/misc/sg_dir.hxx>
20 void fill_memory_filefunc (zlib_filefunc_def*);
27 class Install::PackageArchiveDownloader : public HTTP::Request
30 PackageArchiveDownloader(Install* aOwner) :
31 HTTP::Request("" /* dummy URL */),
34 m_urls = m_owner->package()->downloadUrls();
36 throw sg_exception("no package download URLs");
39 // TODO randomise order of m_urls
41 m_extractPath = aOwner->path().dir();
42 m_extractPath.append("_DOWNLOAD"); // add some temporary value
47 virtual std::string url() const
49 return m_urls.front();
52 virtual void responseHeadersComplete()
54 std::cout << "starting download of " << m_owner->package()->id() << " from "
55 << url() << std::endl;
59 memset(&m_md5, 0, sizeof(MD5_CTX));
63 virtual void gotBodyData(const char* s, int n)
65 m_buffer += std::string(s, n);
66 MD5Update(&m_md5, (unsigned char*) s, n);
69 virtual void responseComplete()
71 if (responseCode() != 200) {
72 SG_LOG(SG_GENERAL, SG_ALERT, "download failure");
78 // convert final sum to hex
79 const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
80 std::stringstream hexMd5;
81 for (int i=0; i<16;++i) {
82 hexMd5 << hexChar[m_md5.digest[i] >> 4];
83 hexMd5 << hexChar[m_md5.digest[i] & 0x0f];
86 if (hexMd5.str() != m_owner->package()->md5()) {
87 SG_LOG(SG_GENERAL, SG_ALERT, "md5 verification failed:\n"
88 << "\t" << hexMd5.str() << "\n\t"
89 << m_owner->package()->md5() << "\n\t"
90 << "downloading from:" << url());
94 std::cout << "MD5 checksum is ok" << std::endl;
97 if (!extractUnzip()) {
98 SG_LOG(SG_GENERAL, SG_WARN, "zip extraction failed");
103 if (m_owner->path().exists()) {
104 //std::cout << "removing existing path" << std::endl;
105 Dir destDir(m_owner->path());
106 destDir.remove(true /* recursive */);
109 m_extractPath.append(m_owner->package()->id());
110 m_extractPath.rename(m_owner->path());
111 m_owner->m_revision = m_owner->package()->revision();
112 m_owner->writeRevisionFile();
117 void extractCurrentFile(unzFile zip, char* buffer, size_t bufferSize)
119 unz_file_info fileInfo;
120 unzGetCurrentFileInfo(zip, &fileInfo,
122 NULL, 0, /* extra field */
123 NULL, 0 /* comment field */);
125 std::string name(buffer);
126 // no absolute paths, no 'up' traversals
127 // we could also look for suspicious file extensions here (forbid .dll, .exe, .so)
128 if ((name[0] == '/') || (name.find("../") != std::string::npos) || (name.find("..\\") != std::string::npos)) {
129 throw sg_format_exception("Bad zip path", name);
132 if (fileInfo.uncompressed_size == 0) {
133 // assume it's a directory for now
134 // since we create parent directories when extracting
135 // a path, we're done here
139 int result = unzOpenCurrentFile(zip);
140 if (result != UNZ_OK) {
141 throw sg_io_exception("opening current zip file failed", sg_location(name));
144 std::ofstream outFile;
146 SGPath path(m_extractPath);
149 // create enclosing directory heirarchy as required
150 Dir parentDir(path.dir());
151 if (!parentDir.exists()) {
152 bool ok = parentDir.create(0755);
154 throw sg_io_exception("failed to create directory heirarchy for extraction", path.c_str());
158 outFile.open(path.c_str(), std::ios::binary | std::ios::trunc | std::ios::out);
159 if (outFile.fail()) {
160 throw sg_io_exception("failed to open output file for writing", path.c_str());
164 int bytes = unzReadCurrentFile(zip, buffer, bufferSize);
166 throw sg_io_exception("unzip failure reading curent archive", sg_location(name));
167 } else if (bytes == 0) {
170 outFile.write(buffer, bytes);
175 unzCloseCurrentFile(zip);
181 zlib_filefunc_def memoryAccessFuncs;
182 fill_memory_filefunc(&memoryAccessFuncs);
184 char bufferName[128];
185 snprintf(bufferName, 128, "%p+%lx", m_buffer.data(), m_buffer.size());
186 unzFile zip = unzOpen2(bufferName, &memoryAccessFuncs);
188 const size_t BUFFER_SIZE = 32 * 1024;
189 void* buf = malloc(BUFFER_SIZE);
192 int result = unzGoToFirstFile(zip);
193 if (result != UNZ_OK) {
194 throw sg_exception("failed to go to first file in archive");
198 extractCurrentFile(zip, (char*) buf, BUFFER_SIZE);
199 result = unzGoToNextFile(zip);
200 if (result == UNZ_END_OF_LIST_OF_FILE) {
202 } else if (result != UNZ_OK) {
203 throw sg_io_exception("failed to go to next file in the archive");
206 } catch (sg_exception& e) {
217 Dir dir(m_extractPath);
218 dir.remove(true /* recursive */);
219 if (m_urls.size() == 1) {
224 m_urls.erase(m_urls.begin()); // pop first URL
230 std::string m_buffer;
231 SGPath m_extractPath;
234 ////////////////////////////////////////////////////////////////////
236 Install::Install(Package* aPkg, const SGPath& aPath) :
244 Install* Install::createFromPath(const SGPath& aPath, Catalog* aCat)
246 std::string id = aPath.file();
247 Package* pkg = aCat->getPackageById(id);
249 throw sg_exception("no package with id:" + id);
251 return new Install(pkg, aPath);
254 void Install::parseRevision()
256 SGPath revisionFile = m_path;
257 revisionFile.append(".revision");
258 if (!revisionFile.exists()) {
263 std::ifstream f(revisionFile.c_str(), std::ios::in);
267 void Install::writeRevisionFile()
269 SGPath revisionFile = m_path;
270 revisionFile.append(".revision");
271 std::ofstream f(revisionFile.c_str(), std::ios::out | std::ios::trunc);
272 f << m_revision << std::endl;
275 bool Install::hasUpdate() const
277 return m_package->revision() > m_revision;
280 void Install::startUpdate()
283 return; // already active
286 m_download = new PackageArchiveDownloader(this);
287 m_package->catalog()->root()->getHTTPClient()->makeRequest(m_download);
290 void Install::uninstall()
297 } // of namespace pkg
299 } // of namespace simgear