]> git.mxchange.org Git - simgear.git/blob - simgear/package/Install.cxx
b471d965259bf8efe3c8def993426005dfaf41d8
[simgear.git] / simgear / package / Install.cxx
1
2
3 #include <simgear/package/Install.hxx>
4
5 #include <boost/foreach.hpp>
6 #include <fstream>
7
8 #include <simgear/package/unzip.h>
9 #include <simgear/package/md5.h>
10
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>
18
19 extern "C" {
20     void fill_memory_filefunc (zlib_filefunc_def*);
21 }
22
23 namespace simgear {
24     
25 namespace pkg {
26
27 class Install::PackageArchiveDownloader : public HTTP::Request
28 {
29 public:
30     PackageArchiveDownloader(Install* aOwner) :
31         HTTP::Request("" /* dummy URL */),
32         m_owner(aOwner)
33     {
34         m_urls = m_owner->package()->downloadUrls();
35         if (m_urls.empty()) {
36             throw sg_exception("no package download URLs");
37         }
38         
39         // TODO randomise order of m_urls
40         
41         m_extractPath = aOwner->path().dir();
42         m_extractPath.append("_DOWNLOAD"); // add some temporary value
43         
44     }
45     
46 protected:
47     virtual std::string url() const
48     {
49         return m_urls.front();
50     }
51     
52     virtual void responseHeadersComplete()
53     {
54         std::cout << "starting download of " << m_owner->package()->id() << " from "
55             << url() << std::endl;
56         Dir d(m_extractPath);
57         d.create(0755);        
58         
59         memset(&m_md5, 0, sizeof(MD5_CTX));
60         MD5Init(&m_md5);
61     }
62     
63     virtual void gotBodyData(const char* s, int n)
64     {
65         m_buffer += std::string(s, n);
66         MD5Update(&m_md5, (unsigned char*) s, n);
67     }
68     
69     virtual void responseComplete()
70     {
71         if (responseCode() != 200) {
72             SG_LOG(SG_GENERAL, SG_ALERT, "download failure");
73             doFailure();
74             return;
75         }
76
77         MD5Final(&m_md5);
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];
84         }
85         
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());
91             doFailure();
92             return;
93         } else {
94             std::cout << "MD5 checksum is ok" << std::endl;
95         }
96         
97         if (!extractUnzip()) {
98             SG_LOG(SG_GENERAL, SG_WARN, "zip extraction failed");
99             doFailure();
100             return;
101         }
102                   
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 */);
107         }
108         
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();
113     }
114     
115 private:
116
117     void extractCurrentFile(unzFile zip, char* buffer, size_t bufferSize)
118     {
119         unz_file_info fileInfo;
120         unzGetCurrentFileInfo(zip, &fileInfo, 
121             buffer, bufferSize, 
122             NULL, 0,  /* extra field */
123             NULL, 0 /* comment field */);
124             
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);
130         }
131         
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
136             return;
137         }
138         
139         int result = unzOpenCurrentFile(zip);
140         if (result != UNZ_OK) {
141             throw sg_io_exception("opening current zip file failed", sg_location(name));
142         }
143             
144         std::ofstream outFile;
145         bool eof = false;
146         SGPath path(m_extractPath);
147         path.append(name);
148                         
149     // create enclosing directory heirarchy as required
150         Dir parentDir(path.dir());
151         if (!parentDir.exists()) {
152             bool ok = parentDir.create(0755);
153             if (!ok) {
154                 throw sg_io_exception("failed to create directory heirarchy for extraction", path.c_str());
155             }
156         }
157             
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());
161         }
162             
163         while (!eof) {
164             int bytes = unzReadCurrentFile(zip, buffer, bufferSize);
165             if (bytes < 0) {
166                 throw sg_io_exception("unzip failure reading curent archive", sg_location(name));
167             } else if (bytes == 0) {
168                 eof = true;
169             } else {
170                 outFile.write(buffer, bytes);
171             }
172         }
173             
174         outFile.close();
175         unzCloseCurrentFile(zip);
176     }
177     
178     bool extractUnzip()
179     {
180         bool result = true;
181         zlib_filefunc_def memoryAccessFuncs;
182         fill_memory_filefunc(&memoryAccessFuncs);
183            
184         char bufferName[128];
185         snprintf(bufferName, 128, "%p+%lx", m_buffer.data(), m_buffer.size());
186         unzFile zip = unzOpen2(bufferName, &memoryAccessFuncs);
187         
188         const size_t BUFFER_SIZE = 32 * 1024;
189         void* buf = malloc(BUFFER_SIZE);
190         
191         try {
192             int result = unzGoToFirstFile(zip);
193             if (result != UNZ_OK) {
194                 throw sg_exception("failed to go to first file in archive");
195             }
196             
197             while (true) {
198                 extractCurrentFile(zip, (char*) buf, BUFFER_SIZE);
199                 result = unzGoToNextFile(zip);
200                 if (result == UNZ_END_OF_LIST_OF_FILE) {
201                     break;
202                 } else if (result != UNZ_OK) {
203                     throw sg_io_exception("failed to go to next file in the archive");
204                 }
205             }
206         } catch (sg_exception& e) {
207             result = false;
208         }
209         
210         free(buf);
211         unzClose(zip);
212         return result;
213     }
214         
215     void doFailure()
216     {
217         Dir dir(m_extractPath);
218         dir.remove(true /* recursive */);
219         if (m_urls.size() == 1) {
220             
221             return;
222         }
223         
224         m_urls.erase(m_urls.begin()); // pop first URL
225     }
226     
227     Install* m_owner;
228     string_list m_urls;
229     MD5_CTX m_md5;
230     std::string m_buffer;
231     SGPath m_extractPath;
232 };
233
234 ////////////////////////////////////////////////////////////////////
235     
236 Install::Install(Package* aPkg, const SGPath& aPath) :
237     m_package(aPkg),
238     m_path(aPath),
239     m_download(NULL)
240 {
241     parseRevision();
242 }
243
244 Install* Install::createFromPath(const SGPath& aPath, Catalog* aCat)
245 {
246     std::string id = aPath.file();
247     Package* pkg = aCat->getPackageById(id);
248     if (!pkg)
249         throw sg_exception("no package with id:" + id);
250     
251     return new Install(pkg, aPath);
252 }
253
254 void Install::parseRevision()
255 {
256     SGPath revisionFile = m_path;
257     revisionFile.append(".revision");
258     if (!revisionFile.exists()) {
259         m_revision = 0;
260         return;
261     }
262     
263     std::ifstream f(revisionFile.c_str(), std::ios::in);
264     f >> m_revision;
265 }
266
267 void Install::writeRevisionFile()
268 {
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;
273 }
274
275 bool Install::hasUpdate() const
276 {
277     return m_package->revision() > m_revision;
278 }
279
280 void Install::startUpdate()
281 {
282     if (m_download) {
283         return; // already active
284     }
285     
286     m_download = new PackageArchiveDownloader(this);
287     m_package->catalog()->root()->getHTTPClient()->makeRequest(m_download);
288 }
289
290 void Install::uninstall()
291 {
292     Dir d(m_path);
293     d.remove(true);
294     delete this;
295 }
296
297 } // of namespace pkg
298
299 } // of namespace simgear