summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hollerbach <mail@marcel-hollerbach.de>2018-12-26 15:31:57 +0100
committerMarcel Hollerbach <mail@marcel-hollerbach.de>2019-02-12 11:19:28 +0100
commit48e5684b3c37b337edd7004e68fc0690b58a84e6 (patch)
treed163c7484bd367e969d40780a0f9ad5fc7e4ddde
parentc3d69f66a69c0def357a5c373a13343e1c01ff5d (diff)
ecore: here comes a command line object
the mixin for now can carry a command, which can be setted as an string. The string is then parsed again, this is done in order to make sure that everything that needs escaping really is escaped or parsed correctly. Differential Revision: https://phab.enlightenment.org/D7516
-rw-r--r--src/Makefile_Ecore.am12
-rw-r--r--src/lib/ecore/Ecore_Eo.h1
-rw-r--r--src/lib/ecore/efl_core_command_line.c267
-rw-r--r--src/lib/ecore/efl_core_command_line.eo80
-rw-r--r--src/lib/ecore/meson.build4
-rw-r--r--src/tests/ecore/efl_app_suite.c1
-rw-r--r--src/tests/ecore/efl_app_suite.h1
-rw-r--r--src/tests/ecore/efl_app_test_cml.c85
-rw-r--r--src/tests/ecore/efl_app_test_cml.eo4
-rw-r--r--src/tests/ecore/meson.build22
10 files changed, 473 insertions, 4 deletions
diff --git a/src/Makefile_Ecore.am b/src/Makefile_Ecore.am
index 5f10ea7f2e..fd6074dec3 100644
--- a/src/Makefile_Ecore.am
+++ b/src/Makefile_Ecore.am
@@ -52,6 +52,10 @@ ecore_eolian_files_public = \
52 lib/ecore/efl_view_model.eo \ 52 lib/ecore/efl_view_model.eo \
53 lib/ecore/efl_core_env.eo \ 53 lib/ecore/efl_core_env.eo \
54 lib/ecore/efl_core_proc_env.eo \ 54 lib/ecore/efl_core_proc_env.eo \
55 lib/ecore/efl_core_command_line.eo
56
57ecore_test_eolian_files = \
58 tests/ecore/efl_app_test_cml.eo
55 59
56ecore_eolian_files = \ 60ecore_eolian_files = \
57 $(ecore_eolian_files_legacy) \ 61 $(ecore_eolian_files_legacy) \
@@ -60,10 +64,14 @@ ecore_eolian_files = \
60ecore_eolian_c = $(ecore_eolian_files:%.eo=%.eo.c) 64ecore_eolian_c = $(ecore_eolian_files:%.eo=%.eo.c)
61ecore_eolian_h = $(ecore_eolian_files:%.eo=%.eo.h) \ 65ecore_eolian_h = $(ecore_eolian_files:%.eo=%.eo.h) \
62 $(ecore_eolian_files_legacy:%.eo=%.eo.legacy.h) 66 $(ecore_eolian_files_legacy:%.eo=%.eo.legacy.h)
67ecore_test_c = $(ecore_test_eolian_files:%.eo=%.eo.c)
68ecore_test_h = $(ecore_test_eolian_files:%.eo=%.eo.h)
63 69
64BUILT_SOURCES += \ 70BUILT_SOURCES += \
65 $(ecore_eolian_c) \ 71 $(ecore_eolian_c) \
66 $(ecore_eolian_h) 72 $(ecore_eolian_h) \
73 $(ecore_test_c) \
74 $(ecore_test_h)
67 75
68ecoreeolianfilesdir = $(datadir)/eolian/include/ecore-@VMAJ@ 76ecoreeolianfilesdir = $(datadir)/eolian/include/ecore-@VMAJ@
69ecoreeolianfiles_DATA = $(ecore_eolian_files_public) lib/ecore/efl_loop_timer.eo 77ecoreeolianfiles_DATA = $(ecore_eolian_files_public) lib/ecore/efl_loop_timer.eo
@@ -100,6 +108,7 @@ lib/ecore/ecore_job.c \
100lib/ecore/ecore_main.c \ 108lib/ecore/ecore_main.c \
101lib/ecore/ecore_event_message.c \ 109lib/ecore/ecore_event_message.c \
102lib/ecore/ecore_event_message_handler.c \ 110lib/ecore/ecore_event_message_handler.c \
111lib/ecore/efl_core_command_line.c \
103lib/ecore/efl_core_env.c \ 112lib/ecore/efl_core_env.c \
104lib/ecore/efl_core_proc_env.c \ 113lib/ecore/efl_core_proc_env.c \
105lib/ecore/efl_app.c \ 114lib/ecore/efl_app.c \
@@ -339,6 +348,7 @@ tests/ecore/efl_app_test_loop.c \
339tests/ecore/efl_app_test_loop_fd.c \ 348tests/ecore/efl_app_test_loop_fd.c \
340tests/ecore/efl_app_test_loop_timer.c \ 349tests/ecore/efl_app_test_loop_timer.c \
341tests/ecore/efl_app_test_promise.c \ 350tests/ecore/efl_app_test_promise.c \
351tests/ecore/efl_app_test_cml.c \
342tests/ecore/efl_app_test_env.c \ 352tests/ecore/efl_app_test_env.c \
343tests/ecore/efl_app_suite.c \ 353tests/ecore/efl_app_suite.c \
344tests/ecore/efl_app_suite.h 354tests/ecore/efl_app_suite.h
diff --git a/src/lib/ecore/Ecore_Eo.h b/src/lib/ecore/Ecore_Eo.h
index 348b0f5b6d..3615219c38 100644
--- a/src/lib/ecore/Ecore_Eo.h
+++ b/src/lib/ecore/Ecore_Eo.h
@@ -28,6 +28,7 @@
28 28
29#include "efl_core_env.eo.h" 29#include "efl_core_env.eo.h"
30#include "efl_core_proc_env.eo.h" 30#include "efl_core_proc_env.eo.h"
31#include "efl_core_command_line.eo.h"
31 32
32#include "efl_loop_message.eo.h" 33#include "efl_loop_message.eo.h"
33#include "efl_loop_message_handler.eo.h" 34#include "efl_loop_message_handler.eo.h"
diff --git a/src/lib/ecore/efl_core_command_line.c b/src/lib/ecore/efl_core_command_line.c
new file mode 100644
index 0000000000..74ae690c26
--- /dev/null
+++ b/src/lib/ecore/efl_core_command_line.c
@@ -0,0 +1,267 @@
1#ifdef HAVE_CONFIG_H
2# include <config.h>
3#endif
4
5#define EFL_CORE_COMMAND_LINE_PROTECTED
6
7#include <Efl_Core.h>
8
9#define MY_CLASS EFL_CORE_COMMAND_LINE_MIXIN
10
11typedef struct {
12 Eina_Bool filled;
13 char *string_command;
14 Eina_Array *command;
15} Efl_Core_Command_Line_Data;
16
17static Eina_Array *
18_unescape(const char *s)
19{
20 Eina_Array *args;
21 const char *p;
22 char *tmp = NULL, *d = NULL;
23 if (!s) return NULL;
24
25 Eina_Bool in_quote_dbl = EINA_FALSE;
26 Eina_Bool in_quote = EINA_FALSE;
27
28 args = eina_array_new(16);
29 if (!args) return NULL;
30 for (p = s; *p; p++)
31 {
32 if (!tmp) tmp = d = strdup(p);
33 if (tmp)
34 {
35 if (in_quote_dbl)
36 {
37 switch (*p)
38 {
39 case '\"':
40 in_quote_dbl = EINA_FALSE;
41 *d = 0;
42 eina_array_push(args, eina_stringshare_add(tmp));
43 free(tmp);
44 tmp = d = NULL;
45 break;
46 case '\\':
47 p++;
48 EINA_FALLTHROUGH
49 default:
50 *d = *p;
51 d++;
52 break;
53 }
54 }
55 else if (in_quote)
56 {
57 switch (*p)
58 {
59 case '\'':
60 in_quote = EINA_FALSE;
61 *d = 0;
62 eina_array_push(args, eina_stringshare_add(tmp));
63 free(tmp);
64 tmp = d = NULL;
65 break;
66 case '\\':
67 p++;
68 EINA_FALLTHROUGH
69 default:
70 *d = *p;
71 d++;
72 break;
73 }
74 }
75 else
76 {
77 switch (*p)
78 {
79 case ' ':
80 case '\t':
81 case '\r':
82 case '\n':
83 *d = 0;
84 eina_array_push(args, eina_stringshare_add(tmp));
85 free(tmp);
86 tmp = d = NULL;
87 break;
88 case '\"':
89 in_quote_dbl = EINA_TRUE;
90 break;
91 case '\'':
92 in_quote = EINA_TRUE;
93 break;
94 case '\\':
95 p++;
96 EINA_FALLTHROUGH
97 default:
98 *d = *p;
99 d++;
100 break;
101 }
102 }
103 }
104 }
105 if (tmp)
106 {
107 *d = 0;
108 eina_array_push(args, eina_stringshare_add(tmp));
109 free(tmp);
110 }
111 return args;
112}
113
114static char *
115_escape(const char *s)
116{
117 Eina_Bool need_quote = EINA_FALSE;
118 const char *p;
119 char *s2 = malloc((strlen(s) * 2) + 1 + 2), *d;
120
121 if (!s2) return NULL;
122
123 for (p = s; *p; p++)
124 {
125 switch (*p)
126 {
127 case '\'':
128 case '\"':
129 case '$':
130 case '#':
131 case ';':
132 case '&':
133 case '`':
134 case '|':
135 case '(':
136 case ')':
137 case '[':
138 case ']':
139 case '{':
140 case '}':
141 case '>':
142 case '<':
143 case '\n':
144 case '\r':
145 case '\t':
146 case ' ':
147 need_quote = EINA_TRUE;
148 default:
149 break;
150 }
151 }
152
153 d = s2;
154 if (need_quote)
155 {
156 *d = '\"';
157 d++;
158 }
159 for (p = s; *p; p++, d++)
160 {
161 switch (*p)
162 {
163 case '\\':
164 case '\'':
165 case '\"':
166 *d = '\\';
167 d++;
168 EINA_FALLTHROUGH
169 default:
170 *d = *p;
171 break;
172 }
173 }
174 if (need_quote)
175 {
176 *d = '\"';
177 d++;
178 }
179 *d = 0;
180 return s2;
181}
182
183EOLIAN static const char*
184_efl_core_command_line_command_get(const Eo *obj EINA_UNUSED, Efl_Core_Command_Line_Data *pd)
185{
186 return eina_strdup(pd->string_command);
187}
188
189EOLIAN static Eina_Accessor*
190_efl_core_command_line_command_access(Eo *obj EINA_UNUSED, Efl_Core_Command_Line_Data *pd)
191{
192 return pd->command ? eina_array_accessor_new(pd->command) : NULL;
193}
194
195static void
196_remove_invalid_chars(char *command)
197{
198 for (unsigned int i = 0; i < strlen(command); ++i)
199 {
200 char c = command[i];
201 if (c < 0x20 || c == 0x7f)
202 command[i] = '\x12';
203 }
204}
205
206EOLIAN static Eina_Bool
207_efl_core_command_line_command_array_set(Eo *obj EINA_UNUSED, Efl_Core_Command_Line_Data *pd, Eina_Array *array)
208{
209 EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->filled, EINA_FALSE);
210 Eina_Strbuf *command = eina_strbuf_new();
211 unsigned int i = 0;
212
213 pd->command = eina_array_new(eina_array_count(array));
214 for (i = 0; i < (array ? eina_array_count(array) : 0); ++i)
215 {
216 char *content = eina_array_data_get(array, i);
217 char *param = calloc(1, strlen(content));
218
219 if (!param)
220 {
221 free(param);
222 while (eina_array_count(pd->command) > 0)
223 eina_stringshare_del(eina_array_pop(pd->command));
224 eina_array_free(pd->command);
225 pd->command = NULL;
226 eina_array_free(array);
227 return EINA_FALSE;
228 }
229
230 //build the command
231 if (i != 0)
232 eina_strbuf_append(command, " ");
233 eina_strbuf_append(command, _escape(content));
234 //convert string to stringshare
235 strcpy(param, content);
236 _remove_invalid_chars(param);
237 eina_array_push(pd->command, eina_stringshare_add(param));
238 free(param);
239 }
240 pd->string_command = eina_strbuf_release(command);
241 pd->filled = EINA_TRUE;
242 eina_array_free(array);
243
244 return EINA_TRUE;
245}
246
247EOLIAN static Eina_Bool
248_efl_core_command_line_command_string_set(Eo *obj EINA_UNUSED, Efl_Core_Command_Line_Data *pd, const char *str)
249{
250 EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->filled, EINA_FALSE);
251
252 pd->string_command = eina_strdup(str);
253 _remove_invalid_chars(pd->string_command);
254 pd->command = _unescape(str);
255 if (!pd->command)
256 {
257 if (pd->string_command)
258 free(pd->string_command);
259 pd->string_command = NULL;
260 return EINA_FALSE;
261 }
262 pd->filled = EINA_TRUE;
263
264 return EINA_TRUE;
265}
266
267#include "efl_core_command_line.eo.c"
diff --git a/src/lib/ecore/efl_core_command_line.eo b/src/lib/ecore/efl_core_command_line.eo
new file mode 100644
index 0000000000..1cbb020856
--- /dev/null
+++ b/src/lib/ecore/efl_core_command_line.eo
@@ -0,0 +1,80 @@
1mixin Efl.Core.Command_Line {
2 [[A mixin that implements standard functions for command lines.
3
4 This object parses the command line that gets passed, later the object can be accessed via accessor or the string directly.
5 ]]
6 methods {
7 @property command {
8 [[ A commandline that encodes arguments in a command string.
9 This command is unix shell-style, thus whitespace separates
10 arguments unless escaped. Also a semi-colon ';', ampersand
11 '&', pipe/bar '|', hash '#', bracket, square brace, brace
12 character ('(', ')', '[', ']', '{', '}'), exclamation
13 mark '!', backquote '`', greator or less than ('>' '<')
14 character unless escaped or in quotes would cause
15 args_count/value to not be generated properly, because
16 it would force complex shell interpretation which
17 will not be supported in evaluating the arg_count/value
18 information, but the final shell may interpret this if this
19 is executed via a command-line shell. To not be a complex
20 shell command, it should be simple with paths, options
21 and variable expansions, but nothing more complex involving
22 the above unescaped characters.
23
24 "cat -option /path/file"
25 "cat 'quoted argument'"
26 "cat ~/path/escaped\ argument"
27 "/bin/cat escaped\ argument $VARIABLE"
28 etc.
29
30 It should not try and use "complex shell features" if you
31 want the arg_count and arg_value set to be correct after
32 setting the command string. For example none of:
33
34 "VAR=x /bin/command && /bin/othercommand >& /dev/null"
35 "VAR=x /bin/command `/bin/othercommand` | /bin/cmd2 && cmd3 &"
36 etc.
37
38 If you set the command the arg_count/value property contents
39 can change and be completely re-evaluated by parsing the
40 command string into an argument array set along with
41 interpreting escapes back into individual argument strings.
42 ]]
43 get {
44
45 }
46 values {
47 commandline : string;
48 }
49 }
50 command_access {
51 [[ Get the accessor which enables access to each argument that got passed to this object. ]]
52 return : accessor<stringshare>;
53 }
54 @property command_array {
55 [[ Use an array to fill this object
56
57 Every element of a string is a argument.
58 ]]
59 set {
60 return : bool; [[On success $true, $false otherwise]]
61 }
62 values {
63 array : array<string> @owned; [[An array where every array field is an argument]]
64 }
65 }
66 @property command_string {
67 [[ Use a string to fill this object
68
69 The string will be split at every unescaped ' ', every resulting substring will be a new argument to the command line.
70 ]]
71 set {
72 return : bool; [[On success $true, $false otherwise]]
73 }
74 values {
75 str : string; [[A command in form of a string]]
76 }
77
78 }
79 }
80}
diff --git a/src/lib/ecore/meson.build b/src/lib/ecore/meson.build
index 98909cb618..375f745abd 100644
--- a/src/lib/ecore/meson.build
+++ b/src/lib/ecore/meson.build
@@ -76,7 +76,8 @@ pub_eo_files = [
76 'efl_composite_model.eo', 76 'efl_composite_model.eo',
77 'efl_view_model.eo', 77 'efl_view_model.eo',
78 'efl_core_env.eo', 78 'efl_core_env.eo',
79 'efl_core_proc_env.eo' 79 'efl_core_proc_env.eo',
80 'efl_core_command_line.eo',
80] 81]
81 82
82foreach eo_file : pub_eo_files 83foreach eo_file : pub_eo_files
@@ -184,6 +185,7 @@ ecore_src = [
184 'efl_appthread.c', 185 'efl_appthread.c',
185 'efl_core_env.c', 186 'efl_core_env.c',
186 'efl_core_proc_env.c', 187 'efl_core_proc_env.c',
188 'efl_core_command_line.c',
187] 189]
188 190
189if sys_windows == true 191if sys_windows == true
diff --git a/src/tests/ecore/efl_app_suite.c b/src/tests/ecore/efl_app_suite.c
index cd26e2d95e..2cab632622 100644
--- a/src/tests/ecore/efl_app_suite.c
+++ b/src/tests/ecore/efl_app_suite.c
@@ -53,6 +53,7 @@ static const Efl_Test_Case etc[] = {
53 { "Promise", efl_app_test_promise_3 }, 53 { "Promise", efl_app_test_promise_3 },
54 { "Promise", efl_app_test_promise_safety }, 54 { "Promise", efl_app_test_promise_safety },
55 { "Env", efl_test_efl_env }, 55 { "Env", efl_test_efl_env },
56 { "CML", efl_test_efl_cml },
56 { NULL, NULL } 57 { NULL, NULL }
57}; 58};
58 59
diff --git a/src/tests/ecore/efl_app_suite.h b/src/tests/ecore/efl_app_suite.h
index 3a66dcdfcf..874d2bb503 100644
--- a/src/tests/ecore/efl_app_suite.h
+++ b/src/tests/ecore/efl_app_suite.h
@@ -12,5 +12,6 @@ void efl_app_test_promise_2(TCase *tc);
12void efl_app_test_promise_3(TCase *tc); 12void efl_app_test_promise_3(TCase *tc);
13void efl_app_test_promise_safety(TCase *tc); 13void efl_app_test_promise_safety(TCase *tc);
14void efl_test_efl_env(TCase *tc); 14void efl_test_efl_env(TCase *tc);
15void efl_test_efl_cml(TCase *tc);
15 16
16#endif /* _EFL_APP_SUITE_H */ 17#endif /* _EFL_APP_SUITE_H */
diff --git a/src/tests/ecore/efl_app_test_cml.c b/src/tests/ecore/efl_app_test_cml.c
new file mode 100644
index 0000000000..1b7cebf552
--- /dev/null
+++ b/src/tests/ecore/efl_app_test_cml.c
@@ -0,0 +1,85 @@
1#ifdef HAVE_CONFIG_H
2# include <config.h>
3#endif
4
5#define EFL_CORE_COMMAND_LINE_PROTECTED
6
7#include <stdio.h>
8#include <unistd.h>
9#define EFL_NOLEGACY_API_SUPPORT
10#include <Efl_Core.h>
11#include "efl_app_suite.h"
12#include "../efl_check.h"
13
14typedef struct {
15
16} Efl_App_Test_CML_Data;
17
18#include "efl_app_test_cml.eo.h"
19#include "efl_app_test_cml.eo.c"
20
21static Eina_Array*
22_construct_array(void)
23{
24 Eina_Array *array = eina_array_new(16);
25
26 eina_array_push(array, "/bin/sh");
27 eina_array_push(array, "-C");
28 eina_array_push(array, "foo");
29 eina_array_push(array, "--test");
30 eina_array_push(array, "--option=done");
31 eina_array_push(array, "--");
32 eina_array_push(array, "asdf --test");
33 return array;
34}
35
36static const char*
37_construct_string(void)
38{
39 return "/bin/sh -C foo --test --option=done -- \"asdf --test\"";
40}
41
42EFL_START_TEST(efl_core_cml_string)
43{
44 Efl_App_Test_CML *cml = efl_add_ref(EFL_APP_TEST_CML_CLASS, NULL);
45 Eina_Array *content = _construct_array();
46 Eina_Stringshare *str;
47 Eina_Bool b;
48 int i = 0;
49
50 b = efl_core_command_line_command_string_set(cml, _construct_string());
51 ck_assert_int_ne(b, 0);
52
53 EINA_ACCESSOR_FOREACH(efl_core_command_line_command_access(cml), i, str)
54 {
55 ck_assert_str_eq(eina_array_data_get(content, i), str);
56 }
57 ck_assert_str_eq(efl_core_command_line_command_get(cml), _construct_string());
58}
59EFL_END_TEST
60
61EFL_START_TEST(efl_core_cml_array)
62{
63 Efl_App_Test_CML *cml = efl_add_ref(EFL_APP_TEST_CML_CLASS, NULL);
64 Eina_Array *content1 = _construct_array();
65 Eina_Array *content2 = _construct_array();
66 Eina_Stringshare *str;
67 Eina_Bool b;
68 int i = 0;
69
70 b = efl_core_command_line_command_array_set(cml, content1);
71 ck_assert_int_ne(b, 0);
72
73 EINA_ACCESSOR_FOREACH(efl_core_command_line_command_access(cml), i, str)
74 {
75 ck_assert_str_eq(eina_array_data_get(content2, i), str);
76 }
77 ck_assert_str_eq(efl_core_command_line_command_get(cml), _construct_string());
78}
79EFL_END_TEST
80
81void efl_test_efl_cml(TCase *tc)
82{
83 tcase_add_test(tc, efl_core_cml_string);
84 tcase_add_test(tc, efl_core_cml_array);
85}
diff --git a/src/tests/ecore/efl_app_test_cml.eo b/src/tests/ecore/efl_app_test_cml.eo
new file mode 100644
index 0000000000..b0877e0cf7
--- /dev/null
+++ b/src/tests/ecore/efl_app_test_cml.eo
@@ -0,0 +1,4 @@
1class Efl.App.Test.CML extends Efl.Object implements Efl.Core.Command_Line
2{
3
4}
diff --git a/src/tests/ecore/meson.build b/src/tests/ecore/meson.build
index e3b4f6c851..c49d941355 100644
--- a/src/tests/ecore/meson.build
+++ b/src/tests/ecore/meson.build
@@ -76,14 +76,32 @@ efl_app_suite_src = [
76 'efl_app_test_loop_fd.c', 76 'efl_app_test_loop_fd.c',
77 'efl_app_test_loop_timer.c', 77 'efl_app_test_loop_timer.c',
78 'efl_app_test_promise.c', 78 'efl_app_test_promise.c',
79 'efl_app_test_env.c' 79 'efl_app_test_env.c',
80 'efl_app_test_cml.c',
80] 81]
81 82
83priv_eo_files = [
84 'efl_app_test_cml.eo',
85]
86
87priv_eo_file_target = []
88foreach eo_file : priv_eo_files
89 priv_eo_file_target += custom_target('eolian_gen_' + eo_file,
90 input : eo_file,
91 output : [eo_file + '.h'],
92 depfile : eo_file + '.d',
93 command : eolian_gen + [ '-I', meson.current_source_dir(), eolian_include_directories,
94 '-o', 'h:' + join_paths(meson.current_build_dir(), eo_file + '.h'),
95 '-o', 'c:' + join_paths(meson.current_build_dir(), eo_file + '.c'),
96 '-o', 'd:' + join_paths(meson.current_build_dir(), eo_file + '.d'),
97 '-gchd', '@INPUT@'])
98endforeach
99
82efl_app_suite_deps = [m] 100efl_app_suite_deps = [m]
83efl_app_suite_deps += ecore 101efl_app_suite_deps += ecore
84 102
85efl_app_suite = executable('efl_app_suite', 103efl_app_suite = executable('efl_app_suite',
86 efl_app_suite_src, 104 efl_app_suite_src, priv_eo_file_target,
87 dependencies: [efl_app_suite_deps, check], 105 dependencies: [efl_app_suite_deps, check],
88 c_args : [ 106 c_args : [
89 '-DTESTS_BUILD_DIR="'+meson.current_build_dir()+'"', 107 '-DTESTS_BUILD_DIR="'+meson.current_build_dir()+'"',