1/*
2 * log.c :  entry point for log RA functions for ra_serf
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#include <apr_uri.h>
27#include <serf.h>
28
29#include "svn_hash.h"
30#include "svn_pools.h"
31#include "svn_ra.h"
32#include "svn_dav.h"
33#include "svn_base64.h"
34#include "svn_xml.h"
35#include "svn_config.h"
36#include "svn_path.h"
37#include "svn_props.h"
38
39#include "private/svn_dav_protocol.h"
40#include "private/svn_string_private.h"
41#include "private/svn_subr_private.h"
42#include "svn_private_config.h"
43
44#include "ra_serf.h"
45#include "../libsvn_ra/ra_loader.h"
46
47
48/*
49 * This enum represents the current state of our XML parsing for a REPORT.
50 */
51enum {
52  INITIAL = 0,
53  REPORT,
54  ITEM,
55  VERSION,
56  CREATOR,
57  DATE,
58  COMMENT,
59  REVPROP,
60  HAS_CHILDREN,
61  ADDED_PATH,
62  REPLACED_PATH,
63  DELETED_PATH,
64  MODIFIED_PATH,
65  SUBTRACTIVE_MERGE
66};
67
68typedef struct log_context_t {
69  apr_pool_t *pool;
70
71  /* parameters set by our caller */
72  const apr_array_header_t *paths;
73  svn_revnum_t start;
74  svn_revnum_t end;
75  int limit;
76  svn_boolean_t changed_paths;
77  svn_boolean_t strict_node_history;
78  svn_boolean_t include_merged_revisions;
79  const apr_array_header_t *revprops;
80  int nest_level; /* used to track mergeinfo nesting levels */
81  int count; /* only incremented when nest_level == 0 */
82
83  /* Collect information for storage into a log entry. Most of the entry
84     members are collected by individual states. revprops and paths are
85     N datapoints per entry.  */
86  apr_hash_t *collect_revprops;
87  apr_hash_t *collect_paths;
88
89  /* log receiver function and baton */
90  svn_log_entry_receiver_t receiver;
91  void *receiver_baton;
92
93  /* pre-1.5 compatibility */
94  svn_boolean_t want_author;
95  svn_boolean_t want_date;
96  svn_boolean_t want_message;
97} log_context_t;
98
99#define D_ "DAV:"
100#define S_ SVN_XML_NAMESPACE
101static const svn_ra_serf__xml_transition_t log_ttable[] = {
102  { INITIAL, S_, "log-report", REPORT,
103    FALSE, { NULL }, FALSE },
104
105  /* Note that we have an opener here. We need to construct a new LOG_ENTRY
106     to record multiple paths.  */
107  { REPORT, S_, "log-item", ITEM,
108    FALSE, { NULL }, TRUE },
109
110  { ITEM, D_, SVN_DAV__VERSION_NAME, VERSION,
111    TRUE, { NULL }, TRUE },
112
113  { ITEM, D_, "creator-displayname", CREATOR,
114    TRUE, { "?encoding", NULL }, TRUE },
115
116  { ITEM, S_, "date", DATE,
117    TRUE, { "?encoding", NULL }, TRUE },
118
119  { ITEM, D_, "comment", COMMENT,
120    TRUE, { "?encoding", NULL }, TRUE },
121
122  { ITEM, S_, "revprop", REVPROP,
123    TRUE, { "name", "?encoding", NULL }, TRUE },
124
125  { ITEM, S_, "has-children", HAS_CHILDREN,
126    FALSE, { NULL }, TRUE },
127
128  { ITEM, S_, "subtractive-merge", SUBTRACTIVE_MERGE,
129    FALSE, { NULL }, TRUE },
130
131  { ITEM, S_, "added-path", ADDED_PATH,
132    TRUE, { "?node-kind", "?text-mods", "?prop-mods",
133            "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
134
135  { ITEM, S_, "replaced-path", REPLACED_PATH,
136    TRUE, { "?node-kind", "?text-mods", "?prop-mods",
137            "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
138
139  { ITEM, S_, "deleted-path", DELETED_PATH,
140    TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
141
142  { ITEM, S_, "modified-path", MODIFIED_PATH,
143    TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
144
145  { 0 }
146};
147
148
149/* Store CDATA into REVPROPS, associated with PROPNAME. If ENCODING is not
150   NULL, then it must base "base64" and CDATA will be decoded first.
151
152   NOTE: PROPNAME must live longer than REVPROPS.  */
153static svn_error_t *
154collect_revprop(apr_hash_t *revprops,
155                const char *propname,
156                const svn_string_t *cdata,
157                const char *encoding)
158{
159  apr_pool_t *result_pool = apr_hash_pool_get(revprops);
160  const svn_string_t *decoded;
161
162  if (encoding)
163    {
164      /* Check for a known encoding type.  This is easy -- there's
165         only one.  */
166      if (strcmp(encoding, "base64") != 0)
167        {
168          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
169                                   _("Unsupported encoding '%s'"),
170                                   encoding);
171        }
172
173      decoded = svn_base64_decode_string(cdata, result_pool);
174    }
175  else
176    {
177      decoded = svn_string_dup(cdata, result_pool);
178    }
179
180  /* Caller has ensured PROPNAME has sufficient lifetime.  */
181  svn_hash_sets(revprops, propname, decoded);
182
183  return SVN_NO_ERROR;
184}
185
186
187/* Record ACTION on the path in CDATA into PATHS. Other properties about
188   the action are pulled from ATTRS.  */
189static svn_error_t *
190collect_path(apr_hash_t *paths,
191             char action,
192             const svn_string_t *cdata,
193             apr_hash_t *attrs)
194{
195  apr_pool_t *result_pool = apr_hash_pool_get(paths);
196  svn_log_changed_path2_t *lcp;
197  const char *copyfrom_path;
198  const char *copyfrom_rev;
199  const char *path;
200
201  lcp = svn_log_changed_path2_create(result_pool);
202  lcp->action = action;
203  lcp->copyfrom_rev = SVN_INVALID_REVNUM;
204
205  /* COPYFROM_* are only recorded for ADDED_PATH and REPLACED_PATH.  */
206  copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
207  copyfrom_rev = svn_hash_gets(attrs, "copyfrom-rev");
208  if (copyfrom_path && copyfrom_rev)
209    {
210      svn_revnum_t rev = SVN_STR_TO_REV(copyfrom_rev);
211
212      if (SVN_IS_VALID_REVNUM(rev))
213        {
214          lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
215          lcp->copyfrom_rev = rev;
216        }
217    }
218
219  lcp->node_kind = svn_node_kind_from_word(svn_hash_gets(attrs, "node-kind"));
220  lcp->text_modified = svn_tristate__from_word(svn_hash_gets(attrs,
221                                                             "text-mods"));
222  lcp->props_modified = svn_tristate__from_word(svn_hash_gets(attrs,
223                                                              "prop-mods"));
224
225  path = apr_pstrmemdup(result_pool, cdata->data, cdata->len);
226  svn_hash_sets(paths, path, lcp);
227
228  return SVN_NO_ERROR;
229}
230
231
232/* Conforms to svn_ra_serf__xml_opened_t  */
233static svn_error_t *
234log_opened(svn_ra_serf__xml_estate_t *xes,
235           void *baton,
236           int entered_state,
237           const svn_ra_serf__dav_props_t *tag,
238           apr_pool_t *scratch_pool)
239{
240  log_context_t *log_ctx = baton;
241
242  if (entered_state == ITEM)
243    {
244      apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
245
246      log_ctx->collect_revprops = apr_hash_make(state_pool);
247      log_ctx->collect_paths = apr_hash_make(state_pool);
248    }
249
250  return SVN_NO_ERROR;
251}
252
253
254/* Conforms to svn_ra_serf__xml_closed_t  */
255static svn_error_t *
256log_closed(svn_ra_serf__xml_estate_t *xes,
257           void *baton,
258           int leaving_state,
259           const svn_string_t *cdata,
260           apr_hash_t *attrs,
261           apr_pool_t *scratch_pool)
262{
263  log_context_t *log_ctx = baton;
264
265  if (leaving_state == ITEM)
266    {
267      svn_log_entry_t *log_entry;
268      const char *rev_str;
269
270      if (log_ctx->limit && (log_ctx->nest_level == 0)
271          && (++log_ctx->count > log_ctx->limit))
272        {
273          return SVN_NO_ERROR;
274        }
275
276      log_entry = svn_log_entry_create(scratch_pool);
277
278      /* Pick up the paths from the context. These have the same lifetime
279         as this state. That is long enough for us to pass the paths to
280         the receiver callback.  */
281      if (apr_hash_count(log_ctx->collect_paths) > 0)
282        {
283          log_entry->changed_paths = log_ctx->collect_paths;
284          log_entry->changed_paths2 = log_ctx->collect_paths;
285        }
286
287      /* ... and same story for the collected revprops.  */
288      log_entry->revprops = log_ctx->collect_revprops;
289
290      log_entry->has_children = svn_hash__get_bool(attrs,
291                                                   "has-children",
292                                                   FALSE);
293      log_entry->subtractive_merge = svn_hash__get_bool(attrs,
294                                                        "subtractive-merge",
295                                                        FALSE);
296
297      rev_str = svn_hash_gets(attrs, "revision");
298      if (rev_str)
299        log_entry->revision = SVN_STR_TO_REV(rev_str);
300      else
301        log_entry->revision = SVN_INVALID_REVNUM;
302
303      /* Give the info to the reporter */
304      SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
305                                log_entry,
306                                scratch_pool));
307
308      if (log_entry->has_children)
309        {
310          log_ctx->nest_level++;
311        }
312      if (! SVN_IS_VALID_REVNUM(log_entry->revision))
313        {
314          SVN_ERR_ASSERT(log_ctx->nest_level);
315          log_ctx->nest_level--;
316        }
317
318      /* These hash tables are going to be unusable once this state's
319         pool is destroyed. But let's not leave stale pointers in
320         structures that have a longer life.  */
321      log_ctx->collect_revprops = NULL;
322      log_ctx->collect_paths = NULL;
323    }
324  else if (leaving_state == VERSION)
325    {
326      svn_ra_serf__xml_note(xes, ITEM, "revision", cdata->data);
327    }
328  else if (leaving_state == CREATOR)
329    {
330      if (log_ctx->want_author)
331        {
332          SVN_ERR(collect_revprop(log_ctx->collect_revprops,
333                                  SVN_PROP_REVISION_AUTHOR,
334                                  cdata,
335                                  svn_hash_gets(attrs, "encoding")));
336        }
337    }
338  else if (leaving_state == DATE)
339    {
340      if (log_ctx->want_date)
341        {
342          SVN_ERR(collect_revprop(log_ctx->collect_revprops,
343                                  SVN_PROP_REVISION_DATE,
344                                  cdata,
345                                  svn_hash_gets(attrs, "encoding")));
346        }
347    }
348  else if (leaving_state == COMMENT)
349    {
350      if (log_ctx->want_message)
351        {
352          SVN_ERR(collect_revprop(log_ctx->collect_revprops,
353                                  SVN_PROP_REVISION_LOG,
354                                  cdata,
355                                  svn_hash_gets(attrs, "encoding")));
356        }
357    }
358  else if (leaving_state == REVPROP)
359    {
360      apr_pool_t *result_pool = apr_hash_pool_get(log_ctx->collect_revprops);
361
362      SVN_ERR(collect_revprop(
363                log_ctx->collect_revprops,
364                apr_pstrdup(result_pool,
365                            svn_hash_gets(attrs, "name")),
366                cdata,
367                svn_hash_gets(attrs, "encoding")
368                ));
369    }
370  else if (leaving_state == HAS_CHILDREN)
371    {
372      svn_ra_serf__xml_note(xes, ITEM, "has-children", "yes");
373    }
374  else if (leaving_state == SUBTRACTIVE_MERGE)
375    {
376      svn_ra_serf__xml_note(xes, ITEM, "subtractive-merge", "yes");
377    }
378  else
379    {
380      char action;
381
382      if (leaving_state == ADDED_PATH)
383        action = 'A';
384      else if (leaving_state == REPLACED_PATH)
385        action = 'R';
386      else if (leaving_state == DELETED_PATH)
387        action = 'D';
388      else
389        {
390          SVN_ERR_ASSERT(leaving_state == MODIFIED_PATH);
391          action = 'M';
392        }
393
394      SVN_ERR(collect_path(log_ctx->collect_paths, action, cdata, attrs));
395    }
396
397  return SVN_NO_ERROR;
398}
399
400
401static svn_error_t *
402create_log_body(serf_bucket_t **body_bkt,
403                void *baton,
404                serf_bucket_alloc_t *alloc,
405                apr_pool_t *pool)
406{
407  serf_bucket_t *buckets;
408  log_context_t *log_ctx = baton;
409
410  buckets = serf_bucket_aggregate_create(alloc);
411
412  svn_ra_serf__add_open_tag_buckets(buckets, alloc,
413                                    "S:log-report",
414                                    "xmlns:S", SVN_XML_NAMESPACE,
415                                    NULL);
416
417  svn_ra_serf__add_tag_buckets(buckets,
418                               "S:start-revision",
419                               apr_ltoa(pool, log_ctx->start),
420                               alloc);
421  svn_ra_serf__add_tag_buckets(buckets,
422                               "S:end-revision",
423                               apr_ltoa(pool, log_ctx->end),
424                               alloc);
425
426  if (log_ctx->limit)
427    {
428      svn_ra_serf__add_tag_buckets(buckets,
429                                   "S:limit", apr_ltoa(pool, log_ctx->limit),
430                                   alloc);
431    }
432
433  if (log_ctx->changed_paths)
434    {
435      svn_ra_serf__add_tag_buckets(buckets,
436                                   "S:discover-changed-paths", NULL,
437                                   alloc);
438    }
439
440  if (log_ctx->strict_node_history)
441    {
442      svn_ra_serf__add_tag_buckets(buckets,
443                                   "S:strict-node-history", NULL,
444                                   alloc);
445    }
446
447  if (log_ctx->include_merged_revisions)
448    {
449      svn_ra_serf__add_tag_buckets(buckets,
450                                   "S:include-merged-revisions", NULL,
451                                   alloc);
452    }
453
454  if (log_ctx->revprops)
455    {
456      int i;
457      for (i = 0; i < log_ctx->revprops->nelts; i++)
458        {
459          char *name = APR_ARRAY_IDX(log_ctx->revprops, i, char *);
460          svn_ra_serf__add_tag_buckets(buckets,
461                                       "S:revprop", name,
462                                       alloc);
463        }
464      if (log_ctx->revprops->nelts == 0)
465        {
466          svn_ra_serf__add_tag_buckets(buckets,
467                                       "S:no-revprops", NULL,
468                                       alloc);
469        }
470    }
471  else
472    {
473      svn_ra_serf__add_tag_buckets(buckets,
474                                   "S:all-revprops", NULL,
475                                   alloc);
476    }
477
478  if (log_ctx->paths)
479    {
480      int i;
481      for (i = 0; i < log_ctx->paths->nelts; i++)
482        {
483          svn_ra_serf__add_tag_buckets(buckets,
484                                       "S:path", APR_ARRAY_IDX(log_ctx->paths, i,
485                                                               const char*),
486                                       alloc);
487        }
488    }
489
490  svn_ra_serf__add_tag_buckets(buckets,
491                               "S:encode-binary-props", NULL,
492                               alloc);
493
494  svn_ra_serf__add_close_tag_buckets(buckets, alloc,
495                                     "S:log-report");
496
497  *body_bkt = buckets;
498  return SVN_NO_ERROR;
499}
500
501svn_error_t *
502svn_ra_serf__get_log(svn_ra_session_t *ra_session,
503                     const apr_array_header_t *paths,
504                     svn_revnum_t start,
505                     svn_revnum_t end,
506                     int limit,
507                     svn_boolean_t discover_changed_paths,
508                     svn_boolean_t strict_node_history,
509                     svn_boolean_t include_merged_revisions,
510                     const apr_array_header_t *revprops,
511                     svn_log_entry_receiver_t receiver,
512                     void *receiver_baton,
513                     apr_pool_t *pool)
514{
515  log_context_t *log_ctx;
516  svn_ra_serf__session_t *session = ra_session->priv;
517  svn_ra_serf__handler_t *handler;
518  svn_ra_serf__xml_context_t *xmlctx;
519  svn_boolean_t want_custom_revprops;
520  svn_revnum_t peg_rev;
521  svn_error_t *err;
522  const char *req_url;
523
524  log_ctx = apr_pcalloc(pool, sizeof(*log_ctx));
525  log_ctx->pool = pool;
526  log_ctx->receiver = receiver;
527  log_ctx->receiver_baton = receiver_baton;
528  log_ctx->paths = paths;
529  log_ctx->start = start;
530  log_ctx->end = end;
531  log_ctx->limit = limit;
532  log_ctx->changed_paths = discover_changed_paths;
533  log_ctx->strict_node_history = strict_node_history;
534  log_ctx->include_merged_revisions = include_merged_revisions;
535  log_ctx->revprops = revprops;
536  log_ctx->nest_level = 0;
537
538  want_custom_revprops = FALSE;
539  if (revprops)
540    {
541      int i;
542      for (i = 0; i < revprops->nelts; i++)
543        {
544          char *name = APR_ARRAY_IDX(revprops, i, char *);
545          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
546            log_ctx->want_author = TRUE;
547          else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
548            log_ctx->want_date = TRUE;
549          else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
550            log_ctx->want_message = TRUE;
551          else
552            want_custom_revprops = TRUE;
553        }
554    }
555  else
556    {
557      log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
558      want_custom_revprops = TRUE;
559    }
560
561  if (want_custom_revprops)
562    {
563      svn_boolean_t has_log_revprops;
564      SVN_ERR(svn_ra_serf__has_capability(ra_session, &has_log_revprops,
565                                          SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
566      if (!has_log_revprops)
567        return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
568                                _("Server does not support custom revprops"
569                                  " via log"));
570    }
571  /* At this point, we may have a deleted file.  So, we'll match ra_neon's
572   * behavior and use the larger of start or end as our 'peg' rev.
573   */
574  peg_rev = (start == SVN_INVALID_REVNUM || start > end) ? start : end;
575
576  SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
577                                      session, NULL /* conn */,
578                                      NULL /* url */, peg_rev,
579                                      pool, pool));
580
581  xmlctx = svn_ra_serf__xml_context_create(log_ttable,
582                                           log_opened, log_closed, NULL,
583                                           log_ctx,
584                                           pool);
585  handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
586
587  handler->method = "REPORT";
588  handler->path = req_url;
589  handler->body_delegate = create_log_body;
590  handler->body_delegate_baton = log_ctx;
591  handler->body_type = "text/xml";
592  handler->conn = session->conns[0];
593  handler->session = session;
594
595  err = svn_ra_serf__context_run_one(handler, pool);
596
597  SVN_ERR(svn_error_compose_create(
598              svn_ra_serf__error_on_status(handler->sline,
599                                           req_url,
600                                           handler->location),
601              err));
602
603  return SVN_NO_ERROR;
604}
605