1 /*
2  * This File is part of Davix, The IO library for HTTP based protocols
3  * Copyright (C) CERN 2013
4  * Author: Adrien Devresse <adrien.devresse@cern.ch>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20 */
21 
22 #include <davix_internal.hpp>
23 #include "davpropxmlparser.hpp"
24 
25 #include <utils/davix_logger_internal.hpp>
26 #include <status/davixstatusrequest.hpp>
27 #include <datetime/datetime_utils.hpp>
28 #include <string_utils/stringutils.hpp>
29 
30 using namespace StrUtil;
31 
32 namespace Davix {
33 
34 const Xml::XmlPTree prop_node(Xml::ElementStart, "propstat");
35 const Xml::XmlPTree prop_collection(Xml::ElementStart, "collection");
36 static std::unique_ptr<Xml::XmlPTree> webDavTree;
37 
38 static std::once_flag _l_init;
39 
40 struct DavPropXMLParser::DavxPropXmlIntern{
DavxPropXmlInternDavix::DavPropXMLParser::DavxPropXmlIntern41     DavxPropXmlIntern() : _stack(),
42         _props(), _current_props(), _last_response_status(500), _last_filename(){
43         _stack.reserve(10);
44         char_buffer.reserve(1024);
45     }
46 
47     // node stack
48     std::vector<Xml::XmlPTree> _stack;
49 
50     // props
51     std::deque<FileProperties> _props;
52     FileProperties _current_props;
53     int _last_response_status;
54     std::string _last_filename;
55 
56     // buffer
57     std::string char_buffer;
58 
appendCharsDavix::DavPropXMLParser::DavxPropXmlIntern59     inline void appendChars(const char *buff, size_t len){
60         char_buffer.append(std::string(buff, len));
61     }
62 
clearDavix::DavPropXMLParser::DavxPropXmlIntern63     inline void clear(){
64         char_buffer.clear();
65     }
66 
67 
add_new_elemDavix::DavPropXMLParser::DavxPropXmlIntern68     inline void add_new_elem(){
69         DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " properties detected ");
70         _current_props.clear();
71         _current_props.filename = _last_filename; // setup the current filename
72         _current_props.info.mode = 0777 | S_IFREG; // default : fake access to everything
73     }
74 
store_new_elemDavix::DavPropXMLParser::DavxPropXmlIntern75     inline void store_new_elem(){
76         DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " end of properties... ");
77         if( _last_response_status > 100
78             && _last_response_status < 400){
79             _props.push_back(_current_props);
80         }else{
81            DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, "Bad status code ! properties dropped");
82         }
83     }
84 
85 };
86 
87 
88 typedef void (*properties_cb)(DavPropXMLParser::DavxPropXmlIntern & par, const std::string & name);
89 
90 
check_last_modified(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)91 static void check_last_modified(DavPropXMLParser::DavxPropXmlIntern & par, const std::string & name){
92     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " getlastmodified found -> parse it ");
93     time_t t = parse_standard_date(name.c_str());
94     if(t == -1){
95         DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, " getlastmodified parsing error : corrupted value ... ignored");
96         t = 0;
97     }
98     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " getlastmodified found -> value {} ", t);
99     par._current_props.info.mtime = t;
100 }
101 
102 
check_creation_date(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)103 static void check_creation_date(DavPropXMLParser::DavxPropXmlIntern & par, const std::string & name){
104     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, "creationdate found -> parse it");
105     time_t t = parse_standard_date(name.c_str());
106     if(t == -1){
107         DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, " creationdate parsing error : corrupted value ... ignored");
108         t = 0;
109     }
110     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " creationdate found -> value {} ", t);
111     par._current_props.info.ctime = t;
112 }
113 
check_is_directory(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)114 static void check_is_directory(DavPropXMLParser::DavxPropXmlIntern & par,  const std::string & name){
115    (void) name;
116    DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " directory pattern found -> set flag IS_DIR");
117    par._current_props.info.mode |=  S_IFDIR;
118    par._current_props.info.mode &= ~(S_IFREG);
119 }
120 
121 
check_content_length(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)122 static void check_content_length(DavPropXMLParser::DavxPropXmlIntern & par,  const std::string & name){
123     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " content length found -> parse it");
124     try{
125         const unsigned long mysize = toType<unsigned long, std::string>()(name);
126         DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " content length found -> {}", mysize);
127         par._current_props.info.size = static_cast<off_t>(mysize);
128     }catch(...){
129         DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, " Invalid content length value in dav response");
130     }
131 }
132 
check_quota_used_bytes(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)133 static void check_quota_used_bytes(DavPropXMLParser::DavxPropXmlIntern & par,  const std::string & name){
134     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " quota used bytes found -> parse it");
135     try{
136         const unsigned long mysize = toType<unsigned long, std::string>()(name);
137         DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " quota used bytes found -> {}", mysize);
138         par._current_props.info.size = static_cast<off_t>(mysize);
139         par._current_props.quota.used_bytes = static_cast<off_t>(mysize);
140     }catch(...){
141         DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, " Invalid quota used bytes in dav response");
142     }
143 }
144 
check_quota_free_space(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)145 static void check_quota_free_space(DavPropXMLParser::DavxPropXmlIntern & par,  const std::string & name){
146     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " quota free space found -> parse it");
147     try{
148         const unsigned long mysize = toType<unsigned long, std::string>()(name);
149         DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " quota free space found -> {}", mysize);
150         par._current_props.quota.free_space = static_cast<off_t>(mysize);
151     }catch(...){
152         DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, " Invalid quota free space in dav response");
153     }
154 }
155 
check_mode_ext(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)156 static void check_mode_ext(DavPropXMLParser::DavxPropXmlIntern & par, const std::string & name){
157     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, "mode_t extension for LCGDM found -> parse it");
158     const unsigned long mymode = strtoul(name.c_str(), NULL, 8);
159     if(mymode == ULONG_MAX){
160         DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, "Invalid mode_t value for the LCGDM extension");
161         errno =0;
162         return;
163     }
164     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, fmt::sprintf(" mode_t extension found -> 0%o", (mode_t) mymode).c_str());
165     par._current_props.info.mode = (mode_t) mymode;
166 }
167 
startswith(const std::string & str,const std::string & prefix)168 static bool startswith(const std::string &str, const std::string &prefix) {
169   if(prefix.size() > str.size()) return false;
170 
171   for(size_t i = 0; i < prefix.size(); i++) {
172     if(str[i] != prefix[i]) return false;
173   }
174   return true;
175 }
176 
check_href(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)177 static void check_href(DavPropXMLParser::DavxPropXmlIntern & par,  const std::string & name){
178     std::string _href(name);
179     rtrim(_href, isSlash()); // remove trailing slash
180     std::string::reverse_iterator it = std::find(_href.rbegin(), _href.rend(), '/');
181     if( it == _href.rend()){
182         par._last_filename.assign(_href);
183     }else{
184         par._last_filename.assign(it.base(), _href.end());
185 
186         if(startswith(name, "https://") || startswith(name, "http://") || startswith(name, "://") || startswith(name, "dav://") || startswith(name, "davs://")) {
187           par._last_filename = Uri::unescapeString(par._last_filename);
188         }
189     }
190    DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " href/filename parsed -> {} ", par._last_filename.c_str() );
191 }
192 
check_status(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & name)193 static void check_status(DavPropXMLParser::DavxPropXmlIntern & par, const std::string & name){
194     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " status found -> parse it");
195     std::string str_status(name);
196     ltrim(str_status, StrUtil::isSpace());
197     std::string::iterator it1, it2;
198     it1 = std::find(str_status.begin(), str_status.end(), ' ');
199     if( it1 != str_status.end()){
200         it2 = std::find(it1+1, str_status.end(), ' ');
201         std::string str_status_parsed(it1+1, it2);
202         unsigned long res = strtoul(str_status_parsed.c_str(), NULL, 10);
203         if(res != ULONG_MAX){
204            DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " status value : {}", res);
205            par._last_response_status = res;
206            return;
207         }
208     }
209     DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, "Invalid dav status field value");
210     errno =0;
211 }
212 
check_owner_uid(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & value)213 static void check_owner_uid(DavPropXMLParser::DavxPropXmlIntern & par, const std::string & value){
214     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " owner found -> parse it");
215     std::string str_owner(value);
216     ltrim(str_owner, StrUtil::isSpace());
217     unsigned long res = strtoul(str_owner.c_str(), NULL, 10);
218     if(res != ULONG_MAX){
219        DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " owner value : {}", res);
220        par._current_props.info.owner = res;
221        return;
222     }
223     DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, "Invalid owner field value");
224 }
225 
check_group_gid(DavPropXMLParser::DavxPropXmlIntern & par,const std::string & value)226 static void check_group_gid(DavPropXMLParser::DavxPropXmlIntern & par, const std::string & value){
227     DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " group found -> parse it");
228     std::string str_group(value);
229     ltrim(str_group, StrUtil::isSpace());
230     unsigned long res = strtoul(str_group.c_str(), NULL, 10);
231     if(res != ULONG_MAX){
232        DAVIX_SLOG(DAVIX_LOG_DEBUG, DAVIX_LOG_XML, " group value : {}", res);
233        par._current_props.info.group = res;
234        return;
235     }
236     DAVIX_SLOG(DAVIX_LOG_VERBOSE, DAVIX_LOG_XML, "Invalid group field value");
237 }
238 
init_webdavTree()239 void init_webdavTree(){
240 
241     // Nodes list
242     webDavTree.reset(new Xml::XmlPTree(Xml::ElementStart, "multistatus"));
243     webDavTree->addChild(Xml::XmlPTree(Xml::ElementStart, "response"));
244     Xml::XmlPTree::iterator it = webDavTree->beginChildren();
245     it->addChild(Xml::XmlPTree(Xml::ElementStart, "href", Xml::XmlPTree::ChildrenList(),  (void*) &check_href));
246     it->addChild(Xml::XmlPTree(Xml::ElementStart, "propstat"));
247     it = (--it->endChildren());
248     it->addChild(Xml::XmlPTree(Xml::ElementStart, "status", Xml::XmlPTree::ChildrenList(), (void*) &check_status));
249     it->addChild(Xml::XmlPTree(Xml::ElementStart, "prop"));
250     it = (--it->endChildren());
251 
252     it->addChild(Xml::XmlPTree(Xml::ElementStart, "getlastmodified", Xml::XmlPTree::ChildrenList(),  (void*) &check_last_modified));
253     it->addChild(Xml::XmlPTree(Xml::ElementStart, "creationdate", Xml::XmlPTree::ChildrenList(), (void*) &check_creation_date));
254     it->addChild(Xml::XmlPTree(Xml::ElementStart, "quota-used-bytes", Xml::XmlPTree::ChildrenList(), (void*) &check_quota_used_bytes));
255     it->addChild(Xml::XmlPTree(Xml::ElementStart, "quota-available-bytes", Xml::XmlPTree::ChildrenList(), (void*) &check_quota_free_space));
256     it->addChild(Xml::XmlPTree(Xml::ElementStart, "getcontentlength", Xml::XmlPTree::ChildrenList(), (void*) &check_content_length));
257 
258     it->addChild(Xml::XmlPTree(Xml::ElementStart, "owner", Xml::XmlPTree::ChildrenList(), (void*) &check_owner_uid));
259     it->addChild(Xml::XmlPTree(Xml::ElementStart, "group", Xml::XmlPTree::ChildrenList(), (void*) &check_group_gid));
260     it->addChild(Xml::XmlPTree(Xml::ElementStart, "mode", Xml::XmlPTree::ChildrenList(), (void*) &check_mode_ext));
261     it->addChild(Xml::XmlPTree(Xml::ElementStart, "resourcetype"));
262 
263     it = (--it->endChildren());
264     it->addChild(Xml::XmlPTree(Xml::ElementStart, "collection", Xml::XmlPTree::ChildrenList(), (void*) &check_is_directory));
265 }
266 
DavPropXMLParser()267 DavPropXMLParser::DavPropXMLParser() :
268     d_ptr(new DavxPropXmlIntern())
269 {
270     std::call_once(_l_init, init_webdavTree);
271 }
272 
~DavPropXMLParser()273 DavPropXMLParser::~DavPropXMLParser(){
274     delete d_ptr;
275 }
276 
277 
getProperties()278 std::deque<FileProperties> & DavPropXMLParser::getProperties(){
279     return d_ptr->_props;
280 }
281 
282 
parserStartElemCb(int parent,const char * nspace,const char * name,const char ** atts)283 int DavPropXMLParser::parserStartElemCb(int parent, const char *nspace, const char *name, const char **atts){
284     (void) parent;
285     (void) name;
286     (void) nspace;
287     (void) atts;
288     // add elem to stack
289     Xml::XmlPTree node(Xml::ElementStart, name);
290     d_ptr->_stack.push_back(node);
291 
292     // if beginning of prop, add new element
293     if(node.compareNode(prop_node)){
294         d_ptr->add_new_elem();
295     }
296     return 1;
297 }
298 
299 
parserCdataCb(int state,const char * cdata,size_t len)300 int DavPropXMLParser::parserCdataCb(int state, const char *cdata, size_t len){
301     (void) state;
302     d_ptr->appendChars(cdata, len);
303     return 0;
304 }
305 
306 
parserEndElemCb(int state,const char * nspace,const char * name)307 int DavPropXMLParser::parserEndElemCb(int state, const char *nspace, const char *name){
308     (void) state;
309     (void) nspace;
310     Xml::XmlPTree node(Xml::ElementStart, name);
311 
312     // find potential interesting data
313     std::vector<Xml::XmlPTree::ptr_type> chain;
314     if(d_ptr->char_buffer.size() != 0 || node.compareNode(prop_collection)){
315         chain = webDavTree->findChain(d_ptr->_stack);
316         if(chain.size() > 0){
317             properties_cb cb = ((properties_cb) chain.at(chain.size()-1)->getMeta());
318             if(cb){
319                 StrUtil::trim(d_ptr->char_buffer);
320                 cb(*d_ptr, d_ptr->char_buffer);
321             }
322         }
323     }
324 
325     // push props
326     if(node.compareNode(prop_node)){
327         d_ptr->store_new_elem();
328     }
329 
330     // cleaning work
331     if(d_ptr->_stack.size()  == 0)
332         throw DavixException(davix_scope_xml_parser(),StatusCode::ParsingError, "Corrupted Parser Stack, Invalid XML");
333     d_ptr->_stack.pop_back();
334     d_ptr->clear();
335     return 0;
336 }
337 
338 } // namespace Davix
339