1251881Speter/*
2251881Speter * blame.c :  entry point for blame RA functions for ra_serf
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#include <apr_uri.h>
25251881Speter#include <serf.h>
26251881Speter
27251881Speter#include "svn_hash.h"
28251881Speter#include "svn_pools.h"
29251881Speter#include "svn_ra.h"
30251881Speter#include "svn_dav.h"
31251881Speter#include "svn_xml.h"
32251881Speter#include "svn_config.h"
33251881Speter#include "svn_delta.h"
34251881Speter#include "svn_path.h"
35251881Speter#include "svn_base64.h"
36251881Speter#include "svn_props.h"
37251881Speter
38251881Speter#include "svn_private_config.h"
39251881Speter
40251881Speter#include "private/svn_string_private.h"
41251881Speter
42251881Speter#include "ra_serf.h"
43251881Speter#include "../libsvn_ra/ra_loader.h"
44251881Speter
45251881Speter
46251881Speter/*
47251881Speter * This enum represents the current state of our XML parsing for a REPORT.
48251881Speter */
49251881Spetertypedef enum blame_state_e {
50299742Sdim  INITIAL = XML_STATE_INITIAL,
51251881Speter  FILE_REVS_REPORT,
52251881Speter  FILE_REV,
53251881Speter  REV_PROP,
54251881Speter  SET_PROP,
55251881Speter  REMOVE_PROP,
56251881Speter  MERGED_REVISION,
57251881Speter  TXDELTA
58251881Speter} blame_state_e;
59251881Speter
60251881Speter
61251881Spetertypedef struct blame_context_t {
62251881Speter  /* pool passed to get_file_revs */
63251881Speter  apr_pool_t *pool;
64251881Speter
65251881Speter  /* parameters set by our caller */
66251881Speter  const char *path;
67251881Speter  svn_revnum_t start;
68251881Speter  svn_revnum_t end;
69251881Speter  svn_boolean_t include_merged_revisions;
70251881Speter
71251881Speter  /* blame handler and baton */
72251881Speter  svn_file_rev_handler_t file_rev;
73251881Speter  void *file_rev_baton;
74251881Speter
75251881Speter  /* As we parse each FILE_REV, we collect data in these variables:
76251881Speter     property changes and new content.  STREAM is valid when we're
77251881Speter     in the TXDELTA state, processing the incoming cdata.  */
78251881Speter  apr_hash_t *rev_props;
79251881Speter  apr_array_header_t *prop_diffs;
80251881Speter  apr_pool_t *state_pool;  /* put property stuff in here  */
81251881Speter
82251881Speter  svn_stream_t *stream;
83251881Speter
84251881Speter} blame_context_t;
85251881Speter
86251881Speter
87251881Speter#define D_ "DAV:"
88251881Speter#define S_ SVN_XML_NAMESPACE
89251881Speterstatic const svn_ra_serf__xml_transition_t blame_ttable[] = {
90251881Speter  { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
91251881Speter    FALSE, { NULL }, FALSE },
92251881Speter
93251881Speter  { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
94251881Speter    FALSE, { "path", "rev", NULL }, TRUE },
95251881Speter
96251881Speter  { FILE_REV, S_, "rev-prop", REV_PROP,
97251881Speter    TRUE, { "name", "?encoding", NULL }, TRUE },
98251881Speter
99251881Speter  { FILE_REV, S_, "set-prop", SET_PROP,
100251881Speter    TRUE, { "name", "?encoding", NULL }, TRUE },
101251881Speter
102251881Speter  { FILE_REV, S_, "remove-prop", REMOVE_PROP,
103251881Speter    FALSE, { "name", NULL }, TRUE },
104251881Speter
105251881Speter  { FILE_REV, S_, "merged-revision", MERGED_REVISION,
106251881Speter    FALSE, { NULL }, TRUE },
107251881Speter
108251881Speter  { FILE_REV, S_, "txdelta", TXDELTA,
109251881Speter    FALSE, { NULL }, TRUE },
110251881Speter
111251881Speter  { 0 }
112251881Speter};
113251881Speter
114251881Speter/* Conforms to svn_ra_serf__xml_opened_t  */
115251881Speterstatic svn_error_t *
116251881Speterblame_opened(svn_ra_serf__xml_estate_t *xes,
117251881Speter             void *baton,
118251881Speter             int entered_state,
119251881Speter             const svn_ra_serf__dav_props_t *tag,
120251881Speter             apr_pool_t *scratch_pool)
121251881Speter{
122251881Speter  blame_context_t *blame_ctx = baton;
123251881Speter
124251881Speter  if (entered_state == FILE_REV)
125251881Speter    {
126251881Speter      apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
127251881Speter
128251881Speter      /* Child elements will store properties in these structures.  */
129251881Speter      blame_ctx->rev_props = apr_hash_make(state_pool);
130251881Speter      blame_ctx->prop_diffs = apr_array_make(state_pool,
131251881Speter                                             5, sizeof(svn_prop_t));
132251881Speter      blame_ctx->state_pool = state_pool;
133251881Speter
134251881Speter      /* Clear this, so we can detect the absence of a TXDELTA.  */
135251881Speter      blame_ctx->stream = NULL;
136251881Speter    }
137251881Speter  else if (entered_state == TXDELTA)
138251881Speter    {
139251881Speter      apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
140251881Speter      apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
141251881Speter      const char *path;
142299742Sdim      const char *rev_str;
143251881Speter      const char *merged_revision;
144251881Speter      svn_txdelta_window_handler_t txdelta;
145251881Speter      void *txdelta_baton;
146299742Sdim      apr_int64_t rev;
147251881Speter
148251881Speter      path = svn_hash_gets(gathered, "path");
149299742Sdim      rev_str = svn_hash_gets(gathered, "rev");
150299742Sdim
151299742Sdim      SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
152251881Speter      merged_revision = svn_hash_gets(gathered, "merged-revision");
153251881Speter
154251881Speter      SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
155299742Sdim                                  path, (svn_revnum_t)rev,
156251881Speter                                  blame_ctx->rev_props,
157251881Speter                                  merged_revision != NULL,
158251881Speter                                  &txdelta, &txdelta_baton,
159251881Speter                                  blame_ctx->prop_diffs,
160251881Speter                                  state_pool));
161251881Speter
162251881Speter      blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
163251881Speter                                              txdelta, txdelta_baton,
164251881Speter                                              TRUE /* error_on_early_close */,
165251881Speter                                              state_pool),
166251881Speter                                            state_pool);
167251881Speter    }
168251881Speter
169251881Speter  return SVN_NO_ERROR;
170251881Speter}
171251881Speter
172251881Speter
173251881Speter/* Conforms to svn_ra_serf__xml_closed_t  */
174251881Speterstatic svn_error_t *
175251881Speterblame_closed(svn_ra_serf__xml_estate_t *xes,
176251881Speter             void *baton,
177251881Speter             int leaving_state,
178251881Speter             const svn_string_t *cdata,
179251881Speter             apr_hash_t *attrs,
180251881Speter             apr_pool_t *scratch_pool)
181251881Speter{
182251881Speter  blame_context_t *blame_ctx = baton;
183251881Speter
184251881Speter  if (leaving_state == FILE_REV)
185251881Speter    {
186251881Speter      /* Note that we test STREAM, but any pointer is currently invalid.
187251881Speter         It was closed when left the TXDELTA state.  */
188251881Speter      if (blame_ctx->stream == NULL)
189251881Speter        {
190251881Speter          const char *path;
191251881Speter          const char *rev;
192251881Speter
193251881Speter          path = svn_hash_gets(attrs, "path");
194251881Speter          rev = svn_hash_gets(attrs, "rev");
195251881Speter
196251881Speter          /* Send a "no content" notification.  */
197251881Speter          SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
198251881Speter                                      path, SVN_STR_TO_REV(rev),
199251881Speter                                      blame_ctx->rev_props,
200251881Speter                                      FALSE /* result_of_merge */,
201251881Speter                                      NULL, NULL, /* txdelta / baton */
202251881Speter                                      blame_ctx->prop_diffs,
203251881Speter                                      scratch_pool));
204251881Speter        }
205251881Speter    }
206251881Speter  else if (leaving_state == MERGED_REVISION)
207251881Speter    {
208251881Speter      svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
209251881Speter    }
210251881Speter  else if (leaving_state == TXDELTA)
211251881Speter    {
212251881Speter      SVN_ERR(svn_stream_close(blame_ctx->stream));
213251881Speter    }
214251881Speter  else
215251881Speter    {
216251881Speter      const char *name;
217251881Speter      const svn_string_t *value;
218251881Speter
219251881Speter      SVN_ERR_ASSERT(leaving_state == REV_PROP
220251881Speter                     || leaving_state == SET_PROP
221251881Speter                     || leaving_state == REMOVE_PROP);
222251881Speter
223251881Speter      name = apr_pstrdup(blame_ctx->state_pool,
224251881Speter                         svn_hash_gets(attrs, "name"));
225251881Speter
226251881Speter      if (leaving_state == REMOVE_PROP)
227251881Speter        {
228251881Speter          value = NULL;
229251881Speter        }
230251881Speter      else
231251881Speter        {
232251881Speter          const char *encoding = svn_hash_gets(attrs, "encoding");
233251881Speter
234251881Speter          if (encoding && strcmp(encoding, "base64") == 0)
235251881Speter            value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
236251881Speter          else
237251881Speter            value = svn_string_dup(cdata, blame_ctx->state_pool);
238251881Speter        }
239251881Speter
240251881Speter      if (leaving_state == REV_PROP)
241251881Speter        {
242251881Speter          svn_hash_sets(blame_ctx->rev_props, name, value);
243251881Speter        }
244251881Speter      else
245251881Speter        {
246251881Speter          svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
247251881Speter
248251881Speter          prop->name = name;
249251881Speter          prop->value = value;
250251881Speter        }
251251881Speter    }
252251881Speter
253251881Speter  return SVN_NO_ERROR;
254251881Speter}
255251881Speter
256251881Speter
257251881Speter/* Conforms to svn_ra_serf__xml_cdata_t  */
258251881Speterstatic svn_error_t *
259251881Speterblame_cdata(svn_ra_serf__xml_estate_t *xes,
260251881Speter            void *baton,
261251881Speter            int current_state,
262251881Speter            const char *data,
263251881Speter            apr_size_t len,
264251881Speter            apr_pool_t *scratch_pool)
265251881Speter{
266251881Speter  blame_context_t *blame_ctx = baton;
267251881Speter
268251881Speter  if (current_state == TXDELTA)
269251881Speter    {
270251881Speter      SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
271251881Speter      /* Ignore the returned LEN value.  */
272251881Speter    }
273251881Speter
274251881Speter  return SVN_NO_ERROR;
275251881Speter}
276251881Speter
277251881Speter
278251881Speter/* Implements svn_ra_serf__request_body_delegate_t */
279251881Speterstatic svn_error_t *
280251881Spetercreate_file_revs_body(serf_bucket_t **body_bkt,
281251881Speter                      void *baton,
282251881Speter                      serf_bucket_alloc_t *alloc,
283299742Sdim                      apr_pool_t *pool /* request pool */,
284299742Sdim                      apr_pool_t *scratch_pool)
285251881Speter{
286251881Speter  serf_bucket_t *buckets;
287251881Speter  blame_context_t *blame_ctx = baton;
288251881Speter
289251881Speter  buckets = serf_bucket_aggregate_create(alloc);
290251881Speter
291251881Speter  svn_ra_serf__add_open_tag_buckets(buckets, alloc,
292251881Speter                                    "S:file-revs-report",
293251881Speter                                    "xmlns:S", SVN_XML_NAMESPACE,
294299742Sdim                                    SVN_VA_NULL);
295251881Speter
296251881Speter  svn_ra_serf__add_tag_buckets(buckets,
297251881Speter                               "S:start-revision", apr_ltoa(pool, blame_ctx->start),
298251881Speter                               alloc);
299251881Speter
300251881Speter  svn_ra_serf__add_tag_buckets(buckets,
301251881Speter                               "S:end-revision", apr_ltoa(pool, blame_ctx->end),
302251881Speter                               alloc);
303251881Speter
304251881Speter  if (blame_ctx->include_merged_revisions)
305251881Speter    {
306299742Sdim      svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
307299742Sdim                                         "S:include-merged-revisions", SVN_VA_NULL);
308251881Speter    }
309251881Speter
310251881Speter  svn_ra_serf__add_tag_buckets(buckets,
311251881Speter                               "S:path", blame_ctx->path,
312251881Speter                               alloc);
313251881Speter
314251881Speter  svn_ra_serf__add_close_tag_buckets(buckets, alloc,
315251881Speter                                     "S:file-revs-report");
316251881Speter
317251881Speter  *body_bkt = buckets;
318251881Speter  return SVN_NO_ERROR;
319251881Speter}
320251881Speter
321251881Spetersvn_error_t *
322251881Spetersvn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
323251881Speter                           const char *path,
324251881Speter                           svn_revnum_t start,
325251881Speter                           svn_revnum_t end,
326251881Speter                           svn_boolean_t include_merged_revisions,
327251881Speter                           svn_file_rev_handler_t rev_handler,
328251881Speter                           void *rev_handler_baton,
329251881Speter                           apr_pool_t *pool)
330251881Speter{
331251881Speter  blame_context_t *blame_ctx;
332251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
333251881Speter  svn_ra_serf__handler_t *handler;
334251881Speter  svn_ra_serf__xml_context_t *xmlctx;
335251881Speter  const char *req_url;
336299742Sdim  svn_revnum_t peg_rev;
337251881Speter
338251881Speter  blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
339251881Speter  blame_ctx->pool = pool;
340251881Speter  blame_ctx->path = path;
341251881Speter  blame_ctx->file_rev = rev_handler;
342251881Speter  blame_ctx->file_rev_baton = rev_handler_baton;
343251881Speter  blame_ctx->start = start;
344251881Speter  blame_ctx->end = end;
345251881Speter  blame_ctx->include_merged_revisions = include_merged_revisions;
346251881Speter
347299742Sdim  /* Since Subversion 1.8 we allow retrieving blames backwards. So we can't
348299742Sdim     just unconditionally use end_rev as the peg revision as before */
349299742Sdim  if (end > start)
350299742Sdim    peg_rev = end;
351299742Sdim  else
352299742Sdim    peg_rev = start;
353299742Sdim
354251881Speter  SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
355299742Sdim                                      session,
356299742Sdim                                      NULL /* url */, peg_rev,
357251881Speter                                      pool, pool));
358251881Speter
359251881Speter  xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
360251881Speter                                           blame_opened,
361251881Speter                                           blame_closed,
362251881Speter                                           blame_cdata,
363251881Speter                                           blame_ctx,
364251881Speter                                           pool);
365299742Sdim  handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
366251881Speter
367251881Speter  handler->method = "REPORT";
368251881Speter  handler->path = req_url;
369251881Speter  handler->body_type = "text/xml";
370251881Speter  handler->body_delegate = create_file_revs_body;
371251881Speter  handler->body_delegate_baton = blame_ctx;
372251881Speter
373299742Sdim  SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
374251881Speter
375299742Sdim  if (handler->sline.code != 200)
376299742Sdim    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
377251881Speter
378299742Sdim  return SVN_NO_ERROR;
379251881Speter}
380