📄 httpdav.c
字号:
/* sitecopy, for managing remote web sites. WebDAV client routines. Copyright (C) 1998-99, Joe Orton <joe@orton.demon.co.uk> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. $Id: httpdav.c,v 1.46.2.18 1999/08/27 10:03:49 joe Exp $*//* This file is a collection of routines to implement a basic WebDAV * client, including an HTTP/1.1 client. * Transparently supports basic and digest authentication. *//* HTTP method implementation: * Call, in this order: * 1. http_request_init() - set up the request * 2. http_request() - make the request * 3. http_request_end() - clean up the request */#include <config.h>#include <sys/types.h>#include <sys/stat.h>#ifdef __EMX__#include <sys/select.h>#endif#include <netinet/in.h>#include <ctype.h>#include <errno.h>#include <fcntl.h>#include <stdio.h>#ifdef HAVE_STRING_H#include <string.h>#endif#ifdef HAVE_STRINGS_H#include <strings.h>#endif #ifdef HAVE_STDLIB_H#include <stdlib.h>#endif /* HAVE_STDLIB_H */#ifdef HAVE_UNISTD_H#include <unistd.h>#endif /* HAVE_UNISTD_H */#ifndef HAVE_SNPRINTF#include <snprintf.h>#endif#include <dates.h>#include <basename.h>#include <dirname.h>#include <strsplit.h>#include "frontend.h"#include "protocol.h"#include "httpauth.h"#include "httpdav.h"#include "common.h"#include "socket.h"#include "base64.h"/* Connection information... */int http_sock;bool http_connected;bool http_disable_expect = false;/* Time in seconds to wait for the server to give us a 100 Continue * after submitting a PUT with an "Expect: 100-continue" header. */#define HTTP_EXPECT_TIMEOUT 15/* 100-continue only used if size > HTTP_EXPECT_MINSIZ */#define HTTP_EXPECT_MINSIZE 512/* Whether the current server respects the Expect: 100-continue header */int http_expect_works; /* == 0 if it we don't know, 1 if it does, -1 if it doesn't support 100-continue */bool http_webdav_server = false;bool http_init_checks = true;bool http_conn_limit = false;/* Warning given out on spoof attack */const char *http_warn_spoof = "The server has switched to using basic authentication from digest\n" "authenticaion, which may be an attempt to discover your password.\n" "Basic auth will NOT be used.";bool http_can_authenticate;http_auth_session_t http_server_auth, http_proxy_auth;unsigned int http_version_major, http_version_minor;const char *http_quotes = "\"'";const char *http_whitespace = " \r\n\t";#ifdef HAVE_LIBEXPAT/* WebDAV Fetch mode */#include <xmlparse.h>typedef enum { dav_xml_multistatus = 0, dav_xml_response, dav_xml_responsedescription, dav_xml_href, dav_xml_propstat, dav_xml_prop, dav_xml_status, dav_xml_getlastmodified, dav_xml_getcontentlength, dav_xml_resourcetype, dav_xml_collection, dav_xml_unknown, dav_xml_root} dav_xml_tag_t;/* We get the tag_t from the names array below */const char *dav_xml_tagnames[] = { "DAV:multistatus", "DAV:response", "DAV:responsedescription", "DAV:href", "DAV:propstat", "DAV:prop", "DAV:status", "DAV:getlastmodified", "DAV:getcontentlength", "DAV:resourcetype", "DAV:collection", NULL, /* end-of-list marker */ "@<root>@" /* filler, so we can index this by dav_xml_tag_t */};typedef struct dav_xml_ns_s dav_xml_ns;/* Linked list of namespace scopes */struct dav_xml_ns_s { char *name; char *value; dav_xml_ns *next;};struct dav_xml_state { dav_xml_tag_t tag; char *tag_name; /* The full tag name */ char *default_ns; /* The current default namespace */ dav_xml_ns *nspaces; /* List of other namespace scopes */ struct dav_xml_state *parent; /* The parent in the tree */};/* We pass around a dav_xmldoc as the userdata using expat. This * maintains the current state of the parse and various other bits and * bobs. Within the parse, we store the current branch of the tree, * i.e., the current element and all its parents, but nothing other * than that. * * The files list is filled as we go (by dav_fetch_gotresource), but * always kept in sorted order, since no ordering of resources in the * PROPFIND response is given. */struct dav_xmldoc { /* Points to the root of the document */ struct dav_xml_state *root; /* Points to the current element in the document */ struct dav_xml_state *current; /* Whether we want to collect CDATA at the moment or not */ bool want_cdata; /* The cdata we've collected so far - grows as we collect * more. */ char *cdata; /* How big the buffer is atm */ size_t cdata_buflen; /* How much cdata we've collected so far */ size_t cdata_len; /* Is it valid? */ bool valid; /* Temporary store of file info */ struct proto_file_t *file; /* The complete fetch list */ struct proto_file_t *files; /* The collection we did a PROPFIND on */ const char *fetch_root;};/* The initial size of the cdata buffer, and the minimum size by which * it grows each time we overflow it. For the PROPFIND requests * we do for sitecopy, the only cdata segments we collect are very small. */#define CDATABUFSIZ 128/* If the cdata buffer size is larger than this when we've finished * with its contents, then we reallocate it. Otherwise, we just * zero it out. Reallocation -> a free() and a malloc(). No realloc -> * just a memset(). */#define CDATASHRINK 128/* Prototypes */static void dav_xml_startelm( void *userdata, const char *tag, const char **atts );static void dav_xml_endelm( void *userdata, const char *tag );static void dav_xml_cdata( void *userdata, const char *cdata, int len );static bool dav_xml_parsetag( struct dav_xml_state *state, const char *tag, const char **atts );static int dav_fetch_getdepth( const char *href );static bool dav_fetch_parse_href( struct dav_xmldoc *doc );static void dav_fetch_gotresource( struct dav_xmldoc *doc );static void dav_xml_parsebody( void *userdata, const char *buffer, const size_t len );static void http_get_content_charset( const char *name, const char *value );char *http_content_charset;#endif /* HAVE_LIBEXPAT *//* Handy macro to free things. */#define DOFREE(x) if( x!=NULL ) free( x )#define EOL "\r\n"#define HTTP_PORT 80const char *http_useragent = PACKAGE "/" VERSION;/* We need to remember the remote host and port even after connecting * the socket e.g. so we can form the Destination: header */struct proto_host_t http_server_host, http_proxy_host;/* This, to store the address of the server we CONNECT to - i.e., * the proxy if we have one, else the server */struct in_addr http_remoteaddr;int http_remoteport;bool http_use_proxy; /* whether we are using the proxy or not */int http_mkdir_works;static int http_response_read( http_req_t *req, char *buffer, size_t buflen );/* Sets up the body size for the given request */static int http_req_bodysize( http_req_t *req );/* The callback for GET requests (to allow signalling progress to the FE) */static void http_get_callback( void *user, const char *buffer, const size_t len );/* Do a dummy MKDIR with PUT */static int http_mkdir_with_put( const char *realdir );/* Holds the error message to be passed up */char http_error[BUFSIZ];/* Put the fixed headers into the request */static void http_req_fixedheaders( http_req_t *req );/* Concatenates the remote server name with :port if port!=80 on to * the end of str. */static void http_strcat_hostname( struct proto_host_t *host, char *str );/* Header parser for OPTIONS requests. */static void http_options_parsehdr( const char *name, const char *value );static int http_parse_status( http_req_t *req, char *status_line );/* Sends the body down the wire */static int http_req_sendbody( http_req_t *req );/* Encodes the absPath sectionf of a URI using %<hex><hex> encoding */static char *uri_abspath_encode( const char *abs_path );/* Decodes a URI */static char *uri_decode( const char *uri );/* Opens the connection to the remote server. * Returns: * PROTO_OK on success * PROTO_CONNECT if the socket couldn't be connected */static int http_open( void );/* Closes the connection. * Always returns PROTO_OK */static int http_close( void );/* This doesn't really connect to the server. * Returns * PROTO_LOOKUP if the hostname of the server could not be resolved * PROTO_LOCALHOST if the hostname of the local machine could not be * found * PROTO_CONNECT if the socket could not be connected * PROTO_OK if the connection was made successfully. */int http_init( const char *remote_root, struct proto_host_t *server, struct proto_host_t *proxy ) { int ret; /* Take a copy of the server information */ memcpy( &http_server_host, server, sizeof(struct proto_host_t) ); fe_connection( fe_namelookup ); if( proxy != NULL ) { memcpy( &http_proxy_host, proxy, sizeof(struct proto_host_t) ); http_remoteport = proxy->port; http_use_proxy = true; if( host_lookup( http_proxy_host.hostname, &http_remoteaddr ) ) return PROTO_LOOKUP; } else { http_remoteport = server->port; http_use_proxy = false; if( host_lookup( http_server_host.hostname, &http_remoteaddr ) ) return PROTO_LOOKUP; } http_connected = false; http_expect_works = http_disable_expect?-1:0; /* we don't know yet */ /* temporary workaround */ http_expect_works = -1; http_mkdir_works = 0; /* we don't know yet */ http_auth_init( &http_server_auth, server->username, server->password ); if( http_use_proxy ) { /* TODO: Implement properly *//* http_auth_init( &http_proxy_auth, proxy->username, proxy->password ); */ } http_can_authenticate = false; ret = http_open(); /* Drop out if that failed, or they don't want the OPTIONS */ if( (!http_init_checks) || (ret != PROTO_OK) ) return ret; /* Capability discovery... we don't care whether this * actually works or not, we just want http_webdav_server * set appropriately. */ (void) http_options( remote_root ); return PROTO_OK;}int http_finish( void ) { DEBUG( DEBUG_HTTP, "http_finish called.\n" ); http_auth_finish( &http_server_auth ); if( http_connected ) http_close( ); return PROTO_OK;}/* Parse the HTTP-Version and Status-Code segments of the * given status_line. Sets http_version_* and req->class,status. * Returns: PROTO_OK on success, PROTO_ERROR otherwise */int http_parse_status( http_req_t *req, char *status_line ) { char *part; DEBUG( DEBUG_HTTP, "HTTP response line: %s", status_line ); /* Save the line for error codes later */ memset( http_error, 0, BUFSIZ ); strncpy( http_error, status_line, BUFSIZ ); /* Strip off the CRLF for the status line */ if( (part = strchr( http_error, '\r' )) != NULL ) *part = '\0'; /* Check they're speaking the right language */ if( strncmp( status_line, "HTTP/", 5 ) != 0 ) return PROTO_ERROR; /* And find out which dialect of this peculiar language * they can talk... */ http_version_major = 0; http_version_minor = 0; /* Note, we're good children, and accept leading zero's on the * version numbers */ for( part = status_line + 5; *part != '\0' && isdigit(*part); part++ ) { http_version_major += http_version_major*10 + (*part-'0'); } if( *part != '.' ) return PROTO_ERROR; for( part++ ; *part != '\0' && isdigit(*part); part++ ) { http_version_minor += http_version_minor*10 + (*part-'0'); } DEBUG( DEBUG_HTTP, "HTTP Version Major: %d, Minor: %d\n", http_version_major, http_version_minor ); if( *part != ' ' ) return PROTO_ERROR; /* Now for the Status-Code. part now points at the space * between "HTTP/x.x YYY". We want YYY... could use atoi, but * probably quicker this way. */ req->status = 100*(part[1]-'0') + 10*(part[2]-'0') + (part[3]-'0'); req->class = part[1]-'0'; /* And we can ignore the Reason-Phrase */ DEBUG( DEBUG_HTTP, "HTTP status code: %d\n", req->status ); return PROTO_OK;}/* Sends the body down the socket. * Returns PROTO_OK on success, PROTO_ERROR otherwise */int http_req_sendbody( http_req_t *req ) { int ret; switch( req->body ) { case http_body_file: ret = transfer( fileno(req->body_file), http_sock, req->body_size ); DEBUG( DEBUG_HTTP, "Sent %d bytes.\n", ret ); rewind( req->body_file ); /* since we may have to send it again */ break; case http_body_buffer: DEBUG( DEBUG_HTTP, "Sending body:\n%s\n", req->body_buffer ); ret = send_string( http_sock, req->body_buffer ); break; default: DEBUG( DEBUG_HTTP, "Argh in http_req_sendbody!" ); ret = -1; } if( ret == -1 ) { /* transfer failed */ return PROTO_ERROR; } else { return PROTO_OK;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -