1 /*
2    'version' for cadaver
3    Copyright (C) 2003-2006, Joe Orton <joe@manyfish.co.uk>
4    Copyright (C) 2002-2003, GRASE Lab, UCSC <grase@cse.ucsc.edu>,
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20 
21 #include "config.h"
22 
23 #ifdef HAVE_STDLIB_H
24 #include <stdlib.h>
25 #endif
26 
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30 
31 #include <time.h>
32 
33 #include <ne_request.h>
34 #include <ne_basic.h>
35 #include <ne_props.h>
36 #include <ne_uri.h>
37 #include <ne_alloc.h>
38 #include <ne_dates.h>
39 
40 #include "i18n.h"
41 #include "commands.h"
42 #include "cadaver.h"
43 #include "utils.h"
44 
45 /* Message body for REPORT */
46 static const char *report_body =
47 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
48 "<D:version-tree xmlns:D=\"DAV:\">"
49 " <D:prop>"
50 "  <D:version-name/>"
51 "  <D:creator-displayname/>"
52 "  <D:getcontentlength/>"
53 "  <D:getlastmodified/>"
54 "  <D:successor-set/>"
55 " </D:prop>"
56 "</D:version-tree>";
57 
58 typedef struct report_res
59 {
60     char *href;
61 
62     /* live props */
63     char *version_name;
64     char *creator_displayname;
65     char *getcontentlength;
66     char *getlastmodified;
67     char *successor_set;
68 
69     struct report_res *next;
70 }
71 report_res;
72 
73 
74 /* Search XML parser context */
75 typedef struct
76 {
77     report_res *root;
78     report_res *curr;
79     int result_num;
80     int start_prop;
81     int err_code;
82 
83     ne_buffer *cdata;
84 }
85 report_ctx;
86 
87 enum
88 {
89     ELEM_multistatus = 1,
90     ELEM_response,
91     ELEM_href,
92     ELEM_prop,
93     ELEM_propstat,
94     ELEM_status,
95 
96     /* props from RFC 2518 , 23 Appendices 23.1 */
97     ELEM_version_name,
98     ELEM_creator_displayname,
99     ELEM_getcontentlength,
100     ELEM_getlastmodified,
101 
102     ELEM_ignore
103 };
104 
105 
106 static const struct ne_xml_idmap report_elements[] = {
107     {"DAV:", "multistatus", ELEM_multistatus},
108     {"DAV:", "response", ELEM_response},
109     {"DAV:", "href", ELEM_href},
110     {"DAV:", "propstat", ELEM_propstat},
111     {"DAV:", "prop", ELEM_prop},
112     {"DAV:", "status", ELEM_status},
113 
114     /* Live props */
115     {"DAV:", "version-name", ELEM_version_name},
116     {"DAV:", "creator-displayname", ELEM_creator_displayname},
117     {"DAV:", "getcontentlength", ELEM_getcontentlength},
118     {"DAV:", "getlastmodified", ELEM_getlastmodified},
119 };
120 
121 /* We do not validate at this point */
122 /*
123 static int validate_report_elements(void *userdata,
124 				    ne_xml_elmid parent, ne_xml_elmid child)
125 {
126     return NE_XML_VALID;
127 }
128 */
129 
130 /* Set xml parser error */
set_xml_error(report_ctx * sctx,const char * format,...)131 static void set_xml_error(report_ctx * sctx, const char *format, ...)
132 {
133     va_list ap;
134     char buf[512];
135 
136     va_start(ap, format);
137     ne_vsnprintf(buf, sizeof buf, format, ap);
138     va_end(ap);
139 
140     ne_set_error(session.sess, "%s", buf);
141     sctx->err_code = NE_ERROR;
142 }
143 
start_element(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)144 static int start_element(void *userdata, int parent,
145 			 const char *nspace,
146 			 const char *name,
147 			 const char **atts)
148 {
149     report_ctx *rctx = (report_ctx *) userdata;
150     int state = ne_xml_mapid(report_elements,
151 			     NE_XML_MAPLEN(report_elements), nspace, name);
152 
153     /* Error occured, ignore remain part */
154     if (rctx->err_code != NE_OK)
155 	return rctx->err_code;
156 
157     ne_buffer_clear(rctx->cdata);
158 
159     switch (state) {
160 
161     case ELEM_response:	/* Start of new response */
162 	rctx->curr = ne_calloc(sizeof(report_res));
163 	rctx->result_num++;
164 	break;
165 
166     case ELEM_prop:		/* Start of prop */
167 	if (rctx->curr == NULL) {
168 	    set_xml_error(rctx, "XML : <%s> is in the wrong place",
169 			  name);
170 	    break;
171 	}
172 	rctx->start_prop = 1;
173 	break;
174 
175     case ELEM_propstat:	/* expecting props */
176     case ELEM_href:		/* href */
177     case ELEM_ignore:
178     default:
179 	break;
180     }
181 
182     return state;
183 }
184 
185 #define REPORT_CP_ELEM(rctx, curr, name, desc, src) \
186 do { \
187       if ((curr) == NULL) \
188          set_xml_error((rctx),  "XML : </%s> is in the wrong place", (name));\
189       else if (src)\
190          (desc) = ne_strdup(src);\
191 } while (0)
192 
193 
cdata_report(void * userdata,int state,const char * buf,size_t len)194 static int cdata_report(void *userdata, int state, const char *buf, size_t len)
195 {
196     report_ctx *rctx = (report_ctx *) userdata;
197     ne_buffer_append(rctx->cdata, buf, len);
198     return 0;
199 }
200 
201 
202 static int
end_element(void * userdata,int state,const char * nspace,const char * name)203 end_element(void *userdata, int state, const char *nspace, const char *name)
204 {
205     report_ctx *rctx = (report_ctx *) userdata;
206     const char *cdata = rctx->cdata->data;
207 
208     /* Error occured, ignore remain part */
209     if (rctx->err_code != NE_OK)
210 	return rctx->err_code;
211 
212     switch (state) {
213     case ELEM_response:	/* End of new response */
214 	/* Nothing to add */
215 	if (rctx->curr == NULL) {
216 	    set_xml_error(rctx, "XML : </%s> is in the wrong place",
217 			  name);
218 	    break;
219 	}
220 
221 	/* No HREF */
222 	if (rctx->curr->href == NULL) {
223 	    set_xml_error(rctx, "XML : No href info in the <%s>...</%s>",
224 			  name, name);
225 	    break;
226 	}
227 	/* make link */
228 	rctx->curr->next = rctx->root;
229 	rctx->root = rctx->curr;
230 	rctx->curr = NULL;
231 	break;
232 
233     case ELEM_href:		/* href */
234 	REPORT_CP_ELEM(rctx, rctx->curr, name, rctx->curr->href, cdata);
235 	break;
236 
237     case ELEM_version_name:
238 	REPORT_CP_ELEM(rctx, rctx->curr, name,
239 		       rctx->curr->version_name, cdata);
240 	break;
241 
242     case ELEM_creator_displayname:
243 	REPORT_CP_ELEM(rctx, rctx->curr, name,
244 		       rctx->curr->creator_displayname, cdata);
245 	break;
246 
247     case ELEM_getcontentlength:
248 	REPORT_CP_ELEM(rctx, rctx->curr, name,
249 		       rctx->curr->getcontentlength, cdata);
250 	break;
251 
252     case ELEM_getlastmodified:
253 	REPORT_CP_ELEM(rctx, rctx->curr, name,
254 		       rctx->curr->getlastmodified, cdata);
255 	break;
256 
257     case ELEM_prop:		/* Start of prop */
258 	if (rctx->curr == NULL)
259 	    set_xml_error(rctx, "XML : </%s> is in the wrong place",
260 			  name);
261 	else			/* stop to props */
262 	    rctx->start_prop = 0;
263 	break;
264 
265     case ELEM_ignore:
266     case ELEM_propstat:	/* expecting props */
267     default:
268 	break;
269     }
270 
271     return NE_OK;
272 }
273 
274 
report_ctx_destroy(report_ctx * sctx)275 static void report_ctx_destroy(report_ctx * sctx)
276 {
277     report_res *res, *res_free;
278 
279     ne_buffer_destroy(sctx->cdata);
280 
281     for (res = sctx->root; res;) {
282 	ne_free(res->href);
283 
284 	/* live props */
285 	if (res->version_name) ne_free(res->version_name);
286 	if (res->creator_displayname) ne_free(res->creator_displayname);
287 	if (res->getcontentlength) ne_free(res->getcontentlength);
288 	if (res->getlastmodified) ne_free(res->getlastmodified);
289 
290 	res_free = res;
291 	res = res->next;
292 	ne_free(res_free);
293     }
294 }
295 
296 /* Give UI feedback for request 'req', which got dispatch return code
297  * 'ret'. */
req_result(ne_request * req,int ret)298 static void req_result(ne_request *req, int ret)
299 {
300     if (ret != NE_OK) {
301         out_result(ret);
302     } else if (ne_get_status(req)->klass != 2) {
303         out_result(NE_ERROR);
304     } else {
305         out_success();
306     }
307 }
308 
simple_request(const char * remote,const char * verb,const char * method)309 static void simple_request(const char *remote, const char *verb,
310                            const char *method)
311 {
312     char *real_remote;
313     ne_request *req;
314     int ret;
315 
316     if (remote != NULL) {
317 	real_remote = resolve_path(session.uri.path, remote, true);
318     } else {
319 	real_remote = ne_strdup(session.uri.path);
320     }
321 
322     out_start(verb, remote);
323 
324     req = ne_request_create(session.sess, method, real_remote);
325 
326     ne_lock_using_resource(req, real_remote, 0);
327 
328     ret = ne_request_dispatch(req);
329 
330     req_result(req, ret);
331 
332     ne_request_destroy(req);
333     free(real_remote);
334 }
335 
execute_version(const char * remote)336 void execute_version(const char *remote)
337 {
338     simple_request(remote, _("Versioning"), "VERSION-CONTROL");
339 }
340 
execute_checkin(const char * remote)341 void execute_checkin(const char *remote)
342 {
343     simple_request(remote, _("Checking in"), "CHECKIN");
344 }
345 
execute_checkout(const char * remote)346 void execute_checkout(const char *remote)
347 {
348     simple_request(remote, _("Checking out"), "CHECKOUT");
349 }
350 
execute_uncheckout(const char * remote)351 void execute_uncheckout(const char *remote)
352 {
353     simple_request(remote, _("Cancelling check out of"), "UNCHECKOUT");
354 }
355 
356 /* displays report results */
display_report_results(report_ctx * rctx)357 static int display_report_results(report_ctx * rctx)
358 {
359     report_res *res;
360 
361     if (rctx->err_code) {
362 	return rctx->err_code;
363     }
364 
365     printf(_(" %d version%s in history:\n"), rctx->result_num,
366            rctx->result_num==1?"":"s");
367 
368     for (res = rctx->root; res; res = res->next) {
369 	long modtime = res->getlastmodified ?
370 	  ne_httpdate_parse(res->getlastmodified) : 0;
371 	int size = res->getcontentlength ? atol(res->getcontentlength) : 0;
372 
373 	printf("%-40s %10d  %s <%s>\n", res->href,
374 	       size, format_time(modtime), res->version_name);
375     }
376 
377     return rctx->err_code;
378 }
379 
do_report(const char * real_remote,report_ctx * rctx)380 static int do_report(const char *real_remote,
381 		     report_ctx *rctx)
382 {
383     int ret;
384     ne_request *req;
385     ne_xml_parser *report_parser;
386 
387     /* create/prep the request */
388     if ((req = ne_request_create(session.sess, "REPORT", real_remote)) == NULL)
389 	return NE_ERROR;
390 
391     /* Plug our XML parser */
392     report_parser = ne_xml_create();
393     ne_xml_push_handler(report_parser, start_element,
394 			cdata_report,
395 			end_element,
396 			rctx);
397 
398     ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
399 
400     ne_add_response_body_reader(req, ne_accept_2xx, ne_xml_parse_v,
401 				report_parser);
402     /* Set body */
403     ne_set_request_body_buffer(req, report_body, strlen(report_body));
404 
405     /* run the request, see what comes back. */
406     ret = ne_request_dispatch(req);
407 
408     if (ne_get_status(req)->klass != 2)
409 	ret = ne_get_status(req)->code;
410 
411     ne_request_destroy(req);
412 
413     return ret;
414 }
415 
416 
execute_history(const char * remote)417 void execute_history(const char *remote)
418 {
419     int ret;
420     char *real_remote;
421     report_ctx *rctx = ne_calloc(sizeof(report_ctx));
422     rctx->cdata = ne_buffer_create();
423 
424     if (remote != NULL) {
425 	real_remote = resolve_path(session.uri.path, remote, false);
426     } else {
427 	real_remote = ne_strdup(session.uri.path);
428     }
429     out_start(_("Version history of"), real_remote);
430 
431     /* Run search */
432     ret = do_report(real_remote, rctx);
433     if (ret != NE_OK) {
434 	/* Print out error message */
435 	out_result(ret);
436     } else {
437         /* show report result */
438 	display_report_results(rctx);
439     }
440 
441     free(real_remote);
442     report_ctx_destroy(rctx);
443 }
444 
445 
execute_label(const char * remote,const char * act,const char * value)446 void execute_label(const char *remote, const char *act, const char *value)
447 {
448     int ret;
449     char *real_remote;
450     ne_request *req;
451     ne_buffer *label_body;
452 
453     if (strcasecmp(act, "add") && strcasecmp(act, "remove") &&
454         strcasecmp(act, "set")) {
455         printf(_("Invalid action `%s' given.\n"), act);
456         return;
457     }
458 
459     if (remote != NULL) {
460 	real_remote = resolve_path(session.uri.path, remote, true);
461     } else {
462 	real_remote = ne_strdup(session.uri.path);
463     }
464 
465     out_start(_("Labelling"), real_remote);
466 
467     /* Create Label Body */
468     label_body = ne_buffer_create();
469 
470     /* Create the request body */
471     ne_buffer_zappend(label_body,
472 		      "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
473 		      "<D:label xmlns:D=\"DAV:\">\n");
474 
475     /* Adction */
476     ne_buffer_concat(label_body, "<D:", act, "><D:label-name>", value,
477 		     "</D:label-name></D:", act, ">", NULL);
478     ne_buffer_zappend(label_body,
479 		      "</D:label>\n");
480 
481     /* create/prep the request */
482     req = ne_request_create(session.sess, "LABEL", real_remote);
483 
484     ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
485 
486     /* Set body */
487     ne_set_request_body_buffer(req, label_body->data,
488 			       ne_buffer_size(label_body));
489 
490     /* run the request, see what comes back. */
491     ret = ne_request_dispatch(req);
492 
493     /* Print out status */
494     req_result(req, ret);
495 
496     ne_buffer_destroy(label_body);
497     ne_request_destroy(req);
498     free(real_remote);
499 }
500 
501 static const ne_propname vcr_props[] = {
502     { "DAV:", "checked-in" },
503     { "DAV:", "checked-out" },
504     { NULL }
505 };
506 
vcr_results(void * userdata,const ne_uri * uri,const ne_prop_result_set * set)507 static void vcr_results(void *userdata, const ne_uri *uri,
508                         const ne_prop_result_set *set)
509 {
510     const char *checkin, *checkout;
511     int *isvcr = userdata;
512 
513     checkin = ne_propset_value(set, &vcr_props[0]);
514     checkout = ne_propset_value(set, &vcr_props[1]);
515 
516     /* is vcr */
517     if (checkin) {
518 	*isvcr = 1;
519     } else if (checkout) {
520 	*isvcr = 2;
521     }
522 }
523 
is_vcr(const char * uri)524 int is_vcr(const char *uri)
525 {
526     int ret, vcr = 0;
527     ne_propfind_handler *pfh = ne_propfind_create(session.sess, uri, NE_DEPTH_ZERO);
528     ret = ne_propfind_named(pfh, vcr_props, vcr_results, &vcr);
529     ne_propfind_destroy(pfh);
530 
531     if (ret != NE_OK)
532 	return 0;
533 
534     return vcr;
535 }
536