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