]> git.donarmstrong.com Git - flightcrew.git/blob - src/FlightCrew/Framework/ValidateEpub.cpp
update changelog, target experimental
[flightcrew.git] / src / FlightCrew / Framework / ValidateEpub.cpp
1 /************************************************************************\r
2 **\r
3 **  Copyright (C) 2010  Strahinja Markovic\r
4 **\r
5 **  This file is part of FlightCrew.\r
6 **\r
7 **  FlightCrew is free software: you can redistribute it and/or modify\r
8 **  it under the terms of the GNU Lesser General Public License as published\r
9 **  by the Free Software Foundation, either version 3 of the License, or\r
10 **  (at your option) any later version.\r
11 **\r
12 **  FlightCrew is distributed in the hope that it will be useful,\r
13 **  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
14 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
15 **  GNU Lesser General Public License for more details.\r
16 **\r
17 **  You should have received a copy of the GNU Lesser General Public License\r
18 **  along with FlightCrew.  If not, see <http://www.gnu.org/licenses/>.\r
19 **\r
20 *************************************************************************/\r
21 \r
22 #include <stdafx.h>\r
23 #include <vector>\r
24 #include "Result.h"\r
25 #include <zipios++/zipextraction.h>\r
26 #include "Misc/TempFolder.h"\r
27 #include "Misc/Utilities.h"\r
28 #include "Validators/Xml/WellFormedXml.h"\r
29 #include <XmlUtils.h>\r
30 #include <XercesInit.h>\r
31 #include <FromXercesStringConverter.h>\r
32 #include <ToXercesStringConverter.h>\r
33 #include <xercesc/util/XMLUri.hpp>\r
34 #include "flightcrew_p.h"\r
35 #include "Validators/Ocf/ContainerSatisfiesSchema.h"\r
36 #include "Validators/Ocf/EncryptionSatisfiesSchema.h"\r
37 #include "Validators/Ocf/SignaturesSatisfiesSchema.h"\r
38 #include "Validators/Ocf/ContainerListsOpf.h"\r
39 #include "Validators/Ocf/ContainerListedOpfPresent.h"\r
40 #include "Validators/Ocf/MimetypeBytesValid.h"\r
41 #include "Validators/Xml/UsesUnicode.h"\r
42 \r
43 namespace FlightCrew\r
44 {\r
45 \r
46 const std::string CONTAINER_XML_NAMESPACE = "urn:oasis:names:tc:opendocument:xmlns:container";\r
47 \r
48 \r
49 std::vector< Result > ValidateMetaInf( const fs::path &path_to_meta_inf )\r
50 {\r
51     fs::path container_xml(  path_to_meta_inf / "container.xml"  );\r
52     fs::path signatures_xml( path_to_meta_inf / "signatures.xml" );\r
53     fs::path manifest_xml(   path_to_meta_inf / "manifest.xml"   );\r
54     fs::path rights_xml(     path_to_meta_inf / "rights.xml"     );\r
55     fs::path metadata_xml(   path_to_meta_inf / "metadata.xml"   );\r
56     fs::path encryption_xml( path_to_meta_inf / "encryption.xml" );\r
57 \r
58     std::vector< Result > results;\r
59 \r
60     if ( fs::exists( container_xml ) )\r
61     {\r
62         Util::Extend( results, ContainerSatisfiesSchema() .ValidateFile( container_xml ) );\r
63         Util::Extend( results, ContainerListsOpf()        .ValidateFile( container_xml ) );\r
64         Util::Extend( results, ContainerListedOpfPresent().ValidateFile( container_xml ) );\r
65     }\r
66 \r
67     else\r
68     {\r
69         results.push_back( Result( ERROR_EPUB_NO_CONTAINER_XML ) );\r
70     }\r
71     \r
72     if ( fs::exists( encryption_xml ) )\r
73      \r
74         Util::Extend( results, EncryptionSatisfiesSchema().ValidateFile( encryption_xml ) );\r
75 \r
76     if ( fs::exists( signatures_xml ) )\r
77     \r
78         Util::Extend( results, SignaturesSatisfiesSchema().ValidateFile( signatures_xml ) );\r
79 \r
80     std::vector< fs::path > all_files;\r
81     all_files.push_back( container_xml  );\r
82     all_files.push_back( signatures_xml );\r
83     all_files.push_back( encryption_xml );\r
84     all_files.push_back( manifest_xml   );\r
85     all_files.push_back( rights_xml     );\r
86     all_files.push_back( metadata_xml   );\r
87 \r
88     foreach( fs::path file, all_files )\r
89     {\r
90         if ( fs::exists( file ) )\r
91 \r
92             Util::Extend( results, UsesUnicode().ValidateFile( file ) );        \r
93     }\r
94 \r
95     // i starts at 3 because we already (implicitly) checked well-formedness  \r
96     // for container.xml, signatures.xml and encryption.xml so \r
97     // we don't want to check it again.\r
98     for ( uint i = 3; i < all_files.size(); ++i )\r
99     {\r
100         if ( fs::exists( all_files[ i ] ) )\r
101 \r
102             Util::Extend( results, WellFormedXml().ValidateFile( all_files[ i ] ) );        \r
103     }    \r
104 \r
105     // There are some possible duplicates\r
106     Util::RemoveDuplicates( results );\r
107     return results;\r
108 }\r
109 \r
110 \r
111 fs::path GetRelativePathToNcx( const xc::DOMDocument &opf )\r
112 {\r
113     std::vector< xc::DOMElement* > items = xe::GetElementsByQName( \r
114         opf, QName( "item", OPF_XML_NAMESPACE ) );\r
115 \r
116     foreach( xc::DOMElement* item, items )\r
117     {\r
118         std::string href       = fromX( item->getAttribute( toX( "href" )       ) );\r
119         std::string media_type = fromX( item->getAttribute( toX( "media-type" ) ) );\r
120 \r
121         if ( xc::XMLUri::isValidURI( true, toX( href ) ) &&\r
122              media_type == NCX_MIME )\r
123         {\r
124             return Util::Utf8PathToBoostPath( Util::UrlDecode( href ) );  \r
125         }\r
126     }\r
127 \r
128     return fs::path();\r
129 }\r
130 \r
131 \r
132 std::vector< fs::path > GetRelativePathsToXhtmlDocuments( const xc::DOMDocument &opf )\r
133 {\r
134     std::vector< xc::DOMElement* > items = xe::GetElementsByQName( \r
135         opf, QName( "item", OPF_XML_NAMESPACE ) );\r
136 \r
137     std::vector< fs::path > paths;\r
138 \r
139     foreach( xc::DOMElement* item, items )\r
140     {\r
141         std::string href       = fromX( item->getAttribute( toX( "href" )       ) );\r
142         std::string media_type = fromX( item->getAttribute( toX( "media-type" ) ) );\r
143 \r
144         if ( xc::XMLUri::isValidURI( true, toX( href ) ) &&\r
145              ( media_type == XHTML_MIME || media_type == OEB_DOC_MIME ) )\r
146         {                    \r
147             paths.push_back( Util::Utf8PathToBoostPath( Util::UrlDecode( href ) ) );\r
148         }\r
149     }\r
150 \r
151     return paths;\r
152 }\r
153 \r
154 \r
155 std::vector< Result > DescendToOpf( const fs::path &path_to_opf )\r
156 {\r
157     WellFormedXml wf_validator;\r
158 \r
159     // We can't continue if the OPF is not well-formed.\r
160     // ValidateOpf will take care of returning any \r
161     // validation results for the OPF\r
162     if ( !wf_validator.ValidateFile( path_to_opf ).empty() )\r
163   \r
164         return std::vector< Result >();\r
165 \r
166     xc::DOMDocument& opf = wf_validator.GetDocument();\r
167     std::vector< Result > results;\r
168 \r
169     fs::path opf_parent    = path_to_opf.parent_path();\r
170     fs::path rel_ncx_path  = GetRelativePathToNcx( opf );\r
171     fs::path full_ncx_path = opf_parent / GetRelativePathToNcx( opf );\r
172 \r
173     if ( !rel_ncx_path.empty() && fs::exists( full_ncx_path ) )\r
174 \r
175         Util::Extend( results, ValidateNcx( full_ncx_path ) );\r
176 \r
177     std::vector< fs::path > xhtml_paths = GetRelativePathsToXhtmlDocuments( opf );\r
178     \r
179     foreach( fs::path rel_xhtml_path, xhtml_paths )\r
180     {\r
181         fs::path full_xhtml_path = opf_parent / rel_xhtml_path;\r
182 \r
183         if ( !rel_xhtml_path.empty() && fs::exists( full_xhtml_path ) )\r
184 \r
185             Util::Extend( results, ValidateXhtml( full_xhtml_path ) );\r
186     }\r
187     \r
188     return results;\r
189 }\r
190 \r
191 \r
192 fs::path GetRelativeOpfPath( const xc::DOMDocument &content_xml )\r
193 {\r
194     std::vector< xc::DOMElement* > rootfiles = xe::GetElementsByQName( \r
195         content_xml, QName( "rootfile", CONTAINER_XML_NAMESPACE ) );\r
196 \r
197     foreach( xc::DOMElement* rootfile, rootfiles )\r
198     {\r
199         std::string full_path_attribute = fromX( rootfile->getAttribute( toX( "full-path"  ) ) );\r
200         std::string media_type          = fromX( rootfile->getAttribute( toX( "media-type" ) ) );\r
201         \r
202         if ( media_type == OEBPS_MIME )                 \r
203                        \r
204             return Util::Utf8PathToBoostPath( full_path_attribute );         \r
205     }\r
206 \r
207     return fs::path();\r
208 }\r
209 \r
210 \r
211 std::vector< Result > DescendToContentXml( const fs::path &path_to_content_xml )\r
212 {\r
213     WellFormedXml wf_validator;   \r
214 \r
215     // We can't continue if content.xml is not well-formed.\r
216     // ValidateMetaInf will take care of returning any \r
217     // validation results for content.xml\r
218     if ( !wf_validator.ValidateFile( path_to_content_xml ).empty() )\r
219   \r
220         return std::vector< Result >();\r
221 \r
222     // The base path for the OPF is the publication root path\r
223     fs::path root_path     = path_to_content_xml.parent_path().parent_path();\r
224     fs::path rel_opf_path  = GetRelativeOpfPath( wf_validator.GetDocument() );\r
225     fs::path full_opf_path = root_path / rel_opf_path;\r
226 \r
227     std::vector< Result > results;\r
228 \r
229     if ( !rel_opf_path.empty() && fs::exists( full_opf_path ) )\r
230     {\r
231         Util::Extend( results, ValidateOpf( full_opf_path ) );\r
232         Util::Extend( results, DescendToOpf( full_opf_path ) );\r
233     }\r
234 \r
235     return results;\r
236 }\r
237 \r
238 void RemoveBasePathFromResultPaths( std::vector< Result > &results, const fs::path &basepath )\r
239 {\r
240     std::string path_prefix = Util::BoostPathToUtf8Path( basepath );\r
241 \r
242     foreach( Result &result, results )\r
243     {\r
244         std::string result_path = result.GetFilepath();\r
245 \r
246         if ( !result_path.empty() )\r
247         {\r
248             std::string relative_path = boost::erase_first_copy( result_path, path_prefix );\r
249 \r
250             // We don't want it to look like an absolute path\r
251             // because it's not.\r
252             if ( boost::starts_with( relative_path, "/" ) )\r
253 \r
254                 boost::erase_first( relative_path, "/" );\r
255 \r
256             result.SetFilepath( relative_path );\r
257         }        \r
258     }\r
259 }\r
260 \r
261 \r
262 void AddEpubFilenameToResultPaths( std::vector< Result > &results, const std::string &epub_name )\r
263 {\r
264     foreach( Result &result, results )\r
265     {\r
266         std::string result_path = result.GetFilepath();\r
267 \r
268         if ( !result_path.empty() )\r
269         \r
270             result.SetFilepath( epub_name + "/" + result_path );        \r
271 \r
272         else\r
273         \r
274             result.SetFilepath( epub_name );        \r
275     }\r
276 }\r
277 \r
278 \r
279 std::vector< Result > ValidateEpubRootFolder( const fs::path &root_folder_path )\r
280 {\r
281     xe::XercesInit init;\r
282 \r
283     if ( !fs::exists( root_folder_path ) )\r
284 \r
285         boost_throw( FileDoesNotExistEx() << ei_FilePath( Util::BoostPathToUtf8Path( root_folder_path ) ) );\r
286 \r
287     std::vector< Result > results;   \r
288     Util::Extend( results, ValidateMetaInf( root_folder_path / "META-INF" ) );\r
289 \r
290     fs::path path_to_content_xml = root_folder_path / "META-INF/container.xml";\r
291 \r
292     if ( !fs::exists( path_to_content_xml ) )\r
293     {\r
294         return results;\r
295     }\r
296     \r
297     Util::Extend( results, DescendToContentXml( path_to_content_xml ) );\r
298 \r
299     RemoveBasePathFromResultPaths( results, root_folder_path );\r
300     return results;\r
301 }\r
302 \r
303 \r
304 std::vector< Result > ValidateEpub( const fs::path &filepath )\r
305 {\r
306     TempFolder temp_folder;\r
307 \r
308     std::vector< Result > results;\r
309 \r
310     try\r
311     {\r
312         zipios::ExtractZipToFolder( filepath, temp_folder.GetPath() );\r
313     }\r
314     \r
315     catch ( std::exception& exception )\r
316     {\r
317         results.push_back( Result( ERROR_EPUB_NOT_VALID_ZIP_ARCHIVE )\r
318                            .SetCustomMessage( exception.what() ) );\r
319         return results;\r
320     }\r
321     \r
322     Util::Extend( results, MimetypeBytesValid().ValidateFile( filepath ) );\r
323     RemoveBasePathFromResultPaths( results, temp_folder.GetPath() );\r
324 \r
325     Util::Extend( results, ValidateEpubRootFolder( temp_folder.GetPath() ) );    \r
326     AddEpubFilenameToResultPaths( results, Util::BoostPathToUtf8Path( filepath.filename() ) );\r
327     return results;\r
328 }\r
329 \r
330 } // namespace FlightCrew\r