1 // Copyright 2008, Google Inc. All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
5 //
6 //  1. Redistributions of source code must retain the above copyright notice,
7 //     this list of conditions and the following disclaimer.
8 //  2. Redistributions in binary form must reproduce the above copyright notice,
9 //     this list of conditions and the following disclaimer in the documentation
10 //     and/or other materials provided with the distribution.
11 //  3. Neither the name of Google Inc. nor the names of its contributors may be
12 //     used to endorse or promote products derived from this software without
13 //     specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 // The file contains the implementation of the KmzFile class methods.
27 
28 #include "kml/engine/kmz_file.h"
29 #include <cstring>
30 #include <set>
31 #include "boost/scoped_ptr.hpp"
32 #include "kml/base/file.h"
33 #include "kml/base/string_util.h"
34 #include "kml/base/zip_file.h"
35 #include "kml/engine/get_links.h"
36 #include "kml/engine/href.h"
37 #include "kml/engine/kml_uri.h"
38 
39 #include <new>
40 
41 using kmlbase::File;
42 using kmlbase::StringVector;
43 using kmlbase::ZipFile;
44 
45 namespace kmlengine {
46 
47 // This is the default name for writing a KML file to a new archive. The
48 // default file for reading is simply the first file in the table of contents
49 // that ends with ".kml".
50 const char kDefaultKmlFilename[] = "doc.kml";
51 
KmzFile(ZipFile * zip_file)52 KmzFile::KmzFile(ZipFile* zip_file) : zip_file_(zip_file) {}
53 
~KmzFile()54 KmzFile::~KmzFile() {}
55 
56 // Static.
OpenFromFile(const char * kmz_filename)57 KmzFile* KmzFile::OpenFromFile(const char* kmz_filename) {
58   if (ZipFile* zipfile = ZipFile::OpenFromFile(kmz_filename)) {
59     return new(std::nothrow) KmzFile(zipfile);
60 
61   }
62   return NULL;
63 }
64 
65 // Static.
OpenFromString(const string & kmz_data)66 KmzFile* KmzFile::OpenFromString(const string& kmz_data) {
67   if (ZipFile* zipfile = ZipFile::OpenFromString(kmz_data)) {
68     return new KmzFile(zipfile);
69   }
70   return NULL;
71 }
72 
73 // Static.
IsKmz(const string & kmz_data)74 bool KmzFile::IsKmz(const string& kmz_data) {
75   return ZipFile::IsZipData(kmz_data);
76 }
77 
78 
set_max_uncompressed_file_size(unsigned int i)79 void KmzFile::set_max_uncompressed_file_size(unsigned int i) {
80   zip_file_->set_max_uncompressed_file_size(i);
81 }
82 
get_max_uncompressed_file_size()83 unsigned int KmzFile::get_max_uncompressed_file_size() {
84   return zip_file_->get_max_uncompressed_file_size();
85 }
86 
87 
ReadKmlAndGetPath(string * output,string * kml_name) const88 bool KmzFile::ReadKmlAndGetPath(string* output,
89                                 string* kml_name) const {
90   if (!output) {
91     return false;
92   }
93   string default_kml;
94   if (!zip_file_->FindFirstOf(".kml", &default_kml)) {
95     return false;
96   }
97   if (!zip_file_->GetEntry(default_kml, output)) {
98     return false;
99   }
100   if (kml_name) {
101     *kml_name = default_kml;
102   }
103   return true;
104 }
105 
ReadKml(string * output) const106 bool KmzFile::ReadKml(string* output) const {
107   return ReadKmlAndGetPath(output, NULL);
108 }
109 
ReadFile(const char * path_in_kmz,string * output) const110 bool KmzFile::ReadFile(const char* path_in_kmz, string* output) const {
111   return zip_file_->GetEntry(path_in_kmz, output);
112 }
113 
List(std::vector<string> * subfiles)114 bool KmzFile::List(std::vector<string>* subfiles) {
115   return zip_file_->GetToc(subfiles);
116 }
117 
SaveToString(string * kmz_bytes)118 bool KmzFile::SaveToString(string* kmz_bytes) {
119   if (!kmz_bytes) {
120     return false;
121   }
122   *kmz_bytes = zip_file_->get_data();
123   return true;
124 }
125 
126 // Static.
Create(const char * kmz_filepath)127 KmzFile* KmzFile::Create(const char* kmz_filepath) {
128   ZipFile* zipfile = ZipFile::Create(kmz_filepath);
129   if (!zipfile) {
130     return NULL;
131   }
132   return new KmzFile(zipfile);
133 }
134 
AddFile(const string & data,const string & path_in_kmz)135 bool KmzFile::AddFile(const string& data, const string& path_in_kmz) {
136   return zip_file_->AddEntry(data, path_in_kmz);
137 }
138 
139 // TODO: the implementation of this function really belongs in base/zip_file.
AddFileList(const string & base_url,const StringVector & file_paths)140 size_t KmzFile::AddFileList(const string& base_url,
141                             const StringVector& file_paths) {
142   size_t error_count = 0;
143   // We remember all stored resources so we can eliminate duplicates.
144   std::set<string> stored_hrefs;
145 
146   StringVector::const_iterator itr;
147   for (itr = file_paths.begin(); itr != file_paths.end(); ++itr) {
148     // Drop the fragment if any to get the stem of the filename.
149     Href href(*itr);
150     if (href.has_fragment()) {
151       href.clear_fragment();
152     }
153 
154     // Normalize the href.
155     string normalized_href;
156     if (!NormalizeHref(href.get_path(), &normalized_href)) {
157       error_count++;
158       continue;
159     }
160 
161     // If the normalized_href points above the base_url, we consider it
162     // invalid.
163     if (normalized_href.substr(0, 2) == "..") {
164       error_count++;
165       continue;
166     }
167 
168     // Detect duplicate resources and skip if found.
169     if (stored_hrefs.find(normalized_href) != stored_hrefs.end()) {
170       continue;  // Not an error.
171     }
172     stored_hrefs.insert(normalized_href);
173 
174     // Try to read the file pointed to by base_url and the normalized href.
175     string relative_path = File::JoinPaths(base_url, normalized_href);
176     string file_data;
177     if (!kmlbase::File::ReadFileToString(relative_path, &file_data)) {
178       error_count++;
179       continue;
180     }
181 
182     // Add the file to the KMZ archive.
183     if (!AddFile(file_data, normalized_href)) {
184       error_count++;
185       continue;
186     }
187   }
188   return error_count;
189 }
190 
191 // Static.
WriteKmz(const char * kmz_filepath,const string & kml)192 bool KmzFile::WriteKmz(const char* kmz_filepath, const string& kml) {
193   boost::scoped_ptr<KmzFile> kmz(KmzFile::Create(kmz_filepath));
194   if (!kmz.get()) {
195     return false;
196   }
197   if (!kmz->AddFile(kml, kDefaultKmlFilename)) {
198     return false;
199   }
200   return kmlbase::File::Exists(kmz_filepath);
201 }
202 
203 // Static.
CreateFromKmlFilepath(const string & kml_filepath,const string & kmz_filepath)204 bool KmzFile::CreateFromKmlFilepath(const string& kml_filepath,
205                                     const string& kmz_filepath) {
206   if (kmz_filepath.empty() || kml_filepath.empty()) {
207     return false;
208   }
209   string kml_data;
210   if (!kmlbase::File::ReadFileToString(kml_filepath, &kml_data)) {
211     return false;
212   }
213 
214   string base_dir;
215   kmlbase::File::SplitFilePath(kml_filepath, &base_dir, NULL);
216 
217   KmlFilePtr kml_file =
218     KmlFile::CreateFromStringWithUrl(kml_data, base_dir, NULL);
219 
220   return CreateFromKmlFile(kml_file, kmz_filepath);
221 }
222 
223 // Static.
CreateFromElement(const kmldom::ElementPtr & element,const string & base_url,const string & kmz_filepath)224 bool KmzFile::CreateFromElement(const kmldom::ElementPtr& element,
225                                 const string& base_url,
226                                 const string& kmz_filepath) {
227   if (kmz_filepath.empty()) {
228     return false;
229   }
230   KmzFilePtr kmz_file = Create(kmz_filepath.c_str());
231   if (!kmz_file) {
232     return false;
233   }
234   const string kml_data = kmldom::SerializePretty(element);
235   // First add the KML file. This is the file opened by default by a client
236   // from a KMZ archive.
237   kmz_file->AddFile(kml_data, kDefaultKmlFilename);
238 
239   // Next gather the local references and add them.
240   StringVector file_paths;
241   if (GetRelativeLinks(kml_data, &file_paths)) {
242     kmz_file->AddFileList(base_url, file_paths);
243   }
244 
245   return kmlbase::File::Exists(kmz_filepath);
246 }
247 
248 // Static.
CreateFromKmlFile(const KmlFilePtr & kml_file,const string & kmz_filepath)249 bool KmzFile::CreateFromKmlFile(const KmlFilePtr& kml_file,
250                                 const string& kmz_filepath) {
251   return KmzFile::CreateFromElement(
252       kml_file->get_root(), kml_file->get_url(), kmz_filepath);
253 }
254 
255 }  // end namespace kmlengine
256