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