1 /*
2 'ls' for cadaver
3 Copyright (C) 2000-2004, 2006, 2008, Joe Orton <joe@manyfish.co.uk>,
4 except where otherwise indicated.
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_props.h>
35 #include <ne_uri.h>
36 #include <ne_alloc.h>
37 #include <ne_dates.h>
38
39 #include "i18n.h"
40 #include "commands.h"
41 #include "cadaver.h"
42 #include "utils.h"
43
44 struct fetch_context {
45 struct resource **list;
46 const char *target; /* Request-URI of the PROPFIND */
47 unsigned int include_target; /* Include resource at href */
48 };
49
50 static const ne_propname ls_props[] = {
51 { "DAV:", "getcontentlength" },
52 { "DAV:", "getlastmodified" },
53 { "http://apache.org/dav/props/", "executable" },
54 { "DAV:", "resourcetype" },
55 { "DAV:", "checked-in" },
56 { "DAV:", "checked-out" },
57 { NULL }
58 };
59
60 #define ELM_resourcetype (NE_PROPS_STATE_TOP + 1)
61 #define ELM_collection (NE_PROPS_STATE_TOP + 2)
62
63 static const struct ne_xml_idmap ls_idmap[] = {
64 { "DAV:", "resourcetype", ELM_resourcetype },
65 { "DAV:", "collection", ELM_collection }
66 };
67
compare_resource(const struct resource * r1,const struct resource * r2)68 static int compare_resource(const struct resource *r1,
69 const struct resource *r2)
70 {
71 /* Sort errors first, then collections, then alphabetically */
72 if (r1->type == resr_error) {
73 return -1;
74 } else if (r2->type == resr_error) {
75 return 1;
76 } else if (r1->type == resr_collection) {
77 if (r2->type != resr_collection) {
78 return -1;
79 } else {
80 return strcmp(r1->uri, r2->uri);
81 }
82 } else {
83 if (r2->type != resr_collection) {
84 return strcmp(r1->uri, r2->uri);
85 } else {
86 return 1;
87 }
88 }
89 }
90
display_ls_line(struct resource * res)91 static void display_ls_line(struct resource *res)
92 {
93 const char *restype;
94 char exec_char, vcr_char, *name;
95
96 switch (res->type) {
97 case resr_normal: restype = ""; break;
98 case resr_reference: restype = _("Ref:"); break;
99 case resr_collection: restype = _("Coll:"); break;
100 default:
101 restype = "???"; break;
102 }
103
104 if (ne_path_has_trailing_slash(res->uri)) {
105 res->uri[strlen(res->uri)-1] = '\0';
106 }
107 name = strrchr(res->uri, '/');
108 if (name != NULL && strlen(name+1) > 0) {
109 name++;
110 } else {
111 name = res->uri;
112 }
113
114 name = ne_path_unescape(name);
115
116 if (res->type == resr_error) {
117 printf(_("Error: %-30s %d %s\n"), name, res->error_status,
118 res->error_reason?res->error_reason:_("unknown"));
119 } else {
120 exec_char = res->is_executable ? '*' : ' ';
121 /* 0: no vcr, 1: checkin, 2: checkout */
122 vcr_char = res->is_vcr==0 ? ' ' : (res->is_vcr==1? '>' : '<');
123 printf("%5s %c%c%-29s %10" FMT_DAV_SIZE_T "u %s\n",
124 restype, vcr_char, exec_char, name,
125 res->size, format_time(res->modtime));
126 }
127
128 free(name);
129 }
130
execute_ls(const char * remote)131 void execute_ls(const char *remote)
132 {
133 int ret;
134 char *real_remote;
135 struct resource *reslist = NULL, *current, *next;
136
137 if (remote != NULL) {
138 real_remote = resolve_path(session.uri.path, remote, true);
139 } else {
140 real_remote = ne_strdup(session.uri.path);
141 }
142 out_start(_("Listing collection"), real_remote);
143 ret = fetch_resource_list(session.sess, real_remote, 1, 0, &reslist);
144 if (ret == NE_OK) {
145 /* Easy this, eh? */
146 if (reslist == NULL) {
147 output(o_finish, _("collection is empty.\n"));
148 } else {
149 out_success();
150 for (current = reslist; current!=NULL; current = next) {
151 next = current->next;
152 if (strlen(current->uri) > strlen(real_remote)) {
153 display_ls_line(current);
154 }
155 free_resource(current);
156 }
157 }
158 } else {
159 out_result(ret);
160 }
161 free(real_remote);
162 }
163
results(void * userdata,const ne_uri * uri,const ne_prop_result_set * set)164 static void results(void *userdata,
165 const ne_uri *uri,
166 const ne_prop_result_set *set)
167 {
168 struct fetch_context *ctx = userdata;
169 struct resource *current, *previous, *newres;
170 const char *clength, *modtime, *isexec;
171 const char *checkin, *checkout;
172 const ne_status *status = NULL;
173 const char *path = uri->path;
174
175 newres = ne_propset_private(set);
176
177 if (ne_path_compare(ctx->target, path) == 0 && !ctx->include_target) {
178 /* This is the target URI */
179 NE_DEBUG(NE_DBG_HTTP, "Skipping target resource.\n");
180 /* Free the private structure. */
181 ne_free(newres);
182 return;
183 }
184
185 newres->uri = ne_strdup(path);
186
187 clength = ne_propset_value(set, &ls_props[0]);
188 modtime = ne_propset_value(set, &ls_props[1]);
189 isexec = ne_propset_value(set, &ls_props[2]);
190 checkin = ne_propset_value(set, &ls_props[4]);
191 checkout = ne_propset_value(set, &ls_props[5]);
192
193
194 if (clength == NULL)
195 status = ne_propset_status(set, &ls_props[0]);
196 if (modtime == NULL)
197 status = ne_propset_status(set, &ls_props[1]);
198
199 if (newres->type == resr_normal && status) {
200 /* It's an error! */
201 newres->error_status = status->code;
202
203 /* Special hack for Apache 1.3/mod_dav */
204 if (strcmp(status->reason_phrase, "status text goes here") == 0) {
205 const char *desc;
206 if (status->code == 401) {
207 desc = _("Authorization Required");
208 } else if (status->klass == 3) {
209 desc = _("Redirect");
210 } else if (status->klass == 5) {
211 desc = _("Server Error");
212 } else {
213 desc = _("Unknown Error");
214 }
215 newres->error_reason = ne_strdup(desc);
216 } else {
217 newres->error_reason = ne_strdup(status->reason_phrase);
218 }
219 newres->type = resr_error;
220 }
221
222 if (isexec && strcasecmp(isexec, "T") == 0) {
223 newres->is_executable = 1;
224 } else {
225 newres->is_executable = 0;
226 }
227
228 if (modtime)
229 newres->modtime = ne_httpdate_parse(modtime);
230
231 if (clength) {
232 char *p;
233
234 newres->size = DAV_STRTOL(clength, &p, 10);
235 if (*p) {
236 newres->size = 0;
237 }
238 }
239
240 /* is vcr */
241 if (checkin) {
242 newres->is_vcr = 1;
243 } else if (checkout) {
244 newres->is_vcr = 2;
245 } else {
246 newres->is_vcr = 0;
247 }
248
249 NE_DEBUG(NE_DBG_HTTP, "End resource %s\n", newres->uri);
250
251 for (current = *ctx->list, previous = NULL; current != NULL;
252 previous = current, current=current->next) {
253 if (compare_resource(current, newres) >= 0) {
254 break;
255 }
256 }
257 if (previous) {
258 previous->next = newres;
259 } else {
260 *ctx->list = newres;
261 }
262 newres->next = current;
263 }
264
ls_startelm(void * userdata,int parent,const char * nspace,const char * name,const char ** atts)265 static int ls_startelm(void *userdata, int parent,
266 const char *nspace, const char *name, const char **atts)
267 {
268 ne_propfind_handler *pfh = userdata;
269 struct resource *r = ne_propfind_current_private(pfh);
270 int state = ne_xml_mapid(ls_idmap, NE_XML_MAPLEN(ls_idmap),
271 nspace, name);
272
273 if (r == NULL ||
274 !((parent == NE_207_STATE_PROP && state == ELM_resourcetype) ||
275 (parent == ELM_resourcetype && state == ELM_collection)))
276 return NE_XML_DECLINE;
277
278 if (state == ELM_collection) {
279 NE_DEBUG(NE_DBG_HTTP, "This is a collection.\n");
280 r->type = resr_collection;
281 }
282
283 return state;
284 }
285
free_resource(struct resource * res)286 void free_resource(struct resource *res)
287 {
288 if (res->uri) ne_free(res->uri);
289 if (res->error_reason) ne_free(res->error_reason);
290 free(res);
291 }
292
free_resource_list(struct resource * res)293 void free_resource_list(struct resource *res)
294 {
295 struct resource *next;
296 for (; res != NULL; res = next) {
297 next = res->next;
298 free_resource(res);
299 }
300 }
301
create_private(void * userdata,const ne_uri * uri)302 static void *create_private(void *userdata,
303 const ne_uri *uri
304 )
305 {
306 return ne_calloc(sizeof(struct resource));
307 }
308
fetch_resource_list(ne_session * sess,const char * uri,int depth,int include_target,struct resource ** reslist)309 int fetch_resource_list(ne_session *sess, const char *uri,
310 int depth, int include_target,
311 struct resource **reslist)
312 {
313 ne_propfind_handler *pfh = ne_propfind_create(sess, uri, depth);
314 int ret;
315 struct fetch_context ctx = {0};
316
317 *reslist = NULL;
318 ctx.list = reslist;
319 ctx.target = uri;
320 ctx.include_target = include_target;
321
322 ne_xml_push_handler(ne_propfind_get_parser(pfh),
323 ls_startelm, NULL, NULL, pfh);
324
325 ne_propfind_set_private(pfh, create_private, NULL, NULL);
326
327 ret = ne_propfind_named(pfh, ls_props, results, &ctx);
328
329 ne_propfind_destroy(pfh);
330
331 return ret;
332 }
333