Back to index

avfs  1.0.1
http_basic.c
Go to the documentation of this file.
00001 /* 
00002    HTTP/1.1 methods
00003    Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009    
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public
00016    License along with this library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
00018    MA 02111-1307, USA
00019 
00020 */
00021 
00022 #include "config.h"
00023 
00024 #include <sys/types.h>
00025 
00026 #ifdef HAVE_STRING_H
00027 #include <string.h>
00028 #endif
00029 
00030 #ifdef HAVE_STDLIB_H
00031 #include <stdlib.h>
00032 #endif
00033 
00034 #include <errno.h>
00035 
00036 #include "http_request.h"
00037 #include "http_basic.h"
00038 #ifdef USE_DAV_LOCKS
00039 #include "dav_locks.h"
00040 #endif
00041 #include "dates.h"
00042 #include "nsocket.h"
00043 #include "neon_i18n.h"
00044 #include "ne_alloc.h"
00045 
00046 /* Header parser to retrieve Last-Modified date */
00047 static void get_lastmodified(void *userdata, const char *value) {
00048     time_t *modtime = userdata;
00049     *modtime = http_dateparse(value);
00050 }
00051 
00052 int http_getmodtime(http_session *sess, const char *uri, time_t *modtime) 
00053 {
00054     http_req *req = http_request_create(sess, "HEAD", uri);
00055     int ret;
00056 
00057     http_add_response_header_handler(req, "Last-Modified", get_lastmodified,
00058                                  modtime);
00059 
00060     *modtime = -1;
00061 
00062     ret = http_request_dispatch(req);
00063 
00064     if (ret == HTTP_OK && http_get_status(req)->klass != 2) {
00065        *modtime = -1;
00066        ret = HTTP_ERROR;
00067     }
00068 
00069     http_request_destroy(req);
00070 
00071     return ret;
00072 }
00073 
00074 /* PUT's stream to URI */
00075 int http_put(http_session *sess, const char *uri, FILE *stream) 
00076 {
00077     http_req *req = http_request_create(sess, "PUT", uri);
00078     int ret;
00079     
00080 #ifdef USE_DAV_LOCKS
00081     dav_lock_using_resource(req, uri, 0);
00082     dav_lock_using_parent(req, uri);
00083 #endif
00084 
00085     http_set_request_body_stream(req, stream);
00086        
00087     ret = http_request_dispatch(req);
00088     
00089     if (ret == HTTP_OK && http_get_status(req)->klass != 2)
00090        ret = HTTP_ERROR;
00091 
00092     http_request_destroy(req);
00093 
00094     return ret;
00095 }
00096 
00097 /* Conditional HTTP put. 
00098  * PUTs stream to uri, returning HTTP_FAILED if resource as URI has
00099  * been modified more recently than 'since'.
00100  */
00101 int 
00102 http_put_if_unmodified(http_session *sess, const char *uri, 
00103                      FILE *stream, time_t since) {
00104     http_req *req;
00105     char *date;
00106     int ret;
00107     
00108     if (http_version_pre_http11(sess)) {
00109        time_t modtime;
00110        /* Server is not minimally HTTP/1.1 compliant.  Do a HEAD to
00111         * check the remote mod time. Of course, this makes the
00112         * operation very non-atomic, but better than nothing. */
00113        ret = http_getmodtime(sess, uri, &modtime);
00114        if (ret != HTTP_OK) return ret;
00115        if (modtime != since)
00116            return HTTP_FAILED;
00117     }
00118 
00119     req = http_request_create(sess, "PUT", uri);
00120 
00121     date = rfc1123_date(since);
00122     /* Add in the conditionals */
00123     http_add_request_header(req, "If-Unmodified-Since", date);
00124     free(date);
00125     
00126 #ifdef USE_DAV_LOCKS
00127     dav_lock_using_resource(req, uri, 0);
00128     /* FIXME: this will give 412 if the resource doesn't exist, since
00129      * PUT may modify the parent... does that matter?  */
00130 #endif
00131 
00132     http_set_request_body_stream(req, stream);
00133 
00134     ret = http_request_dispatch(req);
00135     
00136     if (ret == HTTP_OK) {
00137        if (http_get_status(req)->code == 412) {
00138            ret = HTTP_FAILED;
00139        } else if (http_get_status(req)->klass != 2) {
00140            ret = HTTP_ERROR;
00141        }
00142     }
00143 
00144     http_request_destroy(req);
00145 
00146     return ret;
00147 }
00148 
00149 struct get_context {
00150     int error;
00151     size_t total, progress;
00152     http_block_reader callback; /* used in read_file */
00153     FILE *file; /* used in get_to_fd */
00154     http_content_range *range;
00155     void *userdata;
00156 };
00157 
00158 static void get_callback(void *userdata, const char *block, size_t length) 
00159 {
00160     struct get_context *ctx = userdata;
00161 
00162     DEBUG(DEBUG_HTTP, "Got progress: %d out of %d\n", 
00163           ctx->progress, ctx->total);
00164 
00165     (*ctx->callback)(ctx->userdata, block, length);
00166 
00167     /* Increase progress */
00168     ctx->progress += length;
00169     if (ctx->progress > ctx->total) {
00170        /* Reset the counter if we're uploading it again */
00171        ctx->progress -= ctx->total;
00172     }
00173     sock_call_progress(ctx->progress, ctx->total);
00174 }
00175 
00176 int http_read_file(http_session *sess, const char *uri, 
00177                  http_block_reader reader, void *userdata) {
00178     struct get_context ctx;
00179     http_req *req = http_request_create(sess, "GET", uri);
00180     int ret;
00181     
00182     ctx.total = -1;
00183     ctx.progress = 0;
00184     ctx.callback = reader;
00185     ctx.userdata = userdata;
00186 
00187     /* Read the value of the Content-Length header into ctx.total */
00188     http_add_response_header_handler(req, "Content-Length",
00189                                  http_handle_numeric_header,
00190                                  &ctx.total);
00191     
00192     http_add_response_body_reader(req, http_accept_2xx, get_callback, &ctx);
00193 
00194     ret = http_request_dispatch(req);
00195 
00196     if (ret == HTTP_OK && http_get_status(req)->klass != 2)
00197        ret = HTTP_ERROR;
00198 
00199     http_request_destroy(req);
00200 
00201     return ret;
00202 }
00203 
00204 static void get_to_fd(void *userdata, const char *block, size_t length)
00205 {
00206     struct get_context *ctx = userdata;
00207     FILE *f = ctx->file;
00208     size_t ret;
00209     if (!ctx->error) {
00210        while (length > 0) {
00211            ret = fwrite(block, 1, length, f);
00212            if (ret < 0) {
00213               ctx->error = errno;
00214               break;
00215            } else {
00216               length -= ret;
00217            }
00218        }
00219     }
00220 }
00221 
00222 static int accept_206(void *ud, http_req *req, http_status *st)
00223 {
00224     return (st->code == 206);
00225 }
00226 
00227 static void clength_hdr_handler(void *ud, const char *value)
00228 {
00229     struct get_context *ctx = ud;
00230     off_t len = strtol(value, NULL, 10);
00231     
00232     if (ctx->range->end == -1) {
00233        ctx->range->end = ctx->range->start + len;
00234     }
00235     else if (len != (ctx->range->end - ctx->range->start)) {
00236        DEBUG(DEBUG_HTTP, "Expecting %ld bytes, got entity of length %ld\n", 
00237              (long int) (ctx->range->end - ctx->range->start), 
00238              (long int) len);
00239        ctx->error = 1;
00240     }
00241 }
00242 
00243 static void content_range_hdr_handler(void *ud, const char *value)
00244 {
00245     struct get_context *ctx = ud;
00246 
00247     if (strncmp(value, "bytes ", 6) != 0) {
00248        ctx->error = 1;
00249     }
00250 }
00251 
00252 int http_get_range(http_session *sess, const char *uri, 
00253                  http_content_range *range, FILE *f)
00254 {
00255     http_req *req = http_request_create(sess, "GET", uri);
00256     struct get_context ctx;
00257     int ret;
00258 
00259     if (range->end == -1) {
00260        ctx.total = -1;
00261     } 
00262     else {
00263        ctx.total = range->end - range->start;
00264     }
00265 
00266     ctx.progress = 0;
00267     ctx.callback = get_to_fd;
00268     ctx.userdata = &ctx;
00269     ctx.file = f;
00270     ctx.error = 0;
00271     ctx.range = range;
00272 
00273     http_add_response_header_handler(req, "Content-Length",
00274                                  clength_hdr_handler, &ctx);
00275     http_add_response_header_handler(req, "Content-Range",
00276                                  content_range_hdr_handler,
00277                                  &ctx);
00278 
00279     http_add_response_body_reader(req, accept_206, get_callback, &ctx);
00280 
00281     /* icky casts to long int, which should be at least as large as the
00282      * off_t's */
00283     if (range->end == -1) {
00284        http_print_request_header(req, "Range", "bytes=%ld-", 
00285                               (long int) range->start);
00286     }
00287     else {
00288        http_print_request_header(req, "Range", "bytes=%ld-%ld",
00289                               (long int) range->start, 
00290                               (long int)range->end);
00291     }
00292     http_add_request_header(req, "Accept-Ranges", "bytes");
00293 
00294     ret = http_request_dispatch(req);
00295     
00296     if (ret == HTTP_OK && http_get_status(req)->klass != 2) {
00297        ret = HTTP_ERROR;
00298     }
00299     else if (http_get_status(req)->code != 206) {
00300        http_set_error(sess, _("Server does not allow partial GETs."));
00301        ret = HTTP_ERROR;
00302     }
00303     
00304     http_request_destroy(req);
00305 
00306     return ret;
00307 }
00308 
00309 
00310 /* Get to given stream */
00311 int http_get(http_session *sess, const char *uri, FILE *f)
00312 {
00313     http_req *req = http_request_create(sess, "GET", uri);
00314     struct get_context ctx;
00315     int ret;
00316 
00317     ctx.total = -1;
00318     ctx.progress = 0;
00319     ctx.callback = get_to_fd;
00320     ctx.userdata = &ctx;
00321     ctx.file = f;
00322     ctx.error = 0;
00323 
00324     /* Read the value of the Content-Length header into ctx.total */
00325     http_add_response_header_handler(req, "Content-Length",
00326                                  http_handle_numeric_header,
00327                                  &ctx.total);
00328     
00329     http_add_response_body_reader(req, http_accept_2xx, get_callback, &ctx);
00330 
00331     ret = http_request_dispatch(req);
00332     
00333     if (ctx.error) {
00334        char buf[BUFSIZ];
00335        snprintf(buf, BUFSIZ, 
00336                 _("Could not write to file: %s"), strerror(ctx.error));
00337        http_set_error(sess, buf);
00338        ret = HTTP_ERROR;
00339     }
00340 
00341     if (ret == HTTP_OK && http_get_status(req)->klass != 2) {
00342        ret = HTTP_ERROR;
00343     }
00344 
00345     http_request_destroy(req);
00346 
00347     return ret;
00348 }
00349 
00350 
00351 /* Get to given stream */
00352 int http_post(http_session *sess, const char *uri, FILE *f, const char *buffer)
00353 {
00354     http_req *req = http_request_create(sess, "POST", uri);
00355     struct get_context ctx;
00356     int ret;
00357 
00358     ctx.total = -1;
00359     ctx.progress = 0;
00360     ctx.callback = get_to_fd;
00361     ctx.userdata = &ctx;
00362     ctx.file = f;
00363     ctx.error = 0;
00364 
00365     /* Read the value of the Content-Length header into ctx.total */
00366     http_add_response_header_handler(req, "Content-Length",
00367                                  http_handle_numeric_header, &ctx.total);
00368 
00369     http_add_response_body_reader(req, http_accept_2xx, get_callback, &ctx);
00370 
00371     http_set_request_body_buffer(req, buffer);
00372 
00373     ret = http_request_dispatch(req);
00374     
00375     if (ctx.error) {
00376        char buf[BUFSIZ];
00377        snprintf(buf, BUFSIZ, 
00378                _("Could not write to file: %s"), strerror(ctx.error));
00379        http_set_error(sess, buf);
00380        ret = HTTP_ERROR;
00381     }
00382 
00383     if (ret == HTTP_OK && http_get_status(req)->klass != 2) {
00384        ret = HTTP_ERROR;
00385     }
00386 
00387     http_request_destroy(req);
00388 
00389     return ret;
00390 }
00391 
00392 static void server_hdr_handler(void *userdata, const char *value)
00393 {
00394     char **tokens = split_string(value, ' ', HTTP_QUOTES, NULL);
00395     http_server_capabilities *caps = userdata;
00396     int n;
00397 
00398     for (n = 0; tokens[n] != NULL; n++) {
00399        if (strncasecmp(tokens[n], "Apache/", 7) == 0 && 
00400            strlen(tokens[n]) > 11) { /* 12 == "Apache/1.3.0" */
00401            const char *ver = tokens[n] + 7;
00402            int count;
00403            char **vers;
00404            vers = split_string_c(ver, '.', NULL, NULL, &count);
00405            /* Apache/1.3.6 and before have broken Expect: 100 support */
00406            if (count > 1 && atoi(vers[0]) < 2 && 
00407               atoi(vers[1]) < 4 && atoi(vers[2]) < 7) {
00408               caps->broken_expect100 = 1;
00409            }
00410            split_string_free(vers);
00411        }
00412     }    
00413     
00414     split_string_free(tokens);
00415 }
00416 
00417 void http_content_type_handler(void *userdata, const char *value)
00418 {
00419     http_content_type *ct = userdata;
00420     char *sep, *parms;
00421 
00422     ct->value = ne_strdup(value);
00423     
00424     sep = strchr(ct->value, '/');
00425     if (!sep) {
00426        HTTP_FREE(ct->value);
00427        return;
00428     }
00429 
00430     *++sep = '\0';
00431     ct->type = ct->value;
00432     ct->subtype = sep;
00433     
00434     parms = strchr(ct->value, ';');
00435 
00436     if (parms) {
00437        *parms = '\0';
00438        /* TODO: handle charset. */
00439     }
00440 }
00441 
00442 static void dav_hdr_handler(void *userdata, const char *value)
00443 {
00444     char **classes, **class;
00445     http_server_capabilities *caps = userdata;
00446     
00447     classes = split_string(value, ',', HTTP_QUOTES, HTTP_WHITESPACE);
00448     for (class = classes; *class!=NULL; class++) {
00449 
00450        if (strcmp(*class, "1") == 0) {
00451            caps->dav_class1 = 1;
00452        } else if (strcmp(*class, "2") == 0) {
00453            caps->dav_class2 = 1;
00454        } else if (strcmp(*class, "<http://apache.org/dav/propset/fs/1>") == 0) {
00455            caps->dav_executable = 1;
00456        }
00457     }
00458     
00459     split_string_free(classes);
00460 
00461 }
00462 
00463 int http_options(http_session *sess, const char *uri,
00464                 http_server_capabilities *caps)
00465 {
00466     http_req *req = http_request_create(sess, "OPTIONS", uri);
00467     
00468     int ret;
00469 
00470     http_add_response_header_handler(req, "Server", server_hdr_handler, caps);
00471     http_add_response_header_handler(req, "DAV", dav_hdr_handler, caps);
00472 
00473     ret = http_request_dispatch(req);
00474  
00475     if (ret == HTTP_OK && http_get_status(req)->klass != 2) {
00476        ret = HTTP_ERROR;
00477     }
00478     
00479     http_request_destroy(req);
00480 
00481     return ret;
00482 }