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