summary refs log tree commit diff
path: root/complete.c
diff options
context:
space:
mode:
Diffstat (limited to 'complete.c')
-rw-r--r--complete.c381
1 files changed, 381 insertions, 0 deletions
diff --git a/complete.c b/complete.c
new file mode 100644
index 0000000..ef2478f
--- /dev/null
+++ b/complete.c
@@ -0,0 +1,381 @@
+/*	$OpenBSD: complete.c,v 1.33 2019/05/16 12:44:17 florian Exp $	*/
+/*	$NetBSD: complete.c,v 1.10 1997/08/18 10:20:18 lukem Exp $	*/
+
+/*-
+ * Copyright (c) 1997 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SMALL
+
+/*
+ * FTP user program - command and file completion routines
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ftp_var.h"
+
+static int	     comparstr(const void *, const void *);
+static unsigned char complete_ambiguous(char *, int, StringList *);
+static unsigned char complete_command(char *, int);
+static unsigned char complete_local(char *, int);
+static unsigned char complete_remote(char *, int);
+static void          ftpvis(char *, size_t, const char *, size_t);
+
+static int
+comparstr(const void *a, const void *b)
+{
+	return (strcmp(*(char **)a, *(char **)b));
+}
+
+/*
+ * Determine if complete is ambiguous. If unique, insert.
+ * If no choices, error. If unambiguous prefix, insert that.
+ * Otherwise, list choices. words is assumed to be filtered
+ * to only contain possible choices.
+ * Args:
+ *	word	word which started the match
+ *	list	list by default
+ *	words	stringlist containing possible matches
+ */
+static unsigned char
+complete_ambiguous(char *word, int list, StringList *words)
+{
+	char insertstr[PATH_MAX * 2];
+	char *lastmatch;
+	int i, j;
+	size_t matchlen, wordlen;
+
+	wordlen = strlen(word);
+	if (words->sl_cur == 0)
+		return (CC_ERROR);	/* no choices available */
+
+	if (words->sl_cur == 1) {	/* only once choice available */
+		char *p = words->sl_str[0] + wordlen;
+		ftpvis(insertstr, sizeof(insertstr), p, strlen(p));
+		if (el_insertstr(el, insertstr) == -1)
+			return (CC_ERROR);
+		else
+			return (CC_REFRESH);
+	}
+
+	if (!list) {
+		lastmatch = words->sl_str[0];
+		matchlen = strlen(lastmatch);
+		for (i = 1 ; i < words->sl_cur ; i++) {
+			for (j = wordlen ; j < strlen(words->sl_str[i]); j++)
+				if (lastmatch[j] != words->sl_str[i][j])
+					break;
+			if (j < matchlen)
+				matchlen = j;
+		}
+		if (matchlen > wordlen) {
+			ftpvis(insertstr, sizeof(insertstr),
+			    lastmatch + wordlen, matchlen - wordlen);
+			if (el_insertstr(el, insertstr) == -1)
+				return (CC_ERROR);
+			else
+					/*
+					 * XXX: really want CC_REFRESH_BEEP
+					 */
+				return (CC_REFRESH);
+		}
+	}
+
+	putc('\n', ttyout);
+	qsort(words->sl_str, words->sl_cur, sizeof(char *), comparstr);
+	list_vertical(words);
+	return (CC_REDISPLAY);
+}
+
+/*
+ * Complete a command
+ */
+static unsigned char
+complete_command(char *word, int list)
+{
+	struct cmd *c;
+	StringList *words;
+	size_t wordlen;
+	unsigned char rv;
+
+	words = sl_init();
+	wordlen = strlen(word);
+
+	for (c = cmdtab; c->c_name != NULL; c++) {
+		if (wordlen > strlen(c->c_name))
+			continue;
+		if (strncmp(word, c->c_name, wordlen) == 0)
+			sl_add(words, c->c_name);
+	}
+
+	rv = complete_ambiguous(word, list, words);
+	sl_free(words, 0);
+	return (rv);
+}
+
+/*
+ * Complete a local file
+ */
+static unsigned char
+complete_local(char *word, int list)
+{
+	StringList *words;
+	char dir[PATH_MAX];
+	char *file;
+	DIR *dd;
+	struct dirent *dp;
+	unsigned char rv;
+
+	if ((file = strrchr(word, '/')) == NULL) {
+		dir[0] = '.';
+		dir[1] = '\0';
+		file = word;
+	} else {
+		if (file == word) {
+			dir[0] = '/';
+			dir[1] = '\0';
+		} else {
+			(void)strlcpy(dir, word, (size_t)(file - word) + 1);
+		}
+		file++;
+	}
+
+	if ((dd = opendir(dir)) == NULL)
+		return (CC_ERROR);
+
+	words = sl_init();
+
+	for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
+		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+			continue;
+		if (strlen(file) > dp->d_namlen)
+			continue;
+		if (strncmp(file, dp->d_name, strlen(file)) == 0) {
+			char *tcp;
+
+			tcp = strdup(dp->d_name);
+			if (tcp == NULL)
+				errx(1, "Can't allocate memory for local dir");
+			sl_add(words, tcp);
+		}
+	}
+	closedir(dd);
+
+	rv = complete_ambiguous(file, list, words);
+	sl_free(words, 1);
+	return (rv);
+}
+
+/*
+ * Complete a remote file
+ */
+static unsigned char
+complete_remote(char *word, int list)
+{
+	static StringList *dirlist;
+	static char	 lastdir[PATH_MAX];
+	StringList	*words;
+	char		 dir[PATH_MAX];
+	char		*file, *cp;
+	int		 i;
+	unsigned char	 rv;
+
+	char *dummyargv[] = { "complete", dir, NULL };
+
+	if ((file = strrchr(word, '/')) == NULL) {
+		dir[0] = '.';
+		dir[1] = '\0';
+		file = word;
+	} else {
+		cp = file;
+		while (*cp == '/' && cp > word)
+			cp--;
+		(void)strlcpy(dir, word, (size_t)(cp - word + 2));
+		file++;
+	}
+
+	if (dirchange || strcmp(dir, lastdir) != 0) {	/* dir not cached */
+		char *emesg;
+
+		sl_free(dirlist, 1);
+		dirlist = sl_init();
+
+		mflag = 1;
+		emesg = NULL;
+		if (debug)
+			(void)putc('\n', ttyout);
+		while ((cp = remglob(dummyargv, 0, &emesg)) != NULL) {
+			char *tcp;
+
+			if (!mflag)
+				continue;
+			if (*cp == '\0') {
+				mflag = 0;
+				continue;
+			}
+			tcp = strrchr(cp, '/');
+			if (tcp)
+				tcp++;
+			else
+				tcp = cp;
+			tcp = strdup(tcp);
+			if (tcp == NULL)
+				errx(1, "Can't allocate memory for remote dir");
+			sl_add(dirlist, tcp);
+		}
+		if (emesg != NULL) {
+			fprintf(ttyout, "\n%s\n", emesg);
+			return (CC_REDISPLAY);
+		}
+		(void)strlcpy(lastdir, dir, sizeof lastdir);
+		dirchange = 0;
+	}
+
+	words = sl_init();
+	for (i = 0; i < dirlist->sl_cur; i++) {
+		cp = dirlist->sl_str[i];
+		if (strlen(file) > strlen(cp))
+			continue;
+		if (strncmp(file, cp, strlen(file)) == 0)
+			sl_add(words, cp);
+	}
+	rv = complete_ambiguous(file, list, words);
+	sl_free(words, 0);
+	return (rv);
+}
+
+/*
+ * Generic complete routine
+ */
+unsigned char
+complete(EditLine *el, int ch)
+{
+	static char word[FTPBUFLEN];
+	static int lastc_argc, lastc_argo;
+	struct cmd *c;
+	const LineInfo *lf;
+	int celems, dolist;
+	size_t len;
+
+	lf = el_line(el);
+	len = lf->lastchar - lf->buffer;
+	if (len >= sizeof(line))
+		return (CC_ERROR);
+	(void)memcpy(line, lf->buffer, len);
+	line[len] = '\0';
+	cursor_pos = line + (lf->cursor - lf->buffer);
+	lastc_argc = cursor_argc;	/* remember last cursor pos */
+	lastc_argo = cursor_argo;
+	makeargv();			/* build argc/argv of current line */
+
+	if (cursor_argo >= sizeof(word))
+		return (CC_ERROR);
+
+	dolist = 0;
+			/* if cursor and word is same, list alternatives */
+	if (lastc_argc == cursor_argc && lastc_argo == cursor_argo
+	    && strncmp(word, margv[cursor_argc], cursor_argo) == 0)
+		dolist = 1;
+	else if (cursor_argo)
+		memcpy(word, margv[cursor_argc], cursor_argo);
+	word[cursor_argo] = '\0';
+
+	if (cursor_argc == 0)
+		return (complete_command(word, dolist));
+
+	c = getcmd(margv[0]);
+	if (c == (struct cmd *)-1 || c == 0)
+		return (CC_ERROR);
+	celems = strlen(c->c_complete);
+
+		/* check for 'continuation' completes (which are uppercase) */
+	if ((cursor_argc > celems) && (celems > 0)
+	    && isupper((unsigned char)c->c_complete[celems - 1]))
+		cursor_argc = celems;
+
+	if (cursor_argc > celems)
+		return (CC_ERROR);
+
+	switch (c->c_complete[cursor_argc - 1]) {
+	case 'l':			/* local complete */
+	case 'L':
+		return (complete_local(word, dolist));
+	case 'r':			/* remote complete */
+	case 'R':
+		if (connected != -1) {
+			fputs("\nMust be logged in to complete.\n", ttyout);
+			return (CC_REDISPLAY);
+		}
+		return (complete_remote(word, dolist));
+	case 'c':			/* command complete */
+	case 'C':
+		return (complete_command(word, dolist));
+	case 'n':			/* no complete */
+		return (CC_ERROR);
+	}
+
+	return (CC_ERROR);
+}
+
+/*
+ * Copy characters from src into dst, \ quoting characters that require it.
+ */
+static void
+ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
+{
+	size_t	di, si;
+
+	di = si = 0;
+	while (di + 1 < dstlen && si < srclen && src[si] != '\0') {
+		switch (src[si]) {
+		case '\\':
+		case ' ':
+		case '\t':
+		case '\r':
+		case '\n':
+		case '"':
+			/* Need room for two characters and NUL, avoiding
+			 * incomplete escape sequences at end of dst. */
+			if (di + 3 >= dstlen)
+				break;
+			dst[di++] = '\\';
+			/* FALLTHROUGH */
+		default:
+			dst[di++] = src[si++];
+		}
+	}
+	if (dstlen != 0)
+		dst[di] = '\0';
+}
+#endif /* !SMALL */