1251881Speter/*
2251881Speter * props.c: Utility functions for property handling
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <stdlib.h>
31251881Speter
32251881Speter#include <apr_hash.h>
33251881Speter#include "svn_hash.h"
34251881Speter#include "svn_cmdline.h"
35251881Speter#include "svn_string.h"
36251881Speter#include "svn_error.h"
37251881Speter#include "svn_sorts.h"
38251881Speter#include "svn_subst.h"
39251881Speter#include "svn_props.h"
40251881Speter#include "svn_string.h"
41251881Speter#include "svn_opt.h"
42251881Speter#include "svn_xml.h"
43251881Speter#include "svn_base64.h"
44251881Speter#include "cl.h"
45251881Speter
46251881Speter#include "private/svn_string_private.h"
47251881Speter#include "private/svn_cmdline_private.h"
48251881Speter
49251881Speter#include "svn_private_config.h"
50251881Speter
51251881Speter
52251881Spetersvn_error_t *
53251881Spetersvn_cl__revprop_prepare(const svn_opt_revision_t *revision,
54251881Speter                        const apr_array_header_t *targets,
55251881Speter                        const char **URL,
56251881Speter                        svn_client_ctx_t *ctx,
57251881Speter                        apr_pool_t *pool)
58251881Speter{
59251881Speter  const char *target;
60251881Speter
61251881Speter  if (revision->kind != svn_opt_revision_number
62251881Speter      && revision->kind != svn_opt_revision_date
63251881Speter      && revision->kind != svn_opt_revision_head)
64251881Speter    return svn_error_create
65251881Speter      (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
66251881Speter       _("Must specify the revision as a number, a date or 'HEAD' "
67251881Speter         "when operating on a revision property"));
68251881Speter
69251881Speter  /* There must be exactly one target at this point.  If it was optional and
70251881Speter     unspecified by the user, the caller has already added the implicit '.'. */
71251881Speter  if (targets->nelts != 1)
72251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
73251881Speter                            _("Wrong number of targets specified"));
74251881Speter
75251881Speter  /* (The docs say the target must be either a URL or implicit '.', but
76251881Speter     explicit WC targets are also accepted.) */
77251881Speter  target = APR_ARRAY_IDX(targets, 0, const char *);
78251881Speter  SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool));
79251881Speter  if (*URL == NULL)
80251881Speter    return svn_error_create
81251881Speter      (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
82251881Speter       _("Either a URL or versioned item is required"));
83251881Speter
84251881Speter  return SVN_NO_ERROR;
85251881Speter}
86251881Speter
87251881Spetervoid
88251881Spetersvn_cl__check_boolean_prop_val(const char *propname, const char *propval,
89251881Speter                               apr_pool_t *pool)
90251881Speter{
91251881Speter  svn_stringbuf_t *propbuf;
92251881Speter
93251881Speter  if (!svn_prop_is_boolean(propname))
94251881Speter    return;
95251881Speter
96251881Speter  propbuf = svn_stringbuf_create(propval, pool);
97251881Speter  svn_stringbuf_strip_whitespace(propbuf);
98251881Speter
99251881Speter  if (propbuf->data[0] == '\0'
100251881Speter      || svn_cstring_casecmp(propbuf->data, "0") == 0
101251881Speter      || svn_cstring_casecmp(propbuf->data, "no") == 0
102251881Speter      || svn_cstring_casecmp(propbuf->data, "off") == 0
103251881Speter      || svn_cstring_casecmp(propbuf->data, "false") == 0)
104251881Speter    {
105251881Speter      svn_error_t *err = svn_error_createf
106251881Speter        (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
107251881Speter         _("To turn off the %s property, use 'svn propdel';\n"
108251881Speter           "setting the property to '%s' will not turn it off."),
109251881Speter           propname, propval);
110251881Speter      svn_handle_warning2(stderr, err, "svn: ");
111251881Speter      svn_error_clear(err);
112251881Speter    }
113251881Speter}
114251881Speter
115251881Speter
116251881Speter/* Context for sorting property names */
117251881Speterstruct simprop_context_t
118251881Speter{
119251881Speter  svn_string_t name;    /* The name of the property we're comparing with */
120251881Speter  svn_membuf_t buffer;  /* Buffer for similarity testing */
121251881Speter};
122251881Speter
123251881Speterstruct simprop_t
124251881Speter{
125251881Speter  const char *propname; /* The original svn: property name */
126251881Speter  svn_string_t name;    /* The property name without the svn: prefix */
127251881Speter  unsigned int score;   /* The similarity score */
128251881Speter  apr_size_t diff;      /* Number of chars different from context.name */
129251881Speter  struct simprop_context_t *context; /* Sorting context for qsort() */
130251881Speter};
131251881Speter
132251881Speter/* Similarity test between two property names */
133251881Speterstatic APR_INLINE unsigned int
134251881Spetersimprop_key_diff(const svn_string_t *key, const svn_string_t *ctx,
135251881Speter                 svn_membuf_t *buffer, apr_size_t *diff)
136251881Speter{
137251881Speter  apr_size_t lcs;
138251881Speter  const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs);
139251881Speter  if (key->len > ctx->len)
140251881Speter    *diff = key->len - lcs;
141251881Speter  else
142251881Speter    *diff = ctx->len - lcs;
143251881Speter  return score;
144251881Speter}
145251881Speter
146251881Speter/* Key comparator for qsort for simprop_t */
147251881Speterstatic int
148251881Spetersimprop_compare(const void *pkeya, const void *pkeyb)
149251881Speter{
150251881Speter  struct simprop_t *const keya = *(struct simprop_t *const *)pkeya;
151251881Speter  struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb;
152251881Speter  struct simprop_context_t *const context = keya->context;
153251881Speter
154251881Speter  if (keya->score == -1)
155251881Speter    keya->score = simprop_key_diff(&keya->name, &context->name,
156251881Speter                                   &context->buffer, &keya->diff);
157251881Speter  if (keyb->score == -1)
158251881Speter    keyb->score = simprop_key_diff(&keyb->name, &context->name,
159251881Speter                                   &context->buffer, &keyb->diff);
160251881Speter
161251881Speter  return (keya->score < keyb->score ? 1
162251881Speter          : (keya->score > keyb->score ? -1
163251881Speter             : (keya->diff > keyb->diff ? 1
164251881Speter                : (keya->diff < keyb->diff ? -1 : 0))));
165251881Speter}
166251881Speter
167251881Speter
168251881Speterstatic const char*
169251881Speterforce_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
170251881Speter                          apr_pool_t *scratch_pool)
171251881Speter{
172251881Speter  switch (prop_use)
173251881Speter    {
174251881Speter    case svn_cl__prop_use_set:
175251881Speter      return apr_psprintf(
176251881Speter          scratch_pool,
177251881Speter          _("(To set the '%s' property, re-run with '--force'.)"),
178251881Speter          prop_name);
179251881Speter    case svn_cl__prop_use_edit:
180251881Speter      return apr_psprintf(
181251881Speter          scratch_pool,
182251881Speter          _("(To edit the '%s' property, re-run with '--force'.)"),
183251881Speter          prop_name);
184251881Speter    case svn_cl__prop_use_use:
185251881Speter    default:
186251881Speter      return apr_psprintf(
187251881Speter          scratch_pool,
188251881Speter          _("(To use the '%s' property, re-run with '--force'.)"),
189251881Speter          prop_name);
190251881Speter    }
191251881Speter}
192251881Speter
193251881Speterstatic const char*
194251881Speterwrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name,
195251881Speter                         apr_pool_t *scratch_pool)
196251881Speter{
197251881Speter  switch (prop_use)
198251881Speter    {
199251881Speter    case svn_cl__prop_use_set:
200251881Speter      return apr_psprintf(
201251881Speter          scratch_pool,
202251881Speter          _("'%s' is not a valid %s property name;"
203251881Speter            " re-run with '--force' to set it"),
204251881Speter          prop_name, SVN_PROP_PREFIX);
205251881Speter    case svn_cl__prop_use_edit:
206251881Speter      return apr_psprintf(
207251881Speter          scratch_pool,
208251881Speter          _("'%s' is not a valid %s property name;"
209251881Speter            " re-run with '--force' to edit it"),
210251881Speter          prop_name, SVN_PROP_PREFIX);
211251881Speter    case svn_cl__prop_use_use:
212251881Speter    default:
213251881Speter      return apr_psprintf(
214251881Speter          scratch_pool,
215251881Speter          _("'%s' is not a valid %s property name;"
216251881Speter            " re-run with '--force' to use it"),
217251881Speter          prop_name, SVN_PROP_PREFIX);
218251881Speter    }
219251881Speter}
220251881Speter
221251881Spetersvn_error_t *
222251881Spetersvn_cl__check_svn_prop_name(const char *propname,
223251881Speter                            svn_boolean_t revprop,
224251881Speter                            svn_cl__prop_use_t prop_use,
225251881Speter                            apr_pool_t *scratch_pool)
226251881Speter{
227251881Speter  static const char *const nodeprops[] =
228251881Speter    {
229251881Speter      SVN_PROP_NODE_ALL_PROPS
230251881Speter    };
231251881Speter  static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops);
232251881Speter
233251881Speter  static const char *const revprops[] =
234251881Speter    {
235251881Speter      SVN_PROP_REVISION_ALL_PROPS
236251881Speter    };
237251881Speter  static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops);
238251881Speter
239251881Speter  const char *const *const proplist = (revprop ? revprops : nodeprops);
240251881Speter  const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
241251881Speter
242251881Speter  struct simprop_t **propkeys;
243251881Speter  struct simprop_t *propbuf;
244251881Speter  apr_size_t i;
245251881Speter
246251881Speter  struct simprop_context_t context;
247251881Speter  svn_string_t prefix;
248251881Speter
249251881Speter  context.name.data = propname;
250251881Speter  context.name.len = strlen(propname);
251251881Speter  prefix.data = SVN_PROP_PREFIX;
252251881Speter  prefix.len = strlen(SVN_PROP_PREFIX);
253251881Speter
254251881Speter  svn_membuf__create(&context.buffer, 0, scratch_pool);
255251881Speter
256251881Speter  /* First, check if the name is even close to being in the svn: namespace.
257251881Speter     It must contain a colon in the right place, and we only allow
258251881Speter     one-char typos or a single transposition. */
259251881Speter  if (context.name.len < prefix.len
260251881Speter      || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1])
261251881Speter    return SVN_NO_ERROR;        /* Wrong prefix, ignore */
262251881Speter  else
263251881Speter    {
264251881Speter      apr_size_t lcs;
265251881Speter      const apr_size_t name_len = context.name.len;
266251881Speter      context.name.len = prefix.len; /* Only check up to the prefix length */
267251881Speter      svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs);
268251881Speter      context.name.len = name_len; /* Restore the original propname length */
269251881Speter      if (lcs < prefix.len - 1)
270251881Speter        return SVN_NO_ERROR;    /* Wrong prefix, ignore */
271251881Speter
272251881Speter      /* If the prefix is slightly different, the rest must be
273251881Speter         identical in order to trigger the error. */
274251881Speter      if (lcs == prefix.len - 1)
275251881Speter        {
276251881Speter          for (i = 0; i < numprops; ++i)
277251881Speter            {
278251881Speter              if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len))
279251881Speter                return svn_error_createf(
280251881Speter                  SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
281251881Speter                  _("'%s' is not a valid %s property name;"
282251881Speter                    " did you mean '%s'?\n%s"),
283251881Speter                  propname, SVN_PROP_PREFIX, proplist[i],
284251881Speter                  force_prop_option_message(prop_use, propname, scratch_pool));
285251881Speter            }
286251881Speter          return SVN_NO_ERROR;
287251881Speter        }
288251881Speter    }
289251881Speter
290251881Speter  /* Now find the closest match from amongst the set of reserved
291251881Speter     node or revision property names. Skip the prefix while matching,
292251881Speter     we already know that it's the same and looking at it would only
293251881Speter     skew the results. */
294251881Speter  propkeys = apr_palloc(scratch_pool,
295251881Speter                        numprops * sizeof(struct simprop_t*));
296251881Speter  propbuf = apr_palloc(scratch_pool,
297251881Speter                       numprops * sizeof(struct simprop_t));
298251881Speter  context.name.data += prefix.len;
299251881Speter  context.name.len -= prefix.len;
300251881Speter  for (i = 0; i < numprops; ++i)
301251881Speter    {
302251881Speter      propkeys[i] = &propbuf[i];
303251881Speter      propbuf[i].propname = proplist[i];
304251881Speter      propbuf[i].name.data = proplist[i] + prefix.len;
305251881Speter      propbuf[i].name.len = strlen(propbuf[i].name.data);
306251881Speter      propbuf[i].score = (unsigned int)-1;
307251881Speter      propbuf[i].context = &context;
308251881Speter    }
309251881Speter
310251881Speter  qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
311251881Speter
312251881Speter  if (0 == propkeys[0]->diff)
313251881Speter    return SVN_NO_ERROR;        /* We found an exact match. */
314251881Speter
315251881Speter  /* See if we can suggest a sane alternative spelling */
316251881Speter  for (i = 0; i < numprops; ++i)
317251881Speter    if (propkeys[i]->score < 666) /* 2/3 similarity required */
318251881Speter      break;
319251881Speter
320251881Speter  switch (i)
321251881Speter    {
322251881Speter    case 0:
323251881Speter      /* The best alternative isn't good enough */
324251881Speter      return svn_error_create(
325251881Speter        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
326251881Speter        wrong_prop_error_message(prop_use, propname, scratch_pool));
327251881Speter
328251881Speter    case 1:
329251881Speter      /* There is only one good candidate */
330251881Speter      return svn_error_createf(
331251881Speter        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
332251881Speter        _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"),
333251881Speter        propname, SVN_PROP_PREFIX, propkeys[0]->propname,
334251881Speter        force_prop_option_message(prop_use, propname, scratch_pool));
335251881Speter
336251881Speter    case 2:
337251881Speter      /* Suggest a list of the most likely candidates */
338251881Speter      return svn_error_createf(
339251881Speter        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
340251881Speter        _("'%s' is not a valid %s property name\n"
341251881Speter          "Did you mean '%s' or '%s'?\n%s"),
342251881Speter        propname, SVN_PROP_PREFIX,
343251881Speter        propkeys[0]->propname, propkeys[1]->propname,
344251881Speter        force_prop_option_message(prop_use, propname, scratch_pool));
345251881Speter
346251881Speter    default:
347251881Speter      /* Never suggest more than three candidates */
348251881Speter      return svn_error_createf(
349251881Speter        SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
350251881Speter        _("'%s' is not a valid %s property name\n"
351251881Speter          "Did you mean '%s', '%s' or '%s'?\n%s"),
352251881Speter        propname, SVN_PROP_PREFIX,
353251881Speter        propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname,
354251881Speter        force_prop_option_message(prop_use, propname, scratch_pool));
355251881Speter    }
356251881Speter}
357