1 /*
2  * opt.c :  option and argument parsing for Subversion command lines
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 
26 #define APR_WANT_STRFUNC
27 #include <apr_want.h>
28 
29 #include <stdio.h>
30 #include <string.h>
31 #include <assert.h>
32 #include <apr_pools.h>
33 #include <apr_general.h>
34 #include <apr_lib.h>
35 #include <apr_file_info.h>
36 
37 #include "svn_hash.h"
38 #include "svn_cmdline.h"
39 #include "svn_version.h"
40 #include "svn_types.h"
41 #include "svn_opt.h"
42 #include "svn_error.h"
43 #include "svn_dirent_uri.h"
44 #include "svn_path.h"
45 #include "svn_utf.h"
46 #include "svn_time.h"
47 #include "svn_props.h"
48 #include "svn_ctype.h"
49 
50 #include "private/svn_opt_private.h"
51 
52 #include "opt.h"
53 #include "svn_private_config.h"
54 
55 
56 /*** Code. ***/
57 
58 const svn_opt_subcommand_desc3_t *
svn_opt_get_canonical_subcommand3(const svn_opt_subcommand_desc3_t * table,const char * cmd_name)59 svn_opt_get_canonical_subcommand3(const svn_opt_subcommand_desc3_t *table,
60                                   const char *cmd_name)
61 {
62   int i = 0;
63 
64   if (cmd_name == NULL)
65     return NULL;
66 
67   while (table[i].name) {
68     int j;
69     if (strcmp(cmd_name, table[i].name) == 0)
70       return table + i;
71     for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
72       if (strcmp(cmd_name, table[i].aliases[j]) == 0)
73         return table + i;
74 
75     i++;
76   }
77 
78   /* If we get here, there was no matching subcommand name or alias. */
79   return NULL;
80 }
81 
82 const apr_getopt_option_t *
svn_opt_get_option_from_code3(int code,const apr_getopt_option_t * option_table,const svn_opt_subcommand_desc3_t * command,apr_pool_t * pool)83 svn_opt_get_option_from_code3(int code,
84                               const apr_getopt_option_t *option_table,
85                               const svn_opt_subcommand_desc3_t *command,
86                               apr_pool_t *pool)
87 {
88   apr_size_t i;
89 
90   for (i = 0; option_table[i].optch; i++)
91     if (option_table[i].optch == code)
92       {
93         if (command)
94           {
95             int j;
96 
97             for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
98                          command->desc_overrides[j].optch); j++)
99               if (command->desc_overrides[j].optch == code)
100                 {
101                   apr_getopt_option_t *tmpopt =
102                       apr_palloc(pool, sizeof(*tmpopt));
103                   *tmpopt = option_table[i];
104                   tmpopt->description = command->desc_overrides[j].desc;
105                   return tmpopt;
106                 }
107           }
108         return &(option_table[i]);
109       }
110 
111   return NULL;
112 }
113 
114 /* Like svn_opt_get_option_from_code3(), but also, if CODE appears a second
115  * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
116  * second name, else set it to NULL. */
117 static const apr_getopt_option_t *
get_option_from_code3(const char ** long_alias,int code,const apr_getopt_option_t * option_table,const svn_opt_subcommand_desc3_t * command,apr_pool_t * pool)118 get_option_from_code3(const char **long_alias,
119                       int code,
120                       const apr_getopt_option_t *option_table,
121                       const svn_opt_subcommand_desc3_t *command,
122                       apr_pool_t *pool)
123 {
124   const apr_getopt_option_t *i;
125   const apr_getopt_option_t *opt
126     = svn_opt_get_option_from_code3(code, option_table, command, pool);
127 
128   /* Find a long alias in the table, if there is one. */
129   *long_alias = NULL;
130   for (i = option_table; i->optch; i++)
131     {
132       if (i->optch == code && i->name != opt->name)
133         {
134           *long_alias = i->name;
135           break;
136         }
137     }
138 
139   return opt;
140 }
141 
142 
143 /* Print an option OPT nicely into a STRING allocated in POOL.
144  * If OPT has a single-character short form, then print OPT->name (if not
145  * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
146  * If DOC is set, include the generic documentation string of OPT,
147  * localized to the current locale if a translation is available.
148  */
149 static void
format_option(const char ** string,const apr_getopt_option_t * opt,const char * long_alias,svn_boolean_t doc,apr_pool_t * pool)150 format_option(const char **string,
151               const apr_getopt_option_t *opt,
152               const char *long_alias,
153               svn_boolean_t doc,
154               apr_pool_t *pool)
155 {
156   char *opts;
157 
158   if (opt == NULL)
159     {
160       *string = "?";
161       return;
162     }
163 
164   /* We have a valid option which may or may not have a "short
165      name" (a single-character alias for the long option). */
166   if (opt->optch <= 255)
167     opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
168   else if (long_alias)
169     opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
170   else
171     opts = apr_psprintf(pool, "--%s", opt->name);
172 
173   if (opt->has_arg)
174     opts = apr_pstrcat(pool, opts, _(" ARG"), SVN_VA_NULL);
175 
176   if (doc)
177     opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
178 
179   *string = opts;
180 }
181 
182 void
svn_opt_format_option(const char ** string,const apr_getopt_option_t * opt,svn_boolean_t doc,apr_pool_t * pool)183 svn_opt_format_option(const char **string,
184                       const apr_getopt_option_t *opt,
185                       svn_boolean_t doc,
186                       apr_pool_t *pool)
187 {
188   format_option(string, opt, NULL, doc, pool);
189 }
190 
191 
192 svn_boolean_t
svn_opt_subcommand_takes_option4(const svn_opt_subcommand_desc3_t * command,int option_code,const int * global_options)193 svn_opt_subcommand_takes_option4(const svn_opt_subcommand_desc3_t *command,
194                                  int option_code,
195                                  const int *global_options)
196 {
197   apr_size_t i;
198 
199   for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
200     if (command->valid_options[i] == option_code)
201       return TRUE;
202 
203   if (global_options)
204     for (i = 0; global_options[i]; i++)
205       if (global_options[i] == option_code)
206         return TRUE;
207 
208   return FALSE;
209 }
210 
211 
212 /* Print the canonical command name for CMD, and all its aliases, to
213    STREAM.  If HELP is set, print CMD's help string too, in which case
214    obtain option usage from OPTIONS_TABLE.
215 
216    Include global and experimental options iff VERBOSE is true.
217  */
218 static svn_error_t *
print_command_info3(const svn_opt_subcommand_desc3_t * cmd,const apr_getopt_option_t * options_table,const int * global_options,svn_boolean_t help,svn_boolean_t verbose,apr_pool_t * pool,FILE * stream)219 print_command_info3(const svn_opt_subcommand_desc3_t *cmd,
220                     const apr_getopt_option_t *options_table,
221                     const int *global_options,
222                     svn_boolean_t help,
223                     svn_boolean_t verbose,
224                     apr_pool_t *pool,
225                     FILE *stream)
226 {
227   svn_boolean_t first_time;
228   apr_size_t i;
229 
230   /* Print the canonical command name. */
231   SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
232 
233   /* Print the list of aliases. */
234   first_time = TRUE;
235   for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
236     {
237       if (cmd->aliases[i] == NULL)
238         break;
239 
240       if (first_time) {
241         SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
242         first_time = FALSE;
243       }
244       else
245         SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
246 
247       SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
248     }
249 
250   if (! first_time)
251     SVN_ERR(svn_cmdline_fputs(")", stream, pool));
252 
253   if (help)
254     {
255       const apr_getopt_option_t *option;
256       const char *long_alias;
257       svn_boolean_t have_options = FALSE;
258       svn_boolean_t have_experimental = FALSE;
259 
260       SVN_ERR(svn_cmdline_fprintf(stream, pool, ": "));
261 
262       for (i = 0; i < SVN_OPT_MAX_PARAGRAPHS && cmd->help[i]; i++)
263         {
264           SVN_ERR(svn_cmdline_fprintf(stream, pool, "%s", _(cmd->help[i])));
265         }
266 
267       /* Loop over all valid option codes attached to the subcommand */
268       for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
269         {
270           if (cmd->valid_options[i])
271             {
272               if (!have_options)
273                 {
274                   SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
275                                             stream, pool));
276                   have_options = TRUE;
277                 }
278 
279               /* convert each option code into an option */
280               option = get_option_from_code3(&long_alias, cmd->valid_options[i],
281                                              options_table, cmd, pool);
282 
283               /* print the option's docstring */
284               if (option && option->description)
285                 {
286                   const char *optstr;
287 
288                   if (option->name && strncmp(option->name, "x-", 2) == 0)
289                     {
290                       if (verbose && !have_experimental)
291                         SVN_ERR(svn_cmdline_fputs(_("\nExperimental options:\n"),
292                                                   stream, pool));
293                       have_experimental = TRUE;
294                       if (!verbose)
295                         continue;
296                     }
297 
298                   format_option(&optstr, option, long_alias, TRUE, pool);
299                   SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
300                                               optstr));
301                 }
302             }
303         }
304       /* And global options too */
305       if (verbose && global_options && *global_options)
306         {
307           SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
308                                     stream, pool));
309           have_options = TRUE;
310 
311           for (i = 0; global_options[i]; i++)
312             {
313 
314               /* convert each option code into an option */
315               option = get_option_from_code3(&long_alias, global_options[i],
316                                              options_table, cmd, pool);
317 
318               /* print the option's docstring */
319               if (option && option->description)
320                 {
321                   const char *optstr;
322                   format_option(&optstr, option, long_alias, TRUE, pool);
323                   SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
324                                               optstr));
325                 }
326             }
327         }
328 
329       if (!verbose && global_options && *global_options)
330         SVN_ERR(svn_cmdline_fputs(_("\n(Use '-v' to show global and experimental options.)\n"),
331                                   stream, pool));
332       if (have_options)
333         SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
334     }
335 
336   return SVN_NO_ERROR;
337 }
338 
339 /* The body for svn_opt_print_generic_help3() function with standard error
340  * handling semantic. Handling of errors implemented at caller side. */
341 static svn_error_t *
print_generic_help_body3(const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * opt_table,const char * footer,svn_boolean_t with_experimental,apr_pool_t * pool,FILE * stream)342 print_generic_help_body3(const char *header,
343                          const svn_opt_subcommand_desc3_t *cmd_table,
344                          const apr_getopt_option_t *opt_table,
345                          const char *footer,
346                          svn_boolean_t with_experimental,
347                          apr_pool_t *pool, FILE *stream)
348 {
349   svn_boolean_t have_experimental = FALSE;
350   int i;
351 
352   if (header)
353     SVN_ERR(svn_cmdline_fputs(header, stream, pool));
354 
355   for (i = 0; cmd_table[i].name; i++)
356     {
357       if (strncmp(cmd_table[i].name, "x-", 2) == 0)
358         {
359           if (with_experimental && !have_experimental)
360             SVN_ERR(svn_cmdline_fputs(_("\nExperimental subcommands:\n"),
361                                       stream, pool));
362           have_experimental = TRUE;
363           if (!with_experimental)
364             continue;
365         }
366       SVN_ERR(svn_cmdline_fputs("   ", stream, pool));
367       SVN_ERR(print_command_info3(cmd_table + i, opt_table,
368                                   NULL, FALSE, FALSE,
369                                   pool, stream));
370       SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
371     }
372 
373   if (have_experimental && !with_experimental)
374     SVN_ERR(svn_cmdline_fputs(_("\n(Use '-v' to show experimental subcommands.)\n"),
375                               stream, pool));
376 
377   SVN_ERR(svn_cmdline_fputs("\n", stream, pool));
378 
379   if (footer)
380     SVN_ERR(svn_cmdline_fputs(footer, stream, pool));
381 
382   return SVN_NO_ERROR;
383 }
384 
385 static void
print_generic_help(const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * opt_table,const char * footer,svn_boolean_t with_experimental,apr_pool_t * pool,FILE * stream)386 print_generic_help(const char *header,
387                    const svn_opt_subcommand_desc3_t *cmd_table,
388                    const apr_getopt_option_t *opt_table,
389                    const char *footer,
390                    svn_boolean_t with_experimental,
391                    apr_pool_t *pool, FILE *stream)
392 {
393   svn_error_t *err;
394 
395   err = print_generic_help_body3(header, cmd_table, opt_table, footer,
396                                  with_experimental,
397                                  pool, stream);
398 
399   /* Issue #3014:
400    * Don't print anything on broken pipes. The pipe was likely
401    * closed by the process at the other end. We expect that
402    * process to perform error reporting as necessary.
403    *
404    * ### This assumes that there is only one error in a chain for
405    * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
406   if (err && err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
407     svn_handle_error2(err, stderr, FALSE, "svn: ");
408   svn_error_clear(err);
409 }
410 
411 void
svn_opt_print_generic_help3(const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * opt_table,const char * footer,apr_pool_t * pool,FILE * stream)412 svn_opt_print_generic_help3(const char *header,
413                             const svn_opt_subcommand_desc3_t *cmd_table,
414                             const apr_getopt_option_t *opt_table,
415                             const char *footer,
416                             apr_pool_t *pool, FILE *stream)
417 {
418   print_generic_help(header, cmd_table, opt_table, footer,
419                      TRUE, pool, stream);
420 }
421 
422 
423 /* The body of svn_opt_subcommand_help4(), which see.
424  *
425  * VERBOSE means show also the subcommand's global and experimental options.
426  */
427 static void
subcommand_help(const char * subcommand,const svn_opt_subcommand_desc3_t * table,const apr_getopt_option_t * options_table,const int * global_options,svn_boolean_t verbose,apr_pool_t * pool)428 subcommand_help(const char *subcommand,
429                 const svn_opt_subcommand_desc3_t *table,
430                 const apr_getopt_option_t *options_table,
431                 const int *global_options,
432                 svn_boolean_t verbose,
433                 apr_pool_t *pool)
434 {
435   const svn_opt_subcommand_desc3_t *cmd =
436     svn_opt_get_canonical_subcommand3(table, subcommand);
437   svn_error_t *err;
438 
439   if (cmd)
440     err = print_command_info3(cmd, options_table, global_options,
441                               TRUE, verbose, pool, stdout);
442   else
443     err = svn_cmdline_fprintf(stderr, pool,
444                               _("\"%s\": unknown command.\n\n"), subcommand);
445 
446   if (err) {
447     /* Issue #3014: Don't print anything on broken pipes. */
448     if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
449       svn_handle_error2(err, stderr, FALSE, "svn: ");
450     svn_error_clear(err);
451   }
452 }
453 
454 void
svn_opt_subcommand_help4(const char * subcommand,const svn_opt_subcommand_desc3_t * table,const apr_getopt_option_t * options_table,const int * global_options,apr_pool_t * pool)455 svn_opt_subcommand_help4(const char *subcommand,
456                          const svn_opt_subcommand_desc3_t *table,
457                          const apr_getopt_option_t *options_table,
458                          const int *global_options,
459                          apr_pool_t *pool)
460 {
461   subcommand_help(subcommand, table, options_table, global_options,
462                   TRUE, pool);
463 }
464 
465 
466 
467 /*** Parsing revision and date options. ***/
468 
469 
470 /** Parsing "X:Y"-style arguments. **/
471 
472 /* If WORD matches one of the special revision descriptors,
473  * case-insensitively, set *REVISION accordingly:
474  *
475  *   - For "head", set REVISION->kind to svn_opt_revision_head.
476  *
477  *   - For "prev", set REVISION->kind to svn_opt_revision_previous.
478  *
479  *   - For "base", set REVISION->kind to svn_opt_revision_base.
480  *
481  *   - For "committed", set REVISION->kind to svn_opt_revision_committed.
482  *
483  * If match, return 0, else return -1 and don't touch REVISION.
484  */
485 static int
revision_from_word(svn_opt_revision_t * revision,const char * word)486 revision_from_word(svn_opt_revision_t *revision, const char *word)
487 {
488   if (svn_cstring_casecmp(word, "head") == 0)
489     {
490       revision->kind = svn_opt_revision_head;
491     }
492   else if (svn_cstring_casecmp(word, "prev") == 0)
493     {
494       revision->kind = svn_opt_revision_previous;
495     }
496   else if (svn_cstring_casecmp(word, "base") == 0)
497     {
498       revision->kind = svn_opt_revision_base;
499     }
500   else if (svn_cstring_casecmp(word, "committed") == 0)
501     {
502       revision->kind = svn_opt_revision_committed;
503     }
504   else
505     return -1;
506 
507   return 0;
508 }
509 
510 
511 /* Parse one revision specification.  Return pointer to character
512    after revision, or NULL if the revision is invalid.  Modifies
513    str, so make sure to pass a copy of anything precious.  Uses
514    POOL for temporary allocation. */
parse_one_rev(svn_opt_revision_t * revision,char * str,apr_pool_t * pool)515 static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
516                            apr_pool_t *pool)
517 {
518   char *end, save;
519 
520   /* Allow any number of 'r's to prefix a revision number, because
521      that way if a script pastes svn output into another svn command
522      (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
523      even when compounded.
524 
525      As it happens, none of our special revision words begins with
526      "r".  If any ever do, then this code will have to get smarter.
527 
528      Incidentally, this allows "r{DATE}".  We could avoid that with
529      some trivial code rearrangement, but it's not clear what would
530      be gained by doing so. */
531   while (*str == 'r')
532     str++;
533 
534   if (*str == '{')
535     {
536       svn_boolean_t matched;
537       apr_time_t tm;
538       svn_error_t *err;
539 
540       /* Brackets denote a date. */
541       str++;
542       end = strchr(str, '}');
543       if (!end)
544         return NULL;
545       *end = '\0';
546       err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
547       if (err)
548         {
549           svn_error_clear(err);
550           return NULL;
551         }
552       if (!matched)
553         return NULL;
554       revision->kind = svn_opt_revision_date;
555       revision->value.date = tm;
556       return end + 1;
557     }
558   else if (svn_ctype_isdigit(*str))
559     {
560       /* It's a number. */
561       end = str + 1;
562       while (svn_ctype_isdigit(*end))
563         end++;
564       save = *end;
565       *end = '\0';
566       revision->kind = svn_opt_revision_number;
567       revision->value.number = SVN_STR_TO_REV(str);
568       *end = save;
569       return end;
570     }
571   else if (svn_ctype_isalpha(*str))
572     {
573       end = str + 1;
574       while (svn_ctype_isalpha(*end))
575         end++;
576       save = *end;
577       *end = '\0';
578       if (revision_from_word(revision, str) != 0)
579         return NULL;
580       *end = save;
581       return end;
582     }
583   else
584     return NULL;
585 }
586 
587 
588 int
svn_opt_parse_revision(svn_opt_revision_t * start_revision,svn_opt_revision_t * end_revision,const char * arg,apr_pool_t * pool)589 svn_opt_parse_revision(svn_opt_revision_t *start_revision,
590                        svn_opt_revision_t *end_revision,
591                        const char *arg,
592                        apr_pool_t *pool)
593 {
594   char *left_rev, *right_rev, *end;
595 
596   /* Operate on a copy of the argument. */
597   left_rev = apr_pstrdup(pool, arg);
598 
599   right_rev = parse_one_rev(start_revision, left_rev, pool);
600   if (right_rev && *right_rev == ':')
601     {
602       right_rev++;
603       end = parse_one_rev(end_revision, right_rev, pool);
604       if (!end || *end != '\0')
605         return -1;
606     }
607   else if (!right_rev || *right_rev != '\0')
608     return -1;
609 
610   return 0;
611 }
612 
613 
614 int
svn_opt_parse_revision_to_range(apr_array_header_t * opt_ranges,const char * arg,apr_pool_t * pool)615 svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
616                                 const char *arg,
617                                 apr_pool_t *pool)
618 {
619   svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
620 
621   range->start.kind = svn_opt_revision_unspecified;
622   range->end.kind = svn_opt_revision_unspecified;
623 
624   if (svn_opt_parse_revision(&(range->start), &(range->end),
625                              arg, pool) == -1)
626     return -1;
627 
628   APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
629   return 0;
630 }
631 
632 svn_error_t *
svn_opt_resolve_revisions(svn_opt_revision_t * peg_rev,svn_opt_revision_t * op_rev,svn_boolean_t is_url,svn_boolean_t notice_local_mods,apr_pool_t * pool)633 svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
634                           svn_opt_revision_t *op_rev,
635                           svn_boolean_t is_url,
636                           svn_boolean_t notice_local_mods,
637                           apr_pool_t *pool)
638 {
639   if (peg_rev->kind == svn_opt_revision_unspecified)
640     {
641       if (is_url)
642         {
643           peg_rev->kind = svn_opt_revision_head;
644         }
645       else
646         {
647           if (notice_local_mods)
648             peg_rev->kind = svn_opt_revision_working;
649           else
650             peg_rev->kind = svn_opt_revision_base;
651         }
652     }
653 
654   if (op_rev->kind == svn_opt_revision_unspecified)
655     *op_rev = *peg_rev;
656 
657   return SVN_NO_ERROR;
658 }
659 
660 const char *
svn_opt__revision_to_string(const svn_opt_revision_t * revision,apr_pool_t * result_pool)661 svn_opt__revision_to_string(const svn_opt_revision_t *revision,
662                             apr_pool_t *result_pool)
663 {
664   switch (revision->kind)
665     {
666       case svn_opt_revision_unspecified:
667         return "unspecified";
668       case svn_opt_revision_number:
669         return apr_psprintf(result_pool, "%ld", revision->value.number);
670       case svn_opt_revision_date:
671         /* ### svn_time_to_human_cstring()? */
672         return svn_time_to_cstring(revision->value.date, result_pool);
673       case svn_opt_revision_committed:
674         return "committed";
675       case svn_opt_revision_previous:
676         return "previous";
677       case svn_opt_revision_base:
678         return "base";
679       case svn_opt_revision_working:
680         return "working";
681       case svn_opt_revision_head:
682         return "head";
683       default:
684         return NULL;
685     }
686 }
687 
688 svn_opt_revision_range_t *
svn_opt__revision_range_create(const svn_opt_revision_t * start_revision,const svn_opt_revision_t * end_revision,apr_pool_t * result_pool)689 svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
690                                const svn_opt_revision_t *end_revision,
691                                apr_pool_t *result_pool)
692 {
693   svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
694 
695   range->start = *start_revision;
696   range->end = *end_revision;
697   return range;
698 }
699 
700 svn_opt_revision_range_t *
svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,svn_revnum_t end_revnum,apr_pool_t * result_pool)701 svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
702                                      svn_revnum_t end_revnum,
703                                      apr_pool_t *result_pool)
704 {
705   svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
706 
707   range->start.kind = svn_opt_revision_number;
708   range->start.value.number = start_revnum;
709   range->end.kind = svn_opt_revision_number;
710   range->end.value.number = end_revnum;
711   return range;
712 }
713 
714 
715 
716 /*** Parsing arguments. ***/
717 #define DEFAULT_ARRAY_SIZE 5
718 
719 
720 /* Copy STR into POOL and push the copy onto ARRAY. */
721 static void
array_push_str(apr_array_header_t * array,const char * str,apr_pool_t * pool)722 array_push_str(apr_array_header_t *array,
723                const char *str,
724                apr_pool_t *pool)
725 {
726   /* ### Not sure if this function is still necessary.  It used to
727      convert str to svn_stringbuf_t * and push it, but now it just
728      dups str in pool and pushes the copy.  So its only effect is
729      transfer str's lifetime to pool.  Is that something callers are
730      depending on? */
731 
732   APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
733 }
734 
735 
736 void
svn_opt_push_implicit_dot_target(apr_array_header_t * targets,apr_pool_t * pool)737 svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
738                                  apr_pool_t *pool)
739 {
740   if (targets->nelts == 0)
741     APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
742   assert(targets->nelts);
743 }
744 
745 
746 svn_error_t *
svn_opt_parse_num_args(apr_array_header_t ** args_p,apr_getopt_t * os,int num_args,apr_pool_t * pool)747 svn_opt_parse_num_args(apr_array_header_t **args_p,
748                        apr_getopt_t *os,
749                        int num_args,
750                        apr_pool_t *pool)
751 {
752   int i;
753   apr_array_header_t *args
754     = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
755 
756   /* loop for num_args and add each arg to the args array */
757   for (i = 0; i < num_args; i++)
758     {
759       if (os->ind >= os->argc)
760         {
761           return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
762         }
763       array_push_str(args, os->argv[os->ind++], pool);
764     }
765 
766   *args_p = args;
767   return SVN_NO_ERROR;
768 }
769 
770 svn_error_t *
svn_opt_parse_all_args(apr_array_header_t ** args_p,apr_getopt_t * os,apr_pool_t * pool)771 svn_opt_parse_all_args(apr_array_header_t **args_p,
772                        apr_getopt_t *os,
773                        apr_pool_t *pool)
774 {
775   apr_array_header_t *args
776     = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
777 
778   if (os->ind > os->argc)
779     {
780       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
781     }
782   while (os->ind < os->argc)
783     {
784       array_push_str(args, os->argv[os->ind++], pool);
785     }
786 
787   *args_p = args;
788   return SVN_NO_ERROR;
789 }
790 
791 
792 svn_error_t *
svn_opt_parse_path(svn_opt_revision_t * rev,const char ** truepath,const char * path,apr_pool_t * pool)793 svn_opt_parse_path(svn_opt_revision_t *rev,
794                    const char **truepath,
795                    const char *path /* UTF-8! */,
796                    apr_pool_t *pool)
797 {
798   const char *peg_rev;
799 
800   SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
801 
802   /* Parse the peg revision, if one was found */
803   if (strlen(peg_rev))
804     {
805       int ret;
806       svn_opt_revision_t start_revision, end_revision;
807 
808       end_revision.kind = svn_opt_revision_unspecified;
809 
810       if (peg_rev[1] == '\0')  /* looking at empty peg revision */
811         {
812           ret = 0;
813           start_revision.kind = svn_opt_revision_unspecified;
814           start_revision.value.number = 0;
815         }
816       else  /* looking at non-empty peg revision */
817         {
818           const char *rev_str = &peg_rev[1];
819 
820           /* URLs get treated differently from wc paths. */
821           if (svn_path_is_url(path))
822             {
823               /* URLs are URI-encoded, so we look for dates with
824                  URI-encoded delimiters.  */
825               size_t rev_len = strlen(rev_str);
826               if (rev_len > 6
827                   && rev_str[0] == '%'
828                   && rev_str[1] == '7'
829                   && (rev_str[2] == 'B'
830                       || rev_str[2] == 'b')
831                   && rev_str[rev_len-3] == '%'
832                   && rev_str[rev_len-2] == '7'
833                   && (rev_str[rev_len-1] == 'D'
834                       || rev_str[rev_len-1] == 'd'))
835                 {
836                   rev_str = svn_path_uri_decode(rev_str, pool);
837                 }
838             }
839           ret = svn_opt_parse_revision(&start_revision,
840                                        &end_revision,
841                                        rev_str, pool);
842         }
843 
844       if (ret || end_revision.kind != svn_opt_revision_unspecified)
845         {
846           /* If an svn+ssh URL was used and it contains only one @,
847            * provide an error message that presents a possible solution
848            * to the parsing error (issue #2349). */
849           if (strncmp(path, "svn+ssh://", 10) == 0)
850             {
851               const char *at;
852 
853               at = strchr(path, '@');
854               if (at && strrchr(path, '@') == at)
855                 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
856                                          _("Syntax error parsing peg revision "
857                                            "'%s'; did you mean '%s@'?"),
858                                        &peg_rev[1], path);
859             }
860 
861           return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
862                                    _("Syntax error parsing peg revision '%s'"),
863                                    &peg_rev[1]);
864         }
865       rev->kind = start_revision.kind;
866       rev->value = start_revision.value;
867     }
868   else
869     {
870       /* Didn't find a peg revision. */
871       rev->kind = svn_opt_revision_unspecified;
872     }
873 
874   return SVN_NO_ERROR;
875 }
876 
877 
878 /* Note: This is substantially copied into svn_client_args_to_target_array() in
879  * order to move to libsvn_client while maintaining backward compatibility. */
880 svn_error_t *
svn_opt__args_to_target_array(apr_array_header_t ** targets_p,apr_getopt_t * os,const apr_array_header_t * known_targets,apr_pool_t * pool)881 svn_opt__args_to_target_array(apr_array_header_t **targets_p,
882                               apr_getopt_t *os,
883                               const apr_array_header_t *known_targets,
884                               apr_pool_t *pool)
885 {
886   int i;
887   svn_error_t *err = SVN_NO_ERROR;
888   apr_array_header_t *input_targets =
889     apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
890   apr_array_header_t *output_targets =
891     apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
892 
893   /* Step 1:  create a master array of targets that are in UTF-8
894      encoding, and come from concatenating the targets left by apr_getopt,
895      plus any extra targets (e.g., from the --targets switch.) */
896 
897   for (; os->ind < os->argc; os->ind++)
898     {
899       /* The apr_getopt targets are still in native encoding. */
900       const char *raw_target = os->argv[os->ind];
901       SVN_ERR(svn_utf_cstring_to_utf8
902               ((const char **) apr_array_push(input_targets),
903                raw_target, pool));
904     }
905 
906   if (known_targets)
907     {
908       for (i = 0; i < known_targets->nelts; i++)
909         {
910           /* The --targets array have already been converted to UTF-8,
911              because we needed to split up the list with svn_cstring_split. */
912           const char *utf8_target = APR_ARRAY_IDX(known_targets,
913                                                   i, const char *);
914           APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
915         }
916     }
917 
918   /* Step 2:  process each target.  */
919 
920   for (i = 0; i < input_targets->nelts; i++)
921     {
922       const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
923       const char *true_target;
924       const char *target;      /* after all processing is finished */
925       const char *peg_rev;
926 
927       /*
928        * This is needed so that the target can be properly canonicalized,
929        * otherwise the canonicalization does not treat a ".@BASE" as a "."
930        * with a BASE peg revision, and it is not canonicalized to "@BASE".
931        * If any peg revision exists, it is appended to the final
932        * canonicalized path or URL.  Do not use svn_opt_parse_path()
933        * because the resulting peg revision is a structure that would have
934        * to be converted back into a string.  Converting from a string date
935        * to the apr_time_t field in the svn_opt_revision_value_t and back to
936        * a string would not necessarily preserve the exact bytes of the
937        * input date, so its easier just to keep it in string form.
938        */
939       SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
940                                                  utf8_target, pool));
941 
942       /* URLs and wc-paths get treated differently. */
943       if (svn_path_is_url(true_target))
944         {
945           SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
946                                                  pool));
947         }
948       else  /* not a url, so treat as a path */
949         {
950           const char *base_name;
951 
952           SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
953                                                  pool));
954 
955           /* If the target has the same name as a Subversion
956              working copy administrative dir, skip it. */
957           base_name = svn_dirent_basename(true_target, pool);
958 
959           /* FIXME:
960              The canonical list of administrative directory names is
961              maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
962              That list can't be used here, because that use would
963              create a circular dependency between libsvn_wc and
964              libsvn_subr.  Make sure changes to the lists are always
965              synchronized! */
966           if (0 == strcmp(base_name, ".svn")
967               || 0 == strcmp(base_name, "_svn"))
968             {
969               err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
970                                       err, _("'%s' ends in a reserved name"),
971                                       utf8_target);
972               continue;
973             }
974         }
975 
976       target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL);
977 
978       APR_ARRAY_PUSH(output_targets, const char *) = target;
979     }
980 
981 
982   /* kff todo: need to remove redundancies from targets before
983      passing it to the cmd_func. */
984 
985   *targets_p = output_targets;
986 
987   return err;
988 }
989 
990 svn_error_t *
svn_opt_parse_revprop(apr_hash_t ** revprop_table_p,const char * revprop_spec,apr_pool_t * pool)991 svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
992                       apr_pool_t *pool)
993 {
994   const char *sep, *propname;
995   svn_string_t *propval;
996 
997   if (! *revprop_spec)
998     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
999                             _("Revision property pair is empty"));
1000 
1001   if (! *revprop_table_p)
1002     *revprop_table_p = apr_hash_make(pool);
1003 
1004   sep = strchr(revprop_spec, '=');
1005   if (sep)
1006     {
1007       propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
1008       SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
1009       propval = svn_string_create(sep + 1, pool);
1010     }
1011   else
1012     {
1013       SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
1014       propval = svn_string_create_empty(pool);
1015     }
1016 
1017   if (!svn_prop_name_is_valid(propname))
1018     return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
1019                              _("'%s' is not a valid Subversion property name"),
1020                              propname);
1021 
1022   svn_hash_sets(*revprop_table_p, propname, propval);
1023 
1024   return SVN_NO_ERROR;
1025 }
1026 
1027 svn_error_t *
svn_opt__split_arg_at_peg_revision(const char ** true_target,const char ** peg_revision,const char * utf8_target,apr_pool_t * pool)1028 svn_opt__split_arg_at_peg_revision(const char **true_target,
1029                                    const char **peg_revision,
1030                                    const char *utf8_target,
1031                                    apr_pool_t *pool)
1032 {
1033   const char *peg_start = NULL; /* pointer to the peg revision, if any */
1034   const char *ptr;
1035 
1036   for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
1037         --ptr)
1038     {
1039       /* If we hit a path separator, stop looking.  This is OK
1040           only because our revision specifiers can't contain '/'. */
1041       if (*ptr == '/')
1042         break;
1043 
1044       if (*ptr == '@')
1045         {
1046           peg_start = ptr;
1047           break;
1048         }
1049     }
1050 
1051   if (peg_start)
1052     {
1053       *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1054       if (peg_revision)
1055         *peg_revision = apr_pstrdup(pool, peg_start);
1056     }
1057   else
1058     {
1059       *true_target = utf8_target;
1060       if (peg_revision)
1061         *peg_revision = "";
1062     }
1063 
1064   return SVN_NO_ERROR;
1065 }
1066 
1067 svn_error_t *
svn_opt__arg_canonicalize_url(const char ** url_out,const char * url_in,apr_pool_t * pool)1068 svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1069                               apr_pool_t *pool)
1070 {
1071   const char *target;
1072 
1073   /* Convert to URI. */
1074   target = svn_path_uri_from_iri(url_in, pool);
1075   /* Auto-escape some ASCII characters. */
1076   target = svn_path_uri_autoescape(target, pool);
1077 
1078 #if '/' != SVN_PATH_LOCAL_SEPARATOR
1079   /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1080   if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1081     {
1082       char *p = apr_pstrdup(pool, target);
1083       target = p;
1084 
1085       /* Convert all local-style separators to the canonical ones. */
1086       for (; *p != '\0'; ++p)
1087         if (*p == SVN_PATH_LOCAL_SEPARATOR)
1088           *p = '/';
1089     }
1090 #endif
1091 
1092   /* Verify that no backpaths are present in the URL. */
1093   if (svn_path_is_backpath_present(target))
1094     return svn_error_createf(SVN_ERR_BAD_URL, 0,
1095                              _("URL '%s' contains a '..' element"),
1096                              target);
1097 
1098   /* Strip any trailing '/' and collapse other redundant elements. */
1099   target = svn_uri_canonicalize(target, pool);
1100 
1101   *url_out = target;
1102   return SVN_NO_ERROR;
1103 }
1104 
1105 svn_error_t *
svn_opt__arg_canonicalize_path(const char ** path_out,const char * path_in,apr_pool_t * pool)1106 svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1107                                apr_pool_t *pool)
1108 {
1109   const char *apr_target;
1110   char *truenamed_target; /* APR-encoded */
1111   apr_status_t apr_err;
1112 
1113   /* canonicalize case, and change all separators to '/'. */
1114   SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1115   apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1116                                APR_FILEPATH_TRUENAME, pool);
1117 
1118   if (!apr_err)
1119     /* We have a canonicalized APR-encoded target now. */
1120     apr_target = truenamed_target;
1121   else if (APR_STATUS_IS_ENOENT(apr_err))
1122     /* It's okay for the file to not exist, that just means we
1123        have to accept the case given to the client. We'll use
1124        the original APR-encoded target. */
1125     ;
1126   else
1127     return svn_error_createf(apr_err, NULL,
1128                              _("Error resolving case of '%s'"),
1129                              svn_dirent_local_style(path_in, pool));
1130 
1131   /* convert back to UTF-8. */
1132   SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1133   *path_out = svn_dirent_canonicalize(*path_out, pool);
1134 
1135   return SVN_NO_ERROR;
1136 }
1137 
1138 
1139 svn_error_t *
svn_opt__print_version_info(const char * pgm_name,const char * footer,const svn_version_extended_t * info,svn_boolean_t quiet,svn_boolean_t verbose,apr_pool_t * pool)1140 svn_opt__print_version_info(const char *pgm_name,
1141                             const char *footer,
1142                             const svn_version_extended_t *info,
1143                             svn_boolean_t quiet,
1144                             svn_boolean_t verbose,
1145                             apr_pool_t *pool)
1146 {
1147   if (quiet)
1148     return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1149 
1150   SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1151                                      "   compiled %s, %s on %s\n\n"),
1152                              pgm_name, SVN_VERSION,
1153                              svn_version_ext_build_date(info),
1154                              svn_version_ext_build_time(info),
1155                              svn_version_ext_build_host(info)));
1156   SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1157 
1158   if (footer)
1159     {
1160       SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1161     }
1162 
1163   if (verbose)
1164     {
1165       const apr_array_header_t *libs;
1166 
1167       SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1168       SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1169                                  svn_version_ext_runtime_host(info)));
1170       if (svn_version_ext_runtime_osname(info))
1171         {
1172           SVN_ERR(svn_cmdline_printf(pool, _("  - %s\n"),
1173                                      svn_version_ext_runtime_osname(info)));
1174         }
1175 
1176       libs = svn_version_ext_linked_libs(info);
1177       if (libs && libs->nelts)
1178         {
1179           const svn_version_ext_linked_lib_t *lib;
1180           int i;
1181 
1182           SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1183                                     stdout, pool));
1184           for (i = 0; i < libs->nelts; ++i)
1185             {
1186               lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1187               if (lib->runtime_version)
1188                 SVN_ERR(svn_cmdline_printf(pool,
1189                                            "  - %s %s (compiled with %s)\n",
1190                                            lib->name,
1191                                            lib->runtime_version,
1192                                            lib->compiled_version));
1193               else
1194                 SVN_ERR(svn_cmdline_printf(pool,
1195                                            "  - %s %s (static)\n",
1196                                            lib->name,
1197                                            lib->compiled_version));
1198             }
1199         }
1200 
1201       libs = svn_version_ext_loaded_libs(info);
1202       if (libs && libs->nelts)
1203         {
1204           const svn_version_ext_loaded_lib_t *lib;
1205           int i;
1206 
1207           SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1208                                     stdout, pool));
1209           for (i = 0; i < libs->nelts; ++i)
1210             {
1211               lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1212               if (lib->version)
1213                 SVN_ERR(svn_cmdline_printf(pool,
1214                                            "  - %s   (%s)\n",
1215                                            lib->name, lib->version));
1216               else
1217                 SVN_ERR(svn_cmdline_printf(pool, "  - %s\n", lib->name));
1218             }
1219         }
1220     }
1221 
1222   return SVN_NO_ERROR;
1223 }
1224 
1225 svn_error_t *
svn_opt_print_help5(apr_getopt_t * os,const char * pgm_name,svn_boolean_t print_version,svn_boolean_t quiet,svn_boolean_t verbose,const char * version_footer,const char * header,const svn_opt_subcommand_desc3_t * cmd_table,const apr_getopt_option_t * option_table,const int * global_options,const char * footer,apr_pool_t * pool)1226 svn_opt_print_help5(apr_getopt_t *os,
1227                     const char *pgm_name,
1228                     svn_boolean_t print_version,
1229                     svn_boolean_t quiet,
1230                     svn_boolean_t verbose,
1231                     const char *version_footer,
1232                     const char *header,
1233                     const svn_opt_subcommand_desc3_t *cmd_table,
1234                     const apr_getopt_option_t *option_table,
1235                     const int *global_options,
1236                     const char *footer,
1237                     apr_pool_t *pool)
1238 {
1239   apr_array_header_t *targets = NULL;
1240 
1241   if (os)
1242     SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1243 
1244   if (os && targets->nelts)  /* help on subcommand(s) requested */
1245     {
1246       int i;
1247 
1248       for (i = 0; i < targets->nelts; i++)
1249         {
1250           subcommand_help(APR_ARRAY_IDX(targets, i, const char *),
1251                           cmd_table, option_table, global_options,
1252                           verbose, pool);
1253         }
1254     }
1255   else if (print_version)   /* just --version */
1256     {
1257       SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1258                                           svn_version_extended(verbose, pool),
1259                                           quiet, verbose, pool));
1260     }
1261   else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
1262     print_generic_help(header, cmd_table, option_table, footer,
1263                        verbose,
1264                        pool, stdout);
1265   else                                       /* unknown option or cmd */
1266     SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1267                                 _("Type '%s help' for usage.\n"), pgm_name));
1268 
1269   return SVN_NO_ERROR;
1270 }
1271