/* * lftp - file transfer program * * Copyright (c) 1996-2016 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "HttpDir.h" #include "log.h" #include "url.h" #include "misc.h" #if USE_EXPAT #include struct xml_context { xarray_s stack; Ref fs; Ref fi; xstring base_dir; xstring chardata; void push(const char *); void pop(); void process_chardata(); void set_base_dir(const char *d) { base_dir.set(d); if(base_dir.length()>1) base_dir.chomp('/'); } const xstring_c& top(int i=0) const { return stack.count()>i ? stack[stack.count()-i-1] : xstring_c::null; } bool in(const char *tag) const { return top().eq(tag); } bool in(const char *tag0,const char *tag1) const { return top(0).eq(tag0) && top(1).eq(tag1); } bool has_chardata() const { return chardata.length()>0; } void log_tag(const char *end="") const { const char *tag=top(); Log::global->Format(10,"XML: %*s<%s%s>\n",stack.length()*2,"",end,tag); } void log_tag_end() const { log_tag("/"); } void log_data() const { Log::global->Format(10,"XML: %*s`%s'\n",stack.length()*2+2,"",chardata.get()); } }; void xml_context::push(const char *s) { stack.append(s); log_tag(); if(in("DAV:response")) { delete fi; fi=new FileInfo; } else if(in("DAV:collection")) { fi->SetType(fi->DIRECTORY); fi->SetMode(0755); } chardata.truncate(); } void xml_context::pop() { if(has_chardata()) process_chardata(); if(in("DAV:response")) { if(fi && fi->name) { if(!fs) fs=new FileSet; fs->Add(fi.borrow()); } } log_tag_end(); stack.chop(); } void xml_context::process_chardata() { log_data(); if(in("DAV:href","DAV:response")) { ParsedURL u(chardata,true); xstring& s=u.path; bool is_directory=false; if(s.last_char()=='/') { is_directory=true; s.chomp('/'); fi->SetType(fi->DIRECTORY); fi->SetMode(0755); } else { fi->SetType(fi->NORMAL); fi->SetMode(0644); } if(s.begins_with("/~")) s.set_substr(0,1,0,0); fi->SetName(base_dir.eq(s) && is_directory ? "." : basename_ptr(s)); } else if(in("DAV:getcontentlength")) { long long size_ll=0; if(sscanf(chardata,"%lld",&size_ll)==1) fi->SetSize(size_ll); } else if(in("DAV:getlastmodified")) { time_t tm=Http::atotm(chardata); if(tm!=Http::ATOTM_ERROR) fi->SetDate(tm,0); } else if(in("DAV:creator-displayname")) { fi->SetUser(chardata); } else if(in("http://apache.org/dav/props/executable")) { if(chardata[0]=='T') fi->SetMode(0755); else if(chardata[0]=='F') fi->SetMode(0644); } } static void start_handle(void *data, const char *el, const char **attr) { xml_context *ctx=(xml_context*)data; ctx->push(el); } static void end_handle(void *data, const char *el) { xml_context *ctx=(xml_context*)data; ctx->pop(); } static void chardata_handle(void *data, const char *chardata, int len) { xml_context *ctx=(xml_context*)data; if(!ctx->fi) return; ctx->chardata.append(chardata,len); } FileSet *HttpListInfo::ParseProps(const char *b,int len,const char *base_dir) { XML_Parser p = XML_ParserCreateNS(0,0); if(!p) return 0; xml_context ctx; ctx.set_base_dir(base_dir); XML_SetUserData(p,&ctx); XML_SetElementHandler(p, start_handle, end_handle); XML_SetCharacterDataHandler(p, chardata_handle); if(!XML_Parse(p, b, len, /*eof*/1)) { Log::global->Format(0, "XML Parse error at line %d: %s\n", (int)XML_GetCurrentLineNumber(p), XML_ErrorString(XML_GetErrorCode(p))); XML_ParserFree(p); return 0; } XML_ParserFree(p); return ctx.fs.borrow(); } void HttpDirList::ParsePropsFormat(const char *b,int len,bool eof) { if(len==0) goto end; if(!xml_p) { xml_p=XML_ParserCreateNS(0,0); xml_ctx=new xml_context; xml_ctx->set_base_dir(curr_url->path); XML_SetUserData(xml_p,xml_ctx); XML_SetElementHandler(xml_p, start_handle, end_handle); XML_SetCharacterDataHandler(xml_p, chardata_handle); } if(!XML_Parse(xml_p, b, len, eof)) { Log::global->Format(0, "XML Parse error at line %d: %s\n", (int)XML_GetCurrentLineNumber(xml_p), XML_ErrorString(XML_GetErrorCode(xml_p))); parse_as_html=true; return; } if(!xml_ctx->fs) goto end; xml_ctx->fs->rewind(); for(;;) { FileInfo *info=xml_ctx->fs->curr(); if(!info) break; info->MakeLongName(); buf->Put(info->longname); if(ls_options.append_type) { if(info->filetype==info->DIRECTORY) buf->Put("/"); else if(info->filetype==info->SYMLINK && !info->symlink) buf->Put("@"); } buf->Put("\n"); xml_ctx->fs->next(); } xml_ctx->fs->Empty(); end: if(eof && xml_p) { XML_ParserFree(xml_p); xml_p=0; delete xml_ctx; xml_ctx=0; } } #else // !USE_EXPAT FileSet *HttpListInfo::ParseProps(const char *b,int len,const char *base_dir) { return 0; } void HttpDirList::ParsePropsFormat(const char *b,int len,bool eof) {} #endif // !USE_EXPAT