1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2016 by Alexander V. Lukyanov (lav@yars.free.net)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 
22 #include "FileAccess.h"
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #include <assert.h>
29 #include <stddef.h>
30 #include "ascii_ctype.h"
31 #include <fcntl.h>
32 #include "LsCache.h"
33 #include "log.h"
34 #include "url.h"
35 #include "misc.h"
36 #include "DummyProto.h"
37 #include "netrc.h"
38 #include "ArgV.h"
39 #include "ConnectionSlot.h"
40 #include "SignalHook.h"
41 #include "FileGlob.h"
42 #ifdef WITH_MODULES
43 # include "module.h"
44 #endif
45 
46 xlist_head<FileAccess> FileAccess::all_fa;
47 const FileAccessRef FileAccessRef::null;
48 
Init()49 void FileAccess::Init()
50 {
51    ClassInit();
52 
53    pass_open=false;
54 
55    default_cwd="~";
56    cwd.Set(default_cwd,false,0);
57    limit=FILE_END;
58    real_pos=UNKNOWN_POS;
59    pos=0;
60    mode=CLOSED;
61    retries=0;
62    max_retries=0;
63    opt_date=0;
64    opt_size=0;
65    fileset_for_info=0;
66    error_code=OK;
67    saved_errno=0;
68    mkdir_p=false;
69    rename_f=false;
70    ascii=false;
71    norest_manual=false;
72 
73    entity_size=NO_SIZE;
74    entity_date=NO_DATE;
75 
76    res_prefix=0;
77 
78    chmod_mode=0644;
79 
80    priority=last_priority=0;
81 
82    all_fa.add(all_fa_node);
83 }
84 
FileAccess(const FileAccess * fa)85 FileAccess::FileAccess(const FileAccess *fa)
86    : all_fa_node(this)
87 {
88    Init();
89 
90    cwd=fa->cwd;
91    home=fa->home;
92    user.set(fa->user);
93    pass.set(fa->pass);
94    pass_open=fa->pass_open;
95    hostname.set(fa->hostname);
96    portname.set(fa->portname);
97    vproto.set(fa->vproto);
98 }
99 
~FileAccess()100 FileAccess::~FileAccess()
101 {
102    all_fa_node.remove();
103 }
104 
Open(const char * fn,int mode,off_t offs)105 void  FileAccess::Open(const char *fn,int mode,off_t offs)
106 {
107 #ifdef OPEN_DEBUG
108    printf("%p->FA::Open(%s,%d)\n",this,fn?fn:"NULL",mode);
109 #endif
110    if(IsOpen())
111       Close();
112    Resume();
113    file.set(fn);
114    real_pos=UNKNOWN_POS;
115    pos=offs;
116    this->mode=mode;
117    mkdir_p=false;
118    rename_f=false;
119    Timeout(0);
120 
121    switch((open_mode)mode)
122    {
123    case STORE:
124    case REMOVE:
125    case MAKE_DIR:
126    case CHANGE_MODE:
127       cache->FileChanged(this,file);
128       break;
129    case REMOVE_DIR:
130       cache->FileChanged(this,file);
131       cache->TreeChanged(this,file);
132       break;
133    default:
134       break;
135    }
136 }
137 
StrError(int err)138 const char *FileAccess::StrError(int err)
139 {
140    static xstring str;
141 
142    // note to translators: several errors should not be displayed to user;
143    // so no need to translate them.
144    switch(err)
145    {
146    case(IN_PROGRESS):
147       return("Operation is in progress");
148    case(OK):
149       return("Error 0");
150    case(SEE_ERRNO):
151       if(error)
152 	 return str.vset(error.get(),": ",strerror(saved_errno),NULL);
153       return(strerror(saved_errno));
154    case(LOOKUP_ERROR):
155       return(error);
156    case(NOT_OPEN):   // Actually this means an error in application
157       return("Class is not Open()ed");
158    case(NO_FILE):
159       if(error)
160 	 return str.vset(_("Access failed: "),error.get(),NULL);
161       return(_("File cannot be accessed"));
162    case(NO_HOST):
163       return(_("Not connected"));
164    case(FATAL):
165       if(error)
166 	 return str.vset(_("Fatal error"),": ",error.get(),NULL);
167       return(_("Fatal error"));
168    case(STORE_FAILED):
169       return(_("Store failed - you have to reput"));
170    case(LOGIN_FAILED):
171       if(error)
172 	 return str.vset(_("Login failed"),": ",error.get(),NULL);
173       return(_("Login failed"));
174    case(NOT_SUPP):
175       if(error)
176 	 return str.vset(_("Operation not supported"),": ",error.get(),NULL);
177       return(_("Operation not supported"));
178    case(FILE_MOVED):
179       if(error)
180 	 return str.vset(_("File moved"),": ",error.get(),NULL);
181       else
182 	 return str.vset(_("File moved to `"),location?location.get():"?","'",NULL);
183    }
184    return("");
185 }
186 
Close()187 void FileAccess::Close()
188 {
189    file.set(0);
190    file_url.set(0);
191    file1.set(0);
192    new_cwd=0;
193    mode=CLOSED;
194    opt_date=0;
195    opt_size=0;
196    fileset_for_info=0;
197    retries=0;
198    entity_size=NO_SIZE;
199    entity_date=NO_DATE;
200    ascii=false;
201    norest_manual=false;
202    location.set(0);
203    entity_content_type.set(0);
204    entity_charset.set(0);
205    ClearError();
206 }
207 
Open2(const char * f,const char * f1,open_mode o)208 void FileAccess::Open2(const char *f,const char *f1,open_mode o)
209 {
210    Close();
211    file1.set(f1);
212    Open(f,o);
213 
214    cache->TreeChanged(this,file);
215    cache->FileChanged(this,file);
216    cache->FileChanged(this,file1);
217 }
218 
Rename(const char * rfile,const char * to,bool clobber)219 void FileAccess::Rename(const char *rfile,const char *to,bool clobber)
220 {
221    Open2(rfile,to,RENAME);
222    rename_f=clobber;
223 }
224 
Mkdir(const char * fn,bool allp)225 void FileAccess::Mkdir(const char *fn,bool allp)
226 {
227    Open(fn,MAKE_DIR);
228    mkdir_p=allp;
229 }
230 
MkdirMakeSet() const231 StringSet *FileAccess::MkdirMakeSet() const
232 {
233    StringSet *set=new StringSet;
234    const char *sl=strchr(file,'/');
235    while(sl)
236    {
237       if(sl>file)
238       {
239 	 xstring& tmp=xstring::get_tmp(file,sl-file);
240 	 if(tmp.ne(".") && tmp.ne(".."))
241 	    set->Append(tmp);
242       }
243       sl=strchr(sl+1,'/');
244    }
245    return set;
246 }
247 
SameLocationAs(const FileAccess * fa) const248 bool FileAccess::SameLocationAs(const FileAccess *fa) const
249 {
250    return SameSiteAs(fa);
251 }
SameSiteAs(const FileAccess * fa) const252 bool FileAccess::SameSiteAs(const FileAccess *fa) const
253 {
254    return SameProtoAs(fa);
255 }
256 
GetFileURL(const char * f,int flags) const257 const xstring& FileAccess::GetFileURL(const char *f,int flags) const
258 {
259    const char *proto=GetVisualProto();
260    if(proto[0]==0)
261       return xstring::get_tmp("");
262 
263    ParsedURL u;
264 
265    u.proto.set(proto);
266    if(!(flags&NO_USER))
267       u.user.set(user);
268    if((pass_open || (flags&WITH_PASSWORD)) && !(flags&NO_PASSWORD))
269       u.pass.set(pass);
270    u.host.set(hostname);
271    u.port.set(portname);
272    if(!(flags&NO_PATH))
273    {
274       if(cwd.url)
275       {
276 	 Path f_path(cwd);
277 	 if(f)
278 	    f_path.Change(f,true);
279 	 if(f_path.url)
280 	 {
281 	    int f_path_index=url::path_index(f_path.url);
282 	    return u.CombineTo(xstring::get_tmp(""),home)
283 	       .append(f_path.url+f_path_index);
284 	 }
285       }
286 
287       bool is_dir=((!f || !*f) && !cwd.is_file);
288 
289       if(!f || (f[0]!='/' && f[0]!='~'))
290 	 f=dir_file(cwd.path?cwd.path.get():"~",f);
291       u.path.set(f);
292       if(is_dir && url::dir_needs_trailing_slash(proto) && u.path.last_char()!='/')
293 	 u.path.append('/');
294    }
295    return u.CombineTo(xstring::get_tmp(""),home);
296 }
297 
GetConnectURL(int flags) const298 const xstring& FileAccess::GetConnectURL(int flags) const
299 {
300    return GetFileURL(0,flags);
301 }
302 
Connect(const char * host1,const char * port1)303 void FileAccess::Connect(const char *host1,const char *port1)
304 {
305    Close();
306    hostname.set(host1);
307    portname.set(port1);
308    DontSleep();
309    ResetLocationData();
310 }
311 
Login(const char * user1,const char * pass1)312 void FileAccess::Login(const char *user1,const char *pass1)
313 {
314    Close();
315    user.set(user1);
316    pass.set(pass1);
317    pass_open=false;
318 
319    if(user && pass==0)
320    {
321       xlist_for_each(FileAccess,all_fa,node,o)
322       {
323 	 pass.set(o->pass);
324 	 if(SameSiteAs(o) && o->pass)
325 	    break;
326       }
327       if(!o)
328 	 pass.set(0);
329       if(pass==0 && hostname) // still no pass? Try .netrc
330       {
331 	 NetRC::Entry *nrc=NetRC::LookupHost(hostname,user);
332 	 if(nrc)
333 	    pass.set(nrc->pass);
334       }
335    }
336    ResetLocationData();
337 }
338 
ResetLocationData()339 void FileAccess::ResetLocationData()
340 {
341    cwd.Set(default_cwd,false,0);
342    home.Set((char*)0);
343 }
344 
SetPasswordGlobal(const char * p)345 void FileAccess::SetPasswordGlobal(const char *p)
346 {
347    pass.set(p);
348    xstring save_pass;
349    xlist_for_each(FileAccess,all_fa,node,o)
350    {
351       if(o==this)
352 	 continue;
353       save_pass.set(o->pass);	 // cheat SameSiteAs.
354       o->pass.set(pass);
355       if(!SameSiteAs(o))
356 	 o->pass.set(save_pass);
357    }
358 }
359 
GetInfoArray(FileSet * info)360 void FileAccess::GetInfoArray(FileSet *info)
361 {
362    Open(0,ARRAY_INFO);
363    fileset_for_info=info;
364    fileset_for_info->rewind();
365 }
366 
expand_tilde(xstring & path,const char * home,int i=0)367 static void expand_tilde(xstring &path, const char *home, int i=0)
368 {
369    if(!(path[i]=='~' && (path[i+1]==0 || path[i+1]=='/')))
370       return;
371    char prefix_len=(last_char(home)=='/' ? 2 : 1);
372    if(home[0]=='/' && i>0 && path[i-1]=='/')
373       home++;
374    path.set_substr(i,prefix_len,home);
375 }
376 
ExpandTildeInCWD()377 void  FileAccess::ExpandTildeInCWD()
378 {
379    if(home)
380    {
381       cwd.ExpandTilde(home);
382       if(new_cwd)
383 	 new_cwd->ExpandTilde(home);
384       if(real_cwd)
385 	 expand_tilde(real_cwd,home);
386       if(file)
387 	 expand_tilde(file,home);
388       if(file1)
389 	 expand_tilde(file1,home);
390    }
391 }
set_home(const char * h)392 void FileAccess::set_home(const char *h)
393 {
394    home.Set(h);
395    ExpandTildeInCWD();
396 }
ExpandTildeStatic(const char * s) const397 const char *FileAccess::ExpandTildeStatic(const char *s) const
398 {
399    if(!home || !(s[0]=='~' && (s[1]=='/' || s[1]==0)))
400       return s;
401 
402    static xstring buf;
403    buf.set(s);
404    expand_tilde(buf,home);
405    return buf;
406 }
407 
408 static inline
last_element_is_doubledot(const char * path,const char * end)409 bool last_element_is_doubledot(const char *path,const char *end)
410 {
411    return((end==path+2 && !strncmp(path,"..",2))
412         || (end>path+2 && !strncmp(end-3,"/..",3)));
413 }
414 
device_prefix_len(const char * path) const415 int FileAccess::device_prefix_len(const char *path) const
416 {
417    ResValue dp=Query("device-prefix",hostname);
418    if(dp.is_nil() || !dp.to_bool())
419       return 0;
420    int i=0;
421    while(path[i] && (is_ascii_alnum(path[i]) || strchr("$_-",path[i])))
422       i++;
423    if(i>0 && path[i]==':')
424       return i+1+(path[i+1]=='/');
425    return 0;
426 }
427 
Optimize(xstring & path,int device_prefix_len)428 void FileAccess::Path::Optimize(xstring& path,int device_prefix_len)
429 {
430    int prefix_size=0;
431 
432    if(path[0]=='/' && path[1]=='~' && device_prefix_len==1)
433    {
434       prefix_size=2;
435       while(path[prefix_size]!='/' && path[prefix_size]!='\0')
436 	 prefix_size++;
437    }
438    else if(path[0]=='/')
439    {
440       prefix_size=1;
441       if(path[1]=='/' && (!path[2] || path[2]!='/'))
442 	 prefix_size=2;
443    }
444    else if(path[0]=='~')
445    {
446       prefix_size=1;
447       while(path[prefix_size]!='/' && path[prefix_size]!='\0')
448 	 prefix_size++;
449    }
450    else
451    {
452       // handle VMS and DOS devices.
453       prefix_size=device_prefix_len;
454    }
455 
456    char	 *in;
457    char	 *out;
458 
459    in=out=path.get_non_const()+prefix_size;
460 
461    while((in[0]=='.' && (in[1]=='/' || in[1]==0))
462    || (in>path && in[-1]=='/' && (in[0]=='/'
463 	 || (in[0]=='.' && in[1]=='.' && (in[2]=='/' || in[2]==0)))))
464    {
465       if(in[0]=='.' && in[1]=='.')
466 	 in++;
467       in++;
468       if(*in=='/')
469 	 in++;
470    }
471 
472    while(*in)
473    {
474       if(in[0]=='/')
475       {
476 	 // double slash
477 	 if(in[1]=='/')
478 	 {
479 	    in++;
480 	    continue;
481 	 }
482 	 if(in[1]=='.')
483 	 {
484 	    // . - cur dir
485 	    if(in[2]=='/' || in[2]=='\0')
486 	    {
487 	       in+=2;
488 	       continue;
489 	    }
490 	    // .. - prev dir
491 	    if(in[2]=='.' && (in[3]=='/' || in[3]=='\0'))
492 	    {
493 	       if(last_element_is_doubledot(path+prefix_size,out)
494 	       || out==path
495 	       || (out==path+prefix_size && out[-1]!='/'))
496 	       {
497 		  if(out>path && out[-1]!='/')
498 		     *out++='/';
499 		  *out++='.';
500 		  *out++='.';
501 	       }
502 	       else
503 	       {
504 		  while(out>path+prefix_size && *--out!='/')
505 		     ;
506 	       }
507 	       in+=3;
508 	       continue;
509 	    }
510 	 }
511 	 // don't add slash after prefix with slash
512 	 if(out>path && out[-1]=='/')
513 	 {
514 	    in++;
515 	    continue;
516 	 }
517       }
518       *out++=*in++;
519    }
520    path.truncate(path.length()-(in-out));
521 }
522 
Chdir(const char * path,bool verify)523 void FileAccess::Chdir(const char *path,bool verify)
524 {
525    cwd.ExpandTilde(home);
526 
527    Close();
528    new_cwd=new Path(&cwd);
529    new_cwd->Change(path,false);
530 
531    if(verify)
532       Open(new_cwd->path,CHANGE_DIR);
533    else
534    {
535       cwd.Set(new_cwd);
536       new_cwd=0;
537    }
538 }
539 
PathVerify(const Path & p)540 void FileAccess::PathVerify(const Path &p)
541 {
542    Close();
543    new_cwd=new Path(p);
544    Open(new_cwd->path,CHANGE_DIR);
545 }
546 
Chmod(const char * file,int m)547 void FileAccess::Chmod(const char *file,int m)
548 {
549    chmod_mode=m;
550    Open(file,CHANGE_MODE);
551 }
552 
SetError(int ec,const char * e)553 void FileAccess::SetError(int ec,const char *e)
554 {
555    if(ec==SEE_ERRNO && !saved_errno)
556       saved_errno=errno;
557    if(ec==NO_FILE && file && file[0] && !strstr(e,file))
558       error.vset(e," (",file.get(),")",NULL);
559    else
560       error.set(e);
561    error_code=ec;
562 }
563 
ClearError()564 void FileAccess::ClearError()
565 {
566    saved_errno=0;
567    error_code=OK;
568    error.set(0);
569 }
570 
Fatal(const char * e)571 void FileAccess::Fatal(const char *e)
572 {
573    SetError(FATAL,e);
574 }
575 
SetSuggestedFileName(const char * fn)576 void FileAccess::SetSuggestedFileName(const char *fn)
577 {
578    suggested_filename.set(0);
579    if(fn==0)
580       return;
581 
582    // don't allow subdirectories.
583    if(strchr(fn,'/') || strchr(fn,'\\') || strchr(fn,':'))
584       return;
585    for(int i=0; fn[i]; i++)
586    {
587       // don't allow control chars.
588       if(iscntrl((unsigned char)fn[i]))
589 	 return;
590    }
591    if(!*fn || *fn=='.')
592       return;
593    suggested_filename.set(fn);
594 }
595 
SetFileURL(const char * u)596 void FileAccess::SetFileURL(const char *u)
597 {
598    file_url.set(u);
599    if(new_cwd && mode==CHANGE_DIR)
600       new_cwd->SetURL(u);
601 }
602 
603 FileAccess *SessionPool::pool[pool_size];
604 
Reuse(FileAccess * f)605 void SessionPool::Reuse(FileAccess *f)
606 {
607    if(f==0)
608       return;
609    if(f->GetHostName()==0)
610    {
611       SMTask::Delete(f);
612       return;
613    }
614    f->Close();
615    f->SetPriority(0);
616    int i;
617    for(i=0; i<pool_size; i++)
618    {
619       assert(pool[i]!=f);
620       if(pool[i]==0)
621       {
622 	 pool[i]=f;
623 	 return;
624       }
625    }
626    for(i=0; i<pool_size; i++)
627    {
628       if(f->IsBetterThan(pool[i]))
629       {
630 	 SMTask::Delete(pool[i]);
631 	 pool[i]=f;
632 	 return;
633       }
634    }
635    SMTask::Delete(f);
636 }
637 
Print(FILE * f)638 void SessionPool::Print(FILE *f)
639 {
640    int arr[pool_size];
641    int n=0;
642    int i;
643 
644    for(i=0; i<pool_size; i++)
645    {
646       if(pool[i]==0)
647 	 continue;
648       int j;
649       for(j=0; j<n; j++)
650 	 if(pool[arr[j]]->SameLocationAs(pool[i]))
651 	    break;
652       if(j==n)
653 	 arr[n++]=i;
654    }
655 
656    // sort?
657 
658    for(i=0; i<n; i++)
659       fprintf(f,"%d\t%s\n",arr[i],pool[arr[i]]->GetConnectURL().get());
660 }
661 
GetSession(int n)662 FileAccess *SessionPool::GetSession(int n)
663 {
664    if(n<0 || n>=pool_size)
665       return 0;
666    FileAccess *s=pool[n];
667    pool[n]=0;
668    return s;
669 }
670 
Walk(int * n,const char * proto)671 FileAccess *SessionPool::Walk(int *n,const char *proto)
672 {
673    for( ; *n<pool_size; (*n)++)
674    {
675       if(pool[*n] && !strcmp(pool[*n]->GetProto(),proto))
676 	 return pool[*n];
677    }
678    return 0;
679 }
680 
ClearAll()681 void SessionPool::ClearAll()
682 {
683    int pass=0;
684    for(;;) {
685       int left=0;
686       for(int n=0; n<pool_size; n++) {
687 	 if(!pool[n])
688 	    continue;
689 	 if(pass==0)
690 	    pool[n]->Disconnect();
691 	 if(!pool[n]->IsConnected()) {
692 	    SMTask::Delete(pool[n]);
693 	    pool[n]=0;
694 	 } else {
695 	    left++;
696 	 }
697       }
698       if(left==0)
699 	 break;
700       SMTask::Schedule();
701       SMTask::Block();
702       pass++;
703    }
704 }
705 
SetTryTime(time_t t)706 void FileAccess::SetTryTime(time_t t)
707 {
708    if(t)
709       reconnect_timer.Reset(Time(t));
710    else
711       reconnect_timer.Stop();
712 }
713 
IsBetterThan(const FileAccess * fa) const714 bool FileAccess::IsBetterThan(const FileAccess *fa) const
715 {
716    return(SameProtoAs(fa) && this->IsConnected() > fa->IsConnected());
717 }
718 
Reconfig(const char *)719 void FileAccess::Reconfig(const char *) {}
ConnectVerify()720 void FileAccess::ConnectVerify() { mode=CONNECT_VERIFY; }
CurrentStatus()721 const char *FileAccess::CurrentStatus() { return ""; }
Buffered()722 int FileAccess::Buffered() { return 0; }
IOReady()723 bool FileAccess::IOReady() { return IsOpen(); }
IsConnected() const724 int FileAccess::IsConnected() const { return 0; }
UseCache(bool)725 void FileAccess::UseCache(bool) {}
NeedSizeDateBeforehand()726 bool FileAccess::NeedSizeDateBeforehand() { return false; }
Cleanup()727 void FileAccess::Cleanup() {}
CleanupThis()728 void FileAccess::CleanupThis() {}
MakeListInfo(const char * path)729 ListInfo *FileAccess::MakeListInfo(const char *path) { return 0; }
MakeGlob(const char * pattern)730 Glob *FileAccess::MakeGlob(const char *pattern) { return new NoGlob(pattern); }
MakeDirList(ArgV * a)731 DirList *FileAccess::MakeDirList(ArgV *a) { delete a; return 0; }
732 
CleanupAll()733 void FileAccess::CleanupAll()
734 {
735    xlist_for_each(FileAccess,all_fa,node,o)
736    {
737       Enter(o);
738       o->CleanupThis();
739       Leave(o);
740    }
741 }
742 
NextSameSite(FA * scan) const743 FileAccess *FileAccess::NextSameSite(FA *scan) const
744 {
745    if(scan==0)
746       scan=all_fa.first_obj();
747    else
748       scan=scan->all_fa_node.next_obj();
749    for( ; scan; scan=scan->all_fa_node.next_obj())
750       if(scan!=this && SameSiteAs(scan))
751 	 return scan;
752    return 0;
753 }
754 
New(const char * proto,const char * host,const char * port)755 FileAccess *FileAccess::New(const char *proto,const char *host,const char *port)
756 {
757    ClassInit();
758 
759    if(proto==0)
760       proto="file";
761 
762    if(!strcmp(proto,"slot"))
763    {
764       const FA *session=ConnectionSlot::FindSession(host);
765       return session?session->Clone():0;
766    }
767 
768    FA *session=Protocol::NewSession(proto);
769    if(!session)
770       return 0;
771 
772    const char *n_proto=session->ProtocolSubstitution(host);
773    if(n_proto && strcmp(n_proto,proto))
774    {
775       FA *n_session=Protocol::NewSession(n_proto);
776       if(n_session)
777       {
778 	 Delete(session);
779 	 session=n_session;
780 	 session->SetVisualProto(proto);
781       }
782    }
783 
784    if(host)
785       session->Connect(host,port);
786 
787    return session;
788 }
New(const ParsedURL * u,bool dummy)789 FileAccess *FileAccess::New(const ParsedURL *u,bool dummy)
790 {
791    const char *proto=u->proto?u->proto.get():"file";
792    FileAccess *s=New(proto,u->host);
793    if(!s)
794    {
795       if(!dummy)
796 	 return 0;
797       return new DummyNoProto(proto);
798    }
799    if(strcmp(proto,"slot"))
800       s->Connect(u->host,u->port);
801    if(u->user)
802       s->Login(u->user,u->pass);
803    // path?
804    return s;
805 }
806 
GetNewLocationFA() const807 FileAccess *FileAccess::GetNewLocationFA() const
808 {
809    if(!location)
810       return 0;
811    ParsedURL url(location,true);
812    if(!url.proto)
813       return 0;
814    return FileAccess::New(&url,true);
815 }
816 
817 
818 // FileAccess::Protocol implementation
819 xmap_p<FileAccess::Protocol> FileAccess::Protocol::proto_by_name;
820 
Protocol(const char * proto,SessionCreator * creator)821 FileAccess::Protocol::Protocol(const char *proto, SessionCreator *creator)
822 {
823    this->proto=proto;
824    this->New=creator;
825    proto_by_name.add(proto,this);
826 }
827 
FindProto(const char * proto)828 FileAccess::Protocol *FileAccess::Protocol::FindProto(const char *proto)
829 {
830    return proto_by_name.lookup(proto);
831 }
832 
NewSession(const char * proto)833 FileAccess *FileAccess::Protocol::NewSession(const char *proto)
834 {
835    Protocol *p;
836 
837    p=FindProto(proto);
838    if(p)
839       return p->New();
840 
841 #ifdef WITH_MODULES
842 #define PROTO_PREFIX "proto-"
843    const char *mod=xstring::cat(PROTO_PREFIX,proto,NULL);
844    void *map=module_load(mod,0,0);
845    if(map==0)
846    {
847       fprintf(stderr,"%s\n",module_error_message());
848       return 0;
849    }
850    p=FindProto(proto);
851    if(p)
852       return p->New();
853 #endif
854    return 0;
855 }
856 
857 // FileAccessOperation implementation
SetError(const char * e)858 void FileAccessOperation::SetError(const char *e)
859 {
860    error_text.set(e);
861    done=true;
862 }
SetErrorCached(const char * e)863 void FileAccessOperation::SetErrorCached(const char *e)
864 {
865    SetError(e);
866    error_text.append(_(" [cached]"));
867 }
868 
DirList(FileAccess * s,ArgV * a)869 DirList::DirList(FileAccess *s,ArgV *a)
870    : FileAccessOperation(s), buf(new Buffer()), args(a), color(false)
871 {
872 }
~DirList()873 DirList::~DirList()
874 {
875 }
876 
877 // ListInfo implementation
ListInfo(FileAccess * s,const char * p)878 ListInfo::ListInfo(FileAccess *s,const char *p)
879    : FileAccessOperation(s), exclude_prefix(0), exclude(0), need(0),
880    follow_symlinks(false), try_recursive(false), is_recursive(false)
881 {
882    if(session && p)
883    {
884       saved_cwd=session->GetCwd();
885       session->Chdir(p,false);
886    }
887 }
888 
PrepareToDie()889 void ListInfo::PrepareToDie()
890 {
891    if(session)
892       session->Close();
893    if(session && saved_cwd)
894       session->SetCwd(saved_cwd);
895 }
~ListInfo()896 ListInfo::~ListInfo() {}
897 
898 
899 // Path implementation
init()900 void FileAccess::Path::init()
901 {
902    device_prefix_len=0;
903    is_file=false;
904 }
~Path()905 FileAccess::Path::~Path()
906 {
907 }
Set(const char * new_path,bool new_is_file,const char * new_url,int new_device_prefix_len)908 void FileAccess::Path::Set(const char *new_path,bool new_is_file,const char *new_url,int new_device_prefix_len)
909 {
910    path.set(new_path);
911    is_file=new_is_file;
912    url.set(new_url);
913    device_prefix_len=new_device_prefix_len;
914 }
Set(const Path * o)915 void FileAccess::Path::Set(const Path *o)
916 {
917    Set(o->path,o->is_file,o->url,o->device_prefix_len);
918 }
Change(const char * new_path,bool new_is_file,const char * new_path_enc,int new_device_prefix_len)919 void FileAccess::Path::Change(const char *new_path,bool new_is_file,const char *new_path_enc,int new_device_prefix_len)
920 {
921    if(!new_path && new_path_enc)
922       new_path=url::decode(new_path_enc);
923    if(!new_path || !*new_path)
924       return;
925    const char *bn=basename_ptr(new_path);
926    if(!strcmp(bn,".") || !strcmp(bn,".."))
927       new_is_file=false;
928 
929    int path_index=0;
930    if(url)
931    {
932       path_index=url::path_index(url);
933       xstring new_url_path(url+path_index);
934       if(is_file)
935       {
936 	 dirname_modify(new_url_path);
937 	 if(!new_url_path[0])
938 	    new_url_path.set("/~");
939       }
940       if(new_url_path.last_char()!='/')
941 	 new_url_path.append('/');
942       if(new_path[0]=='/' || new_path[0]=='~' || new_device_prefix_len!=0)
943       {
944 	 bool have_slash=((new_path_enc?new_path_enc:new_path)[0]=='/');
945 	 new_url_path.set(have_slash?"":"/");
946       }
947       if(new_path_enc)
948 	 new_url_path.append(new_path_enc);
949       else
950 	 new_url_path.append(url::encode(new_path,URL_PATH_UNSAFE));
951       if(!new_is_file && url::dir_needs_trailing_slash(url) && new_url_path.last_char()!='/')
952 	 new_url_path.append('/');
953       Optimize(new_url_path,!strncmp(new_url_path,"/~",2));
954       url.truncate(path_index);
955       url.append(new_url_path);
956    }
957 
958    if(new_path[0]!='/' && new_path[0]!='~' && new_device_prefix_len==0
959    && path && path[0])
960    {
961       if(is_file)
962       {
963 	 dirname_modify(path);
964 	 if(!path[0])
965 	    path.set("~");
966       }
967       if(last_char(path)=='/')
968 	 new_path=xstring::format("%s%s",path.get(),new_path);
969       else
970 	 new_path=xstring::format("%s/%s",path.get(),new_path);
971    }
972    path.set(new_path);
973    device_prefix_len=new_device_prefix_len;
974    Optimize();
975    strip_trailing_slashes(path);
976    is_file=new_is_file;
977    if(!strcmp(path,"/") || !strcmp(path,"//"))
978       is_file=false;
979 
980    // sanity check
981    if(url)
982    {
983       ParsedURL u(url);
984       if(u.path.length()>1)
985 	 u.path.chomp('/');
986       if(!u.path.eq(path))
987       {
988 	 LogError(0,"URL mismatch %s [%s] vs %s, dropping URL\n",url.get(),u.path.get(),path.get());
989 	 url.set(0);
990       }
991    }
992 }
operator ==(const Path & p2) const993 bool FileAccess::Path::operator==(const Path &p2) const
994 {
995    const Path &p1=*this;
996    if(p1.is_file!=p2.is_file)
997       return false;
998    if(xstrcmp(p1.path,p2.path))
999       return false;
1000    if(xstrcmp(p1.url,p2.url))
1001       return false;
1002    return true;
1003 }
ExpandTilde(const Path & home)1004 void FileAccess::Path::ExpandTilde(const Path &home)
1005 {
1006    if(!home.path)
1007       return;
1008    if(path && path[0]=='~' && (path[1]=='/' || path[1]=='\0'))
1009    {
1010       device_prefix_len=home.device_prefix_len;
1011       if(path[1]=='\0')
1012 	 is_file=home.is_file;
1013    }
1014    if(url)
1015    {
1016       int pi=url::path_index(url);
1017       if(url[pi]=='/' && url[pi+1]=='~')
1018 	 pi++;
1019       expand_tilde(url,home.url?home.url.get():url::encode(home.path,URL_PATH_UNSAFE).get(),pi);
1020    }
1021    expand_tilde(path,home.path);
1022 }
1023 
1024 #include "DirColors.h"
1025 #include "LocalDir.h"
1026 #include "FileCopy.h"
1027 #include "modconfig.h"
1028 #ifndef MODULE_PROTO_FTP
1029 # include "ftpclass.h"
1030 # define _ftp Ftp::ClassInit()
1031 #else
1032 # define _ftp
1033 #endif
1034 #ifndef MODULE_PROTO_FILE
1035 # include "LocalAccess.h"
1036 # define _file LocalAccess::ClassInit()
1037 #else
1038 # define _file
1039 #endif
1040 #ifndef MODULE_PROTO_HTTP
1041 # include "Http.h"
1042 # define _http Http::ClassInit()
1043 #else
1044 # define _http
1045 #endif
1046 #ifndef MODULE_PROTO_FISH
1047 # include "Fish.h"
1048 # define _fish Fish::ClassInit()
1049 #else
1050 # define _fish
1051 #endif
1052 #ifndef MODULE_PROTO_SFTP
1053 # include "SFtp.h"
1054 # define _sftp SFtp::ClassInit()
1055 #else
1056 # define _sftp
1057 #endif
1058 bool FileAccess::class_inited;
1059 LsCache *FileAccess::cache;
ClassInit()1060 void FileAccess::ClassInit()
1061 {
1062    if(class_inited)
1063       return;
1064    class_inited=true;
1065    cache=new LsCache();
1066 
1067    SignalHook::ClassInit();
1068    ResMgr::ClassInit();
1069 
1070    if(!Log::global)
1071       Log::global=new Log("debug");
1072 
1073    _ftp;
1074    _file;
1075    _http;
1076    _fish;
1077    _sftp;
1078 
1079    // make it link in classes required by modules.
1080    LocalDirectory ld;
1081 }
ClassCleanup()1082 void FileAccess::ClassCleanup()
1083 {
1084    Protocol::ClassCleanup();
1085    call_dynamic_hook("lftp_network_cleanup");
1086    DirColors::DeleteInstance();
1087    delete cache;
1088    cache=0;
1089    FileCopy::fxp_create=0;
1090 }
1091 
operator =(FileAccess * p)1092 const FileAccessRef& FileAccessRef::operator=(FileAccess *p)
1093 {
1094    reuse();
1095    ptr=SMTask::MakeRef(p);
1096    return *this;
1097 }
1098 
1099 // hook-up gnulib...
1100 CDECL_BEGIN
1101 #include "md5.h"
1102 #include "glob.h"
1103 CDECL_END
1104 void *_md5_hook=(void*)md5_init_ctx;
1105 void *_glob_hook=(void*)glob;
1106