]> git.mxchange.org Git - simgear.git/blob - simgear/package/Install.cxx
utf8ToLatin1: return original instead of crashing on non-UTF-8 input
[simgear.git] / simgear / package / Install.cxx
1 // Copyright (C) 2013  James Turner - zakalawe@mac.com
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 //
17
18 #include <simgear/package/Install.hxx>
19
20 #include <boost/foreach.hpp>
21 #include <fstream>
22
23 #include <simgear/package/unzip.h>
24 #include <simgear/package/md5.h>
25
26 #include <simgear/structure/exception.hxx>
27 #include <simgear/package/Catalog.hxx>
28 #include <simgear/package/Package.hxx>
29 #include <simgear/package/Root.hxx>
30 #include <simgear/io/HTTPRequest.hxx>
31 #include <simgear/io/HTTPClient.hxx>
32 #include <simgear/misc/sg_dir.hxx>
33
34 extern "C" {
35     void fill_memory_filefunc (zlib_filefunc_def*);
36 }
37
38 namespace simgear {
39     
40 namespace pkg {
41
42 class Install::PackageArchiveDownloader : public HTTP::Request
43 {
44 public:
45     PackageArchiveDownloader(InstallRef aOwner) :
46         HTTP::Request("" /* dummy URL */),
47         m_owner(aOwner)
48     {
49         m_urls = m_owner->package()->downloadUrls();
50         if (m_urls.empty()) {
51             throw sg_exception("no package download URLs");
52         }
53         
54         // TODO randomise order of m_urls
55         
56         m_extractPath = aOwner->path().dir();
57         m_extractPath.append("_DOWNLOAD"); // add some temporary value
58         
59     }
60     
61 protected:
62     virtual std::string url() const
63     {
64         return m_urls.front();
65     }
66     
67     virtual void responseHeadersComplete()
68     {
69         Dir d(m_extractPath);
70         d.create(0755);        
71         
72         memset(&m_md5, 0, sizeof(SG_MD5_CTX));
73         SG_MD5Init(&m_md5);
74     }
75     
76     virtual void gotBodyData(const char* s, int n)
77     {
78         m_buffer += std::string(s, n);
79         SG_MD5Update(&m_md5, (unsigned char*) s, n);
80         
81         m_owner->installProgress(m_buffer.size(), responseLength());
82     }
83     
84     virtual void onDone()
85     {
86         if (responseCode() != 200) {
87             SG_LOG(SG_GENERAL, SG_ALERT, "download failure");
88             doFailure(Delegate::FAIL_DOWNLOAD);
89             return;
90         }
91
92         unsigned char digest[MD5_DIGEST_LENGTH];
93         SG_MD5Final(digest, &m_md5);
94     // convert final sum to hex
95         const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
96         std::stringstream hexMd5;
97         for (int i=0; i<MD5_DIGEST_LENGTH;++i) {
98             hexMd5 << hexChar[digest[i] >> 4];
99             hexMd5 << hexChar[digest[i] & 0x0f];
100         }
101         
102         if (hexMd5.str() != m_owner->package()->md5()) {
103             SG_LOG(SG_GENERAL, SG_ALERT, "md5 verification failed:\n"
104                 << "\t" << hexMd5.str() << "\n\t"
105                 << m_owner->package()->md5() << "\n\t"
106                 << "downloading from:" << url());
107             doFailure(Delegate::FAIL_CHECKSUM);
108             return;
109         }
110         
111         if (!extractUnzip()) {
112             SG_LOG(SG_GENERAL, SG_WARN, "zip extraction failed");
113             doFailure(Delegate::FAIL_EXTRACT);
114             return;
115         }
116                   
117         if (m_owner->path().exists()) {
118             //std::cout << "removing existing path" << std::endl;
119             Dir destDir(m_owner->path());
120             destDir.remove(true /* recursive */);
121         }
122         
123         m_extractPath.append(m_owner->package()->id());
124         bool ok = m_extractPath.rename(m_owner->path());
125         if (!ok) {
126             doFailure(Delegate::FAIL_FILESYSTEM);
127             return;
128         }
129         
130         m_owner->m_revision = m_owner->package()->revision();
131         m_owner->writeRevisionFile();
132         m_owner->installResult(Delegate::FAIL_SUCCESS);
133     }
134     
135 private:
136
137     void extractCurrentFile(unzFile zip, char* buffer, size_t bufferSize)
138     {
139         unz_file_info fileInfo;
140         unzGetCurrentFileInfo(zip, &fileInfo, 
141             buffer, bufferSize, 
142             NULL, 0,  /* extra field */
143             NULL, 0 /* comment field */);
144             
145         std::string name(buffer);
146     // no absolute paths, no 'up' traversals
147     // we could also look for suspicious file extensions here (forbid .dll, .exe, .so)
148         if ((name[0] == '/') || (name.find("../") != std::string::npos) || (name.find("..\\") != std::string::npos)) {
149             throw sg_format_exception("Bad zip path", name);
150         }
151         
152         if (fileInfo.uncompressed_size == 0) {
153             // assume it's a directory for now
154             // since we create parent directories when extracting
155             // a path, we're done here
156             return;
157         }
158         
159         int result = unzOpenCurrentFile(zip);
160         if (result != UNZ_OK) {
161             throw sg_io_exception("opening current zip file failed", sg_location(name));
162         }
163             
164         std::ofstream outFile;
165         bool eof = false;
166         SGPath path(m_extractPath);
167         path.append(name);
168                         
169     // create enclosing directory heirarchy as required
170         Dir parentDir(path.dir());
171         if (!parentDir.exists()) {
172             bool ok = parentDir.create(0755);
173             if (!ok) {
174                 throw sg_io_exception("failed to create directory heirarchy for extraction", path.c_str());
175             }
176         }
177             
178         outFile.open(path.c_str(), std::ios::binary | std::ios::trunc | std::ios::out);
179         if (outFile.fail()) {
180             throw sg_io_exception("failed to open output file for writing", path.c_str());
181         }
182             
183         while (!eof) {
184             int bytes = unzReadCurrentFile(zip, buffer, bufferSize);
185             if (bytes < 0) {
186                 throw sg_io_exception("unzip failure reading curent archive", sg_location(name));
187             } else if (bytes == 0) {
188                 eof = true;
189             } else {
190                 outFile.write(buffer, bytes);
191             }
192         }
193             
194         outFile.close();
195         unzCloseCurrentFile(zip);
196     }
197     
198     bool extractUnzip()
199     {
200         bool result = true;
201         zlib_filefunc_def memoryAccessFuncs;
202         fill_memory_filefunc(&memoryAccessFuncs);
203            
204         char bufferName[128];
205         snprintf(bufferName, 128, "%p+%lx", m_buffer.data(), m_buffer.size());
206         unzFile zip = unzOpen2(bufferName, &memoryAccessFuncs);
207         
208         const size_t BUFFER_SIZE = 32 * 1024;
209         void* buf = malloc(BUFFER_SIZE);
210         
211         try {
212             int result = unzGoToFirstFile(zip);
213             if (result != UNZ_OK) {
214                 throw sg_exception("failed to go to first file in archive");
215             }
216             
217             while (true) {
218                 extractCurrentFile(zip, (char*) buf, BUFFER_SIZE);
219                 result = unzGoToNextFile(zip);
220                 if (result == UNZ_END_OF_LIST_OF_FILE) {
221                     break;
222                 } else if (result != UNZ_OK) {
223                     throw sg_io_exception("failed to go to next file in the archive");
224                 }
225             }
226         } catch (sg_exception& e) {
227             result = false;
228         }
229         
230         free(buf);
231         unzClose(zip);
232         return result;
233     }
234         
235     void doFailure(Delegate::FailureCode aReason)
236     {
237         Dir dir(m_extractPath);
238         dir.remove(true /* recursive */);
239
240         if (m_urls.size() == 1) {
241             std::cout << "failure:" << aReason << std::endl;
242             m_owner->installResult(aReason);
243             return;
244         }
245         
246         std::cout << "retrying download" << std::endl;
247         m_urls.erase(m_urls.begin()); // pop first URL
248     }
249     
250     InstallRef m_owner;
251     string_list m_urls;
252     SG_MD5_CTX m_md5;
253     std::string m_buffer;
254     SGPath m_extractPath;
255 };
256
257 ////////////////////////////////////////////////////////////////////
258     
259 Install::Install(PackageRef aPkg, const SGPath& aPath) :
260     m_package(aPkg),
261     m_path(aPath),
262     m_download(NULL)
263 {
264     parseRevision();
265 }
266
267 InstallRef Install::createFromPath(const SGPath& aPath, CatalogRef aCat)
268 {
269     std::string id = aPath.file();
270     PackageRef pkg = aCat->getPackageById(id);
271     if (!pkg)
272         throw sg_exception("no package with id:" + id);
273     
274     return new Install(pkg, aPath);
275 }
276
277 void Install::parseRevision()
278 {
279     SGPath revisionFile = m_path;
280     revisionFile.append(".revision");
281     if (!revisionFile.exists()) {
282         m_revision = 0;
283         return;
284     }
285     
286     std::ifstream f(revisionFile.c_str(), std::ios::in);
287     f >> m_revision;
288 }
289
290 void Install::writeRevisionFile()
291 {
292     SGPath revisionFile = m_path;
293     revisionFile.append(".revision");
294     std::ofstream f(revisionFile.c_str(), std::ios::out | std::ios::trunc);
295     f << m_revision << std::endl;
296 }
297
298 bool Install::hasUpdate() const
299 {
300     return m_package->revision() > m_revision;
301 }
302
303 void Install::startUpdate()
304 {
305     if (m_download) {
306         return; // already active
307     }
308     
309     m_download = new PackageArchiveDownloader(this);
310     m_package->catalog()->root()->makeHTTPRequest(m_download);
311     m_package->catalog()->root()->startInstall(this);
312 }
313
314 void Install::uninstall()
315 {
316     Dir d(m_path);
317     d.remove(true);
318     delete this;
319 }
320
321 void Install::installResult(Delegate::FailureCode aReason)
322 {
323     if (aReason == Delegate::FAIL_SUCCESS) {
324         m_package->catalog()->root()->finishInstall(this);
325     } else {
326         m_package->catalog()->root()->failedInstall(this, aReason);
327     }
328 }
329     
330 void Install::installProgress(unsigned int aBytes, unsigned int aTotal)
331 {
332     m_package->catalog()->root()->installProgress(this, aBytes, aTotal);
333 }
334
335     
336 } // of namespace pkg
337
338 } // of namespace simgear