changeset 11:85bce13237cf

Add filter expressions to list command Refactor field parsing so it can be used for parsing fields, field assignments and filter expressions.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Mon, 31 Jul 2017 09:20:21 +0200
parents 17fb30016e64
children 8768fbd09bc5
files cmd.c pwm.1.xml
diffstat 2 files changed, 194 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/cmd.c	Fri Jul 28 15:53:57 2017 +0200
+++ b/cmd.c	Mon Jul 31 09:20:21 2017 +0200
@@ -31,6 +31,7 @@
 #ifdef	HAVE_READPASSPHRASE_H
 #include <readpassphrase.h>
 #endif /* READPASSPHRASE_H */
+#include <regex.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -94,7 +95,7 @@
 static struct cmd cmds[] = {
     { "i", "info", "info", "Show metadata information about the current file",
     cmd_info },
-    { "ls", "list", "list", "List entries", cmd_list },
+    { "ls", "list", "list [field~regex ...]", "List entries", cmd_list },
     { "c", "create", "create field=value ...", "Create entry", cmd_create },
     { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify },
     { "rm", "remove", "remove id", "Delete entry", cmd_remove },
@@ -114,56 +115,17 @@
 };
 
 static enum field_type
-parse_field_name(const char *name)
-{
-	int	i;
-
-	for (i = 0; i < (int)COUNTOF(field_names); i++) {
-		if (strcmp(field_names[i], name) == 0) {
-			return (i);
-		}
-	}
-
-	return (FIELD_UNKNOWN);
-}
-
-static enum field_type
-parse_field_assignment(char *arg, struct record *record)
+parse_field(char *field_arg, int sep, char **valuep)
 {
 	int	i;
 	size_t	field_name_len;
-	char	*value;
 
 	for (i = 0; i < (int)COUNTOF(field_names); i++) {
 		field_name_len = strlen(field_names[i]);
-		if ((strncmp(field_names[i], arg, field_name_len) == 0) &&
-		    (arg[field_name_len] == '=')){
-			value = arg + field_name_len + 1;
-			if (*value == '\0') {
-				/* skip empty assignments */
-				return (i);
-			}
-			switch (i) {
-			case FIELD_GROUP:
-				record->group = value;
-				break;
-			case FIELD_TITLE:
-				record->title = value;
-				break;
-			case FIELD_USERNAME:
-				record->username = value;
-				break;
-			case FIELD_PASSWORD:
-				record->password = value;
-				break;
-			case FIELD_NOTES:
-				record->notes = value;
-				break;
-			case FIELD_URL:
-				record->url = value;
-				break;
-			default:
-				return (FIELD_UNKNOWN);
+		if ((strncmp(field_names[i], field_arg, field_name_len) == 0) &&
+		    (field_arg[field_name_len] == sep)) {
+			if (valuep != NULL) {
+				*valuep = field_arg + field_name_len + 1;
 			}
 			return (i);
 		}
@@ -223,26 +185,125 @@
 static enum cmd_return
 cmd_list(struct pwm_ctx *ctx, int argc, char *argv[])
 {
-	union list_item	**list;
-	size_t		i;
+	int		retval = CMD_ERR;
+	int		i;
+	regex_t		*group_re = NULL;
+	regex_t		*title_re = NULL;
+	regex_t		*username_re = NULL;
+	regex_t		*notes_re = NULL;
+	regex_t		*url_re = NULL;
+	enum field_type	type;
+	char		*value;
+	regex_t		**repp;
+	int		errcode;
+	char		*errbuf;
+	size_t		errbuf_size;
+	union list_item	**list = NULL;
+	size_t		j;
+	struct record	*record;
 
-	if (argc != 1) {
-		return (CMD_USAGE);
+	for (i = 1; i < argc; i++) {
+		type = parse_field(argv[i], '~', &value);
+		if (type == FIELD_UNKNOWN) {
+			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
+			goto out;
+		}
+		if (value[0] == '\0') {
+			/* skip empty expressions */
+			continue;
+		}
+		switch (type) {
+		case FIELD_GROUP:
+			repp = &group_re;
+			break;
+		case FIELD_TITLE:
+			repp = &title_re;
+			break;
+		case FIELD_USERNAME:
+			repp = &username_re;
+			break;
+		case FIELD_NOTES:
+			repp = &notes_re;
+			break;
+		case FIELD_URL:
+			repp = &url_re;
+			break;
+		default:
+			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
+			goto out;
+		}
+
+		if (*repp == NULL) {
+			*repp = xmalloc(sizeof (regex_t));
+		} else {
+			regfree(*repp);
+		}
+		errcode = regcomp(*repp, value, REG_EXTENDED | REG_NOSUB);
+		if (errcode != 0) {
+			errbuf_size = regerror(errcode, *repp, "", 0);
+			errbuf = xmalloc(errbuf_size);
+			regerror(errcode, *repp, errbuf, errbuf_size);
+			fprintf(stderr, "bad regular expression \"%s\"\n",
+			    errbuf);
+			free(errbuf);
+
+			free(*repp);
+			*repp = NULL;
+
+			goto out;
+		}
 	}
 
 	list = pwfile_create_list(ctx);
-	for (i = 0; list[i] != NULL; i++) {
-		if (list[i]->any.type == ITEM_TYPE_GROUP) {
-			printf("[%s]\n", list[i]->group.group);
+	for (j = 0; list[j] != NULL; j++) {
+		if (list[j]->any.type == ITEM_TYPE_GROUP) {
+			printf("[%s]\n", list[j]->group.group);
 		} else {
-			printf("%4u %s\n", list[i]->record.id,
-			    (list[i]->record.title != NULL) ?
-			    list[i]->record.title : "");
+			record = pwfile_get_record(ctx, list[j]->record.id);
+			if (((group_re == NULL) || (regexec(group_re,
+			    record->group, 0, NULL, 0) == 0)) &&
+			    ((title_re == NULL) || (regexec(title_re,
+			    record->title, 0, NULL, 0) == 0)) &&
+			    ((username_re == NULL) || (regexec(username_re,
+			    record->username, 0, NULL, 0) == 0)) &&
+			    ((notes_re == NULL) || (regexec(notes_re,
+			    record->notes, 0, NULL, 0) == 0)) &&
+			    ((url_re == NULL) || (regexec(url_re,
+			    record->url, 0, NULL, 0) == 0))) {
+				printf("%4u %s\n", list[j]->record.id,
+				    (list[j]->record.title != NULL) ?
+				    list[j]->record.title : "");
+			}
+			pwfile_destroy_record(record);
 		}
 	}
+	retval = CMD_OK;
+
+out:
+	if (group_re != NULL) {
+		regfree(group_re);
+		free(group_re);
+	}
+	if (title_re != NULL) {
+		regfree(title_re);
+		free(title_re);
+	}
+	if (username_re != NULL) {
+		regfree(username_re);
+		free(username_re);
+	}
+	if (notes_re != NULL) {
+		regfree(notes_re);
+		free(notes_re);
+	}
+	if (url_re != NULL) {
+		regfree(url_re);
+		free(url_re);
+	}
+
 	pwfile_destroy_list(list);
 
-	return (CMD_OK);
+	return (retval);
 }
 
 static enum cmd_return
@@ -250,15 +311,44 @@
 {
 	int		i;
 	struct record record = { 0 };
+	enum field_type	type;
+	char		*value;
 
 	if (argc < 2) {
 		return (CMD_USAGE);
 	}
 
 	for (i = 1; i < argc; i++) {
-		if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) {
+		type = parse_field(argv[i], '=', &value);
+		if (type == FIELD_UNKNOWN) {
 			fprintf(stderr, "bad field assignment \"%s\"\n",
 			    argv[i]);
+		}
+		if (value[0] == '\0') {
+			/* skip empty assignments */
+			continue;
+		}
+		switch (type) {
+		case FIELD_GROUP:
+			record.group = value;
+			break;
+		case FIELD_TITLE:
+			record.title = value;
+			break;
+		case FIELD_USERNAME:
+			record.username = value;
+			break;
+		case FIELD_PASSWORD:
+			record.password = value;
+			break;
+		case FIELD_NOTES:
+			record.notes = value;
+			break;
+		case FIELD_URL:
+			record.url = value;
+			break;
+		default:
+			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
 			return (CMD_ERR);
 		}
 	}
@@ -273,7 +363,9 @@
 {
 	unsigned int	id;
 	int		i;
-	struct record record = { 0 };
+	struct record	record = { 0 };
+	enum field_type	type;
+	char		*value;
 
 	if (argc < 2) {
 		return (CMD_USAGE);
@@ -285,11 +377,39 @@
 	}
 
 	for (i = 2; i < argc; i++) {
-		if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) {
+		type = parse_field(argv[i], '=', &value);
+		if (type == FIELD_UNKNOWN) {
 			fprintf(stderr, "bad field assignment \"%s\"\n",
 			    argv[i]);
 			return (CMD_ERR);
 		}
+		if (value[0] == '\0') {
+			/* skip empty assignments */
+			continue;
+		}
+		switch (type) {
+		case FIELD_GROUP:
+			record.group = value;
+			break;
+		case FIELD_TITLE:
+			record.title = value;
+			break;
+		case FIELD_USERNAME:
+			record.username = value;
+			break;
+		case FIELD_PASSWORD:
+			record.password = value;
+			break;
+		case FIELD_NOTES:
+			record.notes = value;
+			break;
+		case FIELD_URL:
+			record.url = value;
+			break;
+		default:
+			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
+			return (CMD_ERR);
+		}
 	}
 
 	pwfile_modify_record(ctx, id, &record);
@@ -410,7 +530,7 @@
 	}
 
 	for (i = 2; i < argc; i++) {
-		type = parse_field_name(argv[i]);
+		type = parse_field(argv[i], '\0', NULL);
 		if (type < 0) {
 			fprintf(stderr, "bad field name \"%s\"\n", argv[i]);
 			return (CMD_ERR);
@@ -448,7 +568,7 @@
 		return (CMD_ERR);
 	}
 
-	type = parse_field_name(argv[2]);
+	type = parse_field(argv[2], '\0', NULL);
 	if (type < 0) {
 		fprintf(stderr, "bad field name \"%s\"\n", argv[2]);
 		return (CMD_ERR);
--- a/pwm.1.xml	Fri Jul 28 15:53:57 2017 +0200
+++ b/pwm.1.xml	Mon Jul 31 09:20:21 2017 +0200
@@ -34,7 +34,7 @@
       <email>guido+pwm@berhoerster.name</email>
       <personblurb/>
     </author>
-    <date>28 July, 2017</date>
+    <date>31 July, 2017</date>
   </info>
   <refmeta>
     <refentrytitle>pwm</refentrytitle>
@@ -201,12 +201,21 @@
           <listitem>
             <cmdsynopsis>
               <command>list</command>
+              <arg choice="opt" rep="repeat">
+                <replaceable>field</replaceable>~<replaceable>regex</replaceable>
+              </arg>
             </cmdsynopsis>
             <cmdsynopsis>
               <command>ls</command>
+              <arg choice="opt" rep="repeat">
+                <replaceable>field</replaceable>~<replaceable>regex</replaceable>
+              </arg>
               <sbr/>
             </cmdsynopsis>
-            <para>List all password database entries.</para>
+            <para>List password database entries. If one or more filter
+            expressions are specified, limit the displayed entries to those
+            whose <replaceable>field</replaceable> content matches the extended
+            regular expression <replaceable>regex</replaceable>.</para>
           </listitem>
         </varlistentry>
         <varlistentry>
@@ -529,6 +538,8 @@
       <manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>locale</refentrytitle>
       <manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>regex</refentrytitle>
+      <manvolnum>5</manvolnum></citerefentry>,
       <link xlink:href="https://pwsafe.org/"/></para>
   </refsect1>
 </refentry>