1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2017 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 "trio.h"
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <time.h>
26 #include <unistd.h>
27 #include <sys/time.h>
28 #include <assert.h>
29 #include "xmalloc.h"
30 #include "FileAccess.h"
31 #include "CmdExec.h"
32 #include "alias.h"
33 #include "SignalHook.h"
34 #include "CharReader.h"
35 #include "LsCache.h"
36 #include "complete.h"
37 #include "lftp_rl.h"
38 #include "url.h"
39 #include "ResMgr.h"
40 #include "ColumnOutput.h"
41 #include "FileSetOutput.h"
42 #include "OutputJob.h"
43 #include "misc.h"
44 
45 CDECL_BEGIN
46 #include <glob.h>
47 
48 #define USE_VARARGS 1
49 #define PREFER_STDARG 1
50 #include <readline/readline.h>
51 CDECL_END
52 
53 #ifndef GLOB_PERIOD
54 # define GLOB_PERIOD 0
55 #endif
56 
57 static char *bash_dequote_filename (const char *text, int quote_char);
58 static int lftp_char_is_quoted(const char *string,int eindex);
59 
60 static int len;    // lenght of the word to complete
61 static int cindex; // index in completion array
62 static const char *const *array;
63 static char **vars=NULL;
64 static FileSet *glob_res=NULL;
65 static bool inhibit_tilde;
66 
67 static bool shell_cmd;
68 static bool quote_glob;
69 static bool quote_glob_basename;
70 
command_generator(const char * text,int state)71 char *command_generator(const char *text,int state)
72 {
73    const char *name;
74    static const Alias *alias;
75 
76    /* If this is a new word to complete, initialize now. */
77    if(!state)
78    {
79       cindex=0;
80       alias=Alias::base;
81    }
82 
83    /* Return the next name which partially matches from the command list. */
84    while ((name=CmdExec::CmdByIndex(cindex))!=0)
85    {
86       cindex++;
87       if(name[0]=='.' && len==0)
88 	 continue;   // skip hidden commands
89       if(strncasecmp(name,text,len)==0)
90 	 return(xstrdup(name));
91    }
92 
93    while(alias)
94    {
95       const Alias *tmp=alias;
96       alias=alias->next;
97       if(strncasecmp(tmp->alias,text,len)==0)
98          return(xstrdup(tmp->alias));
99    }
100 
101    /* If no names matched, then return NULL. */
102    return(NULL);
103 }
104 
file_generator(const char * text,int state)105 static char *file_generator(const char *text,int state)
106 {
107    /* If this is a new word to complete, initialize now. */
108    if(!state)
109       cindex=0;
110 
111    if(glob_res==NULL)
112       return NULL;
113 
114    while((*glob_res)[cindex])
115    {
116       const char *name=(*glob_res)[cindex++]->name;
117 
118       if(!name[0])
119 	 continue;
120       return(xstrdup(name));
121    }
122 
123    return NULL;
124 }
125 
126 static bool bookmark_prepend_bm;
bookmark_generator(const char * text,int s)127 static char *bookmark_generator(const char *text,int s)
128 {
129    static int state;
130    const char *t;
131    if(!s)
132    {
133       state=0;
134       lftp_bookmarks.Rewind();
135    }
136    for(;;)
137    {
138       switch(state)
139       {
140       case 0:
141 	 t=lftp_bookmarks.CurrentKey();
142 	 if(!t)
143 	 {
144 	    state=1;
145 	    break;
146 	 }
147 	 if(!lftp_bookmarks.Next())
148 	    state=1;
149 	 if(bookmark_prepend_bm)
150 	 {
151 	    xstring& e=xstring::get_tmp("bm:");
152 	    t=e.append_url_encoded(t,URL_HOST_UNSAFE);
153 	 }
154 	 if(strncmp(t,text,len)==0)
155 	    return xstrdup(t);
156 	 break;
157       case 1:
158 	 return 0;
159       }
160    }
161 }
162 
array_generator(const char * text,int state)163 static char *array_generator(const char *text,int state)
164 {
165    const char *name;
166 
167    /* If this is a new word to complete, initialize now. */
168    if(!state)
169       cindex=0;
170 
171    if(array==NULL)
172       return NULL;
173 
174    while((name=array[cindex++])!=NULL)
175    {
176       if(!name[0])
177 	 continue;
178       if(strncmp(name,text,len)==0)
179 	 return(xstrdup(name));
180    }
181 
182    array=NULL;
183    return NULL;
184 }
185 
vars_generator(const char * text,int state)186 static char *vars_generator(const char *text,int state)
187 {
188    const char *name;
189 
190    /* If this is a new word to complete, initialize now. */
191    if(!state)
192       cindex=0;
193 
194    if(vars==NULL)
195       return NULL;
196 
197    while((name=vars[cindex++])!=NULL)
198    {
199       if(!name[0])
200 	 continue;
201       char *text0=string_alloca(len+2);
202       strncpy(text0,text,len);
203       text0[len]=0;
204       if(ResMgr::VarNameCmp(name,text0)!=ResMgr::DIFFERENT)
205 	 return(xstrdup(name));
206       if(strchr(text0,':')==0)
207       {
208 	 strcat(text0,":");
209 	 if(ResMgr::VarNameCmp(name,text0)!=ResMgr::DIFFERENT)
210 	    return(xstrdup(name));
211       }
212    }
213 
214    return NULL;
215 }
216 
not_dir(char * f)217 static bool not_dir(char *f)
218 {
219    struct stat st;
220    f=tilde_expand(f);
221    bool res=(stat(f,&st)==-1 || !S_ISDIR(st.st_mode));
222    free(f);
223    return res;
224 }
225 
ignore_non_dirs(char ** matches)226 int ignore_non_dirs(char **matches)
227 {
228    // filter out non-dirs.
229    int out=1;
230    for(int i=1; matches[i]; i++)
231    {
232       if(!not_dir(matches[i]))
233 	 matches[out++]=matches[i];
234       else
235 	 free(matches[i]);
236    }
237    matches[out]=0;
238    if(out==1)
239    {
240       // we have only the LCD prefix. Handle it carefully.
241       char *f=matches[0];
242       int len=strlen(f);
243       if((len>2 && f[len-1]=='/') // all files, no dirs.
244       || not_dir(f))		 // or single non dir.
245       {
246 	 // all files, no dirs.
247 	 free(f);
248 	 matches[0]=0;
249       }
250    }
251    return 0;
252 }
253 
find_word(const char * p)254 static const char *find_word(const char *p)
255 {
256    while(CmdExec::is_space(*p))
257       p++;
258    return p;
259 }
copy_word_skip(const char ** p_in,char * buf,int n)260 static bool copy_word_skip(const char **p_in,char *buf,int n)
261 {
262    const char *&p=*p_in;
263    char in_quotes=0;
264    for(;;)
265    {
266       if(!*p)
267 	 break;
268       if(in_quotes)
269       {
270 	 if(*p==in_quotes)
271 	 {
272 	    in_quotes=0,p++;
273 	    continue;
274 	 }
275 	 else if(*p=='\\' && CmdExec::quotable(p[1],in_quotes))
276 	    p++;
277 	 if(buf && n>0)
278 	    *buf++=*p,n--;
279 	 p++;
280 	 continue;
281       }
282       if(CmdExec::is_space(*p))
283 	 break;
284       if(*p=='\\' && CmdExec::quotable(p[1],in_quotes))
285 	 p++;
286       else if(CmdExec::is_quote(*p))
287       {
288 	 in_quotes=*p++;
289 	 continue;
290       }
291       if(buf && n>0)
292 	 *buf++=*p,n--;
293       p++;
294    }
295    if(n>0 && buf)
296       *buf=0;
297    return n>0;
298 }
299 // returns false when buffer overflows
copy_word(char * buf,const char * p,int n)300 static bool copy_word(char *buf,const char *p,int n)
301 {
302    return copy_word_skip(&p,buf,n);
303 }
skip_word(const char * p)304 static const char *skip_word(const char *p)
305 {
306    copy_word_skip(&p,0,0);
307    return p;
308 }
309 
310 enum completion_type
311 {
312    LOCAL, LOCAL_DIR, REMOTE_FILE, REMOTE_DIR, BOOKMARK, COMMAND,
313    STRING_ARRAY, VARIABLE, NO_COMPLETION
314 };
315 
316 // cmd: ptr to command line being completed
317 // start: location of the word being completed
cmd_completion_type(const char * cmd,int start)318 static completion_type cmd_completion_type(const char *cmd,int start)
319 {
320    const char *w=0;
321    char buf[20];  // no commands longer
322    TouchedAlias *used_aliases=0;
323 
324    // try to guess whether the completion word is remote
325    for(;;)
326    {
327       w=find_word(cmd);
328       if(w-cmd == start) // first word is command
329 	 return COMMAND;
330       if(w[0]=='!')
331 	 shell_cmd=true;
332       if(w[0]=='#')
333 	 return NO_COMPLETION;
334       if(w[0]=='!')
335       {
336 	 shell_cmd=quote_glob=true;
337 	 return LOCAL;
338       }
339       if(w[0]=='?')  // help
340 	 return COMMAND;
341       if(w[0]=='(')
342       {
343 	 start-=(w+1-cmd);
344 	 cmd=w+1;
345 	 continue;
346       }
347       if(!copy_word(buf,w,sizeof(buf))
348       || buf[0]==0)
349       {
350 	 TouchedAlias::FreeChain(used_aliases);
351 	 return LOCAL;
352       }
353       const char *alias=Alias::Find(buf);
354       if(alias && !TouchedAlias::IsTouched(alias,used_aliases))
355       {
356 	 used_aliases=new TouchedAlias(alias,used_aliases);
357 	 int buf_len=strlen(buf);
358 	 const char *cmd1=xstring::cat(alias,w+buf_len,NULL);
359 	 cmd=alloca_strdup(cmd1);
360 	 start=start-buf_len+strlen(alias);
361 	 continue;
362       }
363       const char *full=CmdExec::GetFullCommandName(buf);
364       if(full!=buf)
365 	 strcpy(buf,full);
366       TouchedAlias::FreeChain(used_aliases);
367       break;
368    }
369 
370    for(const char *p=cmd+start; p>cmd; )
371    {
372       p--;
373       if((*p=='>' || *p=='|')
374       && !lftp_char_is_quoted(cmd,p-cmd))
375 	 return LOCAL;
376       if(!CmdExec::is_space(*p))
377 	 break;
378    }
379 
380    if(!strcmp(buf,"shell"))
381       shell_cmd=quote_glob=true;
382    if(!strcmp(buf,"glob")
383    || !strcmp(buf,"mget")
384    || !strcmp(buf,"mput")
385    || !strcmp(buf,"mrm"))
386       quote_glob=true;
387 
388    if(!strcmp(buf,"cls"))
389       quote_glob_basename=true;
390 
391    if(!strcmp(buf,"cd")
392    || !strcmp(buf,"mkdir"))
393       return REMOTE_DIR; /* append slash automatically */
394 
395    if(!strcmp(buf,"cat")
396    || !strcmp(buf,"ls")
397    || !strcmp(buf,"cls")
398    || !strcmp(buf,"du")
399    || !strcmp(buf,"edit")
400    || !strcmp(buf,"find")
401    || !strcmp(buf,"recls")
402    || !strcmp(buf,"rels")
403    || !strcmp(buf,"more")
404    || !strcmp(buf,"mrm")
405    || !strcmp(buf,"mv")
406    || !strcmp(buf,"mmv")
407    || !strcmp(buf,"nlist")
408    || !strcmp(buf,"rm")
409    || !strcmp(buf,"rmdir")
410    || !strcmp(buf,"bzcat")
411    || !strcmp(buf,"bzmore")
412    || !strcmp(buf,"zcat")
413    || !strcmp(buf,"zmore"))
414       return REMOTE_FILE;
415 
416    if(!strcmp(buf,"open")
417    || !strcmp(buf,"lftp"))
418       return BOOKMARK;
419 
420    if(!strcmp(buf,"help"))
421       return COMMAND;
422 
423    bool was_o=false;
424    bool was_N=false;
425    bool was_O=false;
426    bool have_R=false;
427    bool have_N=false;
428    bool second=false;
429    int second_start=-1;
430    for(int i=start; i>4; i--)
431    {
432       if(!CmdExec::is_space(rl_line_buffer[i-1]))
433 	 break;
434       if(!strncmp(rl_line_buffer+i-3,"-o",2) && CmdExec::is_space(rl_line_buffer[i-4]))
435       {
436 	 was_o=true;
437 	 break;
438       }
439       if(!strncmp(rl_line_buffer+i-3,"-N",2) && CmdExec::is_space(rl_line_buffer[i-4]))
440       {
441 	 was_N=true;
442 	 break;
443       }
444       if(i-14 >= 0 && !strncmp(rl_line_buffer+i-13, "--newer-than",12) && CmdExec::is_space(rl_line_buffer[i-14]))
445       {
446 	 was_N=true;
447 	 break;
448       }
449       if(!strncmp(rl_line_buffer+i-3,"-O",2) && CmdExec::is_space(rl_line_buffer[i-4]))
450       {
451 	 was_O=true;
452 	 break;
453       }
454    }
455    w=skip_word(find_word(cmd));
456    if(*w)
457    {
458       w=find_word(w);
459       second_start=w-cmd;
460       if(w-cmd==start)	// we complete second word
461 	 second=true;
462    }
463 
464    if(re_match(cmd," -[a-zA-Z]*R[a-zA-Z]* ",0))
465       have_R=true;
466 
467    int arg=0;
468    bool opt_stop=false;
469    for(w=cmd; *w; )
470    {
471       w=find_word(w);
472       if(w-cmd==start || w-cmd+CmdExec::is_quote(*w)==start)
473 	 break;
474       if(w[0]!='-' || opt_stop)
475 	 arg++;
476       else if(re_match(w,"^-[a-zA-Z]*N ",0))
477 	 have_N=true;
478       if(!strncmp(w,"-- ",3))
479 	 opt_stop=true;
480       w=skip_word(w);
481    }
482 
483    if(!strcmp(buf,"get")
484    || !strcmp(buf,"pget")
485    || !strcmp(buf,"get1"))
486    {
487       if(was_O)
488 	 return LOCAL_DIR;
489       if(!was_o)
490 	 return REMOTE_FILE;
491    }
492    if(!strcmp(buf,"mget"))
493       if(!was_O)
494 	 return REMOTE_FILE;
495    if(!strcmp(buf,"put"))
496       if(was_o)
497 	 return REMOTE_FILE;
498    if(!strcmp(buf,"put")
499    || !strcmp(buf,"mput"))
500       if(was_O)
501 	 return REMOTE_DIR;
502    if(!strcmp(buf,"mirror"))
503    {
504       if(was_N)
505 	 return LOCAL;
506       if(have_N)
507 	 arg--;
508       if(have_R ^ (arg!=1))
509 	 return LOCAL_DIR;
510       if(arg>2)
511 	 return NO_COMPLETION;
512       return REMOTE_DIR;
513    }
514    if(!strcmp(buf,"bookmark"))
515    {
516       if(second)
517       {
518 	 array=bookmark_subcmd;
519 	 return STRING_ARRAY;
520       }
521       else
522 	 return BOOKMARK;
523    }
524    if(!strcmp(buf,"chmod"))
525    {
526       if(second)
527 	 return NO_COMPLETION;
528       else
529 	 return REMOTE_FILE;
530    }
531    if(!strcmp(buf,"glob")
532    || !strcmp(buf,"command")
533    || !strcmp(buf,"queue"))
534    {
535       if(second)
536 	 return COMMAND;
537       else
538       {
539 	 // FIXME: infinite alias expansion is possible.
540 	 if(second_start>0 && start>second_start && (int)strlen(cmd)>second_start)
541 	    return cmd_completion_type(cmd+second_start,start-second_start);
542 	 return REMOTE_FILE;
543       }
544    }
545    if(!strcmp(buf,"cache"))
546    {
547       if(second)
548       {
549 	 array=cache_subcmd;
550 	 return STRING_ARRAY;
551       }
552       else
553 	 return NO_COMPLETION;
554    }
555 
556    if(!strcmp(buf,"set"))
557    {
558       if(second)
559       {
560          if(!vars)
561             vars=ResMgr::Generator();
562          return VARIABLE;
563       }
564       else
565          return NO_COMPLETION;
566    }
567 
568    if(!strcmp(buf,"lcd"))
569       return LOCAL_DIR;
570 
571    return LOCAL;
572 }
573 
glob_quote(char * out,const char * in,int len)574 static void glob_quote(char *out,const char *in,int len)
575 {
576    while(len>0)
577    {
578       switch(*in)
579       {
580       case '*': case '?': case '[': case ']':
581 	 if(!quote_glob)
582 	    *out++='\\';
583 	 break;
584       case '\\':
585 	 switch(in[1])
586 	 {
587 	 case '*': case '?': case '[': case ']': case '\\':
588 	    *out++=*in++;  // copy the backslash.
589 	    break;
590 	 default:
591 	    in++; // skip it.
592 	    break;
593 	 }
594 	 break;
595       }
596       *out++=*in;
597       in++;
598       len--;
599    }
600    *out=0;
601 }
602 
603 CmdExec *completion_shell;
604 int remote_completion=0;
605 
606 static bool force_remote=false;
607 
608 /* Attempt to complete on the contents of TEXT.  START and END show the
609    region of TEXT that contains the word to complete.  We can use the
610    entire line in case we want to do some simple parsing.  Return the
611    array of matches, or NULL if there aren't any. */
lftp_completion(const char * text,int start,int end)612 static char **lftp_completion (const char *text,int start,int end)
613 {
614    FileSetOutput fso;
615 
616    completion_shell->RestoreCWD();
617 
618    if(start>end)  // workaround for a bug in readline
619       start=end;
620 
621    GlobURL *rg=0;
622 
623    rl_completion_append_character=' ';
624    rl_ignore_some_completions_function=0;
625    shell_cmd=false;
626    quote_glob=false;
627    quote_glob_basename=false;
628    inhibit_tilde=false;
629    delete glob_res;
630    glob_res=0;
631 
632    completion_type type=cmd_completion_type(rl_line_buffer,start);
633 
634    len=end-start;
635 
636    char *(*generator)(const char *text,int state) = 0;
637 
638    switch(type)
639    {
640    case NO_COMPLETION:
641       rl_attempted_completion_over = 1;
642       return 0;
643    case COMMAND:
644       generator = command_generator;
645       break;
646    case BOOKMARK:
647       bookmark_prepend_bm=false;
648       generator = bookmark_generator;
649       break;
650    case STRING_ARRAY:
651       generator = array_generator;
652       break;
653    case VARIABLE:
654       generator = vars_generator;
655       break;
656 
657       char *pat;
658    case LOCAL:
659    case LOCAL_DIR: {
660       if(force_remote || (url::is_url(text) && remote_completion))
661       {
662 	 if(type==LOCAL_DIR)
663 	    type=REMOTE_DIR;
664 	 goto really_remote;
665       }
666    really_local:
667       fso.parse_res(ResMgr::Query("cmd:cls-completion-default", 0));
668 
669       bool tilde_expanded=false;
670       const char *home=getenv("HOME");
671       int home_len=xstrlen(home);
672       pat=string_alloca((len+home_len)*2+10);
673       if(len>0 && home_len>0 && text[0]=='~' && (len==1 || text[1]=='/'))
674       {
675 	 glob_quote(pat,home,home_len);
676 	 glob_quote(pat+strlen(pat),text+1,len-1);
677 	 if(len==1)
678 	    strcat(pat,"/");
679 	 tilde_expanded=true;
680 	 inhibit_tilde=false;
681       }
682       else
683       {
684 	 glob_quote(pat,text,len);
685 	 inhibit_tilde=true;
686       }
687 
688       /* if we want case-insensitive matching, we need to match everything
689        * in the dir and weed it ourselves (let the generator do it), since
690        * glob() has no casefold option */
691       if(fso.patterns_casefold) {
692 	 rl_variable_bind("completion-ignore-case", "1");
693 
694 	 /* strip back to the last / */
695 	 char *sl = strrchr(pat, '/');
696 	 if(sl) *++sl = 0;
697 	 else pat[0] = 0;
698       } else {
699 	 rl_variable_bind("completion-ignore-case", "0");
700       }
701 
702       strcat(pat,"*");
703 
704       glob_t pglob;
705       glob(pat,GLOB_PERIOD,NULL,&pglob);
706       glob_res=new FileSet;
707       for(int i=0; i<(int)pglob.gl_pathc; i++)
708       {
709 	 char *src=pglob.gl_pathv[i];
710 	 if(tilde_expanded && home_len>0)
711 	 {
712 	    src+=home_len-1;
713 	    *src='~';
714 	 }
715 	 if(!strcmp(basename_ptr(src), ".")) continue;
716 	 if(!strcmp(basename_ptr(src), "..")) continue;
717 	 if(type==LOCAL_DIR && not_dir(src)) continue;
718 
719 	 FileInfo *f = new FileInfo;
720 
721 	 f->LocalFile(src, false);
722 	 glob_res->Add(f);
723       }
724       globfree(&pglob);
725 
726       rl_filename_completion_desired=1;
727       generator = file_generator;
728       break;
729    }
730    case REMOTE_FILE:
731    case REMOTE_DIR: {
732       if(!remote_completion && !force_remote)
733       {
734 	 if(type==REMOTE_DIR)
735 	    type=LOCAL_DIR;
736 	 goto really_local;
737       }
738    really_remote:
739       if(!strncmp(text,"bm:",3) && !strchr(text,'/'))
740       {
741 	 bookmark_prepend_bm=true;
742 	 generator=bookmark_generator;
743 	 rl_completion_append_character='/';
744 	 break;
745       }
746 
747       pat=string_alloca(len*2+10);
748       glob_quote(pat,text,len);
749 
750       if(pat[0]=='~' && pat[1]==0)
751 	 strcat(pat,"/");
752 
753       if(pat[0]=='~' && pat[1]=='/')
754 	 inhibit_tilde=false;
755       else
756 	 inhibit_tilde=true;
757       strcat(pat,"*");
758 
759       completion_shell->session->DontSleep();
760 
761       SignalHook::ResetCount(SIGINT);
762       glob_res=NULL;
763       rg=new GlobURL(completion_shell->session,pat,
764 		     type==REMOTE_DIR?GlobURL::DIRS_ONLY:GlobURL::ALL);
765 
766       rl_save_prompt();
767 
768       fso.parse_res(ResMgr::Query("cmd:cls-completion-default", 0));
769 
770       if(rg)
771       {
772 	 rg->NoInhibitTilde();
773 	 if(fso.patterns_casefold) {
774 	    rl_variable_bind("completion-ignore-case", "1");
775 	    rg->CaseFold();
776 	 } else
777 	    rl_variable_bind("completion-ignore-case", "0");
778 
779 	 Timer status_timer;
780 	 status_timer.SetMilliSeconds(20);
781 
782 	 for(;;)
783 	 {
784 	    SMTask::Schedule();
785 	    if(rg->Done())
786 	       break;
787 	    if(SignalHook::GetCount(SIGINT))
788 	    {
789 	       SignalHook::ResetCount(SIGINT);
790 	       rl_attempted_completion_over = 1;
791 	       delete rg;
792 
793 	       rl_restore_prompt();
794 	       rl_clear_message();
795 
796 	       return 0;
797 	    }
798 
799 	    if(!fso.quiet)
800 	    {
801 	       /* don't set blank status; if we're operating from cache,
802 		* that's all we'll get and it'll look ugly: */
803 	       const char *ret = rg->Status();
804 	       if(*ret)
805 	       {
806 		  if(status_timer.Stopped())
807 		  {
808 		     rl_message ("%s> ", ret);
809 		     status_timer.SetResource("cmd:status-interval",0);
810 		  }
811 	       }
812 	    }
813 
814 	    SMTask::Block();
815 	 }
816 	 glob_res=new FileSet(rg->GetResult());
817 	 glob_res->rewind();
818       }
819       rl_restore_prompt();
820       rl_clear_message();
821 
822       if(glob_res->get_fnum()==1)
823       {
824 	 FileInfo *info=glob_res->curr();
825 	 rl_completion_append_character=' ';
826 	 if(info->defined&info->TYPE && info->filetype==info->DIRECTORY)
827 	    rl_completion_append_character='/';
828       }
829       rl_filename_completion_desired=1;
830       generator = file_generator;
831       break;
832    }
833    } /* end switch */
834 
835    assert(generator);
836 
837    char quoted=((lftp_char_is_quoted(rl_line_buffer,start) &&
838 		 strchr(rl_completer_quote_characters,rl_line_buffer[start-1]))
839 		? rl_line_buffer[start-1] : 0);
840    xstring_ca textq(bash_dequote_filename(text, quoted));
841    len=strlen(textq);
842 
843    char **matches=lftp_rl_completion_matches(textq,generator);
844 
845    if(rg)
846       delete rg;
847 
848    if(vars)
849    {
850       // delete vars?
851    }
852 
853    if(!matches)
854    {
855       rl_attempted_completion_over = 1;
856       return 0;
857    }
858 
859    if(type==REMOTE_DIR)
860       rl_completion_append_character='/';
861 
862    return matches;
863 }
864 
lftp_line_complete()865 extern "C" void lftp_line_complete()
866 {
867    delete glob_res;
868    glob_res=0;
869 }
870 
871 enum { COMPLETE_DQUOTE,COMPLETE_SQUOTE,COMPLETE_BSQUOTE };
872 #define completion_quoting_style COMPLETE_BSQUOTE
873 
874 /* **************************************************************** */
875 /*								    */
876 /*	 Functions for quoting strings to be re-read as input	    */
877 /*								    */
878 /* **************************************************************** */
879 
880 /* Return a new string which is the single-quoted version of STRING.
881    Used by alias and trap, among others. */
882 static char *
single_quote(char * string)883 single_quote (char *string)
884 {
885   int c;
886   char *result, *r, *s;
887 
888   result = (char *)xmalloc (3 + (4 * strlen (string)));
889   r = result;
890   *r++ = '\'';
891 
892   for (s = string; s && (c = *s); s++)
893     {
894       *r++ = c;
895 
896       if (c == '\'')
897 	{
898 	  *r++ = '\\';	/* insert escaped single quote */
899 	  *r++ = '\'';
900 	  *r++ = '\'';	/* start new quoted string */
901 	}
902     }
903 
904   *r++ = '\'';
905   *r = '\0';
906 
907   return (result);
908 }
909 
910 /* Quote STRING using double quotes.  Return a new string. */
911 static char *
double_quote(char * string)912 double_quote (char *string)
913 {
914   int c;
915   char *result, *r, *s;
916 
917   result = (char *)xmalloc (3 + (2 * strlen (string)));
918   r = result;
919   *r++ = '"';
920 
921   for (s = string; s && (c = *s); s++)
922     {
923       switch (c)
924         {
925 	case '$':
926 	case '`':
927 	  if(!shell_cmd)
928 	     goto def;
929 	case '"':
930 	case '\\':
931 	  *r++ = '\\';
932 	default: def:
933 	  *r++ = c;
934 	  break;
935         }
936     }
937 
938   *r++ = '"';
939   *r = '\0';
940 
941   return (result);
942 }
943 
944 /* Quote special characters in STRING using backslashes.  Return a new
945    string. */
946 static char *
backslash_quote(char * string)947 backslash_quote (char *string)
948 {
949   int c;
950   char *result, *r, *s;
951   char *bn = basename_ptr(string);
952 
953   result = (char*)xmalloc (2 * strlen (string) + 1);
954 
955   for (r = result, s = string; s && (c = *s); s++)
956     {
957       switch (c)
958 	{
959 	case '(': case ')':
960 	case '{': case '}':			/* reserved words */
961 	case '^':
962 	case '$': case '`':			/* expansion chars */
963 	  if(!shell_cmd)
964 	    goto def;
965 	case '*': case '[': case '?': case ']':	/* globbing chars */
966 	  if(!shell_cmd && !quote_glob && (!quote_glob_basename || s<bn))
967 	    goto def;
968 	  /*fallthrough*/
969 	case ' ': case '\t': case '\n':		/* IFS white space */
970 	case '"': case '\'': case '\\':		/* quoting chars */
971 	case '|': case '&': case ';':		/* shell metacharacters */
972 	case '<': case '>': case '!':
973 	  *r++ = '\\';
974 	  *r++ = c;
975 	  break;
976 	case '~':				/* tilde expansion */
977 	  if (s == string && inhibit_tilde)
978 	    *r++ = '.', *r++ = '/';
979 	  goto def;
980 	case '#':				/* comment char */
981 	  if(!shell_cmd)
982 	    goto def;
983 	  if (s == string)
984 	    *r++ = '\\';
985 	  /* FALLTHROUGH */
986 	default: def:
987 	  *r++ = c;
988 	  break;
989 	}
990     }
991 
992   *r = '\0';
993   return (result);
994 }
995 
996 #if 0 // no need yet
997 static int
998 contains_shell_metas (char *string)
999 {
1000   char *s;
1001 
1002   for (s = string; s && *s; s++)
1003     {
1004       switch (*s)
1005 	{
1006 	case ' ': case '\t': case '\n':		/* IFS white space */
1007 	case '\'': case '"': case '\\':		/* quoting chars */
1008 	case '|': case '&': case ';':		/* shell metacharacters */
1009 	case '(': case ')': case '<': case '>':
1010 	case '!': case '{': case '}':		/* reserved words */
1011 	case '*': case '[': case '?': case ']':	/* globbing chars */
1012 	case '^':
1013 	case '$': case '`':			/* expansion chars */
1014 	  return (1);
1015 	case '#':
1016 	  if (s == string)			/* comment char */
1017 	    return (1);
1018 	  /* FALLTHROUGH */
1019 	default:
1020 	  break;
1021 	}
1022     }
1023 
1024   return (0);
1025 }
1026 #endif //0
1027 
1028 /* Filename quoting for completion. */
1029 /* A function to strip quotes that are not protected by backquotes.  It
1030    allows single quotes to appear within double quotes, and vice versa.
1031    It should be smarter. */
1032 static char *
bash_dequote_filename(const char * text,int quote_char)1033 bash_dequote_filename (const char *text, int quote_char)
1034 {
1035   char *ret;
1036   const char *p;
1037   char *r;
1038   int l, quoted;
1039 
1040   l = strlen (text);
1041   ret = (char*)xmalloc (l + 1);
1042   for (quoted = quote_char, p = text, r = ret; p && *p; p++)
1043     {
1044       /* Allow backslash-quoted characters to pass through unscathed. */
1045       if (*p == '\\')
1046 	{
1047 	  *r++ = *++p;
1048 	  if (*p == '\0')
1049 	    break;
1050 	  continue;
1051 	}
1052       /* Close quote. */
1053       if (quoted && *p == quoted)
1054         {
1055           quoted = 0;
1056           continue;
1057         }
1058       /* Open quote. */
1059       if (quoted == 0 && (*p == '\'' || *p == '"'))
1060         {
1061           quoted = *p;
1062           continue;
1063         }
1064       *r++ = *p;
1065     }
1066   *r = '\0';
1067   return ret;
1068 }
1069 
1070 /* Quote characters that the readline completion code would treat as
1071    word break characters with backslashes.  Pass backslash-quoted
1072    characters through without examination. */
1073 static char *
quote_word_break_chars(char * text)1074 quote_word_break_chars (char *text)
1075 {
1076   char *ret, *r, *s;
1077   int l;
1078 
1079   l = strlen (text);
1080   ret = (char*)xmalloc ((2 * l) + 1);
1081   for (s = text, r = ret; *s; s++)
1082     {
1083       /* Pass backslash-quoted characters through, including the backslash. */
1084       if (*s == '\\')
1085 	{
1086 	  *r++ = '\\';
1087 	  *r++ = *++s;
1088 	  if (*s == '\0')
1089 	    break;
1090 	  continue;
1091 	}
1092       /* OK, we have an unquoted character.  Check its presence in
1093 	 rl_completer_word_break_characters. */
1094       if (strchr (rl_completer_word_break_characters, *s))
1095         *r++ = '\\';
1096       *r++ = *s;
1097     }
1098   *r = '\0';
1099   return ret;
1100 }
1101 
1102 /* Quote a filename using double quotes, single quotes, or backslashes
1103    depending on the value of completion_quoting_style.  If we're
1104    completing using backslashes, we need to quote some additional
1105    characters (those that readline treats as word breaks), so we call
1106    quote_word_break_chars on the result. */
1107 static char *
bash_quote_filename(char * s,int rtype,char * qcp)1108 bash_quote_filename (char *s, int rtype, char *qcp)
1109 {
1110   char *rtext, *mtext, *ret;
1111   int rlen, cs;
1112 
1113   rtext = (char *)NULL;
1114 
1115   /* If RTYPE == MULT_MATCH, it means that there is
1116      more than one match.  In this case, we do not add
1117      the closing quote or attempt to perform tilde
1118      expansion.  If RTYPE == SINGLE_MATCH, we try
1119      to perform tilde expansion, because single and double
1120      quotes inhibit tilde expansion by the shell. */
1121 
1122   mtext = s;
1123 #if 0
1124   if (mtext[0] == '~' && rtype == SINGLE_MATCH)
1125     mtext = bash_tilde_expand (s);
1126 #endif
1127 
1128   cs = completion_quoting_style;
1129   /* Might need to modify the default completion style based on *qcp,
1130      since it's set to any user-provided opening quote. */
1131   if (*qcp == '"')
1132     cs = COMPLETE_DQUOTE;
1133   else if (*qcp == '\'')
1134     cs = COMPLETE_SQUOTE;
1135 #if defined (BANG_HISTORY)
1136   else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE &&
1137 	   history_expansion_inhibited == 0 && strchr (mtext, '!'))
1138     cs = COMPLETE_BSQUOTE;
1139 
1140   if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE &&
1141         history_expansion_inhibited == 0 && strchr (mtext, '!'))
1142     {
1143       cs = COMPLETE_BSQUOTE;
1144       *qcp = '\0';
1145     }
1146 #endif
1147 
1148   switch (cs)
1149     {
1150     case COMPLETE_DQUOTE:
1151       rtext = double_quote (mtext);
1152       break;
1153     case COMPLETE_SQUOTE:
1154       rtext = single_quote (mtext);
1155       break;
1156     case COMPLETE_BSQUOTE:
1157       rtext = backslash_quote (mtext);
1158       break;
1159     }
1160 
1161   if (mtext != s)
1162     free (mtext);
1163 
1164   /* We may need to quote additional characters: those that readline treats
1165      as word breaks that are not quoted by backslash_quote. */
1166   if (rtext && cs == COMPLETE_BSQUOTE)
1167     {
1168       mtext = quote_word_break_chars (rtext);
1169       free (rtext);
1170       rtext = mtext;
1171     }
1172 
1173   /* Leave the opening quote intact.  The readline completion code takes
1174      care of avoiding doubled opening quotes. */
1175   rlen = strlen (rtext);
1176   ret = (char*)xmalloc (rlen + 1);
1177   strcpy (ret, rtext);
1178 
1179   /* If there are multiple matches, cut off the closing quote. */
1180   if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE)
1181     ret[rlen - 1] = '\0';
1182   free (rtext);
1183   return ret;
1184 }
1185 
skip_quoted(const char * s,int i,char q)1186 static int skip_quoted(const char *s, int i, char q)
1187 {
1188    while(s[i] && s[i]!=q)
1189    {
1190       if(s[i]=='\\' && s[i+1])
1191 	 i++;
1192       i++;
1193    }
1194    if(s[i])
1195       i++;
1196    return i;
1197 }
1198 
lftp_char_is_quoted(const char * string,int eindex)1199 int lftp_char_is_quoted(const char *string,int eindex)
1200 {
1201   int i, pass_next;
1202 
1203   for (i = pass_next = 0; i <= eindex; i++)
1204     {
1205       if (pass_next)
1206         {
1207           pass_next = 0;
1208           if (i >= eindex)
1209             return 1;
1210           continue;
1211         }
1212       else if (string[i] == '"' || string[i] == '\'')
1213         {
1214 	  char quote = string[i];
1215           i = skip_quoted (string, ++i, quote);
1216           if (i > eindex)
1217             return 1;
1218           i--;  /* the skip functions increment past the closing quote. */
1219         }
1220       else if (string[i] == '\\')
1221         {
1222           pass_next = 1;
1223           continue;
1224         }
1225     }
1226   return (0);
1227 }
1228 
1229 extern "C" int (*rl_last_func)(int,int);
lftp_complete_remote(int count,int key)1230 static int lftp_complete_remote(int count,int key)
1231 {
1232    if(rl_last_func == lftp_complete_remote)
1233       rl_last_func = rl_complete;
1234 
1235    force_remote = true;
1236    int ret=rl_complete(count,key);
1237    force_remote = false;
1238    return ret;
1239 }
1240 
lftp_rl_getc(FILE * file)1241 int lftp_rl_getc(FILE *file)
1242 {
1243    SignalHook::DoCount(SIGINT);
1244    SMTaskRef<CharReader> rr(new CharReader(fileno(file)));
1245    CharReader& r=*rr.get_non_const();
1246    for(;;)
1247    {
1248       SMTask::Schedule();
1249       int res=r.GetChar();
1250       if(res==r.EOFCHAR)
1251 	 return EOF;
1252       if(res!=r.NOCHAR)
1253 	 return res;
1254       lftp_rl_redisplay_maybe();
1255       SMTask::Block();
1256       if(SignalHook::GetCount(SIGINT)>0)
1257       {
1258 	 if(rl_line_buffer && rl_end>0)
1259 	    rl_kill_full_line(0,0);
1260 	 return '\n';
1261       }
1262    }
1263 }
1264 
1265 extern int lftp_slot(int,int);
1266 
1267 /* Tell the GNU Readline library how to complete.  We want to try to complete
1268    on command names if this is the first word in the line, or on filenames
1269    if not. */
lftp_readline_init()1270 void lftp_readline_init ()
1271 {
1272    lftp_rl_init(
1273       "lftp",		      // rl_readline_name
1274       lftp_completion,	      // rl_attempted_completion_function
1275       lftp_rl_getc,	      // rl_getc_function
1276       "\"'",		      // rl_completer_quote_characters
1277       " \t\n\"'",	      // rl_completer_word_break_characters
1278       " \t\n\\\"'>;|&()*?[]~!",// rl_filename_quote_characters
1279       bash_quote_filename,    // rl_filename_quoting_function
1280       bash_dequote_filename,  // rl_filename_dequoting_function
1281       lftp_char_is_quoted);   // rl_char_is_quoted_p
1282 
1283    lftp_rl_add_defun("complete-remote",lftp_complete_remote,-1);
1284    lftp_rl_bind("Meta-Tab","complete-remote");
1285 
1286    lftp_rl_add_defun("slot-change",lftp_slot,-1);
1287    char key[7];
1288    strcpy(key,"Meta-N");
1289    for(int i=0; i<10; i++)
1290    {
1291       key[5]='0'+i;
1292       lftp_rl_bind(key,"slot-change");
1293    }
1294 }
1295 
completion_display_list(char ** matches,int len)1296 extern "C" void completion_display_list (char **matches, int len)
1297 {
1298    JobRef<OutputJob> b(new OutputJob((FDStream *) NULL, "completion"));
1299 
1300    if(glob_res) {
1301       /* Our last completion action was of files, and we kept that
1302        * data around.  Take the files in glob_res which are in matches
1303        * and put them in another FileSet.  (This is a little wasteful,
1304        * since we're going to use it briefly and discard it, but it's
1305        * not worth adding temporary-filtering options to FileSet.) */
1306       FileSet tmp;
1307       for(int i = 1; i <= len; i++) {
1308 	 FileInfo *fi = glob_res->FindByName(matches[i]);
1309 	 assert(fi);
1310 	 tmp.Add(new FileInfo(*fi));
1311       }
1312 
1313       FileSetOutput fso;
1314       fso.config(b);
1315 
1316       fso.parse_res(ResMgr::Query("cmd:cls-completion-default", 0));
1317 
1318       fso.print(tmp, b);
1319    } else {
1320       /* Just pass it through ColumnInfo. */
1321       ColumnOutput c;
1322       for(int i = 1; i <= len; i++) {
1323 	 c.append();
1324 	 c.add(matches[i], "");
1325       }
1326       c.print(b, b->GetWidth(), b->IsTTY());
1327    }
1328 
1329    b->PutEOF();
1330 
1331    while(!b->Done())
1332    {
1333       SMTask::Schedule();
1334       if(SignalHook::GetCount(SIGINT))
1335       {
1336 	 SignalHook::ResetCount(SIGINT);
1337 	 break;
1338       }
1339    }
1340 }
1341