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