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 */
51 enum {
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
68 typedef 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
101 static 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. */
153 static svn_error_t *
collect_revprop(apr_hash_t * revprops,const char * propname,const svn_string_t * cdata,const char * encoding)154 collect_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. */
189 static svn_error_t *
collect_path(apr_hash_t * paths,char action,const svn_string_t * cdata,apr_hash_t * attrs)190 collect_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 */
233 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)234 log_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 */
255 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)256 log_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
401 static svn_error_t *
create_log_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool)402 create_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
501 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)502 svn_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