1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Implementation of cmd-csop.h.
3  *@ TODO - better commandline parser that can dive into subcommands could
4  *@ TODO   get rid of a lot of ERR_SYNOPSIS cruft.
5  *@ TODO - _CSOP -> _CCSOP
6  *
7  * Copyright (c) 2017 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8  * SPDX-License-Identifier: ISC
9  *
10  * Permission to use, copy, modify, and/or distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  */
22 #undef su_FILE
23 #define su_FILE cmd_csop
24 #define mx_SOURCE
25 #define mx_SOURCE_CMD_CSOP
26 
27 #ifndef mx_HAVE_AMALGAMATION
28 # include "mx/nail.h"
29 #endif
30 
31 su_EMPTY_FILE()
32 #ifdef mx_HAVE_CMD_CSOP
33 
34 #include <su/cs.h>
35 #include <su/icodec.h>
36 #include <su/mem.h>
37 
38 #include "mx/cmd.h"
39 
40 #include "mx/cmd-csop.h"
41 #include "su/code-in.h"
42 
43 enum a_csop_cmd{
44    a_CSOP_CMD_LENGTH,
45    a_CSOP_CMD_HASH32,
46    a_CSOP_CMD_HASH,
47    a_CSOP_CMD_FIND,
48    a_CSOP_CMD_IFIND, /* v15compat */
49    a_CSOP_CMD_SUBSTRING,
50    a_CSOP_CMD_TRIM,
51    a_CSOP_CMD_TRIM_FRONT,
52    a_CSOP_CMD_TRIM_END,
53    a_CSOP_CMD__MAX
54 };
55 
56 enum a_csop_err{
57    a_CSOP_ERR_NONE,
58    a_CSOP_ERR_SYNOPSIS,
59    a_CSOP_ERR_SUBCMD,
60    a_CSOP_ERR_MOD_NOT_ALLOWED,
61    a_CSOP_ERR_NUM_RANGE,
62    a_CSOP_ERR_STR_OVERFLOW,
63    a_CSOP_ERR_STR_NODATA,
64    a_CSOP_ERR_STR_GENERIC
65 };
66 enum {a_CSOP_ERR__MAX = a_CSOP_ERR_STR_GENERIC};
67 
68 CTA(S(uz,a_CSOP_CMD__MAX | a_CSOP_ERR__MAX) <= 0x7Fu, "Bit range excess");
69 
70 enum a_csop_flags{
71    a_CSOP_NONE,
72    a_CSOP_ERR = 1u<<0, /* There was an error */
73    a_CSOP_SOFTOVERFLOW = 1u<<1,
74    a_CSOP_ISNUM = 1u<<2,
75    a_CSOP_ISDECIMAL = 1u<<3, /* Print only decimal result */
76    a_CSOP_MOD_CASE = 1u<<4, /* Case-insensitive / XY */
77    a_CSOP_MOD_MASK = a_CSOP_MOD_CASE,
78 
79    a_CSOP__FMASK = 0x1FFu,
80    a_CSOP__FSHIFT = 9u,
81    a_CSOP__FCMDMASK = 0xFE00u,
82    a_CSOP__TMP = 1u<<30
83 };
84 /* .csc_cmderr=8-bit, and so a_csop_subcmd can store CMD+MOD flags in 16-bit */
85 CTA(((S(u32,a_CSOP_CMD__MAX | a_CSOP_ERR__MAX) << a_CSOP__FSHIFT) &
86    ~a_CSOP__FCMDMASK) == 0, "Bit ranges overlap");
87 
88 struct a_csop_ctx{
89    u32 csc_flags;
90    u8 csc_cmderr; /* On input, a_vexpr_cmd, on output (maybe) a_vexpr_err */
91    u8 csc__pad[3];
92    char const **csc_argv;
93    char const *csc_cmd_name;
94    char const *csc_varname; /* VPUT support */
95    char const *csc_varres;
96    char const *csc_arg; /* The current arg (_ERR: which caused failure) */
97    s64 csc_lhv;
98    char csc_iencbuf[2+1/* BASE# prefix*/ + su_IENC_BUFFER_SIZE + 1];
99 };
100 
101 struct a_csop_subcmd{
102    u16 css_mpv;
103    char css_name[14];
104 };
105 
106 static struct a_csop_subcmd const a_csop_subcmds[] = {
107 #undef a_X
108 #define a_X(C,F) (S(u16,C) << a_CSOP__FSHIFT) | F
109 
110    {a_X(a_CSOP_CMD_LENGTH, 0), "length"},
111    {a_X(a_CSOP_CMD_HASH, a_CSOP_MOD_CASE), "hash"},
112    {a_X(a_CSOP_CMD_HASH32, a_CSOP_MOD_CASE), "hash32"},
113    {a_X(a_CSOP_CMD_FIND, a_CSOP_MOD_CASE), "find"},
114    {a_X(a_CSOP_CMD_IFIND, 0), "ifind"},
115    {a_X(a_CSOP_CMD_SUBSTRING, 0), "substring"},
116    {a_X(a_CSOP_CMD_TRIM, 0), "trim"},
117    {a_X(a_CSOP_CMD_TRIM_FRONT, 0), "trim-front\0"},
118    {a_X(a_CSOP_CMD_TRIM_END, 0), "trim-end"}
119 
120 #undef a_X
121 };
122 
123 /* Entered with .vc_flags=NONE(|MOD_CASE)? */
124 static void a_csop_cmd(struct a_csop_ctx *cscp);
125 
126 static void
a_csop_cmd(struct a_csop_ctx * cscp)127 a_csop_cmd(struct a_csop_ctx *cscp){
128    uz i;
129    NYD2_IN;
130 
131    switch(cscp->csc_cmderr){
132    default:
133    case a_CSOP_CMD_LENGTH:
134       cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL;
135       if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){
136          cscp->csc_flags |= a_CSOP_ERR;
137          cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
138          break;
139       }
140       cscp->csc_arg = cscp->csc_argv[0];
141 
142       i = su_cs_len(cscp->csc_arg);
143       if(UCMP(64, i, >, S64_MAX)){
144          cscp->csc_flags |= a_CSOP_ERR;
145          cscp->csc_cmderr = a_CSOP_ERR_STR_OVERFLOW;
146          break;
147       }
148       cscp->csc_lhv = S(s64,i);
149       break;
150 
151    case a_CSOP_CMD_HASH32:
152    case a_CSOP_CMD_HASH:
153       cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL;
154       if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){
155          cscp->csc_flags |= a_CSOP_ERR;
156          cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
157          break;
158       }
159       cscp->csc_arg = cscp->csc_argv[0];
160 
161       i = ((cscp->csc_flags & a_CSOP_MOD_CASE)
162             ? su_cs_toolbox_case.tb_hash : su_cs_toolbox.tb_hash
163             )(cscp->csc_arg);
164       if(cscp->csc_cmderr == a_CSOP_CMD_HASH32)
165          i = S(u32,i & U32_MAX);
166       cscp->csc_lhv = S(s64,i);
167       break;
168 
169    case a_CSOP_CMD_IFIND:
170       n_OBSOLETE(_("csop: ifind: simply use find?[case] instead, please"));
171       cscp->csc_flags |= a_CSOP_MOD_CASE;
172       /* FALLTHRU */
173    case a_CSOP_CMD_FIND:
174       cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL;
175       if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] == NIL ||
176             cscp->csc_argv[2] != NIL){
177          cscp->csc_flags |= a_CSOP_ERR;
178          cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
179          break;
180       }
181       cscp->csc_arg = cscp->csc_argv[1];
182 
183       cscp->csc_varres = ((cscp->csc_flags & a_CSOP_MOD_CASE)
184             ? su_cs_find_case : su_cs_find)(cscp->csc_argv[0], cscp->csc_arg);
185       if(cscp->csc_varres == NIL){
186          cscp->csc_flags |= a_CSOP_ERR;
187          cscp->csc_cmderr = a_CSOP_ERR_STR_NODATA;
188          break;
189       }
190       i = P2UZ(cscp->csc_varres - cscp->csc_argv[0]);
191       if(UCMP(64, i, >, S64_MAX)){
192          cscp->csc_flags |= a_CSOP_ERR;
193          cscp->csc_cmderr = a_CSOP_ERR_STR_OVERFLOW;
194          break;
195       }
196       cscp->csc_lhv = S(s64,i);
197       break;
198 
199    case a_CSOP_CMD_SUBSTRING:
200       if(cscp->csc_argv[0] == NIL || (cscp->csc_argv[1] != NIL &&
201             (cscp->csc_argv[2] != NIL && cscp->csc_argv[3] != NIL))){
202          cscp->csc_flags |= a_CSOP_ERR;
203          cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
204          break;
205       }
206       cscp->csc_varres =
207       cscp->csc_arg = cscp->csc_argv[0];
208 
209       i = su_cs_len(cscp->csc_arg);
210 
211       /* Offset */
212       if(cscp->csc_argv[1] == NIL || cscp->csc_argv[1][0] == '\0')
213          cscp->csc_lhv = 0;
214       else if((su_idec_s64_cp(&cscp->csc_lhv, cscp->csc_argv[1], 0, NIL
215                ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
216             ) != su_IDEC_STATE_CONSUMED){
217          cscp->csc_flags |= a_CSOP_ERR;
218          cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE;
219          break;
220       }else if(cscp->csc_lhv < 0){
221          if(UCMP(64, i, <, -cscp->csc_lhv)){
222             cscp->csc_flags |= a_CSOP_ERR;
223             cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE;
224             goto jesubstring_off;
225          }
226          cscp->csc_lhv += i;
227       }
228 
229       if(LIKELY(UCMP(64, i, >=, cscp->csc_lhv))){
230          i -= cscp->csc_lhv;
231          cscp->csc_varres += cscp->csc_lhv;
232       }else{
233 jesubstring_off:
234          if(n_poption & n_PO_D_V)
235             n_err(_("vexpr: substring: offset argument too large: %s\n"),
236                n_shexp_quote_cp(cscp->csc_arg, FAL0));
237          cscp->csc_flags |= a_CSOP_SOFTOVERFLOW;
238       }
239 
240       /* Length */
241       if(cscp->csc_argv[2] != NIL){
242          if(cscp->csc_argv[2][0] == '\0')
243             cscp->csc_lhv = 0;
244          else if((su_idec_s64_cp(&cscp->csc_lhv, cscp->csc_argv[2], 0, NIL
245                   ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
246                ) != su_IDEC_STATE_CONSUMED){
247             cscp->csc_flags |= a_CSOP_ERR;
248             cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE;
249             break;
250          }else if(cscp->csc_lhv < 0){
251             if(UCMP(64, i, <, -cscp->csc_lhv)){
252                goto jesubstring_len;
253             }
254             cscp->csc_lhv += i;
255          }
256 
257          if(UCMP(64, i, >=, cscp->csc_lhv)){
258             if(UCMP(64, i, !=, cscp->csc_lhv))
259                cscp->csc_varres =
260                      savestrbuf(cscp->csc_varres, S(uz,cscp->csc_lhv));
261          }else{
262 jesubstring_len:
263             if(n_poption & n_PO_D_V)
264                n_err(_("vexpr: substring: length argument too large: %s\n"),
265                   n_shexp_quote_cp(cscp->csc_arg, FAL0));
266             cscp->csc_flags |= a_CSOP_SOFTOVERFLOW;
267          }
268       }
269       break;
270 
271    case a_CSOP_CMD_TRIM:{
272       struct str trim;
273       enum n_str_trim_flags stf;
274 
275       stf = n_STR_TRIM_BOTH;
276       if(0){
277    case a_CSOP_CMD_TRIM_FRONT:
278          stf = n_STR_TRIM_FRONT;
279       }else if(0){
280    case a_CSOP_CMD_TRIM_END:
281          stf = n_STR_TRIM_END;
282       }
283 
284       if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){
285          cscp->csc_flags |= a_CSOP_ERR;
286          cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS;
287          break;
288       }
289       cscp->csc_arg = cscp->csc_argv[0];
290 
291       trim.l = su_cs_len(trim.s = UNCONST(char*,cscp->csc_arg));
292       (void)n_str_trim(&trim, stf);
293       cscp->csc_varres = savestrbuf(trim.s, trim.l);
294       }break;
295    }
296 
297    NYD2_OU;
298 }
299 
300 int
c_csop(void * vp)301 c_csop(void *vp){
302    struct a_csop_ctx csc;
303    char const *cp;
304    u32 f;
305    uz i, j;
306    NYD_IN;
307 
308    DVL( su_mem_set(&csc, 0xAA, sizeof csc); )
309    csc.csc_flags = a_CSOP_ERR;
310    csc.csc_cmderr = a_CSOP_ERR_SUBCMD;
311    csc.csc_argv = S(char const**,vp);
312    csc.csc_varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *csc.csc_argv++ : NIL;
313    csc.csc_arg =
314    csc.csc_cmd_name = *csc.csc_argv++;
315    csc.csc_varres = su_empty;
316 
317    if((cp = su_cs_find_c(csc.csc_cmd_name, '?')) != NIL){
318       j = P2UZ(cp - csc.csc_cmd_name);
319       if(cp[1] != '\0' && !su_cs_starts_with_case("case", &cp[1])){
320          n_err(_("csop: invalid modifier used: %s\n"),
321             n_shexp_quote_cp(csc.csc_cmd_name, FAL0));
322          f = a_CSOP_ERR;
323          goto jleave;
324       }
325       f = a_CSOP_MOD_CASE;
326    }else{
327       f = a_CSOP_NONE;
328       if(*csc.csc_cmd_name == '@'){ /* v15compat */
329          n_OBSOLETE2(_("csop: please use ? modifier suffix, "
330                "not @ prefix"), n_shexp_quote_cp(csc.csc_cmd_name, FAL0));
331          ++csc.csc_cmd_name;
332          f = a_CSOP_MOD_CASE;
333       }
334       j = su_cs_len(csc.csc_cmd_name);
335    }
336 
337    for(i = 0; i < NELEM(a_csop_subcmds); ++i){
338       if(su_cs_starts_with_case_n(a_csop_subcmds[i].css_name,
339             csc.csc_cmd_name, j)){
340          csc.csc_cmd_name = a_csop_subcmds[i].css_name;
341          i = a_csop_subcmds[i].css_mpv;
342 
343          if(UNLIKELY(f & a_CSOP_MOD_MASK)){
344             /*u32 f2;
345 
346             f2 = f & a_CSOP_MOD_MASK;*/
347 
348             if(UNLIKELY(!(i & a_CSOP_MOD_MASK))){
349                csc.csc_cmderr = a_CSOP_ERR_MOD_NOT_ALLOWED;
350                break;
351             /*
352             }else if(UNLIKELY(f2 != a_CSOP_MOD_MASK &&
353                   f2 != (i & a_CSOP_MOD_MASK))){
354                csc.csc_cmderr = a_CSOP_ERR_MOD_NOT_SUPPORTED;
355                break;
356             */
357             }
358          }
359 
360          csc.csc_arg = csc.csc_cmd_name;
361          csc.csc_flags = f;
362          i = (i & a_CSOP__FCMDMASK) >> a_CSOP__FSHIFT;
363          csc.csc_cmderr = S(u8,i);
364          a_csop_cmd(&csc);
365          break;
366       }
367    }
368    f = csc.csc_flags;
369 
370    if(LIKELY(!(f & a_CSOP_ERR))){
371       n_pstate_err_no = (f & a_CSOP_SOFTOVERFLOW)
372             ? su_ERR_OVERFLOW : su_ERR_NONE;
373    }else switch(csc.csc_cmderr){
374    case a_CSOP_ERR_NONE:
375       ASSERT(0);
376       break;
377    case a_CSOP_ERR_SYNOPSIS:
378       mx_cmd_print_synopsis(mx_cmd_firstfit("csop"), NIL);
379       n_pstate_err_no = su_ERR_INVAL;
380       goto jenum;
381    case a_CSOP_ERR_SUBCMD:
382       n_err(_("csop: invalid subcommand: %s\n"),
383          n_shexp_quote_cp(csc.csc_arg, FAL0));
384       n_pstate_err_no = su_ERR_INVAL;
385       goto jenum;
386    case a_CSOP_ERR_MOD_NOT_ALLOWED:
387       n_err(_("csop: modifiers not allowed for subcommand: %s\n"),
388          n_shexp_quote_cp(csc.csc_arg, FAL0));
389       n_pstate_err_no = su_ERR_INVAL;
390       goto jenum;
391    case a_CSOP_ERR_NUM_RANGE:
392       n_err(_("csop: numeric argument invalid or out of range: %s\n"),
393          n_shexp_quote_cp(csc.csc_arg, FAL0));
394       n_pstate_err_no = su_ERR_RANGE;
395       goto jenum;
396    default:
397 jenum:
398       f = a_CSOP_ERR | a_CSOP_ISNUM;
399       csc.csc_lhv = -1;
400       break;
401    case a_CSOP_ERR_STR_OVERFLOW:
402       n_err(_("csop: string length or offset overflows datatype\n"));
403       n_pstate_err_no = su_ERR_OVERFLOW;
404       goto jestr;
405    case a_CSOP_ERR_STR_NODATA:
406       n_pstate_err_no = su_ERR_NODATA;
407       /* FALLTHRU*/
408    case a_CSOP_ERR_STR_GENERIC:
409 jestr:
410       csc.csc_varres = su_empty;
411       f = a_CSOP_ERR;
412       break;
413    }
414 
415    /* Generate the variable value content.
416     * Anticipate in our handling below!  (Avoid needless work) */
417    if(f & a_CSOP_ISNUM){
418       cp = su_ienc(csc.csc_iencbuf, csc.csc_lhv, 10, su_IENC_MODE_SIGNED_TYPE);
419       if(cp != NIL)
420          csc.csc_varres = cp;
421       else{
422          f |= a_CSOP_ERR;
423          csc.csc_varres = su_empty;
424       }
425    }
426 
427    if(csc.csc_varname == NIL){
428       /* If there was no error and we are printing a numeric result, print some
429        * more bases for the fun of it */
430       if(csc.csc_varres != NIL &&
431             fprintf(n_stdout, "%s\n", csc.csc_varres) < 0){
432          n_pstate_err_no = su_err_no();
433          f |= a_CSOP_ERR;
434       }
435    }else if(!n_var_vset(csc.csc_varname, S(up,csc.csc_varres))){
436       n_pstate_err_no = su_ERR_NOTSUP;
437       f |= a_CSOP_ERR;
438    }
439 
440 jleave:
441    NYD_OU;
442    return (f & a_CSOP_ERR) ? 1 : 0;
443 }
444 
445 #include "su/code-ou.h"
446 #endif /* mx_HAVE_CMD_CSOP */
447 /* s-it-mode */
448