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
58const svn_opt_subcommand_desc3_t *
59svn_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
82const apr_getopt_option_t *
83svn_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. */
117static const apr_getopt_option_t *
118get_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 */
149static void
150format_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
182void
183svn_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
192svn_boolean_t
193svn_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 */
218static svn_error_t *
219print_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. */
341static svn_error_t *
342print_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
385static void
386print_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
411void
412svn_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 */
427static void
428subcommand_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
454void
455svn_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 */
485static int
486revision_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. */
515static 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
588int
589svn_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
614int
615svn_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
632svn_error_t *
633svn_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
660const char *
661svn_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
688svn_opt_revision_range_t *
689svn_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
700svn_opt_revision_range_t *
701svn_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. */
721static void
722array_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
736void
737svn_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
746svn_error_t *
747svn_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
770svn_error_t *
771svn_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
792svn_error_t *
793svn_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. */
880svn_error_t *
881svn_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
990svn_error_t *
991svn_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
1027svn_error_t *
1028svn_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
1067svn_error_t *
1068svn_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
1105svn_error_t *
1106svn_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
1139svn_error_t *
1140svn_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 on %s\n\n"),
1152                             pgm_name, SVN_VERSION,
1153                             svn_version_ext_build_host(info)));
1154  SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1155
1156  if (footer)
1157    {
1158      SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1159    }
1160
1161  if (verbose)
1162    {
1163      const apr_array_header_t *libs;
1164
1165      SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1166      SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1167                                 svn_version_ext_runtime_host(info)));
1168      if (svn_version_ext_runtime_osname(info))
1169        {
1170          SVN_ERR(svn_cmdline_printf(pool, _("  - %s\n"),
1171                                     svn_version_ext_runtime_osname(info)));
1172        }
1173
1174      libs = svn_version_ext_linked_libs(info);
1175      if (libs && libs->nelts)
1176        {
1177          const svn_version_ext_linked_lib_t *lib;
1178          int i;
1179
1180          SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1181                                    stdout, pool));
1182          for (i = 0; i < libs->nelts; ++i)
1183            {
1184              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1185              if (lib->runtime_version)
1186                SVN_ERR(svn_cmdline_printf(pool,
1187                                           "  - %s %s (compiled with %s)\n",
1188                                           lib->name,
1189                                           lib->runtime_version,
1190                                           lib->compiled_version));
1191              else
1192                SVN_ERR(svn_cmdline_printf(pool,
1193                                           "  - %s %s (static)\n",
1194                                           lib->name,
1195                                           lib->compiled_version));
1196            }
1197        }
1198
1199      libs = svn_version_ext_loaded_libs(info);
1200      if (libs && libs->nelts)
1201        {
1202          const svn_version_ext_loaded_lib_t *lib;
1203          int i;
1204
1205          SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1206                                    stdout, pool));
1207          for (i = 0; i < libs->nelts; ++i)
1208            {
1209              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1210              if (lib->version)
1211                SVN_ERR(svn_cmdline_printf(pool,
1212                                           "  - %s   (%s)\n",
1213                                           lib->name, lib->version));
1214              else
1215                SVN_ERR(svn_cmdline_printf(pool, "  - %s\n", lib->name));
1216            }
1217        }
1218    }
1219
1220  return SVN_NO_ERROR;
1221}
1222
1223svn_error_t *
1224svn_opt_print_help5(apr_getopt_t *os,
1225                    const char *pgm_name,
1226                    svn_boolean_t print_version,
1227                    svn_boolean_t quiet,
1228                    svn_boolean_t verbose,
1229                    const char *version_footer,
1230                    const char *header,
1231                    const svn_opt_subcommand_desc3_t *cmd_table,
1232                    const apr_getopt_option_t *option_table,
1233                    const int *global_options,
1234                    const char *footer,
1235                    apr_pool_t *pool)
1236{
1237  apr_array_header_t *targets = NULL;
1238
1239  if (os)
1240    SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1241
1242  if (os && targets->nelts)  /* help on subcommand(s) requested */
1243    {
1244      int i;
1245
1246      for (i = 0; i < targets->nelts; i++)
1247        {
1248          subcommand_help(APR_ARRAY_IDX(targets, i, const char *),
1249                          cmd_table, option_table, global_options,
1250                          verbose, pool);
1251        }
1252    }
1253  else if (print_version)   /* just --version */
1254    {
1255      SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1256                                          svn_version_extended(verbose, pool),
1257                                          quiet, verbose, pool));
1258    }
1259  else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
1260    print_generic_help(header, cmd_table, option_table, footer,
1261                       verbose,
1262                       pool, stdout);
1263  else                                       /* unknown option or cmd */
1264    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1265                                _("Type '%s help' for usage.\n"), pgm_name));
1266
1267  return SVN_NO_ERROR;
1268}
1269