1 /*
2 Basic HTTP and WebDAV methods
3 Copyright (C) 1999-2021, Joe Orton <joe@manyfish.co.uk>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free
17 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18 MA 02111-1307, USA
19
20 */
21
22 #include "config.h"
23
24 #include <sys/types.h>
25 #include <sys/stat.h> /* for struct stat */
26
27 #ifdef HAVE_STRING_H
28 #include <string.h>
29 #endif
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 #ifdef HAVE_STDLIB_H
34 #include <stdlib.h>
35 #endif
36
37 #include <errno.h>
38
39 #include "ne_request.h"
40 #include "ne_alloc.h"
41 #include "ne_utils.h"
42 #include "ne_basic.h"
43 #include "ne_207.h"
44
45 #ifdef NE_HAVE_DAV
46 #include "ne_uri.h"
47 #include "ne_locks.h"
48 #endif
49
50 #include "ne_dates.h"
51 #include "ne_internal.h"
52
ne_getmodtime(ne_session * sess,const char * uri,time_t * modtime)53 int ne_getmodtime(ne_session *sess, const char *uri, time_t *modtime)
54 {
55 ne_request *req = ne_request_create(sess, "HEAD", uri);
56 const char *value;
57 int ret;
58
59 ret = ne_request_dispatch(req);
60
61 value = ne_get_response_header(req, "Last-Modified");
62
63 if (ret == NE_OK && ne_get_status(req)->klass != 2) {
64 *modtime = -1;
65 ret = NE_ERROR;
66 }
67 else if (value) {
68 *modtime = ne_httpdate_parse(value);
69 }
70 else {
71 *modtime = -1;
72 }
73
74 ne_request_destroy(req);
75
76 return ret;
77 }
78
79 #ifdef NE_LFS
80 #define ne_fstat fstat64
81 typedef struct stat64 struct_stat;
82 #else
83 #define ne_fstat fstat
84 typedef struct stat struct_stat;
85 #endif
86
87 /* PUT's from fd to URI */
ne_put(ne_session * sess,const char * uri,int fd)88 int ne_put(ne_session *sess, const char *uri, int fd)
89 {
90 ne_request *req;
91 struct_stat st;
92 int ret;
93
94 if (ne_fstat(fd, &st)) {
95 int errnum = errno;
96 char buf[200];
97
98 ne_set_error(sess, _("Could not determine file size: %s"),
99 ne_strerror(errnum, buf, sizeof buf));
100 return NE_ERROR;
101 }
102
103 req = ne_request_create(sess, "PUT", uri);
104
105 #ifdef NE_HAVE_DAV
106 ne_lock_using_resource(req, uri, 0);
107 ne_lock_using_parent(req, uri);
108 #endif
109
110 ne_set_request_body_fd(req, fd, 0, st.st_size);
111
112 ret = ne_request_dispatch(req);
113
114 if (ret == NE_OK && ne_get_status(req)->klass != 2)
115 ret = NE_ERROR;
116
117 ne_request_destroy(req);
118
119 return ret;
120 }
121
122 /* Dispatch a GET request REQ, writing the response body to FD fd. If
123 * RANGE is non-NULL, then it is the value of the Range request
124 * header, e.g. "bytes=1-5". Returns an NE_* error code. */
dispatch_to_fd(ne_request * req,int fd,const char * range)125 static int dispatch_to_fd(ne_request *req, int fd, const char *range)
126 {
127 ne_session *const sess = ne_get_session(req);
128 const ne_status *const st = ne_get_status(req);
129 int ret;
130 size_t rlen;
131
132 /* length of bytespec after "bytes=" */
133 rlen = range ? strlen(range + 6) : 0;
134
135 do {
136 const char *value;
137
138 ret = ne_begin_request(req);
139 if (ret != NE_OK) break;
140
141 value = ne_get_response_header(req, "Content-Range");
142
143 /* For a 206 response, check that a Content-Range header is
144 * given which matches the Range request header. */
145 if (range && st->code == 206
146 && (value == NULL || strncmp(value, "bytes ", 6) != 0
147 || strncmp(range + 6, value + 6, rlen)
148 || (range[5 + rlen] != '-' && value[6 + rlen] != '/'))) {
149 ne_set_error(sess, _("Response did not include requested range"));
150 return NE_ERROR;
151 }
152
153 if ((range && st->code == 206) || (!range && st->klass == 2)) {
154 ret = ne_read_response_to_fd(req, fd);
155 } else {
156 ret = ne_discard_response(req);
157 }
158
159 if (ret == NE_OK) ret = ne_end_request(req);
160 } while (ret == NE_RETRY);
161
162 return ret;
163 }
164
get_range_common(ne_session * sess,const char * uri,const char * brange,int fd)165 static int get_range_common(ne_session *sess, const char *uri,
166 const char *brange, int fd)
167
168 {
169 ne_request *req = ne_request_create(sess, "GET", uri);
170 const ne_status *status;
171 int ret;
172
173 ne_add_request_header(req, "Range", brange);
174 ne_add_request_header(req, "Accept-Ranges", "bytes");
175
176 ret = dispatch_to_fd(req, fd, brange);
177
178 status = ne_get_status(req);
179
180 if (ret == NE_OK && status->code == 416) {
181 /* connection is terminated too early with Apache/1.3, so we check
182 * this even if ret == NE_ERROR... */
183 ne_set_error(sess, _("Range is not satisfiable"));
184 ret = NE_ERROR;
185 }
186 else if (ret == NE_OK) {
187 if (status->klass == 2 && status->code != 206) {
188 ne_set_error(sess, _("Resource does not support ranged GET requests"));
189 ret = NE_ERROR;
190 }
191 else if (status->klass != 2) {
192 ret = NE_ERROR;
193 }
194 }
195
196 ne_request_destroy(req);
197
198 return ret;
199 }
200
ne_get_range(ne_session * sess,const char * uri,ne_content_range * range,int fd)201 int ne_get_range(ne_session *sess, const char *uri,
202 ne_content_range *range, int fd)
203 {
204 char brange[64];
205
206 if (range->end == -1) {
207 ne_snprintf(brange, sizeof brange, "bytes=%" FMT_NE_OFF_T "-",
208 range->start);
209 }
210 else {
211 ne_snprintf(brange, sizeof brange,
212 "bytes=%" FMT_NE_OFF_T "-%" FMT_NE_OFF_T,
213 range->start, range->end);
214 }
215
216 return get_range_common(sess, uri, brange, fd);
217 }
218
219 /* Get to given fd */
ne_get(ne_session * sess,const char * uri,int fd)220 int ne_get(ne_session *sess, const char *uri, int fd)
221 {
222 ne_request *req = ne_request_create(sess, "GET", uri);
223 int ret;
224
225 ret = dispatch_to_fd(req, fd, NULL);
226
227 if (ret == NE_OK && ne_get_status(req)->klass != 2) {
228 ret = NE_ERROR;
229 }
230
231 ne_request_destroy(req);
232
233 return ret;
234 }
235
236
237 /* Get to given fd */
ne_post(ne_session * sess,const char * uri,int fd,const char * buffer)238 int ne_post(ne_session *sess, const char *uri, int fd, const char *buffer)
239 {
240 ne_request *req = ne_request_create(sess, "POST", uri);
241 int ret;
242
243 ne_set_request_flag(req, NE_REQFLAG_IDEMPOTENT, 0);
244
245 ne_set_request_body_buffer(req, buffer, strlen(buffer));
246
247 ret = dispatch_to_fd(req, fd, NULL);
248
249 if (ret == NE_OK && ne_get_status(req)->klass != 2) {
250 ret = NE_ERROR;
251 }
252
253 ne_request_destroy(req);
254
255 return ret;
256 }
257
ne_get_content_type(ne_request * req,ne_content_type * ct)258 int ne_get_content_type(ne_request *req, ne_content_type *ct)
259 {
260 const char *value;
261 char *sep, *stype;
262
263 value = ne_get_response_header(req, "Content-Type");
264 if (value == NULL || strchr(value, '/') == NULL) {
265 return -1;
266 }
267
268 ct->value = ne_strdup(value);
269
270 stype = strchr(ct->value, '/');
271
272 *stype++ = '\0';
273 ct->type = ct->value;
274 ct->charset = NULL;
275
276 sep = strchr(stype, ';');
277
278 if (sep) {
279 char *tok;
280 /* look for the charset parameter. TODO; probably better to
281 * hand-carve a parser than use ne_token/strstr/shave here. */
282 *sep++ = '\0';
283 do {
284 tok = ne_qtoken(&sep, ';', "\"\'");
285 if (tok) {
286 tok = strstr(tok, "charset=");
287 if (tok)
288 ct->charset = ne_shave(tok+8, "\"\'");
289 } else {
290 break;
291 }
292 } while (sep != NULL);
293 }
294
295 /* set subtype, losing any trailing whitespace */
296 ct->subtype = ne_shave(stype, " \t");
297
298 if (ct->charset == NULL && ne_strcasecmp(ct->type, "text") == 0) {
299 /* 3280§3.1: text/xml without charset implies us-ascii. */
300 if (ne_strcasecmp(ct->subtype, "xml") == 0)
301 ct->charset = "us-ascii";
302 /* 2616§3.7.1: subtypes of text/ default to charset ISO-8859-1. */
303 else
304 ct->charset = "ISO-8859-1";
305 }
306
307 return 0;
308 }
309
310 static const struct options_map {
311 const char *name;
312 unsigned int cap;
313 } options_map[] = {
314 { "1", NE_CAP_DAV_CLASS1 },
315 { "2", NE_CAP_DAV_CLASS2 },
316 { "3", NE_CAP_DAV_CLASS3 },
317 { "<http://apache.org/dav/propset/fs/1>", NE_CAP_MODDAV_EXEC },
318 { "access-control", NE_CAP_DAV_ACL },
319 { "version-control", NE_CAP_VER_CONTROL },
320 { "checkout-in-place", NE_CAP_CO_IN_PLACE },
321 { "version-history", NE_CAP_VER_HISTORY },
322 { "workspace", NE_CAP_WORKSPACE },
323 { "update", NE_CAP_UPDATE },
324 { "label", NE_CAP_LABEL },
325 { "working-resource", NE_CAP_WORK_RESOURCE },
326 { "merge", NE_CAP_MERGE },
327 { "baseline", NE_CAP_BASELINE },
328 { "version-controlled-collection", NE_CAP_VC_COLLECTION },
329 { "extended-mkcol", NE_CAP_EXT_MKCOL }
330 };
331
parse_dav_header(const char * value,unsigned int * caps)332 static void parse_dav_header(const char *value, unsigned int *caps)
333 {
334 char *tokens = ne_strdup(value), *pnt = tokens;
335
336 *caps = 0;
337
338 do {
339 char *tok = ne_qtoken(&pnt, ',', "\"'");
340 unsigned n;
341
342 if (!tok) break;
343
344 tok = ne_shave(tok, " \r\t\n");
345
346 for (n = 0; n < sizeof(options_map)/sizeof(options_map[0]); n++) {
347 if (strcmp(tok, options_map[n].name) == 0) {
348 *caps |= options_map[n].cap;
349 }
350 }
351 } while (pnt != NULL);
352
353 ne_free(tokens);
354 }
355
ne_options2(ne_session * sess,const char * uri,unsigned int * caps)356 int ne_options2(ne_session *sess, const char *uri, unsigned int *caps)
357 {
358 ne_request *req = ne_request_create(sess, "OPTIONS", uri);
359 int ret = ne_request_dispatch(req);
360 const char *header = ne_get_response_header(req, "DAV");
361
362 if (header) parse_dav_header(header, caps);
363
364 if (ret == NE_OK && ne_get_status(req)->klass != 2) {
365 ret = NE_ERROR;
366 }
367
368 ne_request_destroy(req);
369
370 return ret;
371 }
372
ne_options(ne_session * sess,const char * path,ne_server_capabilities * caps)373 int ne_options(ne_session *sess, const char *path,
374 ne_server_capabilities *caps)
375 {
376 int ret;
377 unsigned int capmask = 0;
378
379 memset(caps, 0, sizeof *caps);
380
381 ret = ne_options2(sess, path, &capmask);
382
383 caps->dav_class1 = capmask & NE_CAP_DAV_CLASS1 ? 1 : 0;
384 caps->dav_class2 = capmask & NE_CAP_DAV_CLASS2 ? 1 : 0;
385 caps->dav_executable = capmask & NE_CAP_MODDAV_EXEC ? 1 : 0;
386
387 return ret;
388 }
389
390 #ifdef NE_HAVE_DAV
391
ne_add_depth_header(ne_request * req,int depth)392 void ne_add_depth_header(ne_request *req, int depth)
393 {
394 const char *value;
395 switch(depth) {
396 case NE_DEPTH_ZERO:
397 value = "0";
398 break;
399 case NE_DEPTH_ONE:
400 value = "1";
401 break;
402 default:
403 value = "infinity";
404 break;
405 }
406 ne_add_request_header(req, "Depth", value);
407 }
408
copy_or_move(ne_session * sess,int is_move,int overwrite,int depth,const char * src,const char * dest)409 static int copy_or_move(ne_session *sess, int is_move, int overwrite,
410 int depth, const char *src, const char *dest)
411 {
412 ne_request *req = ne_request_create( sess, is_move?"MOVE":"COPY", src );
413
414 /* 2518 S8.9.2 says only use Depth: infinity with MOVE. */
415 if (!is_move) {
416 ne_add_depth_header(req, depth);
417 }
418
419 #ifdef NE_HAVE_DAV
420 if (is_move) {
421 ne_lock_using_resource(req, src, NE_DEPTH_INFINITE);
422 }
423 ne_lock_using_resource(req, dest, NE_DEPTH_INFINITE);
424 /* And we need to be able to add members to the destination's parent */
425 ne_lock_using_parent(req, dest);
426 #endif
427
428 if (ne_get_session_flag(sess, NE_SESSFLAG_RFC4918)) {
429 ne_add_request_header(req, "Destination", dest);
430 }
431 else {
432 ne_print_request_header(req, "Destination", "%s://%s%s",
433 ne_get_scheme(sess),
434 ne_get_server_hostport(sess), dest);
435 }
436
437 ne_add_request_header(req, "Overwrite", overwrite?"T":"F");
438
439 return ne_simple_request(sess, req);
440 }
441
ne_copy(ne_session * sess,int overwrite,int depth,const char * src,const char * dest)442 int ne_copy(ne_session *sess, int overwrite, int depth,
443 const char *src, const char *dest)
444 {
445 return copy_or_move(sess, 0, overwrite, depth, src, dest);
446 }
447
ne_move(ne_session * sess,int overwrite,const char * src,const char * dest)448 int ne_move(ne_session *sess, int overwrite,
449 const char *src, const char *dest)
450 {
451 return copy_or_move(sess, 1, overwrite, 0, src, dest);
452 }
453
454 /* Deletes the specified resource. (and in only two lines of code!) */
ne_delete(ne_session * sess,const char * uri)455 int ne_delete(ne_session *sess, const char *uri)
456 {
457 ne_request *req = ne_request_create(sess, "DELETE", uri);
458
459 #ifdef NE_HAVE_DAV
460 ne_lock_using_resource(req, uri, NE_DEPTH_INFINITE);
461 ne_lock_using_parent(req, uri);
462 #endif
463
464 /* joe: I asked on the DAV WG list about whether we might get a
465 * 207 error back from a DELETE... conclusion, you shouldn't if
466 * you don't send the Depth header, since we might be an HTTP/1.1
467 * client and a 2xx response indicates success to them. But
468 * it's all a bit unclear. In any case, DAV servers today do
469 * return 207 to DELETE even if we don't send the Depth header.
470 * So we handle 207 errors appropriately. */
471
472 return ne_simple_request(sess, req);
473 }
474
ne_mkcol(ne_session * sess,const char * uri)475 int ne_mkcol(ne_session *sess, const char *uri)
476 {
477 ne_request *req;
478 char *real_uri;
479 int ret;
480
481 if (ne_path_has_trailing_slash(uri)) {
482 real_uri = ne_strdup(uri);
483 } else {
484 real_uri = ne_concat(uri, "/", NULL);
485 }
486
487 req = ne_request_create(sess, "MKCOL", real_uri);
488
489 #ifdef NE_HAVE_DAV
490 ne_lock_using_resource(req, real_uri, 0);
491 ne_lock_using_parent(req, real_uri);
492 #endif
493
494 ret = ne_simple_request(sess, req);
495
496 ne_free(real_uri);
497
498 return ret;
499 }
500
501 #endif /* NE_HAVE_DAV */
502