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