1/*
2 * props.c: Utility functions for property handling
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
27
28/*** Includes. ***/
29
30#include <stdlib.h>
31
32#include <apr_hash.h>
33#include "svn_hash.h"
34#include "svn_cmdline.h"
35#include "svn_string.h"
36#include "svn_error.h"
37#include "svn_sorts.h"
38#include "svn_subst.h"
39#include "svn_props.h"
40#include "svn_string.h"
41#include "svn_opt.h"
42#include "svn_xml.h"
43#include "svn_base64.h"
44#include "cl.h"
45
46#include "private/svn_string_private.h"
47#include "private/svn_cmdline_private.h"
48
49#include "svn_private_config.h"
50
51
52svn_error_t *
53svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
54                        const apr_array_header_t *targets,
55                        const char **URL,
56                        svn_client_ctx_t *ctx,
57                        apr_pool_t *pool)
58{
59  const char *target;
60
61  if (revision->kind != svn_opt_revision_number
62      && revision->kind != svn_opt_revision_date
63      && revision->kind != svn_opt_revision_head)
64    return svn_error_create
65      (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
66       _("Must specify the revision as a number, a date or 'HEAD' "
67         "when operating on a revision property"));
68
69  /* There must be exactly one target at this point.  If it was optional and
70     unspecified by the user, the caller has already added the implicit '.'. */
71  if (targets->nelts != 1)
72    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
73                            _("Wrong number of targets specified"));
74
75  /* (The docs say the target must be either a URL or implicit '.', but
76     explicit WC targets are also accepted.) */
77  target = APR_ARRAY_IDX(targets, 0, const char *);
78  SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool));
79  if (*URL == NULL)
80    return svn_error_create
81      (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
82       _("Either a URL or versioned item is required"));
83
84  return SVN_NO_ERROR;
85}
86
87void
88svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
89                               apr_pool_t *pool)
90{
91  svn_stringbuf_t *propbuf;
92
93  if (!svn_prop_is_boolean(propname))
94    return;
95
96  propbuf = svn_stringbuf_create(propval, pool);
97  svn_stringbuf_strip_whitespace(propbuf);
98
99  if (propbuf->data[0] == '\0'
100      || svn_cstring_casecmp(propbuf->data, "0") == 0
101      || svn_cstring_casecmp(propbuf->data, "no") == 0
102      || svn_cstring_casecmp(propbuf->data, "off") == 0
103      || svn_cstring_casecmp(propbuf->data, "false") == 0)
104    {
105      svn_error_t *err = svn_error_createf
106        (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
107         _("To turn off the %s property, use 'svn propdel';\n"
108           "setting the property to '%s' will not turn it off."),
109           propname, propval);
110      svn_handle_warning2(stderr, err, "svn: ");
111      svn_error_clear(err);
112    }
113}
114
115
116/* Context for sorting property names */
117struct simprop_context_t
118{
119  svn_string_t name;    /* The name of the property we're comparing with */
120  svn_membuf_t buffer;  /* Buffer for similarity testing */
121};
122
123struct simprop_t
124{
125  const char *propname; /* The original svn: property name */
126  svn_string_t name;    /* The property name without the svn: prefix */
127  unsigned int score;   /* The similarity score */
128  apr_size_t diff;      /* Number of chars different from context.name */
129  struct simprop_context_t *context; /* Sorting context for qsort() */
130};
131
132/* Similarity test between two property names */
133static APR_INLINE unsigned int
134simprop_key_diff(const svn_string_t *key, const svn_string_t *ctx,
135                 svn_membuf_t *buffer, apr_size_t *diff)
136{
137  apr_size_t lcs;
138  const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs);
139  if (key->len > ctx->len)
140    *diff = key->len - lcs;
141  else
142    *diff = ctx->len - lcs;
143  return score;
144}
145
146/* Key comparator for qsort for simprop_t */
147static int
148simprop_compare(const void *pkeya, const void *pkeyb)
149{
150  struct simprop_t *const keya = *(struct simprop_t *const *)pkeya;
151  struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb;
152  struct simprop_context_t *const context = keya->context;
153
154  if (keya->score == -1)
155    keya->score = simprop_key_diff(&keya->name, &context->name,
156                                   &context->buffer, &keya->diff);
157  if (keyb->score == -1)
158    keyb->score = simprop_key_diff(&keyb->name, &context->name,
159                                   &context->buffer, &keyb->diff);
160
161  return (keya->score < keyb->score ? 1
162          : (keya->score > keyb->score ? -1
163             : (keya->diff > keyb->diff ? 1
164                : (keya->diff < keyb->diff ? -1 : 0))));
165}
166
167
168static const char*
169force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
170                          apr_pool_t *scratch_pool)
171{
172  switch (prop_use)
173    {
174    case svn_cl__prop_use_set:
175      return apr_psprintf(
176          scratch_pool,
177          _("(To set the '%s' property, re-run with '--force'.)"),
178          prop_name);
179    case svn_cl__prop_use_edit:
180      return apr_psprintf(
181          scratch_pool,
182          _("(To edit the '%s' property, re-run with '--force'.)"),
183          prop_name);
184    case svn_cl__prop_use_use:
185    default:
186      return apr_psprintf(
187          scratch_pool,
188          _("(To use the '%s' property, re-run with '--force'.)"),
189          prop_name);
190    }
191}
192
193static const char*
194wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name,
195                         apr_pool_t *scratch_pool)
196{
197  switch (prop_use)
198    {
199    case svn_cl__prop_use_set:
200      return apr_psprintf(
201          scratch_pool,
202          _("'%s' is not a valid %s property name;"
203            " re-run with '--force' to set it"),
204          prop_name, SVN_PROP_PREFIX);
205    case svn_cl__prop_use_edit:
206      return apr_psprintf(
207          scratch_pool,
208          _("'%s' is not a valid %s property name;"
209            " re-run with '--force' to edit it"),
210          prop_name, SVN_PROP_PREFIX);
211    case svn_cl__prop_use_use:
212    default:
213      return apr_psprintf(
214          scratch_pool,
215          _("'%s' is not a valid %s property name;"
216            " re-run with '--force' to use it"),
217          prop_name, SVN_PROP_PREFIX);
218    }
219}
220
221svn_error_t *
222svn_cl__check_svn_prop_name(const char *propname,
223                            svn_boolean_t revprop,
224                            svn_cl__prop_use_t prop_use,
225                            apr_pool_t *scratch_pool)
226{
227  static const char *const nodeprops[] =
228    {
229      SVN_PROP_NODE_ALL_PROPS
230    };
231  static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops);
232
233  static const char *const revprops[] =
234    {
235      SVN_PROP_REVISION_ALL_PROPS
236    };
237  static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops);
238
239  const char *const *const proplist = (revprop ? revprops : nodeprops);
240  const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
241
242  struct simprop_t **propkeys;
243  struct simprop_t *propbuf;
244  apr_size_t i;
245
246  struct simprop_context_t context;
247  svn_string_t prefix;
248
249  context.name.data = propname;
250  context.name.len = strlen(propname);
251  prefix.data = SVN_PROP_PREFIX;
252  prefix.len = strlen(SVN_PROP_PREFIX);
253
254  svn_membuf__create(&context.buffer, 0, scratch_pool);
255
256  /* First, check if the name is even close to being in the svn: namespace.
257     It must contain a colon in the right place, and we only allow
258     one-char typos or a single transposition. */
259  if (context.name.len < prefix.len
260      || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1])
261    return SVN_NO_ERROR;        /* Wrong prefix, ignore */
262  else
263    {
264      apr_size_t lcs;
265      const apr_size_t name_len = context.name.len;
266      context.name.len = prefix.len; /* Only check up to the prefix length */
267      svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs);
268      context.name.len = name_len; /* Restore the original propname length */
269      if (lcs < prefix.len - 1)
270        return SVN_NO_ERROR;    /* Wrong prefix, ignore */
271
272      /* If the prefix is slightly different, the rest must be
273         identical in order to trigger the error. */
274      if (lcs == prefix.len - 1)
275        {
276          for (i = 0; i < numprops; ++i)
277            {
278              if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len))
279                return svn_error_createf(
280                  SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
281                  _("'%s' is not a valid %s property name;"
282                    " did you mean '%s'?\n%s"),
283                  propname, SVN_PROP_PREFIX, proplist[i],
284                  force_prop_option_message(prop_use, propname, scratch_pool));
285            }
286          return SVN_NO_ERROR;
287        }
288    }
289
290  /* Now find the closest match from amongst the set of reserved
291     node or revision property names. Skip the prefix while matching,
292     we already know that it's the same and looking at it would only
293     skew the results. */
294  propkeys = apr_palloc(scratch_pool,
295                        numprops * sizeof(struct simprop_t*));
296  propbuf = apr_palloc(scratch_pool,
297                       numprops * sizeof(struct simprop_t));
298  context.name.data += prefix.len;
299  context.name.len -= prefix.len;
300  for (i = 0; i < numprops; ++i)
301    {
302      propkeys[i] = &propbuf[i];
303      propbuf[i].propname = proplist[i];
304      propbuf[i].name.data = proplist[i] + prefix.len;
305      propbuf[i].name.len = strlen(propbuf[i].name.data);
306      propbuf[i].score = (unsigned int)-1;
307      propbuf[i].context = &context;
308    }
309
310  qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
311
312  if (0 == propkeys[0]->diff)
313    return SVN_NO_ERROR;        /* We found an exact match. */
314
315  /* See if we can suggest a sane alternative spelling */
316  for (i = 0; i < numprops; ++i)
317    if (propkeys[i]->score < 666) /* 2/3 similarity required */
318      break;
319
320  switch (i)
321    {
322    case 0:
323      /* The best alternative isn't good enough */
324      return svn_error_create(
325        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
326        wrong_prop_error_message(prop_use, propname, scratch_pool));
327
328    case 1:
329      /* There is only one good candidate */
330      return svn_error_createf(
331        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
332        _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"),
333        propname, SVN_PROP_PREFIX, propkeys[0]->propname,
334        force_prop_option_message(prop_use, propname, scratch_pool));
335
336    case 2:
337      /* Suggest a list of the most likely candidates */
338      return svn_error_createf(
339        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
340        _("'%s' is not a valid %s property name\n"
341          "Did you mean '%s' or '%s'?\n%s"),
342        propname, SVN_PROP_PREFIX,
343        propkeys[0]->propname, propkeys[1]->propname,
344        force_prop_option_message(prop_use, propname, scratch_pool));
345
346    default:
347      /* Never suggest more than three candidates */
348      return svn_error_createf(
349        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
350        _("'%s' is not a valid %s property name\n"
351          "Did you mean '%s', '%s' or '%s'?\n%s"),
352        propname, SVN_PROP_PREFIX,
353        propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname,
354        force_prop_option_message(prop_use, propname, scratch_pool));
355    }
356}
357