base
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/* -*- c -*-
|
||||
Win32 config.h
|
||||
Copyright (C) 1999-2000, Peter Boos <pedib@colorfullife.com>
|
||||
Copyright (C) 2002-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
#if defined(_WIN32) && !defined(WIN32)
|
||||
#define WIN32
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#define NEON_VERSION "0.25.4"
|
||||
#define NE_VERSION_MAJOR (0)
|
||||
#define NE_VERSION_MINOR (25)
|
||||
|
||||
#define HAVE_ERRNO_H
|
||||
#define HAVE_LIMITS_H
|
||||
#define HAVE_STDLIB_H
|
||||
#define HAVE_STRING_H
|
||||
#define HAVE_EXPAT
|
||||
|
||||
#define HAVE_MEMCPY
|
||||
#define HAVE_SETSOCKOPT
|
||||
#define NE_HAVE_DAV
|
||||
#define HAVE_SSPI
|
||||
|
||||
#define NE_FMT_SIZE_T "u"
|
||||
#define NE_FMT_SSIZE_T "d"
|
||||
#define NE_FMT_OFF_T "ld"
|
||||
#define NE_FMT_OFF64_T "I64d"
|
||||
|
||||
//typedef __int64 _off64_t
|
||||
|
||||
/* Win32 uses a underscore, so we use a macro to eliminate that. */
|
||||
#if _MSC_VER == 1500
|
||||
#include <stdio.h>
|
||||
#endif // Visual Studio 2008
|
||||
#define snprintf _snprintf
|
||||
#define vsnprintf _vsnprintf
|
||||
#define strcasecmp strcmpi
|
||||
#define strncasecmp strnicmp
|
||||
#define ssize_t int
|
||||
#define inline __inline
|
||||
#define off_t _off_t
|
||||
#define off64_t __int64
|
||||
#define strtoq strtol
|
||||
|
||||
#ifndef USE_GETADDRINFO
|
||||
#define in_addr_t unsigned int
|
||||
#endif
|
||||
|
||||
#include <io.h>
|
||||
#define read _read
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Memory leak wrappers
|
||||
Copyright (C) 2003, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
/* WARNING: THIS IS AN INTERNAL NEON INTERFACE AND MUST NOT BE USED
|
||||
* from NEON APPLICATIONS. */
|
||||
|
||||
/* This file contains an alternate interface to the memory allocation
|
||||
* wrappers in ne_alloc.c, which perform simple leak detection. It
|
||||
* MUST NOT BE INSTALLED, or used from neon applications. */
|
||||
|
||||
#ifndef MEMLEAK_H
|
||||
#define MEMLEAK_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define ne_malloc(s) ne_malloc_ml(s, __FILE__, __LINE__)
|
||||
#define ne_calloc(s) ne_calloc_ml(s, __FILE__, __LINE__)
|
||||
#define ne_realloc(p, s) ne_realloc_ml(p, s, __FILE__, __LINE__)
|
||||
#define ne_strdup(s) ne_strdup_ml(s, __FILE__, __LINE__)
|
||||
#define ne_strndup(s, n) ne_strndup_ml(s, n, __FILE__, __LINE__)
|
||||
#define ne_free ne_free_ml
|
||||
|
||||
/* Prototypes of allocation functions: */
|
||||
void *ne_malloc_ml(size_t size, const char *file, int line);
|
||||
void *ne_calloc_ml(size_t size, const char *file, int line);
|
||||
void *ne_realloc_ml(void *ptr, size_t s, const char *file, int line);
|
||||
char *ne_strdup_ml(const char *s, const char *file, int line);
|
||||
char *ne_strndup_ml(const char *s, size_t n, const char *file, int line);
|
||||
void ne_free_ml(void *ptr);
|
||||
|
||||
/* Dump the list of currently allocated blocks to 'f'. */
|
||||
void ne_alloc_dump(FILE *f);
|
||||
|
||||
/* Current number of bytes in allocated but not free'd. */
|
||||
extern size_t ne_alloc_used;
|
||||
|
||||
#endif /* MEMLEAK_H */
|
||||
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
WebDAV 207 multi-status response handling
|
||||
Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
/* Generic handling for WebDAV 207 Multi-Status responses. */
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_utils.h"
|
||||
#include "ne_xml.h"
|
||||
#include "ne_207.h"
|
||||
#include "ne_uri.h"
|
||||
#include "ne_basic.h"
|
||||
|
||||
#include "ne_i18n.h"
|
||||
|
||||
struct ne_207_parser_s {
|
||||
ne_207_start_response *start_response;
|
||||
ne_207_end_response *end_response;
|
||||
ne_207_start_propstat *start_propstat;
|
||||
ne_207_end_propstat *end_propstat;
|
||||
ne_xml_parser *parser;
|
||||
void *userdata;
|
||||
|
||||
ne_buffer *cdata;
|
||||
|
||||
/* remember whether we are in a response: the validation
|
||||
* doesn't encapsulate this since we only count as being
|
||||
* "in a response" when we've seen the href element. */
|
||||
int in_response;
|
||||
|
||||
/* current position */
|
||||
void *response, *propstat;
|
||||
/* caching */
|
||||
ne_status status;
|
||||
char *description, *href;
|
||||
};
|
||||
|
||||
#define ELM_multistatus 1
|
||||
#define ELM_response 2
|
||||
#define ELM_responsedescription 3
|
||||
#define ELM_href 4
|
||||
#define ELM_prop (NE_207_STATE_PROP)
|
||||
#define ELM_status 6
|
||||
#define ELM_propstat 7
|
||||
|
||||
static const struct ne_xml_idmap map207[] = {
|
||||
{ "DAV:", "multistatus", ELM_multistatus },
|
||||
{ "DAV:", "response", ELM_response },
|
||||
{ "DAV:", "responsedescription", ELM_responsedescription },
|
||||
{ "DAV:", "href", ELM_href },
|
||||
{ "DAV:", "propstat", ELM_propstat },
|
||||
{ "DAV:", "prop", ELM_prop },
|
||||
{ "DAV:", "status", ELM_status }
|
||||
};
|
||||
|
||||
/* Set the callbacks for the parser */
|
||||
void ne_207_set_response_handlers(ne_207_parser *p,
|
||||
ne_207_start_response *start,
|
||||
ne_207_end_response *end)
|
||||
{
|
||||
p->start_response = start;
|
||||
p->end_response = end;
|
||||
}
|
||||
|
||||
void ne_207_set_propstat_handlers(ne_207_parser *p,
|
||||
ne_207_start_propstat *start,
|
||||
ne_207_end_propstat *end)
|
||||
{
|
||||
p->start_propstat = start;
|
||||
p->end_propstat = end;
|
||||
}
|
||||
|
||||
void *ne_207_get_current_response(ne_207_parser *p)
|
||||
{
|
||||
return p->response;
|
||||
}
|
||||
|
||||
void *ne_207_get_current_propstat(ne_207_parser *p)
|
||||
{
|
||||
return p->propstat;
|
||||
}
|
||||
|
||||
/* return non-zero if (child, parent) is an interesting element */
|
||||
static int can_handle(int parent, int child)
|
||||
{
|
||||
return (parent == 0 && child == ELM_multistatus) ||
|
||||
(parent == ELM_multistatus && child == ELM_response) ||
|
||||
(parent == ELM_response &&
|
||||
(child == ELM_href || child == ELM_status ||
|
||||
child == ELM_propstat || child == ELM_responsedescription)) ||
|
||||
(parent == ELM_propstat &&
|
||||
(child == ELM_prop || child == ELM_status ||
|
||||
child == ELM_responsedescription));
|
||||
}
|
||||
|
||||
static int cdata_207(void *userdata, int state, const char *buf, size_t len)
|
||||
{
|
||||
ne_207_parser *p = userdata;
|
||||
|
||||
if ((state == ELM_href || state == ELM_responsedescription ||
|
||||
state == ELM_status) && p->cdata->used + len < 2048)
|
||||
ne_buffer_append(p->cdata, buf, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_element(void *userdata, int parent,
|
||||
const char *nspace, const char *name,
|
||||
const char **atts)
|
||||
{
|
||||
ne_207_parser *p = userdata;
|
||||
int state = ne_xml_mapid(map207, NE_XML_MAPLEN(map207), nspace, name);
|
||||
|
||||
if (!can_handle(parent, state))
|
||||
return NE_XML_DECLINE;
|
||||
|
||||
/* if not in a response, ignore everything. */
|
||||
if (!p->in_response && state != ELM_response && state != ELM_multistatus &&
|
||||
state != ELM_href)
|
||||
return NE_XML_DECLINE;
|
||||
|
||||
if (state == ELM_propstat && p->start_propstat) {
|
||||
p->propstat = p->start_propstat(p->userdata, p->response);
|
||||
if (p->propstat == NULL) {
|
||||
return NE_XML_ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
ne_buffer_clear(p->cdata);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
#define GIVE_STATUS(p) ((p)->status.reason_phrase?&(p)->status:NULL)
|
||||
|
||||
#define HAVE_CDATA(p) ((p)->cdata->used > 1)
|
||||
|
||||
static int
|
||||
end_element(void *userdata, int state, const char *nspace, const char *name)
|
||||
{
|
||||
ne_207_parser *p = userdata;
|
||||
const char *cdata = ne_shave(p->cdata->data, "\r\n\t ");
|
||||
|
||||
switch (state) {
|
||||
case ELM_responsedescription:
|
||||
if (HAVE_CDATA(p)) {
|
||||
NE_FREE(p->description);
|
||||
p->description = ne_strdup(cdata);
|
||||
}
|
||||
break;
|
||||
case ELM_href:
|
||||
/* Now we have the href, begin the response */
|
||||
if (p->start_response && HAVE_CDATA(p)) {
|
||||
p->response = p->start_response(p->userdata, cdata);
|
||||
p->in_response = 1;
|
||||
}
|
||||
break;
|
||||
case ELM_status:
|
||||
if (HAVE_CDATA(p)) {
|
||||
NE_FREE(p->status.reason_phrase);
|
||||
if (ne_parse_statusline(cdata, &p->status)) {
|
||||
char buf[500];
|
||||
NE_DEBUG(NE_DBG_HTTP, "Status line: %s\n", cdata);
|
||||
ne_snprintf(buf, 500,
|
||||
_("Invalid HTTP status line in status element "
|
||||
"at line %d of response:\nStatus line was: %s"),
|
||||
ne_xml_currentline(p->parser), cdata);
|
||||
ne_xml_set_error(p->parser, buf);
|
||||
return -1;
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_XML, "Decoded status line: %s\n", cdata);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ELM_propstat:
|
||||
if (p->end_propstat)
|
||||
p->end_propstat(p->userdata, p->propstat, GIVE_STATUS(p),
|
||||
p->description);
|
||||
p->propstat = NULL;
|
||||
NE_FREE(p->description);
|
||||
NE_FREE(p->status.reason_phrase);
|
||||
break;
|
||||
case ELM_response:
|
||||
if (!p->in_response) break;
|
||||
if (p->end_response)
|
||||
p->end_response(p->userdata, p->response, GIVE_STATUS(p),
|
||||
p->description);
|
||||
p->response = NULL;
|
||||
p->in_response = 0;
|
||||
NE_FREE(p->status.reason_phrase);
|
||||
NE_FREE(p->description);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ne_207_parser *ne_207_create(ne_xml_parser *parser, void *userdata)
|
||||
{
|
||||
ne_207_parser *p = ne_calloc(sizeof *p);
|
||||
|
||||
p->parser = parser;
|
||||
p->userdata = userdata;
|
||||
p->cdata = ne_buffer_create();
|
||||
|
||||
/* Add handler for the standard 207 elements */
|
||||
ne_xml_push_handler(parser, start_element, cdata_207, end_element, p);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void ne_207_destroy(ne_207_parser *p)
|
||||
{
|
||||
if (p->status.reason_phrase) ne_free(p->status.reason_phrase);
|
||||
ne_buffer_destroy(p->cdata);
|
||||
ne_free(p);
|
||||
}
|
||||
|
||||
int ne_accept_207(void *userdata, ne_request *req, const ne_status *status)
|
||||
{
|
||||
return (status->code == 207);
|
||||
}
|
||||
|
||||
/* Handling of 207 errors: we keep a string buffer, and append
|
||||
* messages to it as they come down.
|
||||
*
|
||||
* Note, 424 means it would have worked but something else went wrong.
|
||||
* We will have had the error for "something else", so we display
|
||||
* that, and skip 424 errors. */
|
||||
|
||||
/* This is passed as userdata to the 207 code. */
|
||||
struct context {
|
||||
char *href;
|
||||
ne_buffer *buf;
|
||||
unsigned int is_error;
|
||||
};
|
||||
|
||||
static void *start_response(void *userdata, const char *href)
|
||||
{
|
||||
struct context *ctx = userdata;
|
||||
NE_FREE(ctx->href);
|
||||
ctx->href = ne_strdup(href);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void handle_error(struct context *ctx, const ne_status *status,
|
||||
const char *description)
|
||||
{
|
||||
if (status && status->klass != 2 && status->code != 424) {
|
||||
char buf[50];
|
||||
ctx->is_error = 1;
|
||||
sprintf(buf, "%d", status->code);
|
||||
ne_buffer_concat(ctx->buf, ctx->href, ": ",
|
||||
buf, " ", status->reason_phrase, "\n", NULL);
|
||||
if (description != NULL) {
|
||||
/* TODO: these can be multi-line. Would be good to
|
||||
* word-wrap this at col 80. */
|
||||
ne_buffer_concat(ctx->buf, " -> ", description, "\n", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void end_response(void *userdata, void *response,
|
||||
const ne_status *status, const char *description)
|
||||
{
|
||||
struct context *ctx = userdata;
|
||||
handle_error(ctx, status, description);
|
||||
}
|
||||
|
||||
static void
|
||||
end_propstat(void *userdata, void *propstat,
|
||||
const ne_status *status, const char *description)
|
||||
{
|
||||
struct context *ctx = userdata;
|
||||
handle_error(ctx, status, description);
|
||||
}
|
||||
|
||||
/* Dispatch a DAV request and handle a 207 error response appropriately */
|
||||
/* TODO: hook up Content-Type parsing; passing charset to XML parser */
|
||||
int ne_simple_request(ne_session *sess, ne_request *req)
|
||||
{
|
||||
int ret;
|
||||
struct context ctx = {0};
|
||||
ne_207_parser *p207;
|
||||
ne_xml_parser *p;
|
||||
|
||||
p = ne_xml_create();
|
||||
p207 = ne_207_create(p, &ctx);
|
||||
/* The error string is progressively written into the
|
||||
* ne_buffer by the element callbacks */
|
||||
ctx.buf = ne_buffer_create();
|
||||
|
||||
ne_207_set_response_handlers(p207, start_response, end_response);
|
||||
ne_207_set_propstat_handlers(p207, NULL, end_propstat);
|
||||
|
||||
ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, p);
|
||||
|
||||
ret = ne_request_dispatch(req);
|
||||
|
||||
if (ret == NE_OK) {
|
||||
if (ne_get_status(req)->code == 207) {
|
||||
if (ne_xml_failed(p)) {
|
||||
/* The parse was invalid */
|
||||
ne_set_error(sess, "%s", ne_xml_get_error(p));
|
||||
ret = NE_ERROR;
|
||||
} else if (ctx.is_error) {
|
||||
/* If we've actually got any error information
|
||||
* from the 207, then set that as the error */
|
||||
ne_set_error(sess, "%s", ctx.buf->data);
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
} else if (ne_get_status(req)->klass != 2) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ne_207_destroy(p207);
|
||||
ne_xml_destroy(p);
|
||||
ne_buffer_destroy(ctx.buf);
|
||||
NE_FREE(ctx.href);
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
WebDAV 207 multi-status response handling
|
||||
Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef DAV207_H
|
||||
#define DAV207_H
|
||||
|
||||
#include "ne_xml.h"
|
||||
#include "ne_request.h" /* for ne_request */
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* The defined state integer for the '{DAV:}prop' element. */
|
||||
#define NE_207_STATE_PROP (50)
|
||||
/* This interface reserves the state integers 'x' where 0 < x < 100 */
|
||||
#define NE_207_STATE_TOP (100)
|
||||
|
||||
/* Handling of 207 multistatus XML documents. A "multistatus"
|
||||
* document is made up of a set of responses, each concerned with a
|
||||
* particular resource. Each response may have an associated result
|
||||
* status and failure description. A response is made up of a set of
|
||||
* propstats, each of which again may have an associated result status
|
||||
* and failure description. */
|
||||
|
||||
/* Start and end response callbacks trigger at the start and end of
|
||||
* each "response" within the multistatus body. 'href' gives the URI
|
||||
* of the resource which is subject of this response. The return
|
||||
* value of a 'start_response' callback is passed as the 'response'
|
||||
* parameter to the corresponding 'end_response' parameter. */
|
||||
typedef void *ne_207_start_response(void *userdata, const char *href);
|
||||
typedef void ne_207_end_response(void *userdata, void *response,
|
||||
const ne_status *status,
|
||||
const char *description);
|
||||
|
||||
/* Similarly, start and end callbacks for each propstat within the
|
||||
* response. The return value of the 'start_response' callback for
|
||||
* the response in which this propstat is contains is passed as the
|
||||
* 'response' parameter. The return value of each 'start_propstat' is
|
||||
* passed as the 'propstat' parameter' to the corresponding
|
||||
* 'end_propstat' callback. If the start_propstat callback returns
|
||||
* NULL, parsing is aborted (the XML parser error must be set by the
|
||||
* callback). */
|
||||
typedef void *ne_207_start_propstat(void *userdata, void *response);
|
||||
typedef void ne_207_end_propstat(void *userdata, void *propstat,
|
||||
const ne_status *status,
|
||||
const char *description);
|
||||
|
||||
typedef struct ne_207_parser_s ne_207_parser;
|
||||
|
||||
/* Create 207 parser an add the handlers the the given parser's
|
||||
* handler stack. */
|
||||
ne_207_parser *ne_207_create(ne_xml_parser *parser, void *userdata);
|
||||
|
||||
/* Register response handling callbacks. */
|
||||
void ne_207_set_response_handlers(ne_207_parser *p,
|
||||
ne_207_start_response *start,
|
||||
ne_207_end_response *end);
|
||||
|
||||
/* Register propstat handling callbacks. */
|
||||
void ne_207_set_propstat_handlers(ne_207_parser *p,
|
||||
ne_207_start_propstat *start,
|
||||
ne_207_end_propstat *end);
|
||||
|
||||
/* Destroy the parser */
|
||||
void ne_207_destroy(ne_207_parser *p);
|
||||
|
||||
/* An acceptance function which only accepts 207 responses */
|
||||
int ne_accept_207(void *userdata, ne_request *req, const ne_status *status);
|
||||
|
||||
void *ne_207_get_current_propstat(ne_207_parser *p);
|
||||
void *ne_207_get_current_response(ne_207_parser *p);
|
||||
|
||||
/* Dispatch request 'req', returning:
|
||||
* NE_ERROR: for a dispatch error, or a non-2xx response, or a
|
||||
* 207 response which contained a non-2xx propstat
|
||||
* NE_OK: for a 2xx response or a 207 response which contained
|
||||
* only 2xx-class propstats.
|
||||
* The request object is destroyed in both cases. */
|
||||
int ne_simple_request(ne_session *sess, ne_request *req);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* DAV207_H */
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Access control
|
||||
Copyright (C) 2001, 2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
/* Contributed by Arun Garg <arung@pspl.co.in> */
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "ne_request.h"
|
||||
#include "ne_locks.h"
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_string.h"
|
||||
#include "ne_acl.h"
|
||||
#include "ne_uri.h"
|
||||
#include "ne_xml.h" /* for NE_XML_MEDIA_TYPE */
|
||||
|
||||
#define EOL "\r\n"
|
||||
|
||||
static ne_buffer *acl_body(ne_acl_entry *right, int count)
|
||||
{
|
||||
ne_buffer *body = ne_buffer_create();
|
||||
int m;
|
||||
|
||||
ne_buffer_zappend(body,
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL
|
||||
"<acl xmlns='DAV:'>" EOL);
|
||||
|
||||
for (m = 0; m < count; m++) {
|
||||
const char *type;
|
||||
|
||||
type = (right[m].type == ne_acl_grant ? "grant" : "deny");
|
||||
|
||||
ne_buffer_concat(body, "<ace>" EOL "<principal>", NULL);
|
||||
|
||||
switch (right[m].apply) {
|
||||
case ne_acl_all:
|
||||
ne_buffer_zappend(body, "<all/>" EOL);
|
||||
break;
|
||||
case ne_acl_property:
|
||||
ne_buffer_concat(body, "<property><", right[m].principal,
|
||||
"/></property>" EOL, NULL);
|
||||
break;
|
||||
case ne_acl_href:
|
||||
ne_buffer_concat(body, "<href>", right[m].principal,
|
||||
"</href>" EOL, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
ne_buffer_concat(body, "</principal>" EOL "<", type, ">" EOL, NULL);
|
||||
|
||||
if (right[m].read == 0)
|
||||
ne_buffer_concat(body,
|
||||
"<privilege>" "<read/>" "</privilege>" EOL,
|
||||
NULL);
|
||||
if (right[m].read_acl == 0)
|
||||
ne_buffer_concat(body,
|
||||
"<privilege>" "<read-acl/>" "</privilege>" EOL,
|
||||
NULL);
|
||||
if (right[m].write == 0)
|
||||
ne_buffer_concat(body,
|
||||
"<privilege>" "<write/>" "</privilege>" EOL,
|
||||
NULL);
|
||||
if (right[m].write_acl == 0)
|
||||
ne_buffer_concat(body,
|
||||
"<privilege>" "<write-acl/>" "</privilege>" EOL,
|
||||
NULL);
|
||||
if (right[m].read_cuprivset == 0)
|
||||
ne_buffer_concat(body,
|
||||
"<privilege>"
|
||||
"<read-current-user-privilege-set/>"
|
||||
"</privilege>" EOL, NULL);
|
||||
ne_buffer_concat(body, "</", type, ">" EOL, NULL);
|
||||
ne_buffer_zappend(body, "</ace>" EOL);
|
||||
}
|
||||
ne_buffer_zappend(body, "</acl>" EOL);
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
int ne_acl_set(ne_session *sess, const char *uri,
|
||||
ne_acl_entry *entries, int numentries)
|
||||
{
|
||||
int ret;
|
||||
ne_request *req = ne_request_create(sess, "ACL", uri);
|
||||
ne_buffer *body = acl_body(entries, numentries);
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
ne_lock_using_resource(req, uri, 0);
|
||||
#endif
|
||||
|
||||
ne_set_request_body_buffer(req, body->data, ne_buffer_size(body));
|
||||
ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
|
||||
ret = ne_request_dispatch(req);
|
||||
|
||||
ne_buffer_destroy(body);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->code == 207) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
|
||||
ne_request_destroy(req);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Access control
|
||||
Copyright (C) 2001, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_ACL_H
|
||||
#define NE_ACL_H
|
||||
|
||||
#include "ne_session.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
typedef struct
|
||||
{
|
||||
enum {
|
||||
ne_acl_href,
|
||||
ne_acl_property,
|
||||
ne_acl_all
|
||||
} apply;
|
||||
|
||||
enum {
|
||||
ne_acl_grant,
|
||||
ne_acl_deny
|
||||
} type;
|
||||
|
||||
char *principal;
|
||||
int read;
|
||||
int read_acl;
|
||||
int write;
|
||||
int write_acl;
|
||||
int read_cuprivset;
|
||||
} ne_acl_entry;
|
||||
|
||||
/* Set the ACL for the given resource to the list of ACL entries. */
|
||||
int ne_acl_set(ne_session *sess, const char *uri,
|
||||
ne_acl_entry entries[], int numentries);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_ACL_H */
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
Replacement memory allocation handling etc.
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ne_alloc.h"
|
||||
|
||||
static ne_oom_callback_fn oom;
|
||||
|
||||
void ne_oom_callback(ne_oom_callback_fn callback)
|
||||
{
|
||||
oom = callback;
|
||||
}
|
||||
|
||||
#ifndef NEON_MEMLEAK
|
||||
|
||||
#define DO_MALLOC(ptr, len) do { \
|
||||
ptr = malloc((len)); \
|
||||
if (!ptr) { \
|
||||
if (oom != NULL) \
|
||||
oom(); \
|
||||
abort(); \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
void *ne_malloc(size_t len)
|
||||
{
|
||||
void *ptr;
|
||||
DO_MALLOC(ptr, len);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *ne_calloc(size_t len)
|
||||
{
|
||||
void *ptr;
|
||||
DO_MALLOC(ptr, len);
|
||||
return memset(ptr, 0, len);
|
||||
}
|
||||
|
||||
void *ne_realloc(void *ptr, size_t len)
|
||||
{
|
||||
void *ret = realloc(ptr, len);
|
||||
if (!ret) {
|
||||
if (oom)
|
||||
oom();
|
||||
abort();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *ne_strdup(const char *s)
|
||||
{
|
||||
char *ret;
|
||||
DO_MALLOC(ret, strlen(s) + 1);
|
||||
return strcpy(ret, s);
|
||||
}
|
||||
|
||||
char *ne_strndup(const char *s, size_t n)
|
||||
{
|
||||
char *new;
|
||||
DO_MALLOC(new, n+1);
|
||||
new[n] = '\0';
|
||||
memcpy(new, s, n);
|
||||
return new;
|
||||
}
|
||||
|
||||
#else /* NEON_MEMLEAK */
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/* Memory-leak detection implementation: ne_malloc and friends are
|
||||
* #defined to ne_malloc_ml etc by memleak.h, which is conditionally
|
||||
* included by config.h. */
|
||||
|
||||
/* memory allocated be ne_*alloc, but not freed. */
|
||||
size_t ne_alloc_used = 0;
|
||||
|
||||
static struct block {
|
||||
void *ptr;
|
||||
size_t len;
|
||||
const char *file;
|
||||
int line;
|
||||
struct block *next;
|
||||
} *blocks = NULL;
|
||||
|
||||
void ne_alloc_dump(FILE *f)
|
||||
{
|
||||
struct block *b;
|
||||
|
||||
for (b = blocks; b != NULL; b = b->next)
|
||||
fprintf(f, "%" NE_FMT_SIZE_T "b@%s:%d%s", b->len, b->file, b->line,
|
||||
b->next?", ":"");
|
||||
}
|
||||
|
||||
static void *tracking_malloc(size_t len, const char *file, int line)
|
||||
{
|
||||
void *ptr = malloc((len));
|
||||
struct block *block;
|
||||
|
||||
if (!ptr) {
|
||||
if (oom) oom();
|
||||
abort();
|
||||
}
|
||||
|
||||
block = malloc(sizeof *block);
|
||||
if (block != NULL) {
|
||||
block->ptr = ptr;
|
||||
block->len = len;
|
||||
block->file = file;
|
||||
block->line = line;
|
||||
block->next = blocks;
|
||||
blocks = block;
|
||||
ne_alloc_used += len;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *ne_malloc_ml(size_t size, const char *file, int line)
|
||||
{
|
||||
return tracking_malloc(size, file, line);
|
||||
}
|
||||
|
||||
void *ne_calloc_ml(size_t size, const char *file, int line)
|
||||
{
|
||||
return memset(tracking_malloc(size, file, line), 0, size);
|
||||
}
|
||||
|
||||
void *ne_realloc_ml(void *ptr, size_t s, const char *file, int line)
|
||||
{
|
||||
void *ret;
|
||||
struct block *b;
|
||||
|
||||
if (ptr == NULL)
|
||||
return tracking_malloc(s, file, line);
|
||||
|
||||
ret = realloc(ptr, s);
|
||||
if (!ret) {
|
||||
if (oom) oom();
|
||||
abort();
|
||||
}
|
||||
|
||||
for (b = blocks; b != NULL; b = b->next) {
|
||||
if (b->ptr == ptr) {
|
||||
ne_alloc_used += s - b->len;
|
||||
b->ptr = ret;
|
||||
b->len = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(b != NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *ne_strdup_ml(const char *s, const char *file, int line)
|
||||
{
|
||||
return strcpy(tracking_malloc(strlen(s) + 1, file, line), s);
|
||||
}
|
||||
|
||||
char *ne_strndup_ml(const char *s, size_t n, const char *file, int line)
|
||||
{
|
||||
char *ret = tracking_malloc(n + 1, file, line);
|
||||
ret[n] = '\0';
|
||||
return memcpy(ret, s, n);
|
||||
}
|
||||
|
||||
void ne_free_ml(void *ptr)
|
||||
{
|
||||
struct block *b, *last = NULL;
|
||||
|
||||
for (b = blocks; b != NULL; last = b, b = b->next) {
|
||||
if (b->ptr == ptr) {
|
||||
ne_alloc_used -= b->len;
|
||||
if (last)
|
||||
last->next = b->next;
|
||||
else
|
||||
blocks = b->next;
|
||||
free(b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
#endif /* NEON_MEMLEAK */
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Replacement memory allocation handling etc.
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_ALLOC_H
|
||||
#define NE_ALLOC_H
|
||||
|
||||
#ifdef WIN32
|
||||
#include <stdlib.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include "ne_defs.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
typedef void (*ne_oom_callback_fn)(void);
|
||||
|
||||
/* Set callback which is called if malloc() returns NULL. */
|
||||
void ne_oom_callback(ne_oom_callback_fn callback);
|
||||
|
||||
#ifndef NEON_MEMLEAK
|
||||
/* Replacements for standard C library memory allocation functions,
|
||||
* which never return NULL. If the C library malloc() returns NULL,
|
||||
* neon will abort(); calling an OOM callback beforehand if one is
|
||||
* registered. The C library will only ever return NULL if the
|
||||
* operating system does not use optimistic memory allocation. */
|
||||
void *ne_malloc(size_t size) ne_attribute_malloc;
|
||||
void *ne_calloc(size_t size) ne_attribute_malloc;
|
||||
void *ne_realloc(void *ptr, size_t s) ne_attribute_malloc;
|
||||
char *ne_strdup(const char *s) ne_attribute_malloc;
|
||||
char *ne_strndup(const char *s, size_t n) ne_attribute_malloc;
|
||||
#define ne_free free
|
||||
#endif
|
||||
|
||||
/* Handy macro to free things: takes an lvalue, and sets to NULL
|
||||
* afterwards. */
|
||||
#define NE_FREE(x) do { if ((x) != NULL) ne_free((x)); (x) = NULL; } while (0)
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_ALLOC_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
HTTP authentication routines
|
||||
Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_AUTH_H
|
||||
#define NE_AUTH_H
|
||||
|
||||
#include "ne_session.h" /* for ne_session */
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* Size of username/password buffers passed to ne_auth_creds
|
||||
* callback. */
|
||||
#define NE_ABUFSIZ (256)
|
||||
|
||||
/* The callback used to request the username and password in the given
|
||||
* realm. The username and password must be copied into the buffers
|
||||
* which are both of size NE_ABUFSIZ. The 'attempt' parameter is zero
|
||||
* on the first call to the callback, and increases by one each time
|
||||
* an attempt to authenticate fails.
|
||||
*
|
||||
* The callback must return zero to indicate that authentication
|
||||
* should be attempted with the username/password, or non-zero to
|
||||
* cancel the request. (if non-zero, username and password are
|
||||
* ignored.) */
|
||||
typedef int (*ne_auth_creds)(void *userdata, const char *realm, int attempt,
|
||||
char *username, char *password);
|
||||
|
||||
/* TOP TIP: if you just wish to try authenticating once (even if the
|
||||
* user gets the username/password wrong), have your implementation of
|
||||
* the callback return the 'attempt' value. */
|
||||
|
||||
/* Set callbacks to provide credentials for server and proxy
|
||||
* authentication. userdata is passed as the first argument to the
|
||||
* callback. The callback is called *indefinitely* until either it
|
||||
* returns non-zero, or authentication is successful. */
|
||||
void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata);
|
||||
void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata);
|
||||
|
||||
/* Clear any stored authentication details for the given session. */
|
||||
void ne_forget_auth(ne_session *sess);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_AUTH_H */
|
||||
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
Basic HTTP and WebDAV methods
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h> /* for struct stat */
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "ne_request.h"
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_utils.h"
|
||||
#include "ne_basic.h"
|
||||
#include "ne_207.h"
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
#include "ne_uri.h"
|
||||
#include "ne_locks.h"
|
||||
#endif
|
||||
|
||||
#include "ne_dates.h"
|
||||
#include "ne_i18n.h"
|
||||
|
||||
int ne_getmodtime(ne_session *sess, const char *uri, time_t *modtime)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "HEAD", uri);
|
||||
const char *value;
|
||||
int ret;
|
||||
|
||||
ret = ne_request_dispatch(req);
|
||||
|
||||
value = ne_get_response_header(req, "Last-Modified");
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass != 2) {
|
||||
*modtime = -1;
|
||||
ret = NE_ERROR;
|
||||
} else if (value) {
|
||||
*modtime = ne_httpdate_parse(value);
|
||||
}
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* PUT's from fd to URI */
|
||||
int ne_put(ne_session *sess, const char *uri, int fd)
|
||||
{
|
||||
ne_request *req;
|
||||
struct stat st;
|
||||
int ret;
|
||||
|
||||
if (fstat(fd, &st)) {
|
||||
int errnum = errno;
|
||||
char buf[200];
|
||||
|
||||
ne_set_error(sess, _("Could not determine file size: %s"),
|
||||
ne_strerror(errnum, buf, sizeof buf));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
req = ne_request_create(sess, "PUT", uri);
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
ne_lock_using_resource(req, uri, 0);
|
||||
ne_lock_using_parent(req, uri);
|
||||
#endif
|
||||
|
||||
ne_set_request_body_fd(req, fd, 0, st.st_size);
|
||||
|
||||
ret = ne_request_dispatch(req);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass != 2)
|
||||
ret = NE_ERROR;
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Dispatch a GET request REQ, writing the response body to FD fd. If
|
||||
* RANGE is non-NULL, then it is the value of the Range request
|
||||
* header, e.g. "bytes=1-5". Returns an NE_* error code. */
|
||||
static int dispatch_to_fd(ne_request *req, int fd, const char *range)
|
||||
{
|
||||
ne_session *const sess = ne_get_session(req);
|
||||
const ne_status *const st = ne_get_status(req);
|
||||
int ret;
|
||||
|
||||
do {
|
||||
const char *value;
|
||||
|
||||
ret = ne_begin_request(req);
|
||||
if (ret != NE_OK) break;
|
||||
|
||||
value = ne_get_response_header(req, "Content-Range");
|
||||
|
||||
/* For a 206 response, check that a Content-Range header is
|
||||
* given which matches the Range request header. */
|
||||
if (range && st->code == 206
|
||||
&& (value == NULL || strncmp(value, "bytes ", 6) != 0
|
||||
|| strcmp(range + 6, value + 6))) {
|
||||
ne_set_error(sess, _("Response did not include requested range"));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
if ((range && st->code == 206) || (!range && st->klass == 2)) {
|
||||
ret = ne_read_response_to_fd(req, fd);
|
||||
} else {
|
||||
ret = ne_discard_response(req);
|
||||
}
|
||||
|
||||
if (ret == NE_OK) ret = ne_end_request(req);
|
||||
} while (ret == NE_RETRY);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ne_get_range(ne_session *sess, const char *uri,
|
||||
ne_content_range *range, int fd)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "GET", uri);
|
||||
const ne_status *status;
|
||||
int ret;
|
||||
char brange[64];
|
||||
|
||||
if (range->end == -1) {
|
||||
ne_snprintf(brange, sizeof brange, "bytes=%" NE_FMT_OFF_T "-",
|
||||
range->start);
|
||||
}
|
||||
else {
|
||||
ne_snprintf(brange, sizeof brange,
|
||||
"bytes=%" NE_FMT_OFF_T "-%" NE_FMT_OFF_T,
|
||||
range->start, range->end);
|
||||
}
|
||||
|
||||
ne_add_request_header(req, "Range", brange);
|
||||
ne_add_request_header(req, "Accept-Ranges", "bytes");
|
||||
|
||||
ret = dispatch_to_fd(req, fd, brange);
|
||||
|
||||
status = ne_get_status(req);
|
||||
|
||||
if (ret == NE_OK && status->code == 416) {
|
||||
/* connection is terminated too early with Apache/1.3, so we check
|
||||
* this even if ret == NE_ERROR... */
|
||||
ne_set_error(sess, _("Range is not satisfiable"));
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
else if (ret == NE_OK) {
|
||||
if (status->klass == 2 && status->code != 206) {
|
||||
ne_set_error(sess, _("Resource does not support ranged GETs."));
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
else if (status->klass != 2) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Get to given fd */
|
||||
int ne_get(ne_session *sess, const char *uri, int fd)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "GET", uri);
|
||||
int ret;
|
||||
|
||||
ret = dispatch_to_fd(req, fd, NULL);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass != 2) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* Get to given fd */
|
||||
int ne_post(ne_session *sess, const char *uri, int fd, const char *buffer)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "POST", uri);
|
||||
int ret;
|
||||
|
||||
ne_set_request_body_buffer(req, buffer, strlen(buffer));
|
||||
|
||||
ret = dispatch_to_fd(req, fd, NULL);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass != 2) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ne_get_content_type(ne_request *req, ne_content_type *ct)
|
||||
{
|
||||
const char *value;
|
||||
char *sep, *stype;
|
||||
|
||||
value = ne_get_response_header(req, "Content-Type");
|
||||
if (value == NULL || strchr(value, '/') == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ct->type = ct->value = ne_strdup(value);
|
||||
|
||||
stype = strchr(ct->value, '/');
|
||||
*stype++ = '\0';
|
||||
ct->charset = NULL;
|
||||
|
||||
sep = strchr(stype, ';');
|
||||
|
||||
if (sep) {
|
||||
char *tok;
|
||||
|
||||
/* Look for the charset parameter: */
|
||||
*sep++ = '\0';
|
||||
do {
|
||||
tok = ne_qtoken(&sep, ';', "\"\'");
|
||||
if (tok) {
|
||||
tok = strstr(tok, "charset=");
|
||||
if (tok)
|
||||
ct->charset = ne_shave(tok+8, "\"\'");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (sep != NULL);
|
||||
}
|
||||
|
||||
/* set subtype, losing any trailing whitespace */
|
||||
ct->subtype = ne_shave(stype, " \t");
|
||||
|
||||
if (ct->charset == NULL && strcasecmp(ct->type, "text") == 0) {
|
||||
/* 3023§3.1: text/xml without charset implies us-ascii. */
|
||||
if (strcasecmp(ct->subtype, "xml") == 0)
|
||||
ct->charset = "us-ascii";
|
||||
/* 2616§3.7.1: subtypes of text/ default to charset ISO-8859-1. */
|
||||
else
|
||||
ct->charset = "ISO-8859-1";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void parse_dav_header(const char *value, ne_server_capabilities *caps)
|
||||
{
|
||||
char *tokens = ne_strdup(value), *pnt = tokens;
|
||||
|
||||
do {
|
||||
char *tok = ne_qtoken(&pnt, ',', "\"'");
|
||||
if (!tok) break;
|
||||
|
||||
tok = ne_shave(tok, " \r\t\n");
|
||||
|
||||
if (strcmp(tok, "1") == 0) {
|
||||
caps->dav_class1 = 1;
|
||||
} else if (strcmp(tok, "2") == 0) {
|
||||
caps->dav_class2 = 1;
|
||||
} else if (strcmp(tok, "<http://apache.org/dav/propset/fs/1>") == 0) {
|
||||
caps->dav_executable = 1;
|
||||
}
|
||||
} while (pnt != NULL);
|
||||
|
||||
ne_free(tokens);
|
||||
}
|
||||
|
||||
int ne_options(ne_session *sess, const char *uri, ne_server_capabilities *caps)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "OPTIONS", uri);
|
||||
int ret = ne_request_dispatch(req);
|
||||
const char *header = ne_get_response_header(req, "DAV");
|
||||
|
||||
if (header) parse_dav_header(header, caps);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass != 2) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
|
||||
void ne_add_depth_header(ne_request *req, int depth)
|
||||
{
|
||||
const char *value;
|
||||
switch(depth) {
|
||||
case NE_DEPTH_ZERO:
|
||||
value = "0";
|
||||
break;
|
||||
case NE_DEPTH_ONE:
|
||||
value = "1";
|
||||
break;
|
||||
default:
|
||||
value = "infinity";
|
||||
break;
|
||||
}
|
||||
ne_add_request_header(req, "Depth", value);
|
||||
}
|
||||
|
||||
static int copy_or_move(ne_session *sess, int is_move, int overwrite,
|
||||
int depth, const char *src, const char *dest)
|
||||
{
|
||||
ne_request *req = ne_request_create( sess, is_move?"MOVE":"COPY", src );
|
||||
|
||||
/* 2518 S8.9.2 says only use Depth: infinity with MOVE. */
|
||||
if (!is_move) {
|
||||
ne_add_depth_header(req, depth);
|
||||
}
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
if (is_move) {
|
||||
ne_lock_using_resource(req, src, NE_DEPTH_INFINITE);
|
||||
}
|
||||
ne_lock_using_resource(req, dest, NE_DEPTH_INFINITE);
|
||||
/* And we need to be able to add members to the destination's parent */
|
||||
ne_lock_using_parent(req, dest);
|
||||
#endif
|
||||
|
||||
ne_print_request_header(req, "Destination", "%s://%s%s",
|
||||
ne_get_scheme(sess),
|
||||
ne_get_server_hostport(sess), dest);
|
||||
|
||||
ne_add_request_header(req, "Overwrite", overwrite?"T":"F");
|
||||
|
||||
return ne_simple_request(sess, req);
|
||||
}
|
||||
|
||||
int ne_copy(ne_session *sess, int overwrite, int depth,
|
||||
const char *src, const char *dest)
|
||||
{
|
||||
return copy_or_move(sess, 0, overwrite, depth, src, dest);
|
||||
}
|
||||
|
||||
int ne_move(ne_session *sess, int overwrite,
|
||||
const char *src, const char *dest)
|
||||
{
|
||||
return copy_or_move(sess, 1, overwrite, 0, src, dest);
|
||||
}
|
||||
|
||||
/* Deletes the specified resource. (and in only two lines of code!) */
|
||||
int ne_delete(ne_session *sess, const char *uri)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "DELETE", uri);
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
ne_lock_using_resource(req, uri, NE_DEPTH_INFINITE);
|
||||
ne_lock_using_parent(req, uri);
|
||||
#endif
|
||||
|
||||
/* joe: I asked on the DAV WG list about whether we might get a
|
||||
* 207 error back from a DELETE... conclusion, you shouldn't if
|
||||
* you don't send the Depth header, since we might be an HTTP/1.1
|
||||
* client and a 2xx response indicates success to them. But
|
||||
* it's all a bit unclear. In any case, DAV servers today do
|
||||
* return 207 to DELETE even if we don't send the Depth header.
|
||||
* So we handle 207 errors appropriately. */
|
||||
|
||||
return ne_simple_request(sess, req);
|
||||
}
|
||||
|
||||
int ne_mkcol(ne_session *sess, const char *uri)
|
||||
{
|
||||
ne_request *req;
|
||||
char *real_uri;
|
||||
int ret;
|
||||
|
||||
if (ne_path_has_trailing_slash(uri)) {
|
||||
real_uri = ne_strdup(uri);
|
||||
} else {
|
||||
real_uri = ne_concat(uri, "/", NULL);
|
||||
}
|
||||
|
||||
req = ne_request_create(sess, "MKCOL", real_uri);
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
ne_lock_using_resource(req, real_uri, 0);
|
||||
ne_lock_using_parent(req, real_uri);
|
||||
#endif
|
||||
|
||||
ret = ne_simple_request(sess, req);
|
||||
|
||||
ne_free(real_uri);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* NE_HAVE_DAV */
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
HTTP/1.1 methods
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_BASIC_H
|
||||
#define NE_BASIC_H
|
||||
|
||||
#include <sys/types.h> /* for time_t */
|
||||
|
||||
#include "ne_request.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* Perform a GET request on resource at 'path', writing the entity
|
||||
* body which is returned to 'fd'. */
|
||||
int ne_get(ne_session *sess, const char *path, int fd);
|
||||
|
||||
/* Perform a PUT request on resource at 'path', reading the entity
|
||||
* body to submit from 'fd'. */
|
||||
int ne_put(ne_session *sess, const char *path, int fd);
|
||||
|
||||
#define NE_DEPTH_ZERO (0)
|
||||
#define NE_DEPTH_ONE (1)
|
||||
#define NE_DEPTH_INFINITE (2)
|
||||
|
||||
/* For ne_copy and ne_move:
|
||||
*
|
||||
* If a resource exists at "dest" and overwrite is zero, the operation
|
||||
* will fail; if overwrite is non-zero, any existing resource will
|
||||
* be over-written.
|
||||
*/
|
||||
|
||||
/* Copy resource from 'src to 'dest' paths. If 'src' identifies a
|
||||
* collection resource, depth may be NE_DEPTH_ZERO to request that the
|
||||
* collection and its properties are to be copied, or
|
||||
* NE_DEPTH_INFINITE to request that the collection and its contents
|
||||
* are to be copied. */
|
||||
int ne_copy(ne_session *sess, int overwrite, int depth,
|
||||
const char *src, const char *dest);
|
||||
|
||||
/* Move resource from 'src' to dest 'path'. */
|
||||
int ne_move(ne_session *sess, int overwrite,
|
||||
const char *src, const char *dest);
|
||||
|
||||
/* Delete resource at 'path'. */
|
||||
int ne_delete(ne_session *sess, const char *path);
|
||||
/* Create a collection at 'path', which MUST have a trailing slash. */
|
||||
int ne_mkcol(ne_session *sess, const char *path);
|
||||
|
||||
/* Adds a Depth: header to a request */
|
||||
void ne_add_depth_header(ne_request *req, int depth);
|
||||
|
||||
/* Retrieve modification time of resource at location 'path', place in
|
||||
* *modtime. (uses HEAD) */
|
||||
int ne_getmodtime(ne_session *sess, const char *path, time_t *modtime);
|
||||
|
||||
typedef struct {
|
||||
const char *type, *subtype;
|
||||
const char *charset;
|
||||
char *value;
|
||||
} ne_content_type;
|
||||
|
||||
/* Retrieve the content-type of the response; returns zero if response
|
||||
* had valid content-type, in which case all fields in *ctype are set
|
||||
* (and never NULL); the caller must free(ctype->value) after use.
|
||||
* Returns non-zero on error, in which case *ctype is not altered. */
|
||||
int ne_get_content_type(ne_request *req, ne_content_type *ctype);
|
||||
|
||||
/* Server capabilities: */
|
||||
typedef struct {
|
||||
unsigned int dav_class1; /* True if Class 1 WebDAV server */
|
||||
unsigned int dav_class2; /* True if Class 2 WebDAV server */
|
||||
unsigned int dav_executable; /* True if supports the 'executable'
|
||||
* property a. la. mod_dav */
|
||||
} ne_server_capabilities;
|
||||
|
||||
/* Determines server capabilities (using OPTIONS). Pass 'path' as "*"
|
||||
* to determine proxy server capabilities if using a proxy server. */
|
||||
int ne_options(ne_session *sess, const char *path,
|
||||
ne_server_capabilities *caps);
|
||||
|
||||
/* Defines a range of bytes, starting at 'start' and ending
|
||||
* at 'end'. 'total' is the number of bytes in the range.
|
||||
*/
|
||||
typedef struct {
|
||||
off_t start, end, total;
|
||||
} ne_content_range;
|
||||
|
||||
/* Partial GET. range->start must be >= 0. range->total is ignored.
|
||||
*
|
||||
* If range->end is -1, then the rest of the resource from start is
|
||||
* requested, and range->total and end are filled in on success.
|
||||
*
|
||||
* Otherwise, bytes from range->start to range->end are requested.
|
||||
*
|
||||
* This will write to the CURRENT position of f; so if you want
|
||||
* to do a resume download, use:
|
||||
* struct ne_content_range range;
|
||||
* range.start = resume_from;
|
||||
* range.end = range.start + 999; (= 1000 bytes)
|
||||
* fseek(myfile, resume_from, SEEK_SET);
|
||||
* ne_get_range(sess, path, &range, myfile); */
|
||||
int ne_get_range(ne_session *sess, const char *path,
|
||||
ne_content_range *range, int fd);
|
||||
|
||||
/* Post using buffer as request-body: stream response into f */
|
||||
int ne_post(ne_session *sess, const char *path, int fd, const char *buffer);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_BASIC_H */
|
||||
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
Handling of compressed HTTP responses
|
||||
Copyright (C) 2001-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include "ne_request.h"
|
||||
#include "ne_compress.h"
|
||||
#include "ne_utils.h"
|
||||
#include "ne_i18n.h"
|
||||
|
||||
#include "ne_private.h"
|
||||
|
||||
#ifdef NE_HAVE_ZLIB
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
/* Adds support for the 'gzip' Content-Encoding in HTTP. gzip is a
|
||||
* file format which wraps the DEFLATE compression algorithm. zlib
|
||||
* implements DEFLATE: we have to unwrap the gzip format (specified in
|
||||
* RFC1952) as it comes off the wire, and hand off chunks of data to
|
||||
* be inflated. */
|
||||
|
||||
struct ne_decompress_s {
|
||||
ne_request *request; /* associated request. */
|
||||
ne_session *session; /* associated session. */
|
||||
/* temporary buffer for holding inflated data. */
|
||||
char outbuf[NE_BUFSIZ];
|
||||
z_stream zstr;
|
||||
int zstrinit; /* non-zero if zstr has been initialized */
|
||||
|
||||
/* pass blocks back to this. */
|
||||
ne_block_reader reader;
|
||||
ne_accept_response acceptor;
|
||||
void *userdata;
|
||||
|
||||
/* buffer for gzip header bytes. */
|
||||
unsigned char header[10];
|
||||
size_t hdrcount; /* bytes in header */
|
||||
|
||||
unsigned char footer[8];
|
||||
size_t footcount; /* bytes in footer. */
|
||||
|
||||
/* CRC32 checksum: odd that zlib uses uLong for this since it is a
|
||||
* 64-bit integer on LP64 platforms. */
|
||||
uLong checksum;
|
||||
|
||||
/* current state. */
|
||||
enum state {
|
||||
NE_Z_BEFORE_DATA, /* not received any response blocks yet. */
|
||||
NE_Z_PASSTHROUGH, /* response not compressed: passing through. */
|
||||
NE_Z_IN_HEADER, /* received a few bytes of response data, but not
|
||||
* got past the gzip header yet. */
|
||||
NE_Z_POST_HEADER, /* waiting for the end of the NUL-terminated bits. */
|
||||
NE_Z_INFLATING, /* inflating response bytes. */
|
||||
NE_Z_AFTER_DATA, /* after data; reading CRC32 & ISIZE */
|
||||
NE_Z_FINISHED /* stream is finished. */
|
||||
} state;
|
||||
};
|
||||
|
||||
/* Convert 'buf' to unsigned int; 'buf' must be 'unsigned char *' */
|
||||
#define BUF2UINT(buf) (((buf)[3]<<24) + ((buf)[2]<<16) + ((buf)[1]<<8) + (buf)[0])
|
||||
|
||||
#define ID1 0x1f
|
||||
#define ID2 0x8b
|
||||
|
||||
#define HDR_DONE 0
|
||||
#define HDR_EXTENDED 1
|
||||
#define HDR_ERROR 2
|
||||
|
||||
#define HDR_ID1(ctx) ((ctx)->header[0])
|
||||
#define HDR_ID2(ctx) ((ctx)->header[1])
|
||||
#define HDR_CMETH(ctx) ((ctx)->header[2])
|
||||
#define HDR_FLAGS(ctx) ((ctx)->header[3])
|
||||
#define HDR_MTIME(ctx) (BUF2UINT(&(ctx)->header[4]))
|
||||
#define HDR_XFLAGS(ctx) ((ctx)->header[8])
|
||||
#define HDR_OS(ctx) ((ctx)->header[9])
|
||||
|
||||
/* parse_header parses the gzip header, sets the next state and returns
|
||||
* HDR_DONE: all done, bytes following are raw DEFLATE data.
|
||||
* HDR_EXTENDED: all done, expect a NUL-termianted string
|
||||
* before the DEFLATE data
|
||||
* HDR_ERROR: invalid header, give up (session error is set).
|
||||
*/
|
||||
static int parse_header(ne_decompress *ctx)
|
||||
{
|
||||
NE_DEBUG(NE_DBG_HTTP, "ID1: %d ID2: %d, cmeth %d, flags %d\n",
|
||||
HDR_ID1(ctx), HDR_ID2(ctx), HDR_CMETH(ctx), HDR_FLAGS(ctx));
|
||||
|
||||
if (HDR_ID1(ctx) != ID1 || HDR_ID2(ctx) != ID2 || HDR_CMETH(ctx) != 8) {
|
||||
ne_set_error(ctx->session, "Compressed stream invalid");
|
||||
return HDR_ERROR;
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTP, "mtime: %d, xflags: %d, os: %d\n",
|
||||
HDR_MTIME(ctx), HDR_XFLAGS(ctx), HDR_OS(ctx));
|
||||
|
||||
/* TODO: we can only handle one NUL-terminated extensions field
|
||||
* currently. Really, we should count the number of bits set, and
|
||||
* skip as many fields as bits set (bailing if any reserved bits
|
||||
* are set. */
|
||||
if (HDR_FLAGS(ctx) == 8) {
|
||||
ctx->state = NE_Z_POST_HEADER;
|
||||
return HDR_EXTENDED;
|
||||
} else if (HDR_FLAGS(ctx) != 0) {
|
||||
ne_set_error(ctx->session, "Compressed stream not supported");
|
||||
return HDR_ERROR;
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTP, "compress: Good stream.\n");
|
||||
|
||||
ctx->state = NE_Z_INFLATING;
|
||||
return HDR_DONE;
|
||||
}
|
||||
|
||||
/* Process extra 'len' bytes of 'buf' which were received after the
|
||||
* DEFLATE data. */
|
||||
static int process_footer(ne_decompress *ctx,
|
||||
const unsigned char *buf, size_t len)
|
||||
{
|
||||
if (len + ctx->footcount > 8) {
|
||||
ne_set_error(ctx->session,
|
||||
"Too many bytes (%" NE_FMT_SIZE_T ") in gzip footer",
|
||||
len);
|
||||
return -1;
|
||||
} else {
|
||||
memcpy(ctx->footer + ctx->footcount, buf, len);
|
||||
ctx->footcount += len;
|
||||
if (ctx->footcount == 8) {
|
||||
uLong crc = BUF2UINT(ctx->footer) & 0xFFFFFFFF;
|
||||
if (crc == ctx->checksum) {
|
||||
ctx->state = NE_Z_FINISHED;
|
||||
NE_DEBUG(NE_DBG_HTTP, "compress: End of response; checksum match.\n");
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_HTTP, "compress: End of response; checksum mismatch: "
|
||||
"given %lu vs computed %lu\n", crc, ctx->checksum);
|
||||
ne_set_error(ctx->session,
|
||||
"Checksum invalid for compressed stream");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A zlib function failed with 'code'; set the session error string
|
||||
* appropriately. */
|
||||
static void set_zlib_error(ne_decompress *ctx, const char *msg, int code)
|
||||
{
|
||||
if (ctx->zstr.msg)
|
||||
ne_set_error(ctx->session, _("%s: %s"), msg, ctx->zstr.msg);
|
||||
else {
|
||||
const char *err;
|
||||
switch (code) {
|
||||
case Z_STREAM_ERROR: err = "stream error"; break;
|
||||
case Z_DATA_ERROR: err = "data corrupt"; break;
|
||||
case Z_MEM_ERROR: err = "out of memory"; break;
|
||||
case Z_BUF_ERROR: err = "buffer error"; break;
|
||||
case Z_VERSION_ERROR: err = "library version mismatch"; break;
|
||||
default: err = "unknown error"; break;
|
||||
}
|
||||
ne_set_error(ctx->session, _("%s: %s (code %d)"), msg, err, code);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inflate response buffer 'buf' of length 'len'. */
|
||||
static int do_inflate(ne_decompress *ctx, const char *buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ctx->zstr.avail_in = len;
|
||||
ctx->zstr.next_in = (unsigned char *)buf;
|
||||
ctx->zstr.total_in = 0;
|
||||
|
||||
do {
|
||||
ctx->zstr.avail_out = sizeof ctx->outbuf;
|
||||
ctx->zstr.next_out = (unsigned char *)ctx->outbuf;
|
||||
ctx->zstr.total_out = 0;
|
||||
|
||||
ret = inflate(&ctx->zstr, Z_NO_FLUSH);
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTP,
|
||||
"compress: inflate %d, %ld bytes out, %d remaining\n",
|
||||
ret, ctx->zstr.total_out, ctx->zstr.avail_in);
|
||||
#if 0
|
||||
NE_DEBUG(NE_DBG_HTTPBODY,
|
||||
"Inflated body block (%ld):\n[%.*s]\n",
|
||||
ctx->zstr.total_out, (int)ctx->zstr.total_out,
|
||||
ctx->outbuf);
|
||||
#endif
|
||||
/* update checksum. */
|
||||
ctx->checksum = crc32(ctx->checksum, (unsigned char *)ctx->outbuf,
|
||||
ctx->zstr.total_out);
|
||||
|
||||
/* pass on the inflated data, if any */
|
||||
if (ctx->zstr.total_out > 0) {
|
||||
int rret = ctx->reader(ctx->userdata, ctx->outbuf,
|
||||
ctx->zstr.total_out);
|
||||
if (rret) return rret;
|
||||
}
|
||||
} while (ret == Z_OK && ctx->zstr.avail_in > 0);
|
||||
|
||||
if (ret == Z_STREAM_END) {
|
||||
NE_DEBUG(NE_DBG_HTTP, "compress: end of data stream, %d bytes remain.\n",
|
||||
ctx->zstr.avail_in);
|
||||
/* process the footer. */
|
||||
ctx->state = NE_Z_AFTER_DATA;
|
||||
return process_footer(ctx, ctx->zstr.next_in, ctx->zstr.avail_in);
|
||||
} else if (ret != Z_OK) {
|
||||
set_zlib_error(ctx, _("Could not inflate data"), ret);
|
||||
return NE_ERROR;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Callback which is passed blocks of the response body. */
|
||||
static int gz_reader(void *ud, const char *buf, size_t len)
|
||||
{
|
||||
ne_decompress *ctx = ud;
|
||||
const char *zbuf;
|
||||
size_t count;
|
||||
const char *hdr;
|
||||
|
||||
if (len == 0) {
|
||||
/* End of response: */
|
||||
switch (ctx->state) {
|
||||
case NE_Z_BEFORE_DATA:
|
||||
hdr = ne_get_response_header(ctx->request, "Content-Encoding");
|
||||
if (hdr && strcasecmp(hdr, "gzip") == 0) {
|
||||
/* response was truncated: return error. */
|
||||
break;
|
||||
}
|
||||
/* else, fall through */
|
||||
case NE_Z_FINISHED: /* complete gzip response */
|
||||
case NE_Z_PASSTHROUGH: /* complete uncompressed response */
|
||||
return ctx->reader(ctx->userdata, buf, 0);
|
||||
default:
|
||||
/* invalid state: truncated response. */
|
||||
break;
|
||||
}
|
||||
/* else: truncated response, fail. */
|
||||
ne_set_error(ctx->session, "Compressed response was truncated");
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
switch (ctx->state) {
|
||||
case NE_Z_PASSTHROUGH:
|
||||
/* move along there. */
|
||||
return ctx->reader(ctx->userdata, buf, len);
|
||||
|
||||
case NE_Z_FINISHED:
|
||||
/* Could argue for tolerance, and ignoring trailing content;
|
||||
* but it could mean something more serious. */
|
||||
if (len > 0) {
|
||||
ne_set_error(ctx->session,
|
||||
"Unexpected content received after compressed stream");
|
||||
return NE_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NE_Z_BEFORE_DATA:
|
||||
/* work out whether this is a compressed response or not. */
|
||||
hdr = ne_get_response_header(ctx->request, "Content-Encoding");
|
||||
if (hdr && strcasecmp(hdr, "gzip") == 0) {
|
||||
int ret;
|
||||
NE_DEBUG(NE_DBG_HTTP, "compress: got gzipped stream.\n");
|
||||
|
||||
/* inflateInit2() works here where inflateInit() doesn't. */
|
||||
ret = inflateInit2(&ctx->zstr, -MAX_WBITS);
|
||||
if (ret != Z_OK) {
|
||||
set_zlib_error(ctx, _("Could not initialize zlib"), ret);
|
||||
return -1;
|
||||
}
|
||||
ctx->zstrinit = 1;
|
||||
|
||||
} else {
|
||||
/* No Content-Encoding header: pass it on. TODO: we could
|
||||
* hack it and register the real callback now. But that
|
||||
* would require add_resp_body_rdr to have defined
|
||||
* ordering semantics etc etc */
|
||||
ctx->state = NE_Z_PASSTHROUGH;
|
||||
return ctx->reader(ctx->userdata, buf, len);
|
||||
}
|
||||
|
||||
ctx->state = NE_Z_IN_HEADER;
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case NE_Z_IN_HEADER:
|
||||
/* copy as many bytes as possible into the buffer. */
|
||||
if (len + ctx->hdrcount > 10) {
|
||||
count = 10 - ctx->hdrcount;
|
||||
} else {
|
||||
count = len;
|
||||
}
|
||||
memcpy(ctx->header + ctx->hdrcount, buf, count);
|
||||
ctx->hdrcount += count;
|
||||
/* have we got the full header yet? */
|
||||
if (ctx->hdrcount != 10) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf += count;
|
||||
len -= count;
|
||||
|
||||
switch (parse_header(ctx)) {
|
||||
case HDR_EXTENDED:
|
||||
if (len == 0)
|
||||
return 0;
|
||||
break;
|
||||
case HDR_ERROR:
|
||||
return NE_ERROR;
|
||||
case HDR_DONE:
|
||||
if (len > 0) {
|
||||
return do_inflate(ctx, buf, len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case NE_Z_POST_HEADER:
|
||||
/* eating the filename string. */
|
||||
zbuf = memchr(buf, '\0', len);
|
||||
if (zbuf == NULL) {
|
||||
/* not found it yet. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTP,
|
||||
"compresss: skipped %" NE_FMT_SIZE_T " header bytes.\n",
|
||||
zbuf - buf);
|
||||
/* found end of string. */
|
||||
len -= (1 + zbuf - buf);
|
||||
buf = zbuf + 1;
|
||||
ctx->state = NE_Z_INFLATING;
|
||||
if (len == 0) {
|
||||
/* end of string was at end of buffer. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case NE_Z_INFLATING:
|
||||
return do_inflate(ctx, buf, len);
|
||||
|
||||
case NE_Z_AFTER_DATA:
|
||||
return process_footer(ctx, (unsigned char *)buf, len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Prepare for a compressed response */
|
||||
static void gz_pre_send(ne_request *r, void *ud, ne_buffer *req)
|
||||
{
|
||||
ne_decompress *ctx = ud;
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTP, "compress: Initialization.\n");
|
||||
|
||||
/* (Re-)Initialize the context */
|
||||
ctx->state = NE_Z_BEFORE_DATA;
|
||||
if (ctx->zstrinit) inflateEnd(&ctx->zstr);
|
||||
ctx->zstrinit = 0;
|
||||
ctx->hdrcount = ctx->footcount = 0;
|
||||
ctx->checksum = crc32(0L, Z_NULL, 0);
|
||||
}
|
||||
|
||||
void ne_decompress_destroy(ne_decompress *ctx)
|
||||
{
|
||||
if (ctx->zstrinit)
|
||||
/* inflateEnd only fails if it's passed NULL etc; ignore
|
||||
* return value. */
|
||||
inflateEnd(&ctx->zstr);
|
||||
|
||||
ne_free(ctx);
|
||||
}
|
||||
|
||||
/* Wrapper for user-passed acceptor function. */
|
||||
static int gz_acceptor(void *userdata, ne_request *req, const ne_status *st)
|
||||
{
|
||||
ne_decompress *ctx = userdata;
|
||||
return ctx->acceptor(ctx->userdata, req, st);
|
||||
}
|
||||
|
||||
ne_decompress *ne_decompress_reader(ne_request *req, ne_accept_response acpt,
|
||||
ne_block_reader rdr, void *userdata)
|
||||
{
|
||||
ne_decompress *ctx = ne_calloc(sizeof *ctx);
|
||||
|
||||
ne_add_request_header(req, "Accept-Encoding", "gzip");
|
||||
|
||||
ne_add_response_body_reader(req, gz_acceptor, gz_reader, ctx);
|
||||
|
||||
ctx->reader = rdr;
|
||||
ctx->userdata = userdata;
|
||||
ctx->session = ne_get_session(req);
|
||||
ctx->request = req;
|
||||
ctx->acceptor = acpt;
|
||||
|
||||
ne__reqhook_pre_send(req, gz_pre_send, ctx);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
#else /* !NE_HAVE_ZLIB */
|
||||
|
||||
/* Pass-through interface present to provide ABI compatibility. */
|
||||
|
||||
ne_decompress *ne_decompress_reader(ne_request *req, ne_accept_response acpt,
|
||||
ne_block_reader rdr, void *userdata)
|
||||
{
|
||||
ne_add_response_body_reader(req, acpt, rdr, userdata);
|
||||
/* an arbitrary return value: don't confuse them by returning NULL. */
|
||||
return (ne_decompress *)req;
|
||||
}
|
||||
|
||||
void ne_decompress_destroy(ne_decompress *dc)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* NE_HAVE_ZLIB */
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Compressed HTTP response handling
|
||||
Copyright (C) 2001-2004, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_COMPRESS_H
|
||||
#define NE_COMPRESS_H
|
||||
|
||||
#include "ne_request.h"
|
||||
|
||||
typedef struct ne_decompress_s ne_decompress;
|
||||
|
||||
/* Call this to register a 'reader' callback which will be passed
|
||||
* blocks of response body (if the 'acceptance' callback is
|
||||
* successful). If the response body is returned compressed by the
|
||||
* server, this reader will receive UNCOMPRESSED blocks.
|
||||
*
|
||||
* Returns pointer to context object which must be passed to
|
||||
* ne_decompress_destroy after the request has been dispatched, to
|
||||
* free any internal state. If an error occurs during decompression,
|
||||
* the request will be aborted and session error string set. */
|
||||
ne_decompress *ne_decompress_reader(ne_request *req, ne_accept_response accpt,
|
||||
ne_block_reader rdr, void *userdata);
|
||||
|
||||
/* Destroys decompression state. */
|
||||
void ne_decompress_destroy(ne_decompress *ctx);
|
||||
|
||||
#endif /* NE_COMPRESS_H */
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
Date manipulation routines
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
Copyright (C) 2004 Jiang Lei <tristone@deluxe.ocn.ne.jp>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <time.h>
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h> /* for TIME_ZONE_INFORMATION */
|
||||
#endif
|
||||
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_dates.h"
|
||||
#include "ne_string.h"
|
||||
|
||||
/* Generic date manipulation routines. */
|
||||
|
||||
/* ISO8601: 2001-01-01T12:30:00Z */
|
||||
#define ISO8601_FORMAT_Z "%04d-%02d-%02dT%02d:%02d:%lfZ"
|
||||
#define ISO8601_FORMAT_M "%04d-%02d-%02dT%02d:%02d:%lf-%02d:%02d"
|
||||
#define ISO8601_FORMAT_P "%04d-%02d-%02dT%02d:%02d:%lf+%02d:%02d"
|
||||
|
||||
/* RFC1123: Sun, 06 Nov 1994 08:49:37 GMT */
|
||||
#define RFC1123_FORMAT "%3s, %02d %3s %4d %02d:%02d:%02d GMT"
|
||||
/* RFC850: Sunday, 06-Nov-94 08:49:37 GMT */
|
||||
#define RFC1036_FORMAT "%10s %2d-%3s-%2d %2d:%2d:%2d GMT"
|
||||
/* asctime: Wed Jun 30 21:49:08 1993 */
|
||||
#define ASCTIME_FORMAT "%3s %3s %2d %2d:%2d:%2d %4d"
|
||||
|
||||
static const char *const rfc1123_weekdays[7] = {
|
||||
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
||||
};
|
||||
static const char *const short_months[12] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
};
|
||||
|
||||
#if defined(HAVE_STRUCT_TM_TM_GMTOFF)
|
||||
#define GMTOFF(t) ((t).tm_gmtoff)
|
||||
#elif defined(HAVE_STRUCT_TM___TM_GMTOFF)
|
||||
#define GMTOFF(t) ((t).__tm_gmtoff)
|
||||
#elif defined(WIN32)
|
||||
#define GMTOFF(t) (gmt_to_local_win32())
|
||||
#else
|
||||
/* FIXME: work out the offset anyway. */
|
||||
#define GMTOFF(t) (0)
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
time_t gmt_to_local_win32(void)
|
||||
{
|
||||
TIME_ZONE_INFORMATION tzinfo;
|
||||
DWORD dwStandardDaylight;
|
||||
long bias;
|
||||
|
||||
dwStandardDaylight = GetTimeZoneInformation(&tzinfo);
|
||||
bias = tzinfo.Bias;
|
||||
|
||||
if (dwStandardDaylight == TIME_ZONE_ID_STANDARD)
|
||||
bias += tzinfo.StandardBias;
|
||||
|
||||
if (dwStandardDaylight == TIME_ZONE_ID_DAYLIGHT)
|
||||
bias += tzinfo.DaylightBias;
|
||||
|
||||
return (- bias * 60);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Returns the time/date GMT, in RFC1123-type format: eg
|
||||
* Sun, 06 Nov 1994 08:49:37 GMT. */
|
||||
char *ne_rfc1123_date(time_t anytime) {
|
||||
struct tm *gmt;
|
||||
char *ret;
|
||||
gmt = gmtime(&anytime);
|
||||
if (gmt == NULL)
|
||||
return NULL;
|
||||
ret = ne_malloc(29 + 1); /* dates are 29 chars long */
|
||||
/* it goes: Sun, 06 Nov 1994 08:49:37 GMT */
|
||||
ne_snprintf(ret, 30, RFC1123_FORMAT,
|
||||
rfc1123_weekdays[gmt->tm_wday], gmt->tm_mday,
|
||||
short_months[gmt->tm_mon], 1900 + gmt->tm_year,
|
||||
gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Takes an ISO-8601-formatted date string and returns the time_t.
|
||||
* Returns (time_t)-1 if the parse fails. */
|
||||
time_t ne_iso8601_parse(const char *date)
|
||||
{
|
||||
struct tm gmt = {0};
|
||||
int off_hour, off_min;
|
||||
double sec;
|
||||
off_t fix;
|
||||
int n;
|
||||
|
||||
/* it goes: ISO8601: 2001-01-01T12:30:00+03:30 */
|
||||
if ((n = sscanf(date, ISO8601_FORMAT_P,
|
||||
&gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday,
|
||||
&gmt.tm_hour, &gmt.tm_min, &sec,
|
||||
&off_hour, &off_min)) == 8) {
|
||||
gmt.tm_sec = (int)sec;
|
||||
fix = - off_hour * 3600 - off_min * 60;
|
||||
}
|
||||
/* it goes: ISO8601: 2001-01-01T12:30:00-03:30 */
|
||||
else if ((n = sscanf(date, ISO8601_FORMAT_M,
|
||||
&gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday,
|
||||
&gmt.tm_hour, &gmt.tm_min, &sec,
|
||||
&off_hour, &off_min)) == 8) {
|
||||
gmt.tm_sec = (int)sec;
|
||||
fix = off_hour * 3600 + off_min * 60;
|
||||
}
|
||||
/* it goes: ISO8601: 2001-01-01T12:30:00Z */
|
||||
else if ((n = sscanf(date, ISO8601_FORMAT_Z,
|
||||
&gmt.tm_year, &gmt.tm_mon, &gmt.tm_mday,
|
||||
&gmt.tm_hour, &gmt.tm_min, &sec)) == 6) {
|
||||
gmt.tm_sec = (int)sec;
|
||||
fix = 0;
|
||||
}
|
||||
else {
|
||||
return (time_t)-1;
|
||||
}
|
||||
|
||||
gmt.tm_year -= 1900;
|
||||
gmt.tm_isdst = -1;
|
||||
gmt.tm_mon--;
|
||||
|
||||
return mktime(&gmt) + fix + GMTOFF(gmt);
|
||||
}
|
||||
|
||||
/* Takes an RFC1123-formatted date string and returns the time_t.
|
||||
* Returns (time_t)-1 if the parse fails. */
|
||||
time_t ne_rfc1123_parse(const char *date)
|
||||
{
|
||||
struct tm gmt = {0};
|
||||
char wkday[4], mon[4];
|
||||
int n;
|
||||
/* it goes: Sun, 06 Nov 1994 08:49:37 GMT */
|
||||
n = sscanf(date, RFC1123_FORMAT,
|
||||
wkday, &gmt.tm_mday, mon, &gmt.tm_year, &gmt.tm_hour,
|
||||
&gmt.tm_min, &gmt.tm_sec);
|
||||
/* Is it portable to check n==7 here? */
|
||||
gmt.tm_year -= 1900;
|
||||
for (n=0; n<12; n++)
|
||||
if (strcmp(mon, short_months[n]) == 0)
|
||||
break;
|
||||
/* tm_mon comes out as 12 if the month is corrupt, which is desired,
|
||||
* since the mktime will then fail */
|
||||
gmt.tm_mon = n;
|
||||
gmt.tm_isdst = -1;
|
||||
return mktime(&gmt) + GMTOFF(gmt);
|
||||
}
|
||||
|
||||
/* Takes a string containing a RFC1036-style date and returns the time_t */
|
||||
time_t ne_rfc1036_parse(const char *date)
|
||||
{
|
||||
struct tm gmt = {0};
|
||||
int n;
|
||||
char wkday[11], mon[4];
|
||||
/* RFC850/1036 style dates: Sunday, 06-Nov-94 08:49:37 GMT */
|
||||
n = sscanf(date, RFC1036_FORMAT,
|
||||
wkday, &gmt.tm_mday, mon, &gmt.tm_year,
|
||||
&gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec);
|
||||
if (n != 7) {
|
||||
return (time_t)-1;
|
||||
}
|
||||
|
||||
/* portable to check n here? */
|
||||
for (n=0; n<12; n++)
|
||||
if (strcmp(mon, short_months[n]) == 0)
|
||||
break;
|
||||
/* tm_mon comes out as 12 if the month is corrupt, which is desired,
|
||||
* since the mktime will then fail */
|
||||
|
||||
/* Defeat Y2K bug. */
|
||||
if (gmt.tm_year < 50)
|
||||
gmt.tm_year += 100;
|
||||
|
||||
gmt.tm_mon = n;
|
||||
gmt.tm_isdst = -1;
|
||||
return mktime(&gmt) + GMTOFF(gmt);
|
||||
}
|
||||
|
||||
|
||||
/* (as)ctime dates are like:
|
||||
* Wed Jun 30 21:49:08 1993
|
||||
*/
|
||||
time_t ne_asctime_parse(const char *date)
|
||||
{
|
||||
struct tm gmt = {0};
|
||||
int n;
|
||||
char wkday[4], mon[4];
|
||||
n = sscanf(date, ASCTIME_FORMAT,
|
||||
wkday, mon, &gmt.tm_mday,
|
||||
&gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec,
|
||||
&gmt.tm_year);
|
||||
/* portable to check n here? */
|
||||
for (n=0; n<12; n++)
|
||||
if (strcmp(mon, short_months[n]) == 0)
|
||||
break;
|
||||
/* tm_mon comes out as 12 if the month is corrupt, which is desired,
|
||||
* since the mktime will then fail */
|
||||
gmt.tm_mon = n;
|
||||
gmt.tm_isdst = -1;
|
||||
return mktime(&gmt) + GMTOFF(gmt);
|
||||
}
|
||||
|
||||
/* HTTP-date parser */
|
||||
time_t ne_httpdate_parse(const char *date)
|
||||
{
|
||||
time_t tmp;
|
||||
tmp = ne_rfc1123_parse(date);
|
||||
if (tmp == -1) {
|
||||
tmp = ne_rfc1036_parse(date);
|
||||
if (tmp == -1)
|
||||
tmp = ne_asctime_parse(date);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Date manipulation routines
|
||||
Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef DATES_H
|
||||
#define DATES_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "ne_defs.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* Date manipulation routines as per RFC1123 and RFC1036 */
|
||||
|
||||
/* Return current date/time in RFC1123 format */
|
||||
char *ne_rfc1123_date(time_t anytime);
|
||||
|
||||
/* Returns time from date/time using the subset of the ISO8601 format
|
||||
* referenced in RFC2518 (e.g as used in the creationdate property in
|
||||
* the DAV: namespace). */
|
||||
time_t ne_iso8601_parse(const char *date);
|
||||
|
||||
/* Returns time from date/time in RFC1123 format */
|
||||
time_t ne_rfc1123_parse(const char *date);
|
||||
|
||||
time_t ne_rfc1036_parse(const char *date);
|
||||
|
||||
/* Parses asctime date string */
|
||||
time_t ne_asctime_parse(const char *date);
|
||||
|
||||
/* Parse an HTTP-date as per RFC2616 */
|
||||
time_t ne_httpdate_parse(const char *date);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* DATES_H */
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Standard definitions for neon headers
|
||||
Copyright (C) 2003-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#undef BEGIN_NEON_DECLS
|
||||
#undef END_NEON_DECLS
|
||||
#ifdef __cplusplus
|
||||
# define BEGIN_NEON_DECLS extern "C" {
|
||||
# define END_NEON_DECLS }
|
||||
#else
|
||||
# define BEGIN_NEON_DECLS /* empty */
|
||||
# define END_NEON_DECLS /* empty */
|
||||
#endif
|
||||
|
||||
/* define ssize_t for Win32 */
|
||||
#if defined(WIN32) && !defined(ssize_t)
|
||||
#define ssize_t int
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#if __GNUC__ >= 3
|
||||
#define ne_attribute_malloc __attribute__((malloc))
|
||||
#else
|
||||
#define ne_attribute_malloc
|
||||
#endif
|
||||
#define ne_attribute(x) __attribute__(x)
|
||||
#else
|
||||
#define ne_attribute(x)
|
||||
#define ne_attribute_malloc
|
||||
#endif
|
||||
|
||||
#ifndef NE_BUFSIZ
|
||||
//#define NE_BUFSIZ 8192*2*2
|
||||
#define NE_BUFSIZ 65536
|
||||
//#define NE_BUFSIZ 8192*10
|
||||
#endif
|
||||
@@ -0,0 +1,853 @@
|
||||
/*
|
||||
neon SSL/TLS support using GNU TLS
|
||||
Copyright (C) 2002-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
Copyright (C) 2004, Aleix Conchillo Flaque <aleix@member.fsf.org>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
//#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
//#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
//#include <gnutls/gnutls.h>
|
||||
//#include <gnutls/pkcs12.h>
|
||||
|
||||
#include "ne_ssl.h"
|
||||
#include "ne_string.h"
|
||||
#include "ne_session.h"
|
||||
#include "ne_i18n.h"
|
||||
|
||||
#include "ne_private.h"
|
||||
#include "ne_privssl.h"
|
||||
|
||||
struct ne_ssl_dname_s {
|
||||
int subject; /* non-zero if this is the subject DN object */
|
||||
gnutls_x509_crt cert;
|
||||
};
|
||||
|
||||
struct ne_ssl_certificate_s {
|
||||
ne_ssl_dname subj_dn, issuer_dn;
|
||||
gnutls_x509_crt subject;
|
||||
ne_ssl_certificate *issuer;
|
||||
char *identity;
|
||||
};
|
||||
|
||||
struct ne_ssl_client_cert_s {
|
||||
gnutls_pkcs12 p12;
|
||||
int decrypted; /* non-zero if successfully decrypted. */
|
||||
ne_ssl_certificate cert;
|
||||
gnutls_x509_privkey pkey;
|
||||
char *friendly_name;
|
||||
};
|
||||
|
||||
/* Returns the highest used index in subject (or issuer) DN of
|
||||
* certificate CERT for OID, or -1 if no RDNs are present in the DN
|
||||
* using that OID. */
|
||||
static int oid_find_highest_index(gnutls_x509_crt cert, int subject, const char *oid)
|
||||
{
|
||||
int ret, idx = -1;
|
||||
|
||||
do {
|
||||
size_t len = 0;
|
||||
|
||||
if (subject)
|
||||
ret = gnutls_x509_crt_get_dn_by_oid(cert, oid, ++idx, 0,
|
||||
NULL, &len);
|
||||
else
|
||||
ret = gnutls_x509_crt_get_issuer_dn_by_oid(cert, oid, ++idx, 0,
|
||||
NULL, &len);
|
||||
} while (ret == GNUTLS_E_SHORT_MEMORY_BUFFER);
|
||||
|
||||
return idx - 1;
|
||||
}
|
||||
|
||||
/* Appends the value of RDN with given oid from certitifcate x5
|
||||
* subject (if subject is non-zero), or issuer DN to buffer 'buf': */
|
||||
static void append_rdn(ne_buffer *buf, gnutls_x509_crt x5, int subject, const char *oid)
|
||||
{
|
||||
int idx, top, ret;
|
||||
char rdn[50];
|
||||
|
||||
top = oid_find_highest_index(x5, subject, oid);
|
||||
|
||||
for (idx = top; idx >= 0; idx--) {
|
||||
size_t rdnlen = sizeof rdn;
|
||||
|
||||
if (subject)
|
||||
ret = gnutls_x509_crt_get_dn_by_oid(x5, oid, idx, 0, rdn, &rdnlen);
|
||||
else
|
||||
ret = gnutls_x509_crt_get_issuer_dn_by_oid(x5, oid, idx, 0, rdn, &rdnlen);
|
||||
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
if (buf->used > 1) {
|
||||
ne_buffer_append(buf, ", ", 2);
|
||||
}
|
||||
|
||||
ne_buffer_append(buf, rdn, rdnlen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char *ne_ssl_readable_dname(const ne_ssl_dname *name)
|
||||
{
|
||||
ne_buffer *buf = ne_buffer_create();
|
||||
#if 0
|
||||
/* this code can be used once there is a released version of GnuTLS
|
||||
* with fixed _get_dn_oid functions */
|
||||
int ret, idx = 0;
|
||||
|
||||
do {
|
||||
char oid[32] = {0};
|
||||
size_t oidlen = sizeof oid;
|
||||
|
||||
ret = name->subject
|
||||
? gnutls_x509_crt_get_dn_oid(name->cert, idx, oid, &oidlen)
|
||||
: gnutls_x509_crt_get_issuer_dn_oid(name->cert, idx, oid, &oidlen);
|
||||
|
||||
if (ret == 0) {
|
||||
append_rdn(buf, name->cert, name->subject, oid);
|
||||
idx++;
|
||||
}
|
||||
} while (ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
|
||||
#else
|
||||
|
||||
#define APPEND_RDN(x) append_rdn(buf, name->cert, name->subject, GNUTLS_OID_##x)
|
||||
|
||||
APPEND_RDN(X520_ORGANIZATIONAL_UNIT_NAME);
|
||||
APPEND_RDN(X520_ORGANIZATION_NAME);
|
||||
APPEND_RDN(X520_LOCALITY_NAME);
|
||||
APPEND_RDN(X520_STATE_OR_PROVINCE_NAME);
|
||||
APPEND_RDN(X520_COUNTRY_NAME);
|
||||
|
||||
if (buf->used == 1) APPEND_RDN(X520_COMMON_NAME);
|
||||
if (buf->used == 1) APPEND_RDN(PKCS9_EMAIL);
|
||||
|
||||
#undef APPEND_RDN
|
||||
#endif
|
||||
|
||||
return ne_buffer_finish(buf);
|
||||
}
|
||||
|
||||
int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2)
|
||||
{
|
||||
#warning incomplete
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ne_ssl_clicert_free(ne_ssl_client_cert *cc)
|
||||
{
|
||||
if (cc->p12)
|
||||
gnutls_pkcs12_deinit(cc->p12);
|
||||
if (cc->decrypted) {
|
||||
if (cc->cert.identity) ne_free(cc->cert.identity);
|
||||
if (cc->pkey) gnutls_x509_privkey_deinit(cc->pkey);
|
||||
if (cc->cert.subject) gnutls_x509_crt_deinit(cc->cert.subject);
|
||||
}
|
||||
if (cc->friendly_name) ne_free(cc->friendly_name);
|
||||
ne_free(cc);
|
||||
}
|
||||
|
||||
void ne_ssl_cert_validity(const ne_ssl_certificate *cert,
|
||||
char *from, char *until)
|
||||
{
|
||||
#warning FIXME strftime not portable
|
||||
if (from) {
|
||||
time_t t = gnutls_x509_crt_get_activation_time(cert->subject);
|
||||
strftime(from, NE_SSL_VDATELEN, "%b %d %H:%M:%S %Y %Z", localtime(&t));
|
||||
}
|
||||
if (until) {
|
||||
time_t t = gnutls_x509_crt_get_expiration_time(cert->subject);
|
||||
strftime(until, NE_SSL_VDATELEN, "%b %d %H:%M:%S %Y %Z", localtime(&t));
|
||||
}
|
||||
}
|
||||
|
||||
/* Return non-zero if hostname from certificate (cn) matches hostname
|
||||
* used for session (hostname). (Wildcard matching is no longer
|
||||
* mandated by RFC3280, but certs are deployed which use wildcards) */
|
||||
static int match_hostname(char *cn, const char *hostname)
|
||||
{
|
||||
const char *dot;
|
||||
NE_DEBUG(NE_DBG_SSL, "Match %s on %s...\n", cn, hostname);
|
||||
dot = strchr(hostname, '.');
|
||||
if (dot == NULL) {
|
||||
char *pnt = strchr(cn, '.');
|
||||
/* hostname is not fully-qualified; unqualify the cn. */
|
||||
if (pnt != NULL) {
|
||||
*pnt = '\0';
|
||||
}
|
||||
}
|
||||
else if (strncmp(cn, "*.", 2) == 0) {
|
||||
hostname = dot + 1;
|
||||
cn += 2;
|
||||
}
|
||||
return !strcasecmp(cn, hostname);
|
||||
}
|
||||
|
||||
/* Check certificate identity. Returns zero if identity matches; 1 if
|
||||
* identity does not match, or <0 if the certificate had no identity.
|
||||
* If 'identity' is non-NULL, store the malloc-allocated identity in
|
||||
* *identity. If 'server' is non-NULL, it must be the network address
|
||||
* of the server in use, and identity must be NULL. */
|
||||
static int check_identity(const char *hostname, gnutls_x509_crt cert,
|
||||
char **identity)
|
||||
{
|
||||
char name[255];
|
||||
unsigned int critical;
|
||||
int ret, seq = 0;
|
||||
int match = 0, found = 0;
|
||||
size_t len;
|
||||
|
||||
do {
|
||||
len = sizeof name;
|
||||
ret = gnutls_x509_crt_get_subject_alt_name(cert, seq, name, &len,
|
||||
&critical);
|
||||
switch (ret) {
|
||||
case GNUTLS_SAN_DNSNAME:
|
||||
if (identity && !found) *identity = ne_strdup(name);
|
||||
match = match_hostname(name, hostname);
|
||||
found = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
seq++;
|
||||
} while (!match && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
|
||||
|
||||
/* Check against the commonName if no DNS alt. names were found,
|
||||
* as per RFC3280. */
|
||||
if (!found) {
|
||||
seq = oid_find_highest_index(cert, 1, GNUTLS_OID_X520_COMMON_NAME);
|
||||
|
||||
if (seq >= 0) {
|
||||
len = sizeof name;
|
||||
name[0] = '\0';
|
||||
ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME,
|
||||
seq, 0, name, &len);
|
||||
if (ret == 0) {
|
||||
if (identity) *identity = ne_strdup(name);
|
||||
match = match_hostname(name, hostname);
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Identity match: %s\n", match ? "good" : "bad");
|
||||
return match ? 0 : 1;
|
||||
}
|
||||
|
||||
/* Populate an ne_ssl_certificate structure from an X509 object. */
|
||||
static ne_ssl_certificate *populate_cert(ne_ssl_certificate *cert,
|
||||
gnutls_x509_crt x5)
|
||||
{
|
||||
cert->subj_dn.cert = x5;
|
||||
cert->subj_dn.subject = 1;
|
||||
cert->issuer_dn.cert = x5;
|
||||
cert->issuer_dn.subject = 0;
|
||||
cert->issuer = NULL;
|
||||
cert->subject = x5;
|
||||
cert->identity = NULL;
|
||||
check_identity("", x5, &cert->identity);
|
||||
return cert;
|
||||
}
|
||||
|
||||
/* Returns a copy certificate of certificate SRC. */
|
||||
static gnutls_x509_crt x509_crt_copy(gnutls_x509_crt src)
|
||||
{
|
||||
int ret;
|
||||
size_t size;
|
||||
gnutls_datum tmp;
|
||||
gnutls_x509_crt dest;
|
||||
|
||||
if (gnutls_x509_crt_init(&dest) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (gnutls_x509_crt_export(src, GNUTLS_X509_FMT_DER, NULL, &size)
|
||||
!= GNUTLS_E_SHORT_MEMORY_BUFFER) {
|
||||
gnutls_x509_crt_deinit(dest);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tmp.data = ne_malloc(size);
|
||||
ret = gnutls_x509_crt_export(src, GNUTLS_X509_FMT_DER, tmp.data, &size);
|
||||
if (ret == 0) {
|
||||
tmp.size = size;
|
||||
ret = gnutls_x509_crt_import(dest, &tmp, GNUTLS_X509_FMT_DER);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
gnutls_x509_crt_deinit(dest);
|
||||
dest = NULL;
|
||||
}
|
||||
|
||||
ne_free(tmp.data);
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* Duplicate a client certificate, which must be in the decrypted state. */
|
||||
static ne_ssl_client_cert *dup_client_cert(const ne_ssl_client_cert *cc)
|
||||
{
|
||||
int ret;
|
||||
ne_ssl_client_cert *newcc = ne_calloc(sizeof *newcc);
|
||||
|
||||
newcc->decrypted = 1;
|
||||
|
||||
ret = gnutls_x509_privkey_init(&newcc->pkey);
|
||||
if (ret != 0) goto dup_error;
|
||||
|
||||
ret = gnutls_x509_privkey_cpy(newcc->pkey, cc->pkey);
|
||||
if (ret != 0) goto dup_error;
|
||||
|
||||
newcc->cert.subject = x509_crt_copy(cc->cert.subject);
|
||||
if (!newcc->cert.subject) goto dup_error;
|
||||
|
||||
if (cc->friendly_name) newcc->friendly_name = ne_strdup(cc->friendly_name);
|
||||
|
||||
populate_cert(&newcc->cert, newcc->cert.subject);
|
||||
return newcc;
|
||||
|
||||
dup_error:
|
||||
if (newcc->pkey) gnutls_x509_privkey_deinit(newcc->pkey);
|
||||
if (newcc->cert.subject) gnutls_x509_crt_deinit(newcc->cert.subject);
|
||||
ne_free(newcc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *cc)
|
||||
{
|
||||
sess->client_cert = dup_client_cert(cc);
|
||||
}
|
||||
|
||||
ne_ssl_context *ne_ssl_context_create(int flags)
|
||||
{
|
||||
ne_ssl_context *ctx = ne_malloc(sizeof *ctx);
|
||||
gnutls_certificate_allocate_credentials(&ctx->cred);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
int ne_ssl_context_keypair(ne_ssl_context *ctx,
|
||||
const char *cert, const char *key)
|
||||
{
|
||||
gnutls_certificate_set_x509_key_file(ctx->cred, cert, key,
|
||||
GNUTLS_X509_FMT_PEM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ne_ssl_context_set_verify(ne_ssl_context *ctx, int required,
|
||||
const char *ca_names, const char *verify_cas)
|
||||
{
|
||||
if (verify_cas) {
|
||||
gnutls_certificate_set_x509_trust_file(ctx->cred, verify_cas,
|
||||
GNUTLS_X509_FMT_PEM);
|
||||
}
|
||||
#warning argh
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ne_ssl_context_destroy(ne_ssl_context *ctx)
|
||||
{
|
||||
gnutls_certificate_free_credentials(ctx->cred);
|
||||
ne_free(ctx);
|
||||
}
|
||||
|
||||
/* Return the certificate chain sent by the peer, or NULL on error. */
|
||||
static ne_ssl_certificate *make_peers_chain(gnutls_session sock)
|
||||
{
|
||||
ne_ssl_certificate *current = NULL, *top = NULL;
|
||||
const gnutls_datum *certs;
|
||||
unsigned int n, count;
|
||||
|
||||
certs = gnutls_certificate_get_peers(sock, &count);
|
||||
if (!certs) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
ne_ssl_certificate *cert = ne_malloc(sizeof *cert);
|
||||
gnutls_x509_crt x5;
|
||||
|
||||
if (gnutls_x509_crt_init(&x5) ||
|
||||
gnutls_x509_crt_import(x5, &certs[n], GNUTLS_X509_FMT_DER)) {
|
||||
/* leak! */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
populate_cert(cert, x5);
|
||||
|
||||
if (top == NULL) {
|
||||
current = top = cert;
|
||||
} else {
|
||||
current->issuer = cert;
|
||||
current = cert;
|
||||
}
|
||||
}
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
/* Verifies an SSL server certificate. */
|
||||
static int check_certificate(ne_session *sess, gnutls_session sock,
|
||||
ne_ssl_certificate *chain)
|
||||
{
|
||||
time_t before, after, now = time(NULL);
|
||||
int ret, failures = 0;
|
||||
|
||||
before = gnutls_x509_crt_get_activation_time(chain->subject);
|
||||
after = gnutls_x509_crt_get_expiration_time(chain->subject);
|
||||
|
||||
if (now < before)
|
||||
failures |= NE_SSL_NOTYETVALID;
|
||||
else if (now > after)
|
||||
failures |= NE_SSL_EXPIRED;
|
||||
|
||||
ret = check_identity(sess->server.hostname, chain->subject, NULL);
|
||||
if (ret < 0) {
|
||||
ne_set_error(sess, _("Server certificate was missing commonName "
|
||||
"attribute in subject name"));
|
||||
return NE_ERROR;
|
||||
} else if (ret > 0) {
|
||||
failures |= NE_SSL_IDMISMATCH;
|
||||
}
|
||||
|
||||
if (gnutls_certificate_verify_peers(sock)) {
|
||||
failures |= NE_SSL_UNTRUSTED;
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Failures = %d\n", failures);
|
||||
|
||||
if (failures == 0) {
|
||||
ret = NE_OK;
|
||||
} else {
|
||||
#warning TODO: set up error string
|
||||
ret = NE_ERROR;
|
||||
if (sess->ssl_verify_fn
|
||||
&& sess->ssl_verify_fn(sess->ssl_verify_ud, failures, chain) == 0)
|
||||
ret = NE_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Negotiate an SSL connection. */
|
||||
int ne__negotiate_ssl(ne_request *req)
|
||||
{
|
||||
ne_session *const sess = ne_get_session(req);
|
||||
ne_ssl_context *const ctx = sess->ssl_context;
|
||||
ne_ssl_certificate *chain;
|
||||
gnutls_session sock;
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Negotiating SSL connection.\n");
|
||||
|
||||
if (ne_sock_connect_ssl(sess->socket, ctx, sess)) {
|
||||
ne_set_error(sess, _("SSL negotiation failed: %s"),
|
||||
ne_sock_error(sess->socket));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
sock = ne__sock_sslsock(sess->socket);
|
||||
|
||||
chain = make_peers_chain(sock);
|
||||
if (chain == NULL) {
|
||||
ne_set_error(sess, _("Server did not send certificate chain"));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
if (check_certificate(sess, sock, chain)) {
|
||||
ne_ssl_cert_free(chain);
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
return NE_OK;
|
||||
}
|
||||
|
||||
const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return &cert->issuer_dn;
|
||||
}
|
||||
|
||||
const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return &cert->subj_dn;
|
||||
}
|
||||
|
||||
const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return cert->issuer;
|
||||
}
|
||||
|
||||
const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return cert->identity;
|
||||
}
|
||||
|
||||
void ne_ssl_context_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert)
|
||||
{
|
||||
gnutls_x509_crt certs = cert->subject;
|
||||
gnutls_certificate_set_x509_trust(ctx->cred, &certs, 1);
|
||||
}
|
||||
|
||||
void ne_ssl_trust_default_ca(ne_session *sess)
|
||||
{
|
||||
#warning incomplete
|
||||
}
|
||||
|
||||
/* Read the contents of file FILENAME into *DATUM. */
|
||||
static int read_to_datum(const char *filename, gnutls_datum *datum)
|
||||
{
|
||||
FILE *f = fopen(filename, "r");
|
||||
ne_buffer *buf;
|
||||
char tmp[4192];
|
||||
size_t len;
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf = ne_buffer_ncreate(8192);
|
||||
while ((len = fread(tmp, 1, sizeof tmp, f)) > 0) {
|
||||
ne_buffer_append(buf, tmp, len);
|
||||
}
|
||||
|
||||
if (!feof(f)) {
|
||||
ne_buffer_destroy(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
datum->size = ne_buffer_size(buf);
|
||||
datum->data = ne_buffer_finish(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parses a PKCS#12 structure and loads the certificate, private key
|
||||
* and friendly name if possible. Returns zero on success, non-zero
|
||||
* on error. */
|
||||
static int pkcs12_parse(gnutls_pkcs12 p12, gnutls_x509_privkey *pkey,
|
||||
gnutls_x509_crt *x5, char **friendly_name,
|
||||
const char *password)
|
||||
{
|
||||
gnutls_pkcs12_bag bag = NULL;
|
||||
int i, j, ret = 0;
|
||||
|
||||
for (i = 0; ret == 0; ++i) {
|
||||
if (bag) gnutls_pkcs12_bag_deinit(bag);
|
||||
|
||||
ret = gnutls_pkcs12_bag_init(&bag);
|
||||
if (ret < 0) continue;
|
||||
|
||||
ret = gnutls_pkcs12_get_bag(p12, i, bag);
|
||||
if (ret < 0) continue;
|
||||
|
||||
gnutls_pkcs12_bag_decrypt(bag, password == NULL ? "" : password);
|
||||
|
||||
for (j = 0; ret == 0 && j < gnutls_pkcs12_bag_get_count(bag); ++j) {
|
||||
gnutls_pkcs12_bag_type type;
|
||||
gnutls_datum data;
|
||||
|
||||
if (friendly_name && *friendly_name == NULL) {
|
||||
char *name;
|
||||
gnutls_pkcs12_bag_get_friendly_name(bag, j, &name);
|
||||
if (name) {
|
||||
if (name[0] == '.') name++; /* weird GnuTLS bug? */
|
||||
*friendly_name = ne_strdup(name);
|
||||
}
|
||||
}
|
||||
|
||||
type = gnutls_pkcs12_bag_get_type(bag, j);
|
||||
switch (type) {
|
||||
case GNUTLS_BAG_PKCS8_KEY:
|
||||
case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY:
|
||||
gnutls_x509_privkey_init(pkey);
|
||||
|
||||
ret = gnutls_pkcs12_bag_get_data(bag, j, &data);
|
||||
if (ret < 0) continue;
|
||||
|
||||
ret = gnutls_x509_privkey_import_pkcs8(*pkey, &data,
|
||||
GNUTLS_X509_FMT_DER,
|
||||
password,
|
||||
0);
|
||||
if (ret < 0) continue;
|
||||
break;
|
||||
case GNUTLS_BAG_CERTIFICATE:
|
||||
gnutls_x509_crt_init(x5);
|
||||
|
||||
ret = gnutls_pkcs12_bag_get_data(bag, j, &data);
|
||||
if (ret < 0) continue;
|
||||
|
||||
ret = gnutls_x509_crt_import(*x5, &data, GNUTLS_X509_FMT_DER);
|
||||
if (ret < 0) continue;
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure last bag is freed */
|
||||
if (bag) gnutls_pkcs12_bag_deinit(bag);
|
||||
|
||||
/* Free in case of error */
|
||||
if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
|
||||
if (*x5) gnutls_x509_crt_deinit(*x5);
|
||||
if (*pkey) gnutls_x509_privkey_deinit(*pkey);
|
||||
if (friendly_name && *friendly_name) ne_free(*friendly_name);
|
||||
}
|
||||
|
||||
if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) ret = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename)
|
||||
{
|
||||
int ret;
|
||||
gnutls_datum data;
|
||||
gnutls_pkcs12 p12;
|
||||
ne_ssl_client_cert *cc;
|
||||
char *friendly_name = NULL;
|
||||
gnutls_x509_crt cert = NULL;
|
||||
gnutls_x509_privkey pkey = NULL;
|
||||
|
||||
if (read_to_datum(filename, &data))
|
||||
return NULL;
|
||||
|
||||
if (gnutls_pkcs12_init(&p12) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = gnutls_pkcs12_import(p12, &data, GNUTLS_X509_FMT_DER, 0);
|
||||
ne_free(data.data);
|
||||
if (ret < 0) {
|
||||
gnutls_pkcs12_deinit(p12);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = pkcs12_parse(p12, &pkey, &cert, &friendly_name, NULL);
|
||||
|
||||
if (gnutls_pkcs12_verify_mac(p12, "") == 0) {
|
||||
cc = ne_calloc(sizeof *cc);
|
||||
cc->pkey = pkey;
|
||||
cc->decrypted = 1;
|
||||
cc->friendly_name = friendly_name;
|
||||
populate_cert(&cc->cert, cert);
|
||||
gnutls_pkcs12_deinit(p12);
|
||||
cc->p12 = NULL;
|
||||
return cc;
|
||||
} else {
|
||||
if (ret == 0) {
|
||||
cc = ne_calloc(sizeof *cc);
|
||||
cc->friendly_name = friendly_name;
|
||||
cc->p12 = p12;
|
||||
return cc;
|
||||
} else {
|
||||
gnutls_pkcs12_deinit(p12);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *cc)
|
||||
{
|
||||
return !cc->decrypted;
|
||||
}
|
||||
|
||||
int ne_ssl_clicert_decrypt(ne_ssl_client_cert *cc, const char *password)
|
||||
{
|
||||
int ret;
|
||||
gnutls_x509_crt cert = NULL;
|
||||
gnutls_x509_privkey pkey = NULL;
|
||||
|
||||
if (gnutls_pkcs12_verify_mac(cc->p12, password) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = pkcs12_parse(cc->p12, &pkey, &cert, NULL, password);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
gnutls_pkcs12_deinit(cc->p12);
|
||||
populate_cert(&cc->cert, cert);
|
||||
cc->pkey = pkey;
|
||||
cc->decrypted = 1;
|
||||
cc->p12 = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *cc)
|
||||
{
|
||||
return &cc->cert;
|
||||
}
|
||||
|
||||
const char *ne_ssl_clicert_name(const ne_ssl_client_cert *ccert)
|
||||
{
|
||||
return ccert->friendly_name;
|
||||
}
|
||||
|
||||
ne_ssl_certificate *ne_ssl_cert_read(const char *filename)
|
||||
{
|
||||
int ret;
|
||||
gnutls_datum data;
|
||||
gnutls_x509_crt x5;
|
||||
|
||||
if (read_to_datum(filename, &data))
|
||||
return NULL;
|
||||
|
||||
if (gnutls_x509_crt_init(&x5) != 0)
|
||||
return NULL;
|
||||
|
||||
ret = gnutls_x509_crt_import(x5, &data, GNUTLS_X509_FMT_PEM);
|
||||
ne_free(data.data);
|
||||
if (ret < 0) {
|
||||
gnutls_x509_crt_deinit(x5);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), x5);
|
||||
}
|
||||
|
||||
int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename)
|
||||
{
|
||||
unsigned char buffer[10*1024];
|
||||
int len = sizeof buffer;
|
||||
|
||||
FILE *fp = fopen(filename, "w");
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
|
||||
if (gnutls_x509_crt_export(cert->subject, GNUTLS_X509_FMT_PEM, buffer,
|
||||
&len) < 0) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fwrite(buffer, len, 1, fp) != 1) {
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fclose(fp) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ne_ssl_cert_free(ne_ssl_certificate *cert)
|
||||
{
|
||||
gnutls_x509_crt_deinit(cert->subject);
|
||||
if (cert->identity) ne_free(cert->identity);
|
||||
if (cert->issuer) ne_ssl_cert_free(cert->issuer);
|
||||
ne_free(cert);
|
||||
}
|
||||
|
||||
int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, const ne_ssl_certificate *c2)
|
||||
{
|
||||
char digest1[NE_SSL_DIGESTLEN], digest2[NE_SSL_DIGESTLEN];
|
||||
|
||||
if (ne_ssl_cert_digest(c1, digest1) || ne_ssl_cert_digest(c2, digest2)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return strcmp(digest1, digest2);
|
||||
}
|
||||
|
||||
/* The certificate import/export format is the base64 encoding of the
|
||||
* raw DER; PEM without the newlines and wrapping. */
|
||||
|
||||
ne_ssl_certificate *ne_ssl_cert_import(const char *data)
|
||||
{
|
||||
int ret;
|
||||
size_t len;
|
||||
unsigned char *der;
|
||||
gnutls_datum buffer = { NULL, 0 };
|
||||
gnutls_x509_crt x5;
|
||||
|
||||
if (gnutls_x509_crt_init(&x5) != 0)
|
||||
return NULL;
|
||||
|
||||
/* decode the base64 to get the raw DER representation */
|
||||
len = ne_unbase64(data, &der);
|
||||
if (len == 0) return NULL;
|
||||
|
||||
buffer.data = der;
|
||||
buffer.size = len;
|
||||
|
||||
ret = gnutls_x509_crt_import(x5, &buffer, GNUTLS_X509_FMT_DER);
|
||||
ne_free(der);
|
||||
|
||||
if (ret < 0) {
|
||||
gnutls_x509_crt_deinit(x5);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), x5);
|
||||
}
|
||||
|
||||
char *ne_ssl_cert_export(const ne_ssl_certificate *cert)
|
||||
{
|
||||
unsigned char *der;
|
||||
size_t len = 0;
|
||||
char *ret;
|
||||
|
||||
/* find the length of the DER encoding. */
|
||||
if (gnutls_x509_crt_export(cert->subject, GNUTLS_X509_FMT_DER, NULL, &len) !=
|
||||
GNUTLS_E_SHORT_MEMORY_BUFFER) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
der = ne_malloc(len);
|
||||
if (gnutls_x509_crt_export(cert->subject, GNUTLS_X509_FMT_DER, der, &len)) {
|
||||
ne_free(der);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = ne_base64(der, len);
|
||||
ne_free(der);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest)
|
||||
{
|
||||
int j, len = 20;
|
||||
char sha1[20], *p;
|
||||
|
||||
if (gnutls_x509_crt_get_fingerprint(cert->subject, GNUTLS_DIG_SHA,
|
||||
sha1, &len) < 0)
|
||||
return -1;
|
||||
|
||||
for (j = 0, p = digest; j < 20; j++) {
|
||||
*p++ = NE_HEX2ASC((sha1[j] >> 4) & 0x0f);
|
||||
*p++ = NE_HEX2ASC(sha1[j] & 0x0f);
|
||||
*p++ = ':';
|
||||
}
|
||||
|
||||
*--p = '\0';
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Internationalization of neon
|
||||
Copyright (C) 1999-2003, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
void neon_i18n_init(void)
|
||||
{
|
||||
#if defined(ENABLE_NLS) && defined(NEON_IS_LIBRARY)
|
||||
/* if neon is build bundled in (i.e., not as a standalone
|
||||
* library), then there is probably no point in this, since the
|
||||
* messages won't be pointing in the right direction.
|
||||
* there's not really any point in doing this if neon is
|
||||
* a library since the messages aren't i18n'ized, but... */
|
||||
bindtextdomain("neon", LOCALEDIR);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Internationalization of neon
|
||||
Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NEON_I18N_H
|
||||
#define NEON_I18N_H
|
||||
|
||||
#undef _
|
||||
#ifdef ENABLE_NLS
|
||||
#include <libintl.h>
|
||||
#define _(str) gettext(str)
|
||||
#else
|
||||
#define _(str) (str)
|
||||
#endif /* ENABLE_NLS */
|
||||
#define N_(str) (str)
|
||||
|
||||
/* Initialize i18n in neon */
|
||||
void neon_i18n_init(void);
|
||||
|
||||
#endif /* NEON_I18N_H */
|
||||
@@ -0,0 +1,831 @@
|
||||
/*
|
||||
WebDAV Class 2 locking operations
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIMITS_H
|
||||
#include <limits.h>
|
||||
#endif
|
||||
|
||||
#include <ctype.h> /* for isdigit() */
|
||||
|
||||
#include "ne_alloc.h"
|
||||
|
||||
#include "ne_request.h"
|
||||
#include "ne_xml.h"
|
||||
#include "ne_locks.h"
|
||||
#include "ne_uri.h"
|
||||
#include "ne_basic.h"
|
||||
#include "ne_props.h"
|
||||
#include "ne_207.h"
|
||||
#include "ne_i18n.h"
|
||||
#include "ne_xmlreq.h"
|
||||
|
||||
#define HOOK_ID "http://webdav.org/neon/hooks/webdav-locking"
|
||||
|
||||
#define EOL "\r\n"
|
||||
|
||||
/* A list of lock objects. */
|
||||
struct lock_list {
|
||||
struct ne_lock *lock;
|
||||
struct lock_list *next, *prev;
|
||||
};
|
||||
|
||||
struct ne_lock_store_s {
|
||||
struct lock_list *locks;
|
||||
struct lock_list *cursor; /* current position in 'locks' */
|
||||
};
|
||||
|
||||
struct lh_req_cookie {
|
||||
const ne_lock_store *store;
|
||||
struct lock_list *submit;
|
||||
};
|
||||
|
||||
/* Context for PROPFIND/lockdiscovery callbacks */
|
||||
struct discover_ctx {
|
||||
ne_session *session;
|
||||
ne_lock_result results;
|
||||
void *userdata;
|
||||
ne_buffer *cdata;
|
||||
};
|
||||
|
||||
/* Context for handling LOCK response */
|
||||
struct lock_ctx {
|
||||
struct ne_lock active; /* activelock */
|
||||
ne_request *req; /* the request in question */
|
||||
char *token; /* the token we're after. */
|
||||
int found;
|
||||
ne_buffer *cdata;
|
||||
};
|
||||
|
||||
/* use the "application" state space. */
|
||||
#define ELM_LOCK_FIRST (NE_PROPS_STATE_TOP + 66)
|
||||
|
||||
#define ELM_lockdiscovery (ELM_LOCK_FIRST)
|
||||
#define ELM_activelock (ELM_LOCK_FIRST + 1)
|
||||
#define ELM_lockscope (ELM_LOCK_FIRST + 2)
|
||||
#define ELM_locktype (ELM_LOCK_FIRST + 3)
|
||||
#define ELM_depth (ELM_LOCK_FIRST + 4)
|
||||
#define ELM_owner (ELM_LOCK_FIRST + 5)
|
||||
#define ELM_timeout (ELM_LOCK_FIRST + 6)
|
||||
#define ELM_locktoken (ELM_LOCK_FIRST + 7)
|
||||
#define ELM_lockinfo (ELM_LOCK_FIRST + 8)
|
||||
#define ELM_write (ELM_LOCK_FIRST + 9)
|
||||
#define ELM_exclusive (ELM_LOCK_FIRST + 10)
|
||||
#define ELM_shared (ELM_LOCK_FIRST + 11)
|
||||
#define ELM_href (ELM_LOCK_FIRST + 12)
|
||||
#define ELM_prop (NE_207_STATE_PROP)
|
||||
|
||||
static const struct ne_xml_idmap element_map[] = {
|
||||
#define ELM(x) { "DAV:", #x, ELM_ ## x }
|
||||
ELM(lockdiscovery), ELM(activelock), ELM(prop), ELM(lockscope),
|
||||
ELM(locktype), ELM(depth), ELM(owner), ELM(timeout), ELM(locktoken),
|
||||
ELM(lockinfo), ELM(lockscope), ELM(locktype), ELM(write), ELM(exclusive),
|
||||
ELM(shared), ELM(href)
|
||||
/* no "lockentry" */
|
||||
#undef ELM
|
||||
};
|
||||
|
||||
static const ne_propname lock_props[] = {
|
||||
{ "DAV:", "lockdiscovery" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/* this simply registers the accessor for the function. */
|
||||
static void lk_create(ne_request *req, void *session,
|
||||
const char *method, const char *uri)
|
||||
{
|
||||
struct lh_req_cookie *lrc = ne_malloc(sizeof *lrc);
|
||||
lrc->store = session;
|
||||
lrc->submit = NULL;
|
||||
ne_set_request_private(req, HOOK_ID, lrc);
|
||||
}
|
||||
|
||||
static void lk_pre_send(ne_request *r, void *userdata, ne_buffer *req)
|
||||
{
|
||||
struct lh_req_cookie *lrc = ne_get_request_private(r, HOOK_ID);
|
||||
|
||||
if (lrc->submit != NULL) {
|
||||
struct lock_list *item;
|
||||
|
||||
/* Add in the If header */
|
||||
ne_buffer_zappend(req, "If:");
|
||||
for (item = lrc->submit; item != NULL; item = item->next) {
|
||||
char *uri = ne_uri_unparse(&item->lock->uri);
|
||||
ne_buffer_concat(req, " <", uri, "> (<",
|
||||
item->lock->token, ">)", NULL);
|
||||
ne_free(uri);
|
||||
}
|
||||
ne_buffer_zappend(req, EOL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert 'lock' into lock list *list. */
|
||||
static void insert_lock(struct lock_list **list, struct ne_lock *lock)
|
||||
{
|
||||
struct lock_list *item = ne_malloc(sizeof *item);
|
||||
if (*list != NULL) {
|
||||
(*list)->prev = item;
|
||||
}
|
||||
item->prev = NULL;
|
||||
item->next = *list;
|
||||
item->lock = lock;
|
||||
*list = item;
|
||||
}
|
||||
|
||||
static void free_list(struct lock_list *list, int destroy)
|
||||
{
|
||||
struct lock_list *next;
|
||||
|
||||
while (list != NULL) {
|
||||
next = list->next;
|
||||
if (destroy)
|
||||
ne_lock_destroy(list->lock);
|
||||
ne_free(list);
|
||||
list = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void lk_destroy(ne_request *req, void *userdata)
|
||||
{
|
||||
struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID);
|
||||
free_list(lrc->submit, 0);
|
||||
ne_free(lrc);
|
||||
}
|
||||
|
||||
void ne_lockstore_destroy(ne_lock_store *store)
|
||||
{
|
||||
free_list(store->locks, 1);
|
||||
ne_free(store);
|
||||
}
|
||||
|
||||
ne_lock_store *ne_lockstore_create(void)
|
||||
{
|
||||
return ne_calloc(sizeof(ne_lock_store));
|
||||
}
|
||||
|
||||
#define CURSOR_RET(s) ((s)->cursor?(s)->cursor->lock:NULL)
|
||||
|
||||
struct ne_lock *ne_lockstore_first(ne_lock_store *store)
|
||||
{
|
||||
store->cursor = store->locks;
|
||||
return CURSOR_RET(store);
|
||||
}
|
||||
|
||||
struct ne_lock *ne_lockstore_next(ne_lock_store *store)
|
||||
{
|
||||
store->cursor = store->cursor->next;
|
||||
return CURSOR_RET(store);
|
||||
}
|
||||
|
||||
void ne_lockstore_register(ne_lock_store *store, ne_session *sess)
|
||||
{
|
||||
/* Register the hooks */
|
||||
ne_hook_create_request(sess, lk_create, store);
|
||||
ne_hook_pre_send(sess, lk_pre_send, store);
|
||||
ne_hook_destroy_request(sess, lk_destroy, store);
|
||||
}
|
||||
|
||||
/* Submit the given lock for the given URI */
|
||||
static void submit_lock(struct lh_req_cookie *lrc, struct ne_lock *lock)
|
||||
{
|
||||
struct lock_list *item;
|
||||
|
||||
/* Check for dups */
|
||||
for (item = lrc->submit; item != NULL; item = item->next) {
|
||||
if (strcasecmp(item->lock->token, lock->token) == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
insert_lock(&lrc->submit, lock);
|
||||
}
|
||||
|
||||
struct ne_lock *ne_lockstore_findbyuri(ne_lock_store *store,
|
||||
const ne_uri *uri)
|
||||
{
|
||||
struct lock_list *cur;
|
||||
|
||||
for (cur = store->locks; cur != NULL; cur = cur->next) {
|
||||
if (ne_uri_cmp(&cur->lock->uri, uri) == 0) {
|
||||
return cur->lock;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ne_lock_using_parent(ne_request *req, const char *path)
|
||||
{
|
||||
struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID);
|
||||
ne_uri u;
|
||||
struct lock_list *item;
|
||||
char *parent;
|
||||
|
||||
if (lrc == NULL)
|
||||
return;
|
||||
|
||||
parent = ne_path_parent(path);
|
||||
if (parent == NULL)
|
||||
return;
|
||||
|
||||
u.authinfo = NULL;
|
||||
ne_fill_server_uri(ne_get_session(req), &u);
|
||||
|
||||
for (item = lrc->store->locks; item != NULL; item = item->next) {
|
||||
|
||||
/* Only care about locks which are on this server. */
|
||||
u.path = item->lock->uri.path;
|
||||
if (ne_uri_cmp(&u, &item->lock->uri))
|
||||
continue;
|
||||
|
||||
/* This lock is needed if it is an infinite depth lock which
|
||||
* covers the parent, or a lock on the parent itself. */
|
||||
if ((item->lock->depth == NE_DEPTH_INFINITE &&
|
||||
ne_path_childof(item->lock->uri.path, parent)) ||
|
||||
ne_path_compare(item->lock->uri.path, parent) == 0) {
|
||||
NE_DEBUG(NE_DBG_LOCKS, "Locked parent, %s on %s\n",
|
||||
item->lock->token, item->lock->uri.path);
|
||||
submit_lock(lrc, item->lock);
|
||||
}
|
||||
}
|
||||
|
||||
u.path = parent; /* handy: makes u.path valid and ne_free(parent). */
|
||||
ne_uri_free(&u);
|
||||
}
|
||||
|
||||
void ne_lock_using_resource(ne_request *req, const char *uri, int depth)
|
||||
{
|
||||
struct lh_req_cookie *lrc = ne_get_request_private(req, HOOK_ID);
|
||||
struct lock_list *item;
|
||||
int match;
|
||||
|
||||
if (lrc == NULL)
|
||||
return;
|
||||
|
||||
/* Iterate over the list of stored locks to see if any of them
|
||||
* apply to this resource */
|
||||
for (item = lrc->store->locks; item != NULL; item = item->next) {
|
||||
|
||||
match = 0;
|
||||
|
||||
if (depth == NE_DEPTH_INFINITE &&
|
||||
ne_path_childof(uri, item->lock->uri.path)) {
|
||||
/* Case 1: this is a depth-infinity request which will
|
||||
* modify a lock somewhere inside the collection. */
|
||||
NE_DEBUG(NE_DBG_LOCKS, "Has child: %s\n", item->lock->token);
|
||||
match = 1;
|
||||
}
|
||||
else if (ne_path_compare(uri, item->lock->uri.path) == 0) {
|
||||
/* Case 2: this request is directly on a locked resource */
|
||||
NE_DEBUG(NE_DBG_LOCKS, "Has direct lock: %s\n", item->lock->token);
|
||||
match = 1;
|
||||
}
|
||||
else if (item->lock->depth == NE_DEPTH_INFINITE &&
|
||||
ne_path_childof(item->lock->uri.path, uri)) {
|
||||
/* Case 3: there is a higher-up infinite-depth lock which
|
||||
* covers the resource that this request will modify. */
|
||||
NE_DEBUG(NE_DBG_LOCKS, "Is child of: %s\n", item->lock->token);
|
||||
match = 1;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
submit_lock(lrc, item->lock);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ne_lockstore_add(ne_lock_store *store, struct ne_lock *lock)
|
||||
{
|
||||
insert_lock(&store->locks, lock);
|
||||
}
|
||||
|
||||
void ne_lockstore_remove(ne_lock_store *store, struct ne_lock *lock)
|
||||
{
|
||||
struct lock_list *item;
|
||||
|
||||
/* Find the lock */
|
||||
for (item = store->locks; item != NULL; item = item->next)
|
||||
if (item->lock == lock)
|
||||
break;
|
||||
|
||||
if (item->prev != NULL) {
|
||||
item->prev->next = item->next;
|
||||
} else {
|
||||
store->locks = item->next;
|
||||
}
|
||||
if (item->next != NULL) {
|
||||
item->next->prev = item->prev;
|
||||
}
|
||||
ne_free(item);
|
||||
}
|
||||
|
||||
struct ne_lock *ne_lock_copy(const struct ne_lock *lock)
|
||||
{
|
||||
struct ne_lock *ret = ne_calloc(sizeof *ret);
|
||||
|
||||
ret->uri.path = ne_strdup(lock->uri.path);
|
||||
ret->uri.host = ne_strdup(lock->uri.host);
|
||||
ret->uri.scheme = ne_strdup(lock->uri.scheme);
|
||||
ret->uri.port = lock->uri.port;
|
||||
ret->token = ne_strdup(lock->token);
|
||||
ret->depth = lock->depth;
|
||||
ret->type = lock->type;
|
||||
ret->scope = lock->scope;
|
||||
if (lock->owner) ret->owner = ne_strdup(lock->owner);
|
||||
ret->timeout = lock->timeout;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct ne_lock *ne_lock_create(void)
|
||||
{
|
||||
struct ne_lock *lock = ne_calloc(sizeof *lock);
|
||||
lock->depth = NE_DEPTH_ZERO;
|
||||
lock->type = ne_locktype_write;
|
||||
lock->scope = ne_lockscope_exclusive;
|
||||
lock->timeout = NE_TIMEOUT_INVALID;
|
||||
return lock;
|
||||
}
|
||||
|
||||
void ne_lock_free(struct ne_lock *lock)
|
||||
{
|
||||
ne_uri_free(&lock->uri);
|
||||
if (lock->owner) {
|
||||
ne_free(lock->owner);
|
||||
lock->owner = NULL;
|
||||
}
|
||||
if (lock->token) {
|
||||
ne_free(lock->token);
|
||||
lock->token = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void ne_lock_destroy(struct ne_lock *lock)
|
||||
{
|
||||
ne_lock_free(lock);
|
||||
ne_free(lock);
|
||||
}
|
||||
|
||||
int ne_unlock(ne_session *sess, const struct ne_lock *lock)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "UNLOCK", lock->uri.path);
|
||||
int ret;
|
||||
|
||||
ne_print_request_header(req, "Lock-Token", "<%s>", lock->token);
|
||||
|
||||
/* UNLOCK of a lock-null resource removes the resource from the
|
||||
* parent collection; so an UNLOCK may modify the parent
|
||||
* collection. (somewhat counter-intuitive, and not easily derived
|
||||
* from 2518.) */
|
||||
ne_lock_using_parent(req, lock->uri.path);
|
||||
|
||||
ret = ne_request_dispatch(req);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass != 2) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
|
||||
ne_request_destroy(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_depth(const char *depth)
|
||||
{
|
||||
if (strcasecmp(depth, "infinity") == 0) {
|
||||
return NE_DEPTH_INFINITE;
|
||||
} else if (isdigit(depth[0])) {
|
||||
return atoi(depth);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static long parse_timeout(const char *timeout)
|
||||
{
|
||||
if (strcasecmp(timeout, "infinite") == 0) {
|
||||
return NE_TIMEOUT_INFINITE;
|
||||
} else if (strncasecmp(timeout, "Second-", 7) == 0) {
|
||||
long to = strtol(timeout+7, NULL, 10);
|
||||
if (to == LONG_MIN || to == LONG_MAX)
|
||||
return NE_TIMEOUT_INVALID;
|
||||
return to;
|
||||
} else {
|
||||
return NE_TIMEOUT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static void discover_results(void *userdata, const char *href,
|
||||
const ne_prop_result_set *set)
|
||||
{
|
||||
struct discover_ctx *ctx = userdata;
|
||||
struct ne_lock *lock = ne_propset_private(set);
|
||||
const ne_status *status = ne_propset_status(set, &lock_props[0]);
|
||||
|
||||
/* Require at least that the lock has a token. */
|
||||
if (lock->token) {
|
||||
if (status && status->klass != 2) {
|
||||
ctx->results(ctx->userdata, NULL, lock->uri.path, status);
|
||||
} else {
|
||||
ctx->results(ctx->userdata, lock, lock->uri.path, NULL);
|
||||
}
|
||||
}
|
||||
else if (status) {
|
||||
ctx->results(ctx->userdata, NULL, href, status);
|
||||
}
|
||||
|
||||
ne_lock_destroy(lock);
|
||||
|
||||
NE_DEBUG(NE_DBG_LOCKS, "End of response for %s\n", href);
|
||||
}
|
||||
|
||||
static int
|
||||
end_element_common(struct ne_lock *l, int state, const char *cdata)
|
||||
{
|
||||
switch (state) {
|
||||
case ELM_write:
|
||||
l->type = ne_locktype_write;
|
||||
break;
|
||||
case ELM_exclusive:
|
||||
l->scope = ne_lockscope_exclusive;
|
||||
break;
|
||||
case ELM_shared:
|
||||
l->scope = ne_lockscope_shared;
|
||||
break;
|
||||
case ELM_depth:
|
||||
NE_DEBUG(NE_DBG_LOCKS, "Got depth: %s\n", cdata);
|
||||
l->depth = parse_depth(cdata);
|
||||
if (l->depth == -1) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case ELM_timeout:
|
||||
NE_DEBUG(NE_DBG_LOCKS, "Got timeout: %s\n", cdata);
|
||||
l->timeout = parse_timeout(cdata);
|
||||
if (l->timeout == NE_TIMEOUT_INVALID) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case ELM_owner:
|
||||
l->owner = strdup(cdata);
|
||||
break;
|
||||
case ELM_href:
|
||||
l->token = strdup(cdata);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* End-element handler for lock discovery PROPFIND response */
|
||||
static int end_element_ldisc(void *userdata, int state,
|
||||
const char *nspace, const char *name)
|
||||
{
|
||||
struct ne_lock *lock = ne_propfind_current_private(userdata);
|
||||
struct discover_ctx *ctx = userdata;
|
||||
|
||||
return end_element_common(lock, state, ctx->cdata->data);
|
||||
}
|
||||
|
||||
static inline int can_accept(int parent, int id)
|
||||
{
|
||||
return (parent == NE_XML_STATEROOT && id == ELM_prop) ||
|
||||
(parent == ELM_prop && id == ELM_lockdiscovery) ||
|
||||
(parent == ELM_lockdiscovery && id == ELM_activelock) ||
|
||||
(parent == ELM_activelock &&
|
||||
(id == ELM_lockscope || id == ELM_locktype ||
|
||||
id == ELM_depth || id == ELM_owner ||
|
||||
id == ELM_timeout || id == ELM_locktoken)) ||
|
||||
(parent == ELM_lockscope &&
|
||||
(id == ELM_exclusive || id == ELM_shared)) ||
|
||||
(parent == ELM_locktype && id == ELM_write) ||
|
||||
(parent == ELM_locktoken && id == ELM_href);
|
||||
}
|
||||
|
||||
static int ld_startelm(void *userdata, int parent,
|
||||
const char *nspace, const char *name,
|
||||
const char **atts)
|
||||
{
|
||||
struct discover_ctx *ctx = userdata;
|
||||
int id = ne_xml_mapid(element_map, NE_XML_MAPLEN(element_map),
|
||||
nspace, name);
|
||||
|
||||
ne_buffer_clear(ctx->cdata);
|
||||
|
||||
if (can_accept(parent, id))
|
||||
return id;
|
||||
else
|
||||
return NE_XML_DECLINE;
|
||||
}
|
||||
|
||||
#define MAX_CDATA (256)
|
||||
|
||||
static int lk_cdata(void *userdata, int state,
|
||||
const char *cdata, size_t len)
|
||||
{
|
||||
struct lock_ctx *ctx = userdata;
|
||||
|
||||
if (ctx->cdata->used + len < MAX_CDATA)
|
||||
ne_buffer_append(ctx->cdata, cdata, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ld_cdata(void *userdata, int state,
|
||||
const char *cdata, size_t len)
|
||||
{
|
||||
struct discover_ctx *ctx = userdata;
|
||||
|
||||
if (ctx->cdata->used + len < MAX_CDATA)
|
||||
ne_buffer_append(ctx->cdata, cdata, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lk_startelm(void *userdata, int parent,
|
||||
const char *nspace, const char *name,
|
||||
const char **atts)
|
||||
{
|
||||
struct lock_ctx *ctx = userdata;
|
||||
int id;
|
||||
|
||||
id = ne_xml_mapid(element_map, NE_XML_MAPLEN(element_map), nspace, name);
|
||||
|
||||
NE_DEBUG(NE_DBG_LOCKS, "lk_startelm: %s => %d\n", name, id);
|
||||
|
||||
if (id == 0)
|
||||
return NE_XML_DECLINE;
|
||||
|
||||
if (parent == 0 && ctx->token == NULL) {
|
||||
const char *token = ne_get_response_header(ctx->req, "Lock-Token");
|
||||
/* at the root element; retrieve the Lock-Token header,
|
||||
* and bail if it wasn't given. */
|
||||
if (token == NULL) {
|
||||
ne_set_error(ne_get_session(ctx->req), "%s",
|
||||
_("LOCK response missing Lock-Token header"));
|
||||
return NE_XML_ABORT;
|
||||
}
|
||||
|
||||
if (token[0] == '<') token++;
|
||||
ctx->token = ne_strdup(token);
|
||||
ne_shave(ctx->token, ">");
|
||||
NE_DEBUG(NE_DBG_LOCKS, "lk_startelm: Finding token %s\n",
|
||||
ctx->token);
|
||||
}
|
||||
|
||||
/* TODO: only accept 'prop' as root for LOCK response */
|
||||
if (!can_accept(parent, id))
|
||||
return NE_XML_DECLINE;
|
||||
|
||||
if (id == ELM_activelock && !ctx->found) {
|
||||
/* a new activelock */
|
||||
ne_lock_free(&ctx->active);
|
||||
memset(&ctx->active, 0, sizeof ctx->active);
|
||||
ctx->active.timeout = NE_TIMEOUT_INVALID;
|
||||
}
|
||||
|
||||
ne_buffer_clear(ctx->cdata);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/* End-element handler for LOCK response */
|
||||
static int lk_endelm(void *userdata, int state,
|
||||
const char *nspace, const char *name)
|
||||
{
|
||||
struct lock_ctx *ctx = userdata;
|
||||
|
||||
if (ctx->found)
|
||||
return 0;
|
||||
|
||||
if (end_element_common(&ctx->active, state, ctx->cdata->data))
|
||||
return -1;
|
||||
|
||||
if (state == ELM_activelock) {
|
||||
if (ctx->active.token && strcmp(ctx->active.token, ctx->token) == 0) {
|
||||
ctx->found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *ld_create(void *userdata, const char *href)
|
||||
{
|
||||
struct discover_ctx *ctx = userdata;
|
||||
struct ne_lock *lk = ne_lock_create();
|
||||
|
||||
if (ne_uri_parse(href, &lk->uri) != 0) {
|
||||
ne_lock_destroy(lk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!lk->uri.host)
|
||||
ne_fill_server_uri(ctx->session, &lk->uri);
|
||||
|
||||
return lk;
|
||||
}
|
||||
|
||||
/* Discover all locks on URI */
|
||||
int ne_lock_discover(ne_session *sess, const char *uri,
|
||||
ne_lock_result callback, void *userdata)
|
||||
{
|
||||
ne_propfind_handler *handler;
|
||||
struct discover_ctx ctx = {0};
|
||||
int ret;
|
||||
|
||||
ctx.results = callback;
|
||||
ctx.userdata = userdata;
|
||||
ctx.session = sess;
|
||||
ctx.cdata = ne_buffer_create();
|
||||
|
||||
handler = ne_propfind_create(sess, uri, NE_DEPTH_ZERO);
|
||||
|
||||
ne_propfind_set_private(handler, ld_create, &ctx);
|
||||
|
||||
ne_xml_push_handler(ne_propfind_get_parser(handler),
|
||||
ld_startelm, ld_cdata, end_element_ldisc, handler);
|
||||
|
||||
ret = ne_propfind_named(handler, lock_props, discover_results, &ctx);
|
||||
|
||||
ne_buffer_destroy(ctx.cdata);
|
||||
ne_propfind_destroy(handler);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void add_timeout_header(ne_request *req, long timeout)
|
||||
{
|
||||
if (timeout == NE_TIMEOUT_INFINITE) {
|
||||
ne_add_request_header(req, "Timeout", "Infinite");
|
||||
}
|
||||
else if (timeout != NE_TIMEOUT_INVALID && timeout > 0) {
|
||||
ne_print_request_header(req, "Timeout", "Second-%ld", timeout);
|
||||
}
|
||||
/* just ignore it if timeout == 0 or invalid. */
|
||||
}
|
||||
|
||||
int ne_lock(ne_session *sess, struct ne_lock *lock)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "LOCK", lock->uri.path);
|
||||
ne_buffer *body = ne_buffer_create();
|
||||
ne_xml_parser *parser = ne_xml_create();
|
||||
int ret, parse_failed;
|
||||
struct lock_ctx ctx;
|
||||
|
||||
memset(&ctx, 0, sizeof ctx);
|
||||
ctx.cdata = ne_buffer_create();
|
||||
ctx.req = req;
|
||||
|
||||
ne_xml_push_handler(parser, lk_startelm, lk_cdata, lk_endelm, &ctx);
|
||||
|
||||
/* Create the body */
|
||||
ne_buffer_concat(body, "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL
|
||||
"<lockinfo xmlns='DAV:'>" EOL " <lockscope>",
|
||||
lock->scope==ne_lockscope_exclusive?
|
||||
"<exclusive/>":"<shared/>",
|
||||
"</lockscope>" EOL
|
||||
"<locktype><write/></locktype>", NULL);
|
||||
|
||||
if (lock->owner) {
|
||||
ne_buffer_concat(body, "<owner>", lock->owner, "</owner>" EOL, NULL);
|
||||
}
|
||||
ne_buffer_zappend(body, "</lockinfo>" EOL);
|
||||
|
||||
ne_set_request_body_buffer(req, body->data, ne_buffer_size(body));
|
||||
ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
|
||||
ne_add_depth_header(req, lock->depth);
|
||||
add_timeout_header(req, lock->timeout);
|
||||
|
||||
/* TODO:
|
||||
* By 2518, we need this only if we are creating a lock-null resource.
|
||||
* Since we don't KNOW whether the lock we're given is a lock-null
|
||||
* or not, we cover our bases.
|
||||
*/
|
||||
ne_lock_using_parent(req, lock->uri.path);
|
||||
/* This one is clearer from 2518 sec 8.10.4. */
|
||||
ne_lock_using_resource(req, lock->uri.path, lock->depth);
|
||||
|
||||
ret = ne_xml_dispatch_request(req, parser);
|
||||
|
||||
ne_buffer_destroy(body);
|
||||
ne_buffer_destroy(ctx.cdata);
|
||||
parse_failed = ne_xml_failed(parser);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass == 2) {
|
||||
if (ctx.token == NULL) {
|
||||
ret = NE_ERROR;
|
||||
ne_set_error(sess, _("No Lock-Token header given"));
|
||||
}
|
||||
else if (parse_failed) {
|
||||
ret = NE_ERROR;
|
||||
ne_set_error(sess, "%s", ne_xml_get_error(parser));
|
||||
}
|
||||
else if (ne_get_status(req)->code == 207) {
|
||||
ret = NE_ERROR;
|
||||
/* TODO: set the error string appropriately */
|
||||
}
|
||||
else if (ctx.found) {
|
||||
/* it worked: copy over real lock details if given. */
|
||||
if (lock->token) ne_free(lock->token);
|
||||
lock->token = ctx.token;
|
||||
ctx.token = NULL;
|
||||
if (ctx.active.timeout != NE_TIMEOUT_INVALID)
|
||||
lock->timeout = ctx.active.timeout;
|
||||
lock->scope = ctx.active.scope;
|
||||
lock->type = ctx.active.type;
|
||||
if (ctx.active.depth >= 0)
|
||||
lock->depth = ctx.active.depth;
|
||||
if (ctx.active.owner) {
|
||||
if (lock->owner) ne_free(lock->owner);
|
||||
lock->owner = ctx.active.owner;
|
||||
ctx.active.owner = NULL;
|
||||
}
|
||||
} else {
|
||||
ret = NE_ERROR;
|
||||
ne_set_error(sess, _("Response missing activelock for %s"),
|
||||
ctx.token);
|
||||
}
|
||||
} else if (ret == NE_OK /* && status != 2xx */) {
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
|
||||
ne_lock_free(&ctx.active);
|
||||
if (ctx.token) ne_free(ctx.token);
|
||||
ne_request_destroy(req);
|
||||
ne_xml_destroy(parser);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ne_lock_refresh(ne_session *sess, struct ne_lock *lock)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "LOCK", lock->uri.path);
|
||||
ne_xml_parser *parser = ne_xml_create();
|
||||
int ret;
|
||||
struct lock_ctx ctx;
|
||||
|
||||
memset(&ctx, 0, sizeof ctx);
|
||||
ctx.cdata = ne_buffer_create();
|
||||
ctx.req = req;
|
||||
ctx.token = lock->token;
|
||||
|
||||
/* Handle the response and update *lock appropriately. */
|
||||
ne_xml_push_handler(parser, lk_startelm, lk_cdata, lk_endelm, &ctx);
|
||||
|
||||
/* For a lock refresh, submitting only this lock token must be
|
||||
* sufficient. */
|
||||
ne_print_request_header(req, "If", "(<%s>)", lock->token);
|
||||
add_timeout_header(req, lock->timeout);
|
||||
|
||||
ret = ne_xml_dispatch_request(req, parser);
|
||||
|
||||
if (ret == NE_OK) {
|
||||
if (ne_get_status(req)->klass != 2) {
|
||||
ret = NE_ERROR; /* and use default session error */
|
||||
} else if (ne_xml_failed(parser)) {
|
||||
ret = NE_ERROR;
|
||||
ne_set_error(sess, "%s", ne_xml_get_error(parser));
|
||||
} else if (!ctx.found) {
|
||||
ne_set_error(sess, _("No activelock for <%s> returned in "
|
||||
"LOCK refresh response"), lock->token);
|
||||
ret = NE_ERROR;
|
||||
} else /* success! */ {
|
||||
/* update timeout for passed-in lock structure. */
|
||||
lock->timeout = ctx.active.timeout;
|
||||
}
|
||||
}
|
||||
|
||||
ne_lock_free(&ctx.active);
|
||||
ne_buffer_destroy(ctx.cdata);
|
||||
ne_request_destroy(req);
|
||||
ne_xml_destroy(parser);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
WebDAV Class 2 locking operations
|
||||
Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_LOCKS_H
|
||||
#define NE_LOCKS_H
|
||||
|
||||
#include "ne_request.h" /* for ne_session + ne_request */
|
||||
#include "ne_uri.h" /* for ne_uri */
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* The scope of a lock */
|
||||
enum ne_lock_scope {
|
||||
ne_lockscope_exclusive,
|
||||
ne_lockscope_shared
|
||||
};
|
||||
|
||||
/* Lock type. Only write locks are defined in RFC2518. */
|
||||
enum ne_lock_type {
|
||||
ne_locktype_write
|
||||
};
|
||||
|
||||
/* A lock object. */
|
||||
struct ne_lock {
|
||||
ne_uri uri;
|
||||
int depth; /* the depth of the lock (NE_DEPTH_*). */
|
||||
enum ne_lock_type type;
|
||||
enum ne_lock_scope scope;
|
||||
char *token; /* the lock token: uniquely identifies this lock. */
|
||||
char *owner; /* string describing the owner of the lock. */
|
||||
long timeout; /* timeout in seconds. (or NE_TIMEOUT_*) */
|
||||
};
|
||||
/* NB: struct ne_lock Would be typedef'ed to ne_lock except lock is
|
||||
* a verb and a noun, so we already have ne_lock the function. Damn
|
||||
* the English language. */
|
||||
|
||||
#define NE_TIMEOUT_INFINITE -1
|
||||
#define NE_TIMEOUT_INVALID -2
|
||||
|
||||
/* Create a depth zero, exclusive write lock, with default timeout
|
||||
* (allowing a server to pick a default). token, owner and uri are
|
||||
* unset. */
|
||||
struct ne_lock *ne_lock_create(void);
|
||||
|
||||
/* HINT: to initialize uri host/port/scheme for the lock's URI, use
|
||||
* ne_fill_server_uri from ne_session.h. */
|
||||
|
||||
/* Deep-copy a lock structure: strdup's any of path, token, owner,
|
||||
* hostport which are set. */
|
||||
struct ne_lock *ne_lock_copy(const struct ne_lock *lock);
|
||||
|
||||
/* Free a lock structure; free's any of any of the URI, token and
|
||||
* owner which are set, but not the lock object itself. */
|
||||
void ne_lock_free(struct ne_lock *lock);
|
||||
|
||||
/* Like ne_lock_free; but free's the lock object itself too. */
|
||||
void ne_lock_destroy(struct ne_lock *lock);
|
||||
|
||||
/* ne_lock_store: an opaque type which is used to store a set of lock
|
||||
* objects. */
|
||||
typedef struct ne_lock_store_s ne_lock_store;
|
||||
|
||||
/* Create a lock store. */
|
||||
ne_lock_store *ne_lockstore_create(void);
|
||||
|
||||
/* Register the lock store 'store' with the HTTP session 'sess': any
|
||||
* operations made using 'sess' which operate on a locked resource,
|
||||
* can use the locks from 'store' if needed. */
|
||||
void ne_lockstore_register(ne_lock_store *store, ne_session *sess);
|
||||
|
||||
/* Destroy a lock store, free'ing any locks remaining inside. */
|
||||
void ne_lockstore_destroy(ne_lock_store *store);
|
||||
|
||||
/* Add a lock to the store: the store then "owns" the lock object, and
|
||||
* you must not free it. The lock MUST have all of:
|
||||
* - a completed URI structure: scheme, host, port, and path all set
|
||||
* - a valid lock token
|
||||
* - a valid depth
|
||||
*/
|
||||
void ne_lockstore_add(ne_lock_store *store, struct ne_lock *lock);
|
||||
|
||||
/* Remove given lock object from store: 'lock' MUST point to a lock
|
||||
* object which is known to be in the store. */
|
||||
void ne_lockstore_remove(ne_lock_store *store, struct ne_lock *lock);
|
||||
|
||||
/* Returns the first lock in the lock store, or NULL if the store is
|
||||
* empty. */
|
||||
struct ne_lock *ne_lockstore_first(ne_lock_store *store);
|
||||
|
||||
/* After ne_lockstore_first has been called; returns the next lock in
|
||||
* the lock store, or NULL if there are no more locks stored.
|
||||
* Behaviour is undefined if ne_lockstore_first has not been called on
|
||||
* 'store' since the store was created, or the last time this function
|
||||
* returned NULL for the store.. */
|
||||
struct ne_lock *ne_lockstore_next(ne_lock_store *store);
|
||||
|
||||
/* Find a lock in the store for the given server, and with the given
|
||||
* path. */
|
||||
struct ne_lock *ne_lockstore_findbyuri(ne_lock_store *store,
|
||||
const ne_uri *uri);
|
||||
|
||||
/* Issue a LOCK request for the given lock. Requires that the uri,
|
||||
* depth, type, scope, and timeout members of 'lock' are filled in.
|
||||
* owner and token must be malloc-allocated if not NULL; and may be
|
||||
* free()d by this function. On successful return, lock->token will
|
||||
* contain the lock token. */
|
||||
int ne_lock(ne_session *sess, struct ne_lock *lock);
|
||||
|
||||
/* Issue an UNLOCK request for the given lock */
|
||||
int ne_unlock(ne_session *sess, const struct ne_lock *lock);
|
||||
|
||||
/* Refresh a lock. Updates lock->timeout appropriately. */
|
||||
int ne_lock_refresh(ne_session *sess, struct ne_lock *lock);
|
||||
|
||||
/* Callback for lock discovery. If 'lock' is NULL, something went
|
||||
* wrong performing lockdiscovery for the resource, look at 'status'
|
||||
* for the details.
|
||||
*
|
||||
* If lock is non-NULL, at least lock->uri and lock->token will be
|
||||
* filled in; and status will be NULL. */
|
||||
typedef void (*ne_lock_result)(void *userdata, const struct ne_lock *lock,
|
||||
const char *uri, const ne_status *status);
|
||||
|
||||
/* Perform lock discovery on the given path. 'result' is called with
|
||||
* the results (possibly >1 times). */
|
||||
int ne_lock_discover(ne_session *sess, const char *path,
|
||||
ne_lock_result result, void *userdata);
|
||||
|
||||
|
||||
/* The ne_lock_using_* functions should be used before dispatching a
|
||||
* request which modify resources. If a lock store has been
|
||||
* registered with the session associated with the request, and locks
|
||||
* are present in the lock store which cover the resources which are
|
||||
* being modified by the request, then the appropriate lock tokens are
|
||||
* submitted in the request headers. */
|
||||
|
||||
/* Indicate that request 'req' will modify the resource at 'path', and
|
||||
* is an operation of given 'depth'. */
|
||||
void ne_lock_using_resource(ne_request *req, const char *path, int depth);
|
||||
|
||||
/* Indicate that request 'req' will modify the parent collection of
|
||||
* the resource found at 'path' (for instance when removing the
|
||||
* resource from the collection). */
|
||||
void ne_lock_using_parent(ne_request *req, const char *path);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_LOCKS_H */
|
||||
@@ -0,0 +1,432 @@
|
||||
/* md5.c - Functions to compute MD5 message digest of files or memory blocks
|
||||
according to the definition of MD5 in RFC 1321 from April 1992.
|
||||
Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
|
||||
This file is part of the GNU C Library.
|
||||
|
||||
The GNU C Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
The GNU C Library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with the GNU C Library; see the file COPYING.LIB. If not,
|
||||
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
Boston, MA 02111-1307, USA. */
|
||||
|
||||
/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995. */
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#if STDC_HEADERS || defined _LIBC
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
#else
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
# ifndef HAVE_MEMCPY
|
||||
# define memcpy(d, s, n) bcopy ((s), (d), (n))
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <ctype.h> /* for tolower */
|
||||
|
||||
#include "ne_md5.h"
|
||||
#include "ne_string.h" /* for NE_ASC2HEX */
|
||||
|
||||
#ifdef _LIBC
|
||||
# include <endian.h>
|
||||
# if __BYTE_ORDER == __BIG_ENDIAN
|
||||
# define WORDS_BIGENDIAN 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define md5_init_ctx ne_md5_init_ctx
|
||||
#define md5_process_block ne_md5_process_block
|
||||
#define md5_process_bytes ne_md5_process_bytes
|
||||
#define md5_finish_ctx ne_md5_finish_ctx
|
||||
#define md5_read_ctx ne_md5_read_ctx
|
||||
#define md5_stream ne_md5_stream
|
||||
#define md5_ctx ne_md5_ctx
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
# define SWAP(n) \
|
||||
(((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
|
||||
#else
|
||||
# define SWAP(n) (n)
|
||||
#endif
|
||||
|
||||
|
||||
/* This array contains the bytes used to pad the buffer to the next
|
||||
64-byte boundary. (RFC 1321, 3.1: Step 1) */
|
||||
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
|
||||
|
||||
|
||||
/* Initialize structure containing state of computation.
|
||||
(RFC 1321, 3.3: Step 3) */
|
||||
void
|
||||
md5_init_ctx (ctx)
|
||||
struct md5_ctx *ctx;
|
||||
{
|
||||
ctx->A = 0x67452301;
|
||||
ctx->B = 0xefcdab89;
|
||||
ctx->C = 0x98badcfe;
|
||||
ctx->D = 0x10325476;
|
||||
|
||||
ctx->total[0] = ctx->total[1] = 0;
|
||||
ctx->buflen = 0;
|
||||
}
|
||||
|
||||
/* Put result from CTX in first 16 bytes following RESBUF. The result
|
||||
must be in little endian byte order.
|
||||
|
||||
IMPORTANT: On some systems it is required that RESBUF is correctly
|
||||
aligned for a 32 bits value. */
|
||||
void *
|
||||
md5_read_ctx (ctx, resbuf)
|
||||
const struct md5_ctx *ctx;
|
||||
void *resbuf;
|
||||
{
|
||||
((md5_uint32 *) resbuf)[0] = SWAP (ctx->A);
|
||||
((md5_uint32 *) resbuf)[1] = SWAP (ctx->B);
|
||||
((md5_uint32 *) resbuf)[2] = SWAP (ctx->C);
|
||||
((md5_uint32 *) resbuf)[3] = SWAP (ctx->D);
|
||||
|
||||
return resbuf;
|
||||
}
|
||||
|
||||
/* Process the remaining bytes in the internal buffer and the usual
|
||||
prolog according to the standard and write the result to RESBUF.
|
||||
|
||||
IMPORTANT: On some systems it is required that RESBUF is correctly
|
||||
aligned for a 32 bits value. */
|
||||
void *
|
||||
md5_finish_ctx (ctx, resbuf)
|
||||
struct md5_ctx *ctx;
|
||||
void *resbuf;
|
||||
{
|
||||
/* Take yet unprocessed bytes into account. */
|
||||
md5_uint32 bytes = ctx->buflen;
|
||||
size_t pad;
|
||||
|
||||
/* Now count remaining bytes. */
|
||||
ctx->total[0] += bytes;
|
||||
if (ctx->total[0] < bytes)
|
||||
++ctx->total[1];
|
||||
|
||||
pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
|
||||
memcpy (&ctx->buffer[bytes], fillbuf, pad);
|
||||
|
||||
/* Put the 64-bit file length in *bits* at the end of the buffer. */
|
||||
*(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3);
|
||||
*(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) |
|
||||
(ctx->total[0] >> 29));
|
||||
|
||||
/* Process last bytes. */
|
||||
md5_process_block (ctx->buffer, bytes + pad + 8, ctx);
|
||||
|
||||
return md5_read_ctx (ctx, resbuf);
|
||||
}
|
||||
|
||||
/* Compute MD5 message digest for bytes read from STREAM. The
|
||||
resulting message digest number will be written into the 16 bytes
|
||||
beginning at RESBLOCK. */
|
||||
int
|
||||
md5_stream (stream, resblock)
|
||||
FILE *stream;
|
||||
void *resblock;
|
||||
{
|
||||
/* Important: BLOCKSIZE must be a multiple of 64. */
|
||||
#define BLOCKSIZE 4096
|
||||
struct md5_ctx ctx;
|
||||
char buffer[BLOCKSIZE + 72];
|
||||
size_t sum;
|
||||
|
||||
/* Initialize the computation context. */
|
||||
md5_init_ctx (&ctx);
|
||||
|
||||
/* Iterate over full file contents. */
|
||||
while (1)
|
||||
{
|
||||
/* We read the file in blocks of BLOCKSIZE bytes. One call of the
|
||||
computation function processes the whole buffer so that with the
|
||||
next round of the loop another block can be read. */
|
||||
size_t n;
|
||||
sum = 0;
|
||||
|
||||
/* Read block. Take care for partial reads. */
|
||||
do
|
||||
{
|
||||
n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);
|
||||
|
||||
sum += n;
|
||||
}
|
||||
while (sum < BLOCKSIZE && n != 0);
|
||||
if (n == 0 && ferror (stream))
|
||||
return 1;
|
||||
|
||||
/* If end of file is reached, end the loop. */
|
||||
if (n == 0)
|
||||
break;
|
||||
|
||||
/* Process buffer with BLOCKSIZE bytes. Note that
|
||||
BLOCKSIZE % 64 == 0
|
||||
*/
|
||||
md5_process_block (buffer, BLOCKSIZE, &ctx);
|
||||
}
|
||||
|
||||
/* Add the last bytes if necessary. */
|
||||
if (sum > 0)
|
||||
md5_process_bytes (buffer, sum, &ctx);
|
||||
|
||||
/* Construct result in desired memory. */
|
||||
md5_finish_ctx (&ctx, resblock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
md5_process_bytes (buffer, len, ctx)
|
||||
const void *buffer;
|
||||
size_t len;
|
||||
struct md5_ctx *ctx;
|
||||
{
|
||||
/* When we already have some bits in our internal buffer concatenate
|
||||
both inputs first. */
|
||||
if (ctx->buflen != 0)
|
||||
{
|
||||
size_t left_over = ctx->buflen;
|
||||
size_t add = 128 - left_over > len ? len : 128 - left_over;
|
||||
|
||||
memcpy (&ctx->buffer[left_over], buffer, add);
|
||||
ctx->buflen += add;
|
||||
|
||||
if (left_over + add > 64)
|
||||
{
|
||||
md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx);
|
||||
/* The regions in the following copy operation cannot overlap. */
|
||||
memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
|
||||
(left_over + add) & 63);
|
||||
ctx->buflen = (left_over + add) & 63;
|
||||
}
|
||||
|
||||
buffer = (const char *) buffer + add;
|
||||
len -= add;
|
||||
}
|
||||
|
||||
/* Process available complete blocks. */
|
||||
if (len > 64)
|
||||
{
|
||||
md5_process_block (buffer, len & ~63, ctx);
|
||||
buffer = (const char *) buffer + (len & ~63);
|
||||
len &= 63;
|
||||
}
|
||||
|
||||
/* Move remaining bytes in internal buffer. */
|
||||
if (len > 0)
|
||||
{
|
||||
memcpy (ctx->buffer, buffer, len);
|
||||
ctx->buflen = len;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* These are the four functions used in the four steps of the MD5 algorithm
|
||||
and defined in the RFC 1321. The first function is a little bit optimized
|
||||
(as found in Colin Plumbs public domain implementation). */
|
||||
/* #define FF(b, c, d) ((b & c) | (~b & d)) */
|
||||
#define FF(b, c, d) (d ^ (b & (c ^ d)))
|
||||
#define FG(b, c, d) FF (d, b, c)
|
||||
#define FH(b, c, d) (b ^ c ^ d)
|
||||
#define FI(b, c, d) (c ^ (b | ~d))
|
||||
|
||||
/* Process LEN bytes of BUFFER, accumulating context into CTX.
|
||||
It is assumed that LEN % 64 == 0. */
|
||||
|
||||
void
|
||||
md5_process_block (buffer, len, ctx)
|
||||
const void *buffer;
|
||||
size_t len;
|
||||
struct md5_ctx *ctx;
|
||||
{
|
||||
md5_uint32 correct_words[16];
|
||||
const unsigned char *words = buffer;
|
||||
const unsigned char *endp = words + len;
|
||||
md5_uint32 A = ctx->A;
|
||||
md5_uint32 B = ctx->B;
|
||||
md5_uint32 C = ctx->C;
|
||||
md5_uint32 D = ctx->D;
|
||||
|
||||
/* First increment the byte count. RFC 1321 specifies the possible
|
||||
length of the file up to 2^64 bits. Here we only compute the
|
||||
number of bytes. Do a double word increment. */
|
||||
ctx->total[0] += len;
|
||||
if (ctx->total[0] < len)
|
||||
++ctx->total[1];
|
||||
|
||||
/* Process all bytes in the buffer with 64 bytes in each round of
|
||||
the loop. */
|
||||
while (words < endp)
|
||||
{
|
||||
md5_uint32 *cwp = correct_words;
|
||||
md5_uint32 A_save = A;
|
||||
md5_uint32 B_save = B;
|
||||
md5_uint32 C_save = C;
|
||||
md5_uint32 D_save = D;
|
||||
|
||||
/* First round: using the given function, the context and a constant
|
||||
the next context is computed. Because the algorithms processing
|
||||
unit is a 32-bit word and it is determined to work on words in
|
||||
little endian byte order we perhaps have to change the byte order
|
||||
before the computation. To reduce the work for the next steps
|
||||
we store the swapped words in the array CORRECT_WORDS. */
|
||||
|
||||
#define OP(a, b, c, d, s, T) \
|
||||
do \
|
||||
{ \
|
||||
md5_uint32 WORD_ = (md5_uint32)words[0] | ((md5_uint32)words[1] << 8) \
|
||||
| ((md5_uint32)words[2] << 16) | ((md5_uint32)words[3] << 24); \
|
||||
a += FF (b, c, d) + (*cwp++ = WORD_) + T; \
|
||||
words += 4; \
|
||||
CYCLIC (a, s); \
|
||||
a += b; \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/* It is unfortunate that C does not provide an operator for
|
||||
cyclic rotation. Hope the C compiler is smart enough. */
|
||||
#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
|
||||
|
||||
/* Before we start, one word to the strange constants.
|
||||
They are defined in RFC 1321 as
|
||||
|
||||
T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
|
||||
*/
|
||||
|
||||
/* Round 1. */
|
||||
OP (A, B, C, D, 7, 0xd76aa478);
|
||||
OP (D, A, B, C, 12, 0xe8c7b756);
|
||||
OP (C, D, A, B, 17, 0x242070db);
|
||||
OP (B, C, D, A, 22, 0xc1bdceee);
|
||||
OP (A, B, C, D, 7, 0xf57c0faf);
|
||||
OP (D, A, B, C, 12, 0x4787c62a);
|
||||
OP (C, D, A, B, 17, 0xa8304613);
|
||||
OP (B, C, D, A, 22, 0xfd469501);
|
||||
OP (A, B, C, D, 7, 0x698098d8);
|
||||
OP (D, A, B, C, 12, 0x8b44f7af);
|
||||
OP (C, D, A, B, 17, 0xffff5bb1);
|
||||
OP (B, C, D, A, 22, 0x895cd7be);
|
||||
OP (A, B, C, D, 7, 0x6b901122);
|
||||
OP (D, A, B, C, 12, 0xfd987193);
|
||||
OP (C, D, A, B, 17, 0xa679438e);
|
||||
OP (B, C, D, A, 22, 0x49b40821);
|
||||
|
||||
/* For the second to fourth round we have the possibly swapped words
|
||||
in CORRECT_WORDS. Redefine the macro to take an additional first
|
||||
argument specifying the function to use. */
|
||||
#undef OP
|
||||
#define OP(f, a, b, c, d, k, s, T) \
|
||||
do \
|
||||
{ \
|
||||
a += f (b, c, d) + correct_words[k] + T; \
|
||||
CYCLIC (a, s); \
|
||||
a += b; \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/* Round 2. */
|
||||
OP (FG, A, B, C, D, 1, 5, 0xf61e2562);
|
||||
OP (FG, D, A, B, C, 6, 9, 0xc040b340);
|
||||
OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
|
||||
OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
|
||||
OP (FG, A, B, C, D, 5, 5, 0xd62f105d);
|
||||
OP (FG, D, A, B, C, 10, 9, 0x02441453);
|
||||
OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
|
||||
OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
|
||||
OP (FG, A, B, C, D, 9, 5, 0x21e1cde6);
|
||||
OP (FG, D, A, B, C, 14, 9, 0xc33707d6);
|
||||
OP (FG, C, D, A, B, 3, 14, 0xf4d50d87);
|
||||
OP (FG, B, C, D, A, 8, 20, 0x455a14ed);
|
||||
OP (FG, A, B, C, D, 13, 5, 0xa9e3e905);
|
||||
OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8);
|
||||
OP (FG, C, D, A, B, 7, 14, 0x676f02d9);
|
||||
OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
|
||||
|
||||
/* Round 3. */
|
||||
OP (FH, A, B, C, D, 5, 4, 0xfffa3942);
|
||||
OP (FH, D, A, B, C, 8, 11, 0x8771f681);
|
||||
OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
|
||||
OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
|
||||
OP (FH, A, B, C, D, 1, 4, 0xa4beea44);
|
||||
OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9);
|
||||
OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60);
|
||||
OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
|
||||
OP (FH, A, B, C, D, 13, 4, 0x289b7ec6);
|
||||
OP (FH, D, A, B, C, 0, 11, 0xeaa127fa);
|
||||
OP (FH, C, D, A, B, 3, 16, 0xd4ef3085);
|
||||
OP (FH, B, C, D, A, 6, 23, 0x04881d05);
|
||||
OP (FH, A, B, C, D, 9, 4, 0xd9d4d039);
|
||||
OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
|
||||
OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
|
||||
OP (FH, B, C, D, A, 2, 23, 0xc4ac5665);
|
||||
|
||||
/* Round 4. */
|
||||
OP (FI, A, B, C, D, 0, 6, 0xf4292244);
|
||||
OP (FI, D, A, B, C, 7, 10, 0x432aff97);
|
||||
OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
|
||||
OP (FI, B, C, D, A, 5, 21, 0xfc93a039);
|
||||
OP (FI, A, B, C, D, 12, 6, 0x655b59c3);
|
||||
OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92);
|
||||
OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
|
||||
OP (FI, B, C, D, A, 1, 21, 0x85845dd1);
|
||||
OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f);
|
||||
OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
|
||||
OP (FI, C, D, A, B, 6, 15, 0xa3014314);
|
||||
OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
|
||||
OP (FI, A, B, C, D, 4, 6, 0xf7537e82);
|
||||
OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
|
||||
OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
|
||||
OP (FI, B, C, D, A, 9, 21, 0xeb86d391);
|
||||
|
||||
/* Add the starting values of the context. */
|
||||
A += A_save;
|
||||
B += B_save;
|
||||
C += C_save;
|
||||
D += D_save;
|
||||
}
|
||||
|
||||
/* Put checksum in context given as argument. */
|
||||
ctx->A = A;
|
||||
ctx->B = B;
|
||||
ctx->C = C;
|
||||
ctx->D = D;
|
||||
}
|
||||
|
||||
/* Writes the ASCII representation of the MD5 digest into the
|
||||
* given buffer, which must be at least 33 characters long. */
|
||||
void ne_md5_to_ascii(const unsigned char md5_buf[16], char *buffer)
|
||||
{
|
||||
int count;
|
||||
for (count = 0; count<16; count++) {
|
||||
buffer[count*2] = NE_HEX2ASC(md5_buf[count] >> 4);
|
||||
buffer[count*2+1] = NE_HEX2ASC(md5_buf[count] & 0x0f);
|
||||
}
|
||||
buffer[32] = '\0';
|
||||
}
|
||||
|
||||
/* Reads the ASCII representation of an MD5 digest. The buffer must
|
||||
* be at least 32 characters long. */
|
||||
void ne_ascii_to_md5(const char *buffer, unsigned char md5_buf[16])
|
||||
{
|
||||
int count;
|
||||
for (count = 0; count<16; count++) {
|
||||
md5_buf[count] = ((NE_ASC2HEX(buffer[count*2])) << 4) |
|
||||
NE_ASC2HEX(buffer[count*2+1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/* Declaration of functions and data types used for MD5 sum computing
|
||||
library functions.
|
||||
Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
|
||||
This file is part of the GNU C Library.
|
||||
|
||||
The GNU C Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public License as
|
||||
published by the Free Software Foundation; either version 2 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
The GNU C Library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with the GNU C Library; see the file COPYING.LIB. If not,
|
||||
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
Boston, MA 02111-1307, USA. */
|
||||
|
||||
#ifndef NEON_MD5_H
|
||||
#define NEON_MD5_H 1
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined HAVE_LIMITS_H || _LIBC
|
||||
# include <limits.h>
|
||||
#endif
|
||||
|
||||
/* The following contortions are an attempt to use the C preprocessor
|
||||
to determine an unsigned integral type that is 32 bits wide. An
|
||||
alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
|
||||
doing that would require that the configure script compile and *run*
|
||||
the resulting executable. Locally running cross-compiled executables
|
||||
is usually not possible. */
|
||||
|
||||
#ifdef _LIBC
|
||||
# include <sys/types.h>
|
||||
typedef u_int32_t md5_uint32;
|
||||
#else
|
||||
# if defined __STDC__ && __STDC__
|
||||
# define UINT_MAX_32_BITS 4294967295U
|
||||
# else
|
||||
# define UINT_MAX_32_BITS 0xFFFFFFFF
|
||||
# endif
|
||||
|
||||
/* If UINT_MAX isn't defined, assume it's a 32-bit type.
|
||||
This should be valid for all systems GNU cares about because
|
||||
that doesn't include 16-bit systems, and only modern systems
|
||||
(that certainly have <limits.h>) have 64+-bit integral types. */
|
||||
|
||||
# ifndef UINT_MAX
|
||||
# define UINT_MAX UINT_MAX_32_BITS
|
||||
# endif
|
||||
|
||||
# if UINT_MAX == UINT_MAX_32_BITS
|
||||
typedef unsigned int md5_uint32;
|
||||
# else
|
||||
# if USHRT_MAX == UINT_MAX_32_BITS
|
||||
typedef unsigned short md5_uint32;
|
||||
# else
|
||||
# if ULONG_MAX == UINT_MAX_32_BITS
|
||||
typedef unsigned long md5_uint32;
|
||||
# else
|
||||
/* The following line is intended to evoke an error.
|
||||
Using #error is not portable enough. */
|
||||
"Cannot determine unsigned 32-bit data type."
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#undef __P
|
||||
#if defined (__STDC__) && __STDC__
|
||||
# define __P(x) x
|
||||
#else
|
||||
# define __P(x) ()
|
||||
#endif
|
||||
|
||||
/* Structure to save state of computation between the single steps. */
|
||||
struct ne_md5_ctx
|
||||
{
|
||||
md5_uint32 A;
|
||||
md5_uint32 B;
|
||||
md5_uint32 C;
|
||||
md5_uint32 D;
|
||||
|
||||
md5_uint32 total[2];
|
||||
md5_uint32 buflen;
|
||||
char buffer[128];
|
||||
};
|
||||
|
||||
/*
|
||||
* The following three functions are build up the low level used in
|
||||
* the functions `md5_stream' and `md5_buffer'.
|
||||
*/
|
||||
|
||||
/* Initialize structure containing state of computation.
|
||||
(RFC 1321, 3.3: Step 3) */
|
||||
extern void ne_md5_init_ctx __P ((struct ne_md5_ctx *ctx));
|
||||
|
||||
/* Starting with the result of former calls of this function (or the
|
||||
initialization function update the context for the next LEN bytes
|
||||
starting at BUFFER.
|
||||
It is necessary that LEN is a multiple of 64!!! */
|
||||
extern void ne_md5_process_block __P ((const void *buffer, size_t len,
|
||||
struct ne_md5_ctx *ctx));
|
||||
|
||||
/* Starting with the result of former calls of this function (or the
|
||||
initialization function update the context for the next LEN bytes
|
||||
starting at BUFFER.
|
||||
It is NOT required that LEN is a multiple of 64. */
|
||||
extern void ne_md5_process_bytes __P ((const void *buffer, size_t len,
|
||||
struct ne_md5_ctx *ctx));
|
||||
|
||||
/* Process the remaining bytes in the buffer and put result from CTX
|
||||
in first 16 bytes following RESBUF. The result is always in little
|
||||
endian byte order, so that a byte-wise output yields to the wanted
|
||||
ASCII representation of the message digest.
|
||||
|
||||
IMPORTANT: On some systems it is required that RESBUF is correctly
|
||||
aligned for a 32 bits value. */
|
||||
extern void *ne_md5_finish_ctx __P ((struct ne_md5_ctx *ctx, void *resbuf));
|
||||
|
||||
|
||||
/* Put result from CTX in first 16 bytes following RESBUF. The result is
|
||||
always in little endian byte order, so that a byte-wise output yields
|
||||
to the wanted ASCII representation of the message digest.
|
||||
|
||||
IMPORTANT: On some systems it is required that RESBUF is correctly
|
||||
aligned for a 32 bits value. */
|
||||
extern void *ne_md5_read_ctx __P ((const struct ne_md5_ctx *ctx, void *resbuf));
|
||||
|
||||
|
||||
/* Compute MD5 message digest for bytes read from STREAM. The
|
||||
resulting message digest number will be written into the 16 bytes
|
||||
beginning at RESBLOCK. */
|
||||
extern int ne_md5_stream __P ((FILE *stream, void *resblock));
|
||||
|
||||
/* MD5 ascii->binary conversion */
|
||||
void ne_md5_to_ascii(const unsigned char md5_buf[16], char *buffer);
|
||||
void ne_ascii_to_md5(const char *buffer, unsigned char md5_buf[16]);
|
||||
|
||||
#endif /* NEON_MD5_H */
|
||||
@@ -0,0 +1,924 @@
|
||||
/*
|
||||
neon SSL/TLS support using OpenSSL
|
||||
Copyright (C) 2002-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
Portions are:
|
||||
Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "ne_ssl.h"
|
||||
#include "ne_string.h"
|
||||
#include "ne_session.h"
|
||||
#include "ne_i18n.h"
|
||||
|
||||
#include "ne_private.h"
|
||||
#include "ne_privssl.h"
|
||||
|
||||
/* OpenSSL 0.9.6 compatibility */
|
||||
#if OPENSSL_VERSION_NUMBER < 0x0090700fL
|
||||
#define PKCS12_unpack_authsafes M_PKCS12_unpack_authsafes
|
||||
#define PKCS12_unpack_p7data M_PKCS12_unpack_p7data
|
||||
/* cast away lack of const-ness */
|
||||
#define OBJ_cmp(a,b) OBJ_cmp((ASN1_OBJECT *)(a), (ASN1_OBJECT *)(b))
|
||||
#endif
|
||||
|
||||
struct ne_ssl_dname_s {
|
||||
X509_NAME *dn;
|
||||
};
|
||||
|
||||
struct ne_ssl_certificate_s {
|
||||
ne_ssl_dname subj_dn, issuer_dn;
|
||||
X509 *subject;
|
||||
ne_ssl_certificate *issuer;
|
||||
char *identity;
|
||||
};
|
||||
|
||||
struct ne_ssl_client_cert_s {
|
||||
PKCS12 *p12;
|
||||
int decrypted; /* non-zero if successfully decrypted. */
|
||||
ne_ssl_certificate cert;
|
||||
EVP_PKEY *pkey;
|
||||
char *friendly_name;
|
||||
};
|
||||
|
||||
/* Append an ASN.1 DirectoryString STR to buffer BUF as UTF-8.
|
||||
* Returns zero on success or non-zero on error. */
|
||||
static int append_dirstring(ne_buffer *buf, ASN1_STRING *str)
|
||||
{
|
||||
unsigned char *tmp = (unsigned char *)""; /* initialize to workaround 0.9.6 bug */
|
||||
int len;
|
||||
|
||||
switch (str->type) {
|
||||
case V_ASN1_UTF8STRING:
|
||||
case V_ASN1_IA5STRING: /* definitely ASCII */
|
||||
case V_ASN1_VISIBLESTRING: /* probably ASCII */
|
||||
case V_ASN1_PRINTABLESTRING: /* subset of ASCII */
|
||||
ne_buffer_append(buf, (char *)str->data, str->length);
|
||||
break;
|
||||
case V_ASN1_UNIVERSALSTRING:
|
||||
case V_ASN1_T61STRING: /* let OpenSSL convert it as ISO-8859-1 */
|
||||
case V_ASN1_BMPSTRING:
|
||||
len = ASN1_STRING_to_UTF8(&tmp, str);
|
||||
if (len > 0) {
|
||||
ne_buffer_append(buf, (char *)tmp, len);
|
||||
OPENSSL_free(tmp);
|
||||
break;
|
||||
} else {
|
||||
ERR_clear_error();
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
NE_DEBUG(NE_DBG_SSL, "Could not convert DirectoryString type %d\n",
|
||||
str->type);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns a malloc-allocate version of IA5 string AS. Really only
|
||||
* here to prevent char * vs unsigned char * type mismatches without
|
||||
* losing all hope at type-safety. */
|
||||
static char *dup_ia5string(const ASN1_IA5STRING *as)
|
||||
{
|
||||
unsigned char *data = as->data;
|
||||
return ne_strndup((char *)data, as->length);
|
||||
}
|
||||
|
||||
char *ne_ssl_readable_dname(const ne_ssl_dname *name)
|
||||
{
|
||||
int n, flag = 0;
|
||||
ne_buffer *dump = ne_buffer_create();
|
||||
const ASN1_OBJECT * const cname = OBJ_nid2obj(NID_commonName),
|
||||
* const email = OBJ_nid2obj(NID_pkcs9_emailAddress);
|
||||
|
||||
for (n = X509_NAME_entry_count(name->dn); n > 0; n--) {
|
||||
X509_NAME_ENTRY *ent = X509_NAME_get_entry(name->dn, n-1);
|
||||
|
||||
/* Skip commonName or emailAddress except if there is no other
|
||||
* attribute in dname. */
|
||||
if ((OBJ_cmp(ent->object, cname) && OBJ_cmp(ent->object, email)) ||
|
||||
(!flag && n == 1)) {
|
||||
if (flag++)
|
||||
ne_buffer_append(dump, ", ", 2);
|
||||
|
||||
if (append_dirstring(dump, ent->value))
|
||||
ne_buffer_czappend(dump, "???");
|
||||
}
|
||||
}
|
||||
|
||||
return ne_buffer_finish(dump);
|
||||
}
|
||||
|
||||
int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2)
|
||||
{
|
||||
return X509_NAME_cmp(dn1->dn, dn2->dn);
|
||||
}
|
||||
|
||||
void ne_ssl_clicert_free(ne_ssl_client_cert *cc)
|
||||
{
|
||||
if (cc->p12)
|
||||
PKCS12_free(cc->p12);
|
||||
if (cc->decrypted) {
|
||||
if (cc->cert.identity) ne_free(cc->cert.identity);
|
||||
EVP_PKEY_free(cc->pkey);
|
||||
X509_free(cc->cert.subject);
|
||||
}
|
||||
if (cc->friendly_name) ne_free(cc->friendly_name);
|
||||
ne_free(cc);
|
||||
}
|
||||
|
||||
/* Map a server cert verification into a string. */
|
||||
static void verify_err(ne_session *sess, int failures)
|
||||
{
|
||||
struct {
|
||||
int bit;
|
||||
const char *str;
|
||||
} reasons[] = {
|
||||
{ NE_SSL_NOTYETVALID, N_("certificate is not yet valid") },
|
||||
{ NE_SSL_EXPIRED, N_("certificate has expired") },
|
||||
{ NE_SSL_IDMISMATCH, N_("certificate issued for a different hostname") },
|
||||
{ NE_SSL_UNTRUSTED, N_("issuer is not trusted") },
|
||||
{ 0, NULL }
|
||||
};
|
||||
int n, flag = 0;
|
||||
|
||||
strcpy(sess->error, _("Server certificate verification failed: "));
|
||||
|
||||
for (n = 0; reasons[n].bit; n++) {
|
||||
if (failures & reasons[n].bit) {
|
||||
if (flag) strncat(sess->error, ", ", sizeof sess->error);
|
||||
strncat(sess->error, _(reasons[n].str), sizeof sess->error);
|
||||
flag = 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Format an ASN1 time to a string. 'buf' must be at least of size
|
||||
* 'NE_SSL_VDATELEN'. */
|
||||
static void asn1time_to_string(ASN1_TIME *tm, char *buf)
|
||||
{
|
||||
BIO *bio;
|
||||
|
||||
strncpy(buf, _("[invalid date]"), NE_SSL_VDATELEN-1);
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if (bio) {
|
||||
if (ASN1_TIME_print(bio, tm))
|
||||
BIO_read(bio, buf, NE_SSL_VDATELEN-1);
|
||||
BIO_free(bio);
|
||||
}
|
||||
}
|
||||
|
||||
void ne_ssl_cert_validity(const ne_ssl_certificate *cert,
|
||||
char *from, char *until)
|
||||
{
|
||||
ASN1_TIME *notBefore = X509_get_notBefore(cert->subject);
|
||||
ASN1_TIME *notAfter = X509_get_notAfter(cert->subject);
|
||||
|
||||
if (from) asn1time_to_string(notBefore, from);
|
||||
if (until) asn1time_to_string(notAfter, until);
|
||||
}
|
||||
|
||||
/* Return non-zero if hostname from certificate (cn) matches hostname
|
||||
* used for session (hostname). (Wildcard matching is no longer
|
||||
* mandated by RFC3280, but certs are deployed which use wildcards) */
|
||||
static int match_hostname(char *cn, const char *hostname)
|
||||
{
|
||||
const char *dot;
|
||||
NE_DEBUG(NE_DBG_SSL, "Match %s on %s...\n", cn, hostname);
|
||||
dot = strchr(hostname, '.');
|
||||
if (dot == NULL) {
|
||||
char *pnt = strchr(cn, '.');
|
||||
/* hostname is not fully-qualified; unqualify the cn. */
|
||||
if (pnt != NULL) {
|
||||
*pnt = '\0';
|
||||
}
|
||||
}
|
||||
else if (strncmp(cn, "*.", 2) == 0) {
|
||||
hostname = dot + 1;
|
||||
cn += 2;
|
||||
}
|
||||
return !strcasecmp(cn, hostname);
|
||||
}
|
||||
|
||||
/* Check certificate identity. Returns zero if identity matches; 1 if
|
||||
* identity does not match, or <0 if the certificate had no identity.
|
||||
* If 'identity' is non-NULL, store the malloc-allocated identity in
|
||||
* *identity. */
|
||||
static int check_identity(const char *hostname, X509 *cert, char **identity)
|
||||
{
|
||||
STACK_OF(GENERAL_NAME) *names;
|
||||
int match = 0, found = 0;
|
||||
|
||||
names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
||||
if (names) {
|
||||
int n;
|
||||
|
||||
/* subjectAltName contains a sequence of GeneralNames */
|
||||
for (n = 0; n < sk_GENERAL_NAME_num(names) && !match; n++) {
|
||||
GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, n);
|
||||
|
||||
/* handle dNSName and iPAddress name extensions only. */
|
||||
if (nm->type == GEN_DNS) {
|
||||
char *name = dup_ia5string(nm->d.ia5);
|
||||
if (identity && !found) *identity = ne_strdup(name);
|
||||
match = match_hostname(name, hostname);
|
||||
ne_free(name);
|
||||
found = 1;
|
||||
} else if (nm->type == GEN_IPADD) {
|
||||
/* compare IP address with server IP address. */
|
||||
ne_inet_addr *ia;
|
||||
if (nm->d.ip->length == 4)
|
||||
ia = ne_iaddr_make(ne_iaddr_ipv4, nm->d.ip->data);
|
||||
else if (nm->d.ip->length == 16)
|
||||
ia = ne_iaddr_make(ne_iaddr_ipv6, nm->d.ip->data);
|
||||
else
|
||||
ia = NULL;
|
||||
/* ne_iaddr_make returns NULL if address type is unsupported */
|
||||
if (ia != NULL) { /* address type was supported. */
|
||||
char buf[128];
|
||||
|
||||
match = strcmp(hostname,
|
||||
ne_iaddr_print(ia, buf, sizeof buf)) == 0;
|
||||
found = 1;
|
||||
ne_iaddr_free(ia);
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_SSL, "iPAddress name with unsupported "
|
||||
"address type (length %d), skipped.\n",
|
||||
nm->d.ip->length);
|
||||
}
|
||||
} /* TODO: handle uniformResourceIdentifier too */
|
||||
|
||||
}
|
||||
/* free the whole stack. */
|
||||
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
|
||||
}
|
||||
|
||||
/* Check against the commonName if no DNS alt. names were found,
|
||||
* as per RFC3280. */
|
||||
if (!found) {
|
||||
X509_NAME *subj = X509_get_subject_name(cert);
|
||||
X509_NAME_ENTRY *entry;
|
||||
ne_buffer *cname = ne_buffer_ncreate(30);
|
||||
int idx = -1, lastidx;
|
||||
|
||||
/* find the most specific commonName attribute. */
|
||||
do {
|
||||
lastidx = idx;
|
||||
idx = X509_NAME_get_index_by_NID(subj, NID_commonName, lastidx);
|
||||
} while (idx >= 0);
|
||||
|
||||
if (lastidx < 0) {
|
||||
/* no commonNmae attributes at all. */
|
||||
ne_buffer_destroy(cname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* extract the string from the entry */
|
||||
entry = X509_NAME_get_entry(subj, lastidx);
|
||||
if (append_dirstring(cname, X509_NAME_ENTRY_get_data(entry))) {
|
||||
ne_buffer_destroy(cname);
|
||||
return -1;
|
||||
}
|
||||
if (identity) *identity = ne_strdup(cname->data);
|
||||
match = match_hostname(cname->data, hostname);
|
||||
ne_buffer_destroy(cname);
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Identity match for '%s': %s\n", hostname,
|
||||
match ? "good" : "bad");
|
||||
return match ? 0 : 1;
|
||||
}
|
||||
|
||||
/* Populate an ne_ssl_certificate structure from an X509 object. */
|
||||
static ne_ssl_certificate *populate_cert(ne_ssl_certificate *cert, X509 *x5)
|
||||
{
|
||||
cert->subj_dn.dn = X509_get_subject_name(x5);
|
||||
cert->issuer_dn.dn = X509_get_issuer_name(x5);
|
||||
cert->issuer = NULL;
|
||||
cert->subject = x5;
|
||||
/* Retrieve the cert identity; pass a dummy hostname to match. */
|
||||
cert->identity = NULL;
|
||||
check_identity("", x5, &cert->identity);
|
||||
return cert;
|
||||
}
|
||||
|
||||
/* Return a linked list of certificate objects from an OpenSSL chain. */
|
||||
static ne_ssl_certificate *make_chain(STACK_OF(X509) *chain)
|
||||
{
|
||||
int n, count = sk_X509_num(chain);
|
||||
ne_ssl_certificate *top = NULL, *current = NULL;
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Chain depth: %d\n", count);
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
ne_ssl_certificate *cert = ne_malloc(sizeof *cert);
|
||||
populate_cert(cert, X509_dup(sk_X509_value(chain, n)));
|
||||
#ifdef NE_DEBUGGING
|
||||
if (ne_debug_mask & NE_DBG_SSL) {
|
||||
fprintf(ne_debug_stream, "Cert #%d:\n", n);
|
||||
X509_print_fp(ne_debug_stream, cert->subject);
|
||||
}
|
||||
#endif
|
||||
if (top == NULL) {
|
||||
current = top = cert;
|
||||
} else {
|
||||
current->issuer = cert;
|
||||
current = cert;
|
||||
}
|
||||
}
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
/* Verifies an SSL server certificate. */
|
||||
static int check_certificate(ne_session *sess, SSL *ssl, ne_ssl_certificate *chain)
|
||||
{
|
||||
X509 *cert = chain->subject;
|
||||
ASN1_TIME *notBefore = X509_get_notBefore(cert);
|
||||
ASN1_TIME *notAfter = X509_get_notAfter(cert);
|
||||
int ret, failures = 0;
|
||||
long result;
|
||||
|
||||
/* check expiry dates */
|
||||
if (X509_cmp_current_time(notBefore) >= 0)
|
||||
failures |= NE_SSL_NOTYETVALID;
|
||||
else if (X509_cmp_current_time(notAfter) <= 0)
|
||||
failures |= NE_SSL_EXPIRED;
|
||||
|
||||
/* Check certificate was issued to this server; pass network
|
||||
* address of server if a proxy is not in use. */
|
||||
ret = check_identity(sess->server.hostname, cert, NULL);
|
||||
if (ret < 0) {
|
||||
ne_set_error(sess, _("Server certificate was missing commonName "
|
||||
"attribute in subject name"));
|
||||
return NE_ERROR;
|
||||
} else if (ret > 0) failures |= NE_SSL_IDMISMATCH;
|
||||
|
||||
/* get the result of the cert verification out of OpenSSL */
|
||||
result = SSL_get_verify_result(ssl);
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Verify result: %ld = %s\n", result,
|
||||
X509_verify_cert_error_string(result));
|
||||
|
||||
switch (result) {
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
||||
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
||||
/* TODO: and probably more result codes here... */
|
||||
failures |= NE_SSL_UNTRUSTED;
|
||||
break;
|
||||
/* ignore these, since we've already noticed them: */
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
/* cert was trusted: */
|
||||
case X509_V_OK:
|
||||
break;
|
||||
default:
|
||||
/* TODO: tricky to handle the 30-odd failure cases OpenSSL
|
||||
* presents here (see x509_vfy.h), and present a useful API to
|
||||
* the application so it in turn can then present a meaningful
|
||||
* UI to the user. The only thing to do really would be to
|
||||
* pass back the error string, but that's not localisable. So
|
||||
* just fail the verification here - better safe than
|
||||
* sorry. */
|
||||
ne_set_error(sess, _("Certificate verification error: %s"),
|
||||
X509_verify_cert_error_string(result));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
if (failures == 0) {
|
||||
/* verified OK! */
|
||||
ret = NE_OK;
|
||||
} else {
|
||||
/* Set up the error string. */
|
||||
verify_err(sess, failures);
|
||||
ret = NE_ERROR;
|
||||
/* Allow manual override */
|
||||
if (sess->ssl_verify_fn &&
|
||||
sess->ssl_verify_fn(sess->ssl_verify_ud, failures, chain) == 0)
|
||||
ret = NE_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Duplicate a client certificate, which must be in the decrypted state. */
|
||||
static ne_ssl_client_cert *dup_client_cert(const ne_ssl_client_cert *cc)
|
||||
{
|
||||
ne_ssl_client_cert *newcc = ne_calloc(sizeof *newcc);
|
||||
|
||||
newcc->decrypted = 1;
|
||||
newcc->pkey = cc->pkey;
|
||||
if (cc->friendly_name)
|
||||
newcc->friendly_name = ne_strdup(cc->friendly_name);
|
||||
|
||||
populate_cert(&newcc->cert, cc->cert.subject);
|
||||
|
||||
cc->cert.subject->references++;
|
||||
cc->pkey->references++;
|
||||
return newcc;
|
||||
}
|
||||
|
||||
/* Callback invoked when the SSL server requests a client certificate. */
|
||||
static int provide_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey)
|
||||
{
|
||||
ne_session *const sess = SSL_get_app_data(ssl);
|
||||
|
||||
if (!sess->client_cert && sess->ssl_provide_fn) {
|
||||
ne_ssl_dname **dnames = NULL;
|
||||
int n, count = 0;
|
||||
STACK_OF(X509_NAME) *ca_list = SSL_get_client_CA_list(ssl);
|
||||
|
||||
count = ca_list ? sk_X509_NAME_num(ca_list) : 0;
|
||||
|
||||
if (count > 0) {
|
||||
dnames = ne_malloc(count * sizeof(ne_ssl_dname *));
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
dnames[n] = ne_malloc(sizeof(ne_ssl_dname));
|
||||
dnames[n]->dn = sk_X509_NAME_value(ca_list, n);
|
||||
}
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Calling client certificate provider...\n");
|
||||
sess->ssl_provide_fn(sess->ssl_provide_ud, sess,
|
||||
(const ne_ssl_dname *const *)dnames, count);
|
||||
if (count) {
|
||||
for (n = 0; n < count; n++)
|
||||
ne_free(dnames[n]);
|
||||
ne_free(dnames);
|
||||
}
|
||||
}
|
||||
|
||||
if (sess->client_cert) {
|
||||
ne_ssl_client_cert *const cc = sess->client_cert;
|
||||
NE_DEBUG(NE_DBG_SSL, "Supplying client certificate.\n");
|
||||
cc->pkey->references++;
|
||||
cc->cert.subject->references++;
|
||||
*cert = cc->cert.subject;
|
||||
*pkey = cc->pkey;
|
||||
return 1;
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_SSL, "No client certificate supplied.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *cc)
|
||||
{
|
||||
sess->client_cert = dup_client_cert(cc);
|
||||
}
|
||||
|
||||
ne_ssl_context *ne_ssl_context_create(int mode)
|
||||
{
|
||||
ne_ssl_context *ctx = ne_calloc(sizeof *ctx);
|
||||
if (mode == NE_SSL_CTX_CLIENT) {
|
||||
ctx->ctx = SSL_CTX_new(SSLv23_client_method());
|
||||
ctx->sess = NULL;
|
||||
/* set client cert callback. */
|
||||
SSL_CTX_set_client_cert_cb(ctx->ctx, provide_client_cert);
|
||||
/* enable workarounds for buggy SSL server implementations */
|
||||
SSL_CTX_set_options(ctx->ctx, SSL_OP_ALL);
|
||||
} else if (mode == NE_SSL_CTX_SERVER) {
|
||||
ctx->ctx = SSL_CTX_new(SSLv23_server_method());
|
||||
} else {
|
||||
ctx->ctx = SSL_CTX_new(SSLv2_server_method());
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
int ne_ssl_context_keypair(ne_ssl_context *ctx, const char *cert,
|
||||
const char *key)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = SSL_CTX_use_PrivateKey_file(ctx->ctx, key, SSL_FILETYPE_PEM);
|
||||
if (ret == 1) {
|
||||
ret = SSL_CTX_use_certificate_file(ctx->ctx, cert, SSL_FILETYPE_PEM);
|
||||
}
|
||||
|
||||
return ret == 1 ? 0 : -1;
|
||||
}
|
||||
|
||||
int ne_ssl_context_set_verify(ne_ssl_context *ctx,
|
||||
int required,
|
||||
const char *ca_names,
|
||||
const char *verify_cas)
|
||||
{
|
||||
if (required) {
|
||||
SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER |
|
||||
SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
|
||||
}
|
||||
if (ca_names) {
|
||||
SSL_CTX_set_client_CA_list(ctx->ctx,
|
||||
SSL_load_client_CA_file(ca_names));
|
||||
}
|
||||
if (verify_cas) {
|
||||
SSL_CTX_load_verify_locations(ctx->ctx, verify_cas, NULL);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ne_ssl_context_destroy(ne_ssl_context *ctx)
|
||||
{
|
||||
SSL_CTX_free(ctx->ctx);
|
||||
if (ctx->sess)
|
||||
SSL_SESSION_free(ctx->sess);
|
||||
ne_free(ctx);
|
||||
}
|
||||
|
||||
/* For internal use only. */
|
||||
int ne__negotiate_ssl(ne_request *req)
|
||||
{
|
||||
ne_session *sess = ne_get_session(req);
|
||||
ne_ssl_context *ctx = sess->ssl_context;
|
||||
SSL *ssl;
|
||||
STACK_OF(X509) *chain;
|
||||
int freechain = 0; /* non-zero if chain should be free'd. */
|
||||
|
||||
NE_DEBUG(NE_DBG_SSL, "Doing SSL negotiation.\n");
|
||||
|
||||
if (ne_sock_connect_ssl(sess->socket, ctx, sess)) {
|
||||
if (ctx->sess) {
|
||||
/* remove cached session. */
|
||||
SSL_SESSION_free(ctx->sess);
|
||||
ctx->sess = NULL;
|
||||
}
|
||||
ne_set_error(sess, _("SSL negotiation failed: %s"),
|
||||
ne_sock_error(sess->socket));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
ssl = ne__sock_sslsock(sess->socket);
|
||||
|
||||
chain = SSL_get_peer_cert_chain(ssl);
|
||||
/* For an SSLv2 connection, the cert chain will always be NULL. */
|
||||
if (chain == NULL) {
|
||||
X509 *cert = SSL_get_peer_certificate(ssl);
|
||||
if (cert) {
|
||||
chain = sk_X509_new_null();
|
||||
sk_X509_push(chain, cert);
|
||||
freechain = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (chain == NULL || sk_X509_num(chain) == 0) {
|
||||
ne_set_error(sess, _("SSL server did not present certificate"));
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
if (sess->server_cert) {
|
||||
int diff = X509_cmp(sk_X509_value(chain, 0), sess->server_cert->subject);
|
||||
if (freechain) sk_X509_free(chain); /* no longer need the chain */
|
||||
if (diff) {
|
||||
/* This could be a MITM attack: fail the request. */
|
||||
ne_set_error(sess, _("Server certificate changed: "
|
||||
"connection intercepted?"));
|
||||
return NE_ERROR;
|
||||
}
|
||||
/* certificate has already passed verification: no need to
|
||||
* verify it again. */
|
||||
} else {
|
||||
/* new connection: create the chain. */
|
||||
ne_ssl_certificate *cert = make_chain(chain);
|
||||
|
||||
if (freechain) sk_X509_free(chain); /* no longer need the chain */
|
||||
|
||||
if (check_certificate(sess, ssl, cert)) {
|
||||
NE_DEBUG(NE_DBG_SSL, "SSL certificate checks failed: %s\n",
|
||||
sess->error);
|
||||
ne_ssl_cert_free(cert);
|
||||
return NE_ERROR;
|
||||
}
|
||||
/* remember the chain. */
|
||||
sess->server_cert = cert;
|
||||
}
|
||||
|
||||
if (ctx->sess) {
|
||||
SSL_SESSION *newsess = SSL_get0_session(ssl);
|
||||
/* Replace the session if it has changed. */
|
||||
if (newsess != ctx->sess || SSL_SESSION_cmp(ctx->sess, newsess)) {
|
||||
SSL_SESSION_free(ctx->sess);
|
||||
ctx->sess = SSL_get1_session(ssl); /* bumping the refcount */
|
||||
}
|
||||
} else {
|
||||
/* Store the session. */
|
||||
ctx->sess = SSL_get1_session(ssl);
|
||||
}
|
||||
|
||||
if (sess->notify_cb) {
|
||||
sess->notify_cb(sess->notify_ud, ne_conn_secure,
|
||||
SSL_get_version(ssl));
|
||||
}
|
||||
|
||||
return NE_OK;
|
||||
}
|
||||
|
||||
const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return &cert->issuer_dn;
|
||||
}
|
||||
|
||||
const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return &cert->subj_dn;
|
||||
}
|
||||
|
||||
const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return cert->issuer;
|
||||
}
|
||||
|
||||
const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return cert->identity;
|
||||
}
|
||||
|
||||
void ne_ssl_context_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert)
|
||||
{
|
||||
X509_STORE *store = SSL_CTX_get_cert_store(ctx->ctx);
|
||||
|
||||
X509_STORE_add_cert(store, cert->subject);
|
||||
}
|
||||
|
||||
void ne_ssl_trust_default_ca(ne_session *sess)
|
||||
{
|
||||
X509_STORE *store = SSL_CTX_get_cert_store(sess->ssl_context->ctx);
|
||||
|
||||
X509_STORE_set_default_paths(store);
|
||||
}
|
||||
|
||||
/* Find a friendly name in a PKCS12 structure the hard way, without
|
||||
* decrypting the parts which are encrypted.. */
|
||||
static char *find_friendly_name(PKCS12 *p12)
|
||||
{
|
||||
STACK_OF(PKCS7) *safes = PKCS12_unpack_authsafes(p12);
|
||||
int n, m;
|
||||
char *name = NULL;
|
||||
|
||||
if (safes == NULL) return NULL;
|
||||
|
||||
/* Iterate over the unpacked authsafes: */
|
||||
for (n = 0; n < sk_PKCS7_num(safes) && !name; n++) {
|
||||
PKCS7 *safe = sk_PKCS7_value(safes, n);
|
||||
STACK_OF(PKCS12_SAFEBAG) *bags;
|
||||
|
||||
/* Only looking for unencrypted authsafes. */
|
||||
if (OBJ_obj2nid(safe->type) != NID_pkcs7_data) continue;
|
||||
|
||||
bags = PKCS12_unpack_p7data(safe);
|
||||
if (!bags) continue;
|
||||
|
||||
/* Iterate through the bags, picking out a friendly name */
|
||||
for (m = 0; m < sk_PKCS12_SAFEBAG_num(bags) && !name; m++) {
|
||||
PKCS12_SAFEBAG *bag = sk_PKCS12_SAFEBAG_value(bags, m);
|
||||
name = PKCS12_get_friendlyname(bag);
|
||||
}
|
||||
|
||||
sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free);
|
||||
}
|
||||
|
||||
sk_PKCS7_pop_free(safes, PKCS7_free);
|
||||
return name;
|
||||
}
|
||||
|
||||
ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename)
|
||||
{
|
||||
PKCS12 *p12;
|
||||
FILE *fp;
|
||||
X509 *cert;
|
||||
EVP_PKEY *pkey;
|
||||
ne_ssl_client_cert *cc;
|
||||
|
||||
fp = fopen(filename, "rb");
|
||||
if (fp == NULL)
|
||||
return NULL;
|
||||
|
||||
p12 = d2i_PKCS12_fp(fp, NULL);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (p12 == NULL) {
|
||||
ERR_clear_error();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Try parsing with no password. */
|
||||
if (PKCS12_parse(p12, NULL, &pkey, &cert, NULL) == 1) {
|
||||
/* Success - no password needed for decryption. */
|
||||
int len = 0;
|
||||
unsigned char *name = X509_alias_get0(cert, &len);
|
||||
|
||||
cc = ne_calloc(sizeof *cc);
|
||||
cc->pkey = pkey;
|
||||
cc->decrypted = 1;
|
||||
if (name && len > 0)
|
||||
cc->friendly_name = ne_strndup((char *)name, len);
|
||||
populate_cert(&cc->cert, cert);
|
||||
PKCS12_free(p12);
|
||||
return cc;
|
||||
} else {
|
||||
/* Failed to parse the file */
|
||||
int err = ERR_get_error();
|
||||
ERR_clear_error();
|
||||
if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 &&
|
||||
ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) {
|
||||
/* Decryption error due to bad password. */
|
||||
cc = ne_calloc(sizeof *cc);
|
||||
cc->friendly_name = find_friendly_name(p12);
|
||||
cc->p12 = p12;
|
||||
return cc;
|
||||
} else {
|
||||
/* Some parse error, give up. */
|
||||
PKCS12_free(p12);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *cc)
|
||||
{
|
||||
return !cc->decrypted;
|
||||
}
|
||||
|
||||
int ne_ssl_clicert_decrypt(ne_ssl_client_cert *cc, const char *password)
|
||||
{
|
||||
X509 *cert;
|
||||
EVP_PKEY *pkey;
|
||||
|
||||
if (PKCS12_parse(cc->p12, password, &pkey, &cert, NULL) != 1) {
|
||||
ERR_clear_error();
|
||||
return -1;
|
||||
}
|
||||
|
||||
PKCS12_free(cc->p12);
|
||||
populate_cert(&cc->cert, cert);
|
||||
cc->pkey = pkey;
|
||||
cc->decrypted = 1;
|
||||
cc->p12 = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *cc)
|
||||
{
|
||||
return &cc->cert;
|
||||
}
|
||||
|
||||
const char *ne_ssl_clicert_name(const ne_ssl_client_cert *ccert)
|
||||
{
|
||||
return ccert->friendly_name;
|
||||
}
|
||||
|
||||
ne_ssl_certificate *ne_ssl_cert_read(const char *filename)
|
||||
{
|
||||
FILE *fp = fopen(filename, "r");
|
||||
X509 *cert;
|
||||
|
||||
if (fp == NULL)
|
||||
return NULL;
|
||||
|
||||
cert = PEM_read_X509(fp, NULL, NULL, NULL);
|
||||
fclose(fp);
|
||||
|
||||
if (cert == NULL) {
|
||||
NE_DEBUG(NE_DBG_SSL, "d2i_X509_fp failed: %s\n",
|
||||
ERR_reason_error_string(ERR_get_error()));
|
||||
ERR_clear_error();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), cert);
|
||||
}
|
||||
|
||||
int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename)
|
||||
{
|
||||
FILE *fp = fopen(filename, "w");
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
|
||||
if (PEM_write_X509(fp, cert->subject) != 1) {
|
||||
ERR_clear_error();
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fclose(fp) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ne_ssl_cert_free(ne_ssl_certificate *cert)
|
||||
{
|
||||
X509_free(cert->subject);
|
||||
if (cert->issuer)
|
||||
ne_ssl_cert_free(cert->issuer);
|
||||
if (cert->identity)
|
||||
ne_free(cert->identity);
|
||||
ne_free(cert);
|
||||
}
|
||||
|
||||
int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, const ne_ssl_certificate *c2)
|
||||
{
|
||||
return X509_cmp(c1->subject, c2->subject);
|
||||
}
|
||||
|
||||
/* The certificate import/export format is the base64 encoding of the
|
||||
* raw DER; PEM without the newlines and wrapping. */
|
||||
|
||||
ne_ssl_certificate *ne_ssl_cert_import(const char *data)
|
||||
{
|
||||
unsigned char *der, *p;
|
||||
size_t len;
|
||||
X509 *x5;
|
||||
|
||||
/* decode the base64 to get the raw DER representation */
|
||||
len = ne_unbase64(data, &der);
|
||||
if (len == 0) return NULL;
|
||||
|
||||
p = der;
|
||||
x5 = d2i_X509(NULL, &p, len); /* p is incremented */
|
||||
ne_free(der);
|
||||
if (x5 == NULL) {
|
||||
ERR_clear_error();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return populate_cert(ne_calloc(sizeof(struct ne_ssl_certificate_s)), x5);
|
||||
}
|
||||
|
||||
char *ne_ssl_cert_export(const ne_ssl_certificate *cert)
|
||||
{
|
||||
int len;
|
||||
unsigned char *der, *p;
|
||||
char *ret;
|
||||
|
||||
/* find the length of the DER encoding. */
|
||||
len = i2d_X509(cert->subject, NULL);
|
||||
|
||||
p = der = ne_malloc(len);
|
||||
i2d_X509(cert->subject, &p); /* p is incremented */
|
||||
|
||||
ret = ne_base64(der, len);
|
||||
ne_free(der);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if SHA_DIGEST_LENGTH != 20
|
||||
# error SHA digest length is not 20 bytes
|
||||
#endif
|
||||
|
||||
int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest)
|
||||
{
|
||||
unsigned char sha1[EVP_MAX_MD_SIZE];
|
||||
unsigned int len, j;
|
||||
char *p;
|
||||
|
||||
if (!X509_digest(cert->subject, EVP_sha1(), sha1, &len) || len != 20) {
|
||||
ERR_clear_error();
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (j = 0, p = digest; j < 20; j++) {
|
||||
*p++ = NE_HEX2ASC((sha1[j] >> 4) & 0x0f);
|
||||
*p++ = NE_HEX2ASC(sha1[j] & 0x0f);
|
||||
*p++ = ':';
|
||||
}
|
||||
|
||||
p[-1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
HTTP Request Handling
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
/* THIS IS NOT A PUBLIC INTERFACE. You CANNOT include this header file
|
||||
* from an application. */
|
||||
|
||||
#ifndef NE_PRIVATE_H
|
||||
#define NE_PRIVATE_H
|
||||
|
||||
#include "ne_request.h"
|
||||
#include "ne_socket.h"
|
||||
#include "ne_ssl.h"
|
||||
|
||||
struct host_info {
|
||||
char *hostname;
|
||||
unsigned int port;
|
||||
ne_sock_addr *address; /* if non-NULL, result of resolving 'hostname'. */
|
||||
/* current network address obtained from 'address' being used. */
|
||||
const ne_inet_addr *current;
|
||||
char *hostport; /* URI hostport segment */
|
||||
};
|
||||
|
||||
/* Store every registered callback in a generic container, and cast
|
||||
* the function pointer when calling it. */
|
||||
struct hook {
|
||||
void (*fn)(void);
|
||||
void *userdata;
|
||||
const char *id; /* non-NULL for accessors. */
|
||||
struct hook *next;
|
||||
};
|
||||
|
||||
#define HAVE_HOOK(st,func) (st->hook->hooks->func != NULL)
|
||||
#define HOOK_FUNC(st, func) (*st->hook->hooks->func)
|
||||
|
||||
/* Session support. */
|
||||
struct ne_session_s {
|
||||
/* Connection information */
|
||||
ne_socket *socket;
|
||||
|
||||
/* non-zero if connection has been established. */
|
||||
int connected;
|
||||
|
||||
/* non-zero if connection has persisted beyond one request. */
|
||||
int persisted;
|
||||
|
||||
int is_http11; /* >0 if connected server is known to be
|
||||
* HTTP/1.1 compliant. */
|
||||
|
||||
char *scheme;
|
||||
struct host_info server, proxy;
|
||||
|
||||
/* application-provided address list */
|
||||
const ne_inet_addr **addrlist;
|
||||
size_t numaddrs, curaddr;
|
||||
|
||||
/* Settings */
|
||||
unsigned int use_proxy:1; /* do we have a proxy server? */
|
||||
unsigned int no_persist:1; /* set to disable persistent connections */
|
||||
unsigned int use_ssl:1; /* whether a secure connection is required */
|
||||
unsigned int in_connect:1; /* doing a proxy CONNECT */
|
||||
|
||||
ne_progress progress_cb;
|
||||
void *progress_ud;
|
||||
|
||||
ne_notify_status notify_cb;
|
||||
void *notify_ud;
|
||||
|
||||
int rdtimeout; /* read timeout. */
|
||||
|
||||
struct hook *create_req_hooks, *pre_send_hooks, *post_send_hooks;
|
||||
struct hook *destroy_req_hooks, *destroy_sess_hooks, *private;
|
||||
|
||||
char *user_agent; /* full User-Agent: header field */
|
||||
|
||||
#ifdef NE_HAVE_SSL
|
||||
ne_ssl_client_cert *client_cert;
|
||||
ne_ssl_certificate *server_cert;
|
||||
ne_ssl_context *ssl_context;
|
||||
#endif
|
||||
|
||||
/* Server cert verification callback: */
|
||||
ne_ssl_verify_fn ssl_verify_fn;
|
||||
void *ssl_verify_ud;
|
||||
/* Client cert provider callback: */
|
||||
ne_ssl_provide_fn ssl_provide_fn;
|
||||
void *ssl_provide_ud;
|
||||
|
||||
/* Error string */
|
||||
char error[512];
|
||||
};
|
||||
|
||||
/* Pushes block of 'count' bytes at 'buf'. Returns non-zero on
|
||||
* error. */
|
||||
typedef int (*ne_push_fn)(void *userdata, const char *buf, size_t count);
|
||||
|
||||
/* Do the SSL negotiation. */
|
||||
int ne__negotiate_ssl(ne_request *req);
|
||||
|
||||
/* Hack to fix ne_compress layer problems */
|
||||
void ne__reqhook_pre_send(ne_request *sess, ne_pre_send_fn fn, void *userdata);
|
||||
|
||||
#endif /* HTTP_PRIVATE_H */
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
SSL interface definitions internal to neon.
|
||||
Copyright (C) 2003, 2004, Joe Orton <joe@manyfish.co.uk>
|
||||
Copyright (C) 2004, Aleix Conchillo Flaque <aleix@member.fsf.org>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
/* THIS IS NOT A PUBLIC INTERFACE. You CANNOT include this header file
|
||||
* from an application. */
|
||||
|
||||
#ifndef NE_PRIVSSL_H
|
||||
#define NE_PRIVSSL_H
|
||||
|
||||
/* This is the private interface between ne_socket, ne_gnutls and
|
||||
* ne_openssl. */
|
||||
|
||||
#include "ne_ssl.h"
|
||||
#include "ne_socket.h"
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
struct ne_ssl_context_s {
|
||||
SSL_CTX *ctx;
|
||||
SSL_SESSION *sess;
|
||||
};
|
||||
|
||||
typedef SSL *ne_ssl_socket;
|
||||
|
||||
#endif /* HAVE_OPENSSL */
|
||||
|
||||
#ifdef HAVE_GNUTLS
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
struct ne_ssl_context_s {
|
||||
gnutls_certificate_credentials cred;
|
||||
/* TODO: store session here too */
|
||||
};
|
||||
|
||||
typedef gnutls_session ne_ssl_socket;
|
||||
|
||||
#endif /* HAVE_GNUTLS */
|
||||
|
||||
ne_ssl_socket ne__sock_sslsock(ne_socket *sock);
|
||||
|
||||
#endif /* NE_PRIVSSL_H */
|
||||
@@ -0,0 +1,637 @@
|
||||
/*
|
||||
WebDAV property manipulation
|
||||
Copyright (C) 2000-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_xml.h"
|
||||
#include "ne_props.h"
|
||||
#include "ne_basic.h"
|
||||
#include "ne_locks.h"
|
||||
#include "ne_i18n.h"
|
||||
|
||||
/* don't store flat props with a value > 10K */
|
||||
#define MAX_FLATPROP_LEN (102400)
|
||||
|
||||
#define EOL "\r\n"
|
||||
|
||||
struct ne_propfind_handler_s {
|
||||
ne_session *sess;
|
||||
ne_request *request;
|
||||
|
||||
int has_props; /* whether we've already written some
|
||||
* props to the body. */
|
||||
ne_buffer *body;
|
||||
|
||||
ne_207_parser *parser207;
|
||||
ne_xml_parser *parser;
|
||||
|
||||
/* Callback to create the private structure. */
|
||||
ne_props_create_complex private_creator;
|
||||
void *private_userdata;
|
||||
|
||||
/* Current propset, or NULL if none being processed. */
|
||||
ne_prop_result_set *current;
|
||||
|
||||
ne_buffer *value; /* current flat property value */
|
||||
int depth; /* nesting depth within a flat property */
|
||||
|
||||
ne_props_result callback;
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
#define ELM_flatprop (NE_207_STATE_TOP - 1)
|
||||
|
||||
/* We build up the results of one 'response' element in memory. */
|
||||
struct prop {
|
||||
char *name, *nspace, *value, *lang;
|
||||
/* Store a ne_propname here too, for convienience. pname.name =
|
||||
* name, pname.nspace = nspace, but they are const'ed in pname. */
|
||||
ne_propname pname;
|
||||
};
|
||||
|
||||
#define NSPACE(x) ((x) ? (x) : "")
|
||||
|
||||
struct propstat {
|
||||
struct prop *props;
|
||||
int numprops;
|
||||
ne_status status;
|
||||
};
|
||||
|
||||
/* Results set. */
|
||||
struct ne_prop_result_set_s {
|
||||
struct propstat *pstats;
|
||||
int numpstats, counter;
|
||||
void *private;
|
||||
char *href;
|
||||
};
|
||||
|
||||
#define MAX_PROP_COUNTER (1024)
|
||||
|
||||
static int
|
||||
startelm(void *userdata, int state, const char *name, const char *nspace,
|
||||
const char **atts);
|
||||
static int
|
||||
endelm(void *userdata, int state, const char *name, const char *nspace);
|
||||
|
||||
/* Handle character data; flat property value. */
|
||||
static int chardata(void *userdata, int state, const char *data, size_t len)
|
||||
{
|
||||
ne_propfind_handler *hdl = userdata;
|
||||
|
||||
if (state == ELM_flatprop && hdl->value->length < MAX_FLATPROP_LEN)
|
||||
ne_buffer_append(hdl->value, data, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ne_xml_parser *ne_propfind_get_parser(ne_propfind_handler *handler)
|
||||
{
|
||||
return handler->parser;
|
||||
}
|
||||
|
||||
ne_request *ne_propfind_get_request(ne_propfind_handler *handler)
|
||||
{
|
||||
return handler->request;
|
||||
}
|
||||
|
||||
static int propfind(ne_propfind_handler *handler,
|
||||
ne_props_result results, void *userdata)
|
||||
{
|
||||
int ret;
|
||||
ne_request *req = handler->request;
|
||||
|
||||
/* Register the flat property handler to catch any properties
|
||||
* which the user isn't handling as 'complex'. */
|
||||
ne_xml_push_handler(handler->parser, startelm, chardata, endelm, handler);
|
||||
|
||||
handler->callback = results;
|
||||
handler->userdata = userdata;
|
||||
|
||||
ne_set_request_body_buffer(req, handler->body->data,
|
||||
ne_buffer_size(handler->body));
|
||||
|
||||
ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
|
||||
|
||||
ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v,
|
||||
handler->parser);
|
||||
|
||||
ret = ne_request_dispatch(req);
|
||||
|
||||
if (ret == NE_OK && ne_get_status(req)->klass != 2) {
|
||||
ret = NE_ERROR;
|
||||
} else if (ne_xml_failed(handler->parser)) {
|
||||
ne_set_error(handler->sess, "%s", ne_xml_get_error(handler->parser));
|
||||
ret = NE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void set_body(ne_propfind_handler *hdl, const ne_propname *names)
|
||||
{
|
||||
ne_buffer *body = hdl->body;
|
||||
int n;
|
||||
|
||||
if (!hdl->has_props) {
|
||||
ne_buffer_zappend(body, "<prop>" EOL);
|
||||
hdl->has_props = 1;
|
||||
}
|
||||
|
||||
for (n = 0; names[n].name != NULL; n++) {
|
||||
ne_buffer_concat(body, "<", names[n].name, " xmlns=\"",
|
||||
NSPACE(names[n].nspace), "\"/>" EOL, NULL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int ne_propfind_allprop(ne_propfind_handler *handler,
|
||||
ne_props_result results, void *userdata)
|
||||
{
|
||||
ne_buffer_zappend(handler->body, "<allprop/></propfind>" EOL);
|
||||
return propfind(handler, results, userdata);
|
||||
}
|
||||
|
||||
int ne_propfind_named(ne_propfind_handler *handler, const ne_propname *props,
|
||||
ne_props_result results, void *userdata)
|
||||
{
|
||||
set_body(handler, props);
|
||||
ne_buffer_zappend(handler->body, "</prop></propfind>" EOL);
|
||||
return propfind(handler, results, userdata);
|
||||
}
|
||||
|
||||
|
||||
/* The easy one... PROPPATCH */
|
||||
int ne_proppatch(ne_session *sess, const char *uri,
|
||||
const ne_proppatch_operation *items)
|
||||
{
|
||||
ne_request *req = ne_request_create(sess, "PROPPATCH", uri);
|
||||
ne_buffer *body = ne_buffer_create();
|
||||
int n, ret;
|
||||
|
||||
/* Create the request body */
|
||||
ne_buffer_zappend(body, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" EOL
|
||||
"<D:propertyupdate xmlns:D=\"DAV:\">");
|
||||
|
||||
for (n = 0; items[n].name != NULL; n++) {
|
||||
const char *elm = (items[n].type == ne_propset) ? "set" : "remove";
|
||||
|
||||
/* <set><prop><prop-name>value</prop-name></prop></set> */
|
||||
ne_buffer_concat(body, "<D:", elm, "><D:prop>"
|
||||
"<", items[n].name->name, NULL);
|
||||
|
||||
if (items[n].name->nspace) {
|
||||
ne_buffer_concat(body, " xmlns=\"", items[n].name->nspace, "\"", NULL);
|
||||
}
|
||||
|
||||
if (items[n].type == ne_propset) {
|
||||
ne_buffer_concat(body, ">", items[n].value, NULL);
|
||||
} else {
|
||||
ne_buffer_append(body, ">", 1);
|
||||
}
|
||||
|
||||
ne_buffer_concat(body, "</", items[n].name->name, "></D:prop></D:", elm, ">"
|
||||
EOL, NULL);
|
||||
}
|
||||
|
||||
ne_buffer_zappend(body, "</D:propertyupdate>" EOL);
|
||||
|
||||
ne_set_request_body_buffer(req, body->data, ne_buffer_size(body));
|
||||
ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
|
||||
|
||||
#ifdef NE_HAVE_DAV
|
||||
ne_lock_using_resource(req, uri, NE_DEPTH_ZERO);
|
||||
#endif
|
||||
|
||||
ret = ne_simple_request(sess, req);
|
||||
|
||||
ne_buffer_destroy(body);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Compare two property names. */
|
||||
static int pnamecmp(const ne_propname *pn1, const ne_propname *pn2)
|
||||
{
|
||||
if (pn1->nspace == NULL && pn2->nspace != NULL) {
|
||||
return 1;
|
||||
} else if (pn1->nspace != NULL && pn2->nspace == NULL) {
|
||||
return -1;
|
||||
} else if (pn1->nspace == NULL) {
|
||||
return strcmp(pn1->name, pn2->name);
|
||||
} else {
|
||||
return (strcmp(pn1->nspace, pn2->nspace) ||
|
||||
strcmp(pn1->name, pn2->name));
|
||||
}
|
||||
}
|
||||
|
||||
/* Find property in 'set' with name 'pname'. If found, set pstat_ret
|
||||
* to the containing propstat, likewise prop_ret, and returns zero.
|
||||
* If not found, returns non-zero. */
|
||||
static int findprop(const ne_prop_result_set *set, const ne_propname *pname,
|
||||
struct propstat **pstat_ret, struct prop **prop_ret)
|
||||
{
|
||||
|
||||
int ps, p;
|
||||
|
||||
for (ps = 0; ps < set->numpstats; ps++) {
|
||||
for (p = 0; p < set->pstats[ps].numprops; p++) {
|
||||
struct prop *prop = &set->pstats[ps].props[p];
|
||||
|
||||
if (pnamecmp(&prop->pname, pname) == 0) {
|
||||
if (pstat_ret != NULL)
|
||||
*pstat_ret = &set->pstats[ps];
|
||||
if (prop_ret != NULL)
|
||||
*prop_ret = prop;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *ne_propset_value(const ne_prop_result_set *set,
|
||||
const ne_propname *pname)
|
||||
{
|
||||
struct prop *prop;
|
||||
|
||||
if (findprop(set, pname, NULL, &prop)) {
|
||||
return NULL;
|
||||
} else {
|
||||
return prop->value;
|
||||
}
|
||||
}
|
||||
|
||||
const char *ne_propset_lang(const ne_prop_result_set *set,
|
||||
const ne_propname *pname)
|
||||
{
|
||||
struct prop *prop;
|
||||
|
||||
if (findprop(set, pname, NULL, &prop)) {
|
||||
return NULL;
|
||||
} else {
|
||||
return prop->lang;
|
||||
}
|
||||
}
|
||||
|
||||
void *ne_propfind_current_private(ne_propfind_handler *handler)
|
||||
{
|
||||
return handler->current ? handler->current->private : NULL;
|
||||
}
|
||||
|
||||
void *ne_propset_private(const ne_prop_result_set *set)
|
||||
{
|
||||
return set->private;
|
||||
}
|
||||
|
||||
int ne_propset_iterate(const ne_prop_result_set *set,
|
||||
ne_propset_iterator iterator, void *userdata)
|
||||
{
|
||||
int ps, p;
|
||||
|
||||
for (ps = 0; ps < set->numpstats; ps++) {
|
||||
for (p = 0; p < set->pstats[ps].numprops; p++) {
|
||||
struct prop *prop = &set->pstats[ps].props[p];
|
||||
int ret = iterator(userdata, &prop->pname, prop->value,
|
||||
&set->pstats[ps].status);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ne_status *ne_propset_status(const ne_prop_result_set *set,
|
||||
const ne_propname *pname)
|
||||
{
|
||||
struct propstat *pstat;
|
||||
|
||||
if (findprop(set, pname, &pstat, NULL)) {
|
||||
/* TODO: it is tempting to return a dummy status object here
|
||||
* rather than NULL, which says "Property result was not given
|
||||
* by server." but I'm not sure if this is best left to the
|
||||
* client. */
|
||||
return NULL;
|
||||
} else {
|
||||
return &pstat->status;
|
||||
}
|
||||
}
|
||||
|
||||
static void *start_response(void *userdata, const char *href)
|
||||
{
|
||||
ne_prop_result_set *set = ne_calloc(sizeof(*set));
|
||||
ne_propfind_handler *hdl = userdata;
|
||||
|
||||
set->href = ne_strdup(href);
|
||||
|
||||
if (hdl->private_creator != NULL) {
|
||||
set->private = hdl->private_creator(hdl->private_userdata, href);
|
||||
}
|
||||
|
||||
hdl->current = set;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
static void *start_propstat(void *userdata, void *response)
|
||||
{
|
||||
ne_prop_result_set *set = response;
|
||||
ne_propfind_handler *hdl = userdata;
|
||||
struct propstat *pstat;
|
||||
int n;
|
||||
|
||||
if (++hdl->current->counter == MAX_PROP_COUNTER) {
|
||||
ne_xml_set_error(hdl->parser, _("Response exceeds maximum property count"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
n = set->numpstats;
|
||||
set->pstats = ne_realloc(set->pstats, sizeof(struct propstat) * (n+1));
|
||||
set->numpstats = n+1;
|
||||
|
||||
pstat = &set->pstats[n];
|
||||
memset(pstat, 0, sizeof(*pstat));
|
||||
|
||||
/* And return this as the new pstat. */
|
||||
return &set->pstats[n];
|
||||
}
|
||||
|
||||
static int startelm(void *userdata, int parent,
|
||||
const char *nspace, const char *name, const char **atts)
|
||||
{
|
||||
ne_propfind_handler *hdl = userdata;
|
||||
struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
|
||||
struct prop *prop;
|
||||
int n;
|
||||
const char *lang;
|
||||
|
||||
/* Just handle all children of propstat and their descendants. */
|
||||
if ((parent != NE_207_STATE_PROP && parent != ELM_flatprop)
|
||||
|| pstat == NULL)
|
||||
return NE_XML_DECLINE;
|
||||
|
||||
if (parent == ELM_flatprop) {
|
||||
/* collecting the flatprop value. */
|
||||
hdl->depth++;
|
||||
if (hdl->value->used < MAX_FLATPROP_LEN)
|
||||
ne_buffer_concat(hdl->value, "<", name, ">", NULL);
|
||||
return ELM_flatprop;
|
||||
}
|
||||
|
||||
/* Enforce maximum number of properties per resource to prevent a
|
||||
* memory exhaustion attack by a hostile server. */
|
||||
if (++hdl->current->counter == MAX_PROP_COUNTER) {
|
||||
ne_xml_set_error(hdl->parser, _("Response exceeds maximum property count"));
|
||||
return NE_XML_ABORT;
|
||||
}
|
||||
|
||||
/* Add a property to this propstat */
|
||||
n = pstat->numprops;
|
||||
|
||||
pstat->props = ne_realloc(pstat->props, sizeof(struct prop) * (n + 1));
|
||||
pstat->numprops = n+1;
|
||||
|
||||
/* Fill in the new property. */
|
||||
prop = &pstat->props[n];
|
||||
|
||||
prop->pname.name = prop->name = ne_strdup(name);
|
||||
if (nspace[0] == '\0') {
|
||||
prop->pname.nspace = prop->nspace = NULL;
|
||||
} else {
|
||||
prop->pname.nspace = prop->nspace = ne_strdup(nspace);
|
||||
}
|
||||
prop->value = NULL;
|
||||
|
||||
NE_DEBUG(NE_DBG_XML, "Got property #%d: {%s}%s.\n", n,
|
||||
NSPACE(prop->nspace), prop->name);
|
||||
|
||||
/* This is under discussion at time of writing (April '01), and it
|
||||
* looks like we need to retrieve the xml:lang property from any
|
||||
* element here or above.
|
||||
*
|
||||
* Also, I think we might need attribute namespace handling here. */
|
||||
lang = ne_xml_get_attr(hdl->parser, atts, NULL, "xml:lang");
|
||||
if (lang != NULL) {
|
||||
prop->lang = ne_strdup(lang);
|
||||
NE_DEBUG(NE_DBG_XML, "Property language is %s\n", prop->lang);
|
||||
} else {
|
||||
prop->lang = NULL;
|
||||
}
|
||||
|
||||
hdl->depth = 0;
|
||||
|
||||
return ELM_flatprop;
|
||||
}
|
||||
|
||||
static int endelm(void *userdata, int state,
|
||||
const char *nspace, const char *name)
|
||||
{
|
||||
ne_propfind_handler *hdl = userdata;
|
||||
struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
|
||||
int n;
|
||||
|
||||
if (hdl->depth > 0) {
|
||||
/* nested. */
|
||||
if (hdl->value->used < MAX_FLATPROP_LEN)
|
||||
ne_buffer_concat(hdl->value, "</", name, ">", NULL);
|
||||
hdl->depth--;
|
||||
} else {
|
||||
/* end of the current property value */
|
||||
n = pstat->numprops - 1;
|
||||
pstat->props[n].value = ne_buffer_finish(hdl->value);
|
||||
hdl->value = ne_buffer_create();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void end_propstat(void *userdata, void *pstat_v,
|
||||
const ne_status *status,
|
||||
const char *description)
|
||||
{
|
||||
struct propstat *pstat = pstat_v;
|
||||
|
||||
/* Nothing to do if no status was given. */
|
||||
if (!status) return;
|
||||
|
||||
/* If we get a non-2xx response back here, we wipe the value for
|
||||
* each of the properties in this propstat, so the caller knows to
|
||||
* look at the status instead. It's annoying, since for each prop
|
||||
* we will have done an unnecessary strdup("") above, but there is
|
||||
* no easy way round that given the fact that we don't know
|
||||
* whether we've got an error or not till after we get the
|
||||
* property element.
|
||||
*
|
||||
* Interestingly IIS breaks the 2518 DTD and puts the status
|
||||
* element first in the propstat. This is useful since then we
|
||||
* *do* know whether each subsequent empty prop element means, but
|
||||
* we can't rely on that here. */
|
||||
if (status->klass != 2) {
|
||||
int n;
|
||||
|
||||
for (n = 0; n < pstat->numprops; n++) {
|
||||
ne_free(pstat->props[n].value);
|
||||
pstat->props[n].value = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* copy the status structure, and dup the reason phrase. */
|
||||
pstat->status = *status;
|
||||
pstat->status.reason_phrase = ne_strdup(status->reason_phrase);
|
||||
}
|
||||
|
||||
/* Frees up a results set */
|
||||
static void free_propset(ne_prop_result_set *set)
|
||||
{
|
||||
int n;
|
||||
|
||||
for (n = 0; n < set->numpstats; n++) {
|
||||
int m;
|
||||
struct propstat *p = &set->pstats[n];
|
||||
|
||||
for (m = 0; m < p->numprops; m++) {
|
||||
NE_FREE(p->props[m].nspace);
|
||||
ne_free(p->props[m].name);
|
||||
NE_FREE(p->props[m].lang);
|
||||
NE_FREE(p->props[m].value);
|
||||
}
|
||||
|
||||
if (p->status.reason_phrase)
|
||||
ne_free(p->status.reason_phrase);
|
||||
if (p->props)
|
||||
ne_free(p->props);
|
||||
}
|
||||
|
||||
if (set->pstats)
|
||||
ne_free(set->pstats);
|
||||
ne_free(set->href);
|
||||
ne_free(set);
|
||||
}
|
||||
|
||||
static void end_response(void *userdata, void *resource,
|
||||
const ne_status *status,
|
||||
const char *description)
|
||||
{
|
||||
ne_propfind_handler *handler = userdata;
|
||||
ne_prop_result_set *set = resource;
|
||||
|
||||
/* Pass back the results for this resource. */
|
||||
if (handler->callback && set->numpstats > 0)
|
||||
handler->callback(handler->userdata, set->href, set);
|
||||
|
||||
/* Clean up the propset tree we've just built. */
|
||||
free_propset(set);
|
||||
handler->current = NULL;
|
||||
}
|
||||
|
||||
ne_propfind_handler *
|
||||
ne_propfind_create(ne_session *sess, const char *uri, int depth)
|
||||
{
|
||||
ne_propfind_handler *ret = ne_calloc(sizeof(ne_propfind_handler));
|
||||
|
||||
ret->parser = ne_xml_create();
|
||||
ret->parser207 = ne_207_create(ret->parser, ret);
|
||||
ret->sess = sess;
|
||||
ret->body = ne_buffer_create();
|
||||
ret->request = ne_request_create(sess, "PROPFIND", uri);
|
||||
ret->value = ne_buffer_create();
|
||||
|
||||
ne_add_depth_header(ret->request, depth);
|
||||
|
||||
ne_207_set_response_handlers(ret->parser207,
|
||||
start_response, end_response);
|
||||
|
||||
ne_207_set_propstat_handlers(ret->parser207, start_propstat,
|
||||
end_propstat);
|
||||
|
||||
/* The start of the request body is fixed: */
|
||||
ne_buffer_concat(ret->body,
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL
|
||||
"<propfind xmlns=\"DAV:\">", NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Destroy a propfind handler */
|
||||
void ne_propfind_destroy(ne_propfind_handler *handler)
|
||||
{
|
||||
ne_buffer_destroy(handler->value);
|
||||
if (handler->current)
|
||||
free_propset(handler->current);
|
||||
ne_207_destroy(handler->parser207);
|
||||
ne_xml_destroy(handler->parser);
|
||||
ne_buffer_destroy(handler->body);
|
||||
ne_request_destroy(handler->request);
|
||||
ne_free(handler);
|
||||
}
|
||||
|
||||
int ne_simple_propfind(ne_session *sess, const char *href, int depth,
|
||||
const ne_propname *props,
|
||||
ne_props_result results, void *userdata)
|
||||
{
|
||||
ne_propfind_handler *hdl;
|
||||
int ret;
|
||||
|
||||
hdl = ne_propfind_create(sess, href, depth);
|
||||
if (props != NULL) {
|
||||
ret = ne_propfind_named(hdl, props, results, userdata);
|
||||
} else {
|
||||
ret = ne_propfind_allprop(hdl, results, userdata);
|
||||
}
|
||||
|
||||
ne_propfind_destroy(hdl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ne_propnames(ne_session *sess, const char *href, int depth,
|
||||
ne_props_result results, void *userdata)
|
||||
{
|
||||
ne_propfind_handler *hdl;
|
||||
int ret;
|
||||
|
||||
hdl = ne_propfind_create(sess, href, depth);
|
||||
|
||||
ne_buffer_zappend(hdl->body, "<propname/></propfind>");
|
||||
|
||||
ret = propfind(hdl, results, userdata);
|
||||
|
||||
ne_propfind_destroy(hdl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ne_propfind_set_private(ne_propfind_handler *hdl,
|
||||
ne_props_create_complex creator,
|
||||
void *userdata)
|
||||
{
|
||||
hdl->private_creator = creator;
|
||||
hdl->private_userdata = userdata;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
WebDAV Properties manipulation
|
||||
Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_PROPS_H
|
||||
#define NE_PROPS_H
|
||||
|
||||
#include "ne_request.h"
|
||||
#include "ne_207.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* There are two interfaces for fetching properties. The first is
|
||||
* 'ne_simple_propfind', which is relatively simple, and easy to use,
|
||||
* but only lets you fetch FLAT properties, i.e. properties which are
|
||||
* just a string of bytes. The complex interface is 'ne_propfind_*',
|
||||
* which is complicated, and hard to use, but lets you parse
|
||||
* structured properties, i.e. properties which have XML content. */
|
||||
|
||||
/* The 'ne_simple_propfind' interface. ***
|
||||
*
|
||||
* ne_simple_propfind allows you to fetch a set of properties for a
|
||||
* single resource, or a tree of resources. You set the operation
|
||||
* going by passing these arguments:
|
||||
*
|
||||
* - the session which should be used.
|
||||
* - the URI and the depth of the operation (0, 1, infinite)
|
||||
* - the names of the properties which you want to fetch
|
||||
* - a results callback, and the userdata for the callback.
|
||||
*
|
||||
* For each resource found, the results callback is called, passing
|
||||
* you two things along with the userdata you passed in originally:
|
||||
*
|
||||
* - the URI of the resource (const char *href)
|
||||
* - the properties results set (const ne_prop_result_set *results)
|
||||
* */
|
||||
|
||||
/* The name of a WebDAV property. 'nspace' may be NULL. */
|
||||
typedef struct {
|
||||
const char *nspace, *name;
|
||||
} ne_propname;
|
||||
|
||||
typedef struct ne_prop_result_set_s ne_prop_result_set;
|
||||
|
||||
/* Get the value of a given property. Will return NULL if there was an
|
||||
* error fetching this property on this resource. Call
|
||||
* ne_propset_result to get the response-status if so. */
|
||||
const char *ne_propset_value(const ne_prop_result_set *set,
|
||||
const ne_propname *propname);
|
||||
|
||||
/* Returns the status structure for fetching the given property on
|
||||
* this resource. This function will return NULL if the server did not
|
||||
* return the property (which is a server error). */
|
||||
const ne_status *ne_propset_status(const ne_prop_result_set *set,
|
||||
const ne_propname *propname);
|
||||
|
||||
/* Returns the private pointer for the given propset. */
|
||||
void *ne_propset_private(const ne_prop_result_set *set);
|
||||
|
||||
/* Return language string of property (may be NULL). */
|
||||
const char *ne_propset_lang(const ne_prop_result_set *set,
|
||||
const ne_propname *pname);
|
||||
|
||||
/* ne_propset_iterate iterates over a properties result set,
|
||||
* calling the callback for each property in the set. userdata is
|
||||
* passed as the first argument to the callback. value may be NULL,
|
||||
* indicating an error occurred fetching this property: look at
|
||||
* status for the error in that case.
|
||||
*
|
||||
* If the iterator returns non-zero, ne_propset_iterate will return
|
||||
* immediately with that value.
|
||||
*/
|
||||
typedef int (*ne_propset_iterator)(void *userdata,
|
||||
const ne_propname *pname,
|
||||
const char *value,
|
||||
const ne_status *status);
|
||||
|
||||
/* Iterate over all the properties in 'set', calling 'iterator'
|
||||
* for each, passing 'userdata' as the first argument to callback.
|
||||
*
|
||||
* Returns:
|
||||
* whatever value iterator returns.
|
||||
*/
|
||||
int ne_propset_iterate(const ne_prop_result_set *set,
|
||||
ne_propset_iterator iterator, void *userdata);
|
||||
|
||||
/* Callback for handling the results of fetching properties for a
|
||||
* single resource (named by 'href'). The results are stored in the
|
||||
* result set 'results': use ne_propset_* to examine this object. */
|
||||
typedef void (*ne_props_result)(void *userdata, const char *href,
|
||||
const ne_prop_result_set *results);
|
||||
|
||||
/* Fetch properties for a resource (if depth == NE_DEPTH_ZERO),
|
||||
* or a tree of resources (if depth == NE_DEPTH_ONE or _INFINITE).
|
||||
*
|
||||
* Names of the properties required must be given in 'props',
|
||||
* or if props is NULL, *all* properties are fetched.
|
||||
*
|
||||
* 'results' is called for each resource in the response, userdata is
|
||||
* passed as the first argument to the callback. It is important to
|
||||
* note that the callback is called as the response is read off the
|
||||
* socket, so don't do anything silly in it (e.g. sleep(100), or call
|
||||
* any functions which use this session).
|
||||
*
|
||||
* Note that if 'depth' is NE_DEPTH_INFINITY, some servers may refuse
|
||||
* the request.
|
||||
*
|
||||
* Returns NE_*. */
|
||||
int ne_simple_propfind(ne_session *sess, const char *path, int depth,
|
||||
const ne_propname *props,
|
||||
ne_props_result results, void *userdata);
|
||||
|
||||
/* The properties of a resource can be manipulated using ne_proppatch.
|
||||
* A single proppatch request may include any number of individual
|
||||
* "set" and "remove" operations, and is defined to have
|
||||
* "all-or-nothing" semantics, so either all the operations succeed,
|
||||
* or none do. */
|
||||
|
||||
/* A proppatch operation may either set a property to have a new
|
||||
* value, in which case 'type' must be ne_propset, and 'value' must be
|
||||
* non-NULL; or it can remove a property; in which case 'type' must be
|
||||
* ne_propremove, and 'value' is ignored. In both cases, 'name' must
|
||||
* be set to the name of the property to alter. */
|
||||
enum ne_proppatch_optype {
|
||||
ne_propset,
|
||||
ne_propremove
|
||||
};
|
||||
typedef struct {
|
||||
const ne_propname *name;
|
||||
enum ne_proppatch_optype type;
|
||||
const char *value;
|
||||
} ne_proppatch_operation;
|
||||
|
||||
/* Execute a set of property operations 'ops' on 'path'. 'ops' is an
|
||||
* array terminated by an operation with a NULL 'name' field. Returns
|
||||
* NE_*. */
|
||||
int ne_proppatch(ne_session *sess, const char *path,
|
||||
const ne_proppatch_operation *ops);
|
||||
|
||||
/* Retrieve property names for the resources at 'path'. 'results'
|
||||
* callback is called for each resource. Use 'ne_propset_iterate' on
|
||||
* the passed results object to retrieve the list of property names.
|
||||
* */
|
||||
int ne_propnames(ne_session *sess, const char *path, int depth,
|
||||
ne_props_result results, void *userdata);
|
||||
|
||||
/* The complex, you-do-all-the-work, property fetch interface:
|
||||
*/
|
||||
|
||||
struct ne_propfind_handler_s;
|
||||
typedef struct ne_propfind_handler_s ne_propfind_handler;
|
||||
|
||||
/* Retrieve the 'private' pointer for the current propset for the
|
||||
* given handler, as returned by the ne_props_create_complex callback
|
||||
* installed using 'ne_propfind_set_private'. If this callback was
|
||||
* not registered, this function will return NULL. */
|
||||
void *ne_propfind_current_private(ne_propfind_handler *handler);
|
||||
|
||||
/* Create a PROPFIND handler, for the given resource or set of
|
||||
* resources.
|
||||
*
|
||||
* Depth must be one of NE_DEPTH_*. */
|
||||
ne_propfind_handler *
|
||||
ne_propfind_create(ne_session *sess, const char *path, int depth);
|
||||
|
||||
/* Return the XML parser for the given handler (only need if you want
|
||||
* to handle complex properties). */
|
||||
ne_xml_parser *ne_propfind_get_parser(ne_propfind_handler *handler);
|
||||
|
||||
/* This interface reserves the state integer range 'x' where 0 < x
|
||||
* and x < NE_PROPS_STATE_TOP. */
|
||||
#define NE_PROPS_STATE_TOP (NE_207_STATE_TOP + 100)
|
||||
|
||||
/* Return the request object for the given handler. You MUST NOT use
|
||||
* ne_set_request_body_* on this request object. (this call is only
|
||||
* needed if for instance, you want to add extra headers to the
|
||||
* PROPFIND request). The result of using the request pointer after
|
||||
* ne_propfind_destroy(handler) has been called is undefined. */
|
||||
ne_request *ne_propfind_get_request(ne_propfind_handler *handler);
|
||||
|
||||
/* A "complex property" has a value which is structured XML. To handle
|
||||
* complex properties, you must set up and register an XML handler
|
||||
* which will understand the elements which make up such properties.
|
||||
* The handler must be registered with the parser returned by
|
||||
* 'ne_propfind_get_parser'.
|
||||
*
|
||||
* To store the parsed value of the property, a 'private' structure is
|
||||
* allocated in each propset (i.e. one per resource). When parsing the
|
||||
* property value elements, for each new resource encountered in the
|
||||
* response, the 'creator' callback is called to retrieve a 'private'
|
||||
* structure for this resource.
|
||||
*
|
||||
* Whilst in XML element callbacks you will have registered to handle
|
||||
* complex properties, you can use the 'ne_propfind_current_private'
|
||||
* call to retrieve the pointer to this private structure.
|
||||
*
|
||||
* To retrieve this 'private' structure from the propset in the
|
||||
* results callback, simply call 'ne_propset_private'.
|
||||
* */
|
||||
typedef void *(*ne_props_create_complex)(void *userdata,
|
||||
const char *href);
|
||||
|
||||
void ne_propfind_set_private(ne_propfind_handler *handler,
|
||||
ne_props_create_complex creator,
|
||||
void *userdata);
|
||||
|
||||
/* Fetch all properties.
|
||||
*
|
||||
* Returns NE_*. */
|
||||
int ne_propfind_allprop(ne_propfind_handler *handler,
|
||||
ne_props_result result, void *userdata);
|
||||
|
||||
/* Fetch all properties with names listed in array 'names', which is
|
||||
* terminated by a property with a NULL name field. For each resource
|
||||
* encountered, the result callback will be invoked, passing in
|
||||
* 'userdata' as the first argument.
|
||||
*
|
||||
* Returns NE_*. */
|
||||
int ne_propfind_named(ne_propfind_handler *handler,
|
||||
const ne_propname *names,
|
||||
ne_props_result result, void *userdata);
|
||||
|
||||
/* Destroy a propfind handler after use. */
|
||||
void ne_propfind_destroy(ne_propfind_handler *handler);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_PROPS_H */
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
HTTP-redirect support
|
||||
Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#include "ne_session.h"
|
||||
#include "ne_request.h"
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_uri.h"
|
||||
#include "ne_redirect.h"
|
||||
#include "ne_i18n.h"
|
||||
#include "ne_string.h"
|
||||
|
||||
#define REDIRECT_ID "http://www.webdav.org/neon/hooks/http-redirect"
|
||||
|
||||
struct redirect {
|
||||
char *requri;
|
||||
int valid; /* non-zero if .uri contains a redirect */
|
||||
ne_uri uri;
|
||||
ne_session *sess;
|
||||
};
|
||||
|
||||
static void
|
||||
create(ne_request *req, void *session, const char *method, const char *uri)
|
||||
{
|
||||
struct redirect *red = session;
|
||||
NE_FREE(red->requri);
|
||||
red->requri = ne_strdup(uri);
|
||||
}
|
||||
|
||||
#define REDIR(n) ((n) == 301 || (n) == 302 || (n) == 303 || \
|
||||
(n) == 307)
|
||||
|
||||
static int post_send(ne_request *req, void *private, const ne_status *status)
|
||||
{
|
||||
struct redirect *red = private;
|
||||
const char *location = ne_get_response_header(req, "Location");
|
||||
ne_buffer *path = NULL;
|
||||
int ret;
|
||||
|
||||
/* Don't do anything for non-redirect status or no Location header. */
|
||||
if (!REDIR(status->code) || location == NULL)
|
||||
return NE_OK;
|
||||
|
||||
if (strstr(location, "://") == NULL && location[0] != '/') {
|
||||
char *pnt;
|
||||
|
||||
path = ne_buffer_create();
|
||||
ne_buffer_zappend(path, red->requri);
|
||||
pnt = strrchr(path->data, '/');
|
||||
|
||||
if (pnt && pnt[1] != '\0') {
|
||||
/* Chop off last path segment. */
|
||||
pnt[1] = '\0';
|
||||
ne_buffer_altered(path);
|
||||
}
|
||||
ne_buffer_zappend(path, location);
|
||||
location = path->data;
|
||||
}
|
||||
|
||||
/* free last uri. */
|
||||
ne_uri_free(&red->uri);
|
||||
|
||||
/* Parse the Location header */
|
||||
if (ne_uri_parse(location, &red->uri) || red->uri.path == NULL) {
|
||||
red->valid = 0;
|
||||
ne_set_error(red->sess, _("Could not parse redirect location."));
|
||||
ret = NE_ERROR;
|
||||
} else {
|
||||
/* got a valid redirect. */
|
||||
red->valid = 1;
|
||||
ret = NE_REDIRECT;
|
||||
|
||||
if (!red->uri.host) {
|
||||
/* Not an absoluteURI: breaks 2616 but everybody does it. */
|
||||
ne_fill_server_uri(red->sess, &red->uri);
|
||||
}
|
||||
}
|
||||
|
||||
if (path) ne_buffer_destroy(path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void free_redirect(void *cookie)
|
||||
{
|
||||
struct redirect *red = cookie;
|
||||
ne_uri_free(&red->uri);
|
||||
if (red->requri)
|
||||
ne_free(red->requri);
|
||||
ne_free(red);
|
||||
}
|
||||
|
||||
void ne_redirect_register(ne_session *sess)
|
||||
{
|
||||
struct redirect *red = ne_calloc(sizeof *red);
|
||||
|
||||
red->sess = sess;
|
||||
|
||||
ne_hook_create_request(sess, create, red);
|
||||
ne_hook_post_send(sess, post_send, red);
|
||||
ne_hook_destroy_session(sess, free_redirect, red);
|
||||
|
||||
ne_set_session_private(sess, REDIRECT_ID, red);
|
||||
}
|
||||
|
||||
const ne_uri *ne_redirect_location(ne_session *sess)
|
||||
{
|
||||
struct redirect *red = ne_get_session_private(sess, REDIRECT_ID);
|
||||
|
||||
if (red && red->valid)
|
||||
return &red->uri;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
HTTP-redirect support
|
||||
Copyright (C) 1999-2002, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_REDIRECT_H
|
||||
#define NE_REDIRECT_H
|
||||
|
||||
#include "ne_request.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* Register redirect handling: if a redirection response is given, the
|
||||
* request will fail with the NE_REDIRECT code, and the destinsation
|
||||
* of the redirect can be retrieved using ne_redirect_location(). */
|
||||
void ne_redirect_register(ne_session *sess);
|
||||
|
||||
/* Returns location of last redirect. Will return NULL if no redirect
|
||||
* has been encountered for given session, or the last redirect
|
||||
* encountered could not be parsed. */
|
||||
const ne_uri *ne_redirect_location(ne_session *sess);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_REDIRECT_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
HTTP Request Handling
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_REQUEST_H
|
||||
#define NE_REQUEST_H
|
||||
|
||||
#include "ne_utils.h" /* For ne_status */
|
||||
#include "ne_string.h" /* For ne_buffer */
|
||||
#include "ne_session.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
#define NE_OK (0) /* Success */
|
||||
#define NE_ERROR (1) /* Generic error; use ne_get_error(session) for message */
|
||||
#define NE_LOOKUP (2) /* Server or proxy hostname lookup failed */
|
||||
#define NE_AUTH (3) /* User authentication failed on server */
|
||||
#define NE_PROXYAUTH (4) /* User authentication failed on proxy */
|
||||
#define NE_CONNECT (5) /* Could not connect to server */
|
||||
#define NE_TIMEOUT (6) /* Connection timed out */
|
||||
#define NE_FAILED (7) /* The precondition failed */
|
||||
#define NE_RETRY (8) /* Retry request (ne_end_request ONLY) */
|
||||
#define NE_REDIRECT (9) /* See ne_redirect.h */
|
||||
|
||||
/* Opaque object representing a single HTTP request. */
|
||||
typedef struct ne_request_s ne_request;
|
||||
|
||||
/***** Request Handling *****/
|
||||
|
||||
/* Create a request in session 'sess', with given method and path.
|
||||
* 'path' must conform to the 'abs_path' grammar in RFC2396, with an
|
||||
* optional "? query" part, and MUST be URI-escaped by the caller. */
|
||||
ne_request *ne_request_create(ne_session *sess,
|
||||
const char *method, const char *path);
|
||||
|
||||
/* The request body will be taken from 'size' bytes of 'buffer'. */
|
||||
void ne_set_request_body_buffer(ne_request *req, const char *buffer,
|
||||
size_t size);
|
||||
|
||||
/* The request body will be taken from 'length' bytes read from the
|
||||
* file descriptor 'fd', starting from file offset 'offset'. */
|
||||
void ne_set_request_body_fd(ne_request *req, int fd,
|
||||
off_t offset, off_t length);
|
||||
|
||||
#ifdef NE_LFS
|
||||
/* Alternate version of ne_set_request_body_fd taking off64_t
|
||||
* offset type for systems supporting _LARGEFILE64_SOURCE. */
|
||||
void ne_set_request_body_fd64(ne_request *req, int fd,
|
||||
off64_t offset, off64_t length);
|
||||
#endif
|
||||
|
||||
/* "Pull"-based request body provider: a callback which is invoked to
|
||||
* provide blocks of request body on demand.
|
||||
*
|
||||
* Before each time the body is provided, the callback will be called
|
||||
* once with buflen == 0. The body may have to be provided >1 time
|
||||
* per request (for authentication retries etc.).
|
||||
*
|
||||
* For a call with buflen == 0, the callback must return zero on success
|
||||
* or non-zero on error; the session error string must be set on error.
|
||||
* For a call with buflen > 0, the callback must return:
|
||||
* <0 : error, abort request; session error string must be set.
|
||||
* 0 : ignore 'buffer' contents, end of body.
|
||||
* 0 < x <= buflen : buffer contains x bytes of body data. */
|
||||
typedef ssize_t (*ne_provide_body)(void *userdata,
|
||||
char *buffer, size_t buflen);
|
||||
|
||||
/* Install a callback which is invoked as needed to provide the
|
||||
* request body, a block at a time. The total size of the request
|
||||
* body is 'length'; the callback must ensure that it returns no more
|
||||
* than 'length' bytes in total. */
|
||||
void ne_set_request_body_provider(ne_request *req, off_t length,
|
||||
ne_provide_body provider, void *userdata);
|
||||
|
||||
#ifdef NE_LFS
|
||||
/* Duplicate version of ne_set_request_body_provider, taking an off64_t
|
||||
* offset. */
|
||||
void ne_set_request_body_provider64(ne_request *req, off64_t length,
|
||||
ne_provide_body provider, void *userdata);
|
||||
#endif
|
||||
|
||||
/* Handling response bodies; two callbacks must be provided:
|
||||
*
|
||||
* 1) 'acceptance' callback: determines whether you want to handle the
|
||||
* response body given the response-status information, e.g., if you
|
||||
* only want 2xx responses, say so here.
|
||||
*
|
||||
* 2) 'reader' callback: passed blocks of the response-body as they
|
||||
* arrive, if the acceptance callback returned non-zero. */
|
||||
|
||||
/* 'acceptance' callback type. Return non-zero to accept the response,
|
||||
* else zero to ignore it. */
|
||||
typedef int (*ne_accept_response)(void *userdata, ne_request *req,
|
||||
const ne_status *st);
|
||||
|
||||
/* An 'acceptance' callback which only accepts 2xx-class responses.
|
||||
* Ignores userdata. */
|
||||
int ne_accept_2xx(void *userdata, ne_request *req, const ne_status *st);
|
||||
|
||||
/* An acceptance callback which accepts all responses. Ignores
|
||||
* userdata. */
|
||||
int ne_accept_always(void *userdata, ne_request *req, const ne_status *st);
|
||||
|
||||
/* Callback for reading a block of data. Returns zero on success, or
|
||||
* non-zero on error. If returning an error, the response will be
|
||||
* aborted and the callback will not be invoked again. The request
|
||||
* dispatch (or ne_read_response_block call) will fail with NE_ERROR;
|
||||
* the session error string should have been set by the callback. */
|
||||
typedef int (*ne_block_reader)(void *userdata, const char *buf, size_t len);
|
||||
|
||||
/* Add a response reader for the given request, with the given
|
||||
* acceptance function. userdata is passed as the first argument to
|
||||
* the acceptance + reader callbacks.
|
||||
*
|
||||
* The acceptance callback is called once each time the request is
|
||||
* sent: it may be sent >1 time because of authentication retries etc.
|
||||
* For each time the acceptance callback is called, if it returns
|
||||
* non-zero, blocks of the response body will be passed to the reader
|
||||
* callback as the response is read. After all the response body has
|
||||
* been read, the callback will be called with a 'len' argument of
|
||||
* zero. */
|
||||
void ne_add_response_body_reader(ne_request *req, ne_accept_response accpt,
|
||||
ne_block_reader reader, void *userdata);
|
||||
|
||||
/* Retrieve the value of the response header field with given name;
|
||||
* returns NULL if no response header with given name was found. The
|
||||
* return value is valid only until the next call to either
|
||||
* ne_request_destroy or ne_begin_request for this request. */
|
||||
const char *ne_get_response_header(ne_request *req, const char *name);
|
||||
|
||||
/* Iterator interface for response headers: if passed a NULL cursor,
|
||||
* returns the first header; if passed a non-NULL cursor pointer,
|
||||
* returns the next header. The return value is a cursor pointer: if
|
||||
* it is non-NULL, *name and *value are set to the name and value of
|
||||
* the header field. If the return value is NULL, no more headers are
|
||||
* found, *name and *value are undefined.
|
||||
*
|
||||
* The order in which response headers is returned is undefined. Both
|
||||
* the cursor and name/value pointers are valid only until the next
|
||||
* call to either ne_request_destroy or ne_begin_request for this
|
||||
* request. */
|
||||
void *ne_response_header_iterate(ne_request *req, void *cursor,
|
||||
const char **name, const char **value);
|
||||
|
||||
/* Adds a header to the request with given name and value. */
|
||||
void ne_add_request_header(ne_request *req, const char *name,
|
||||
const char *value);
|
||||
/* Adds a header to the request with given name, using printf-like
|
||||
* format arguments for the value. */
|
||||
void ne_print_request_header(ne_request *req, const char *name,
|
||||
const char *format, ...)
|
||||
ne_attribute((format(printf, 3, 4)));
|
||||
|
||||
/* ne_request_dispatch: Sends the given request, and reads the
|
||||
* response. Returns:
|
||||
* - NE_OK if the request was sent and response read successfully
|
||||
* - NE_AUTH, NE_PROXYAUTH for a server or proxy server authentication error
|
||||
* - NE_CONNECT if connection could not be established
|
||||
* - NE_TIMEOUT if an timeout occurred sending or reading from the server
|
||||
* - NE_ERROR for other fatal dispatch errors
|
||||
* On any error, the session error string is set. On success or
|
||||
* authentication error, the actual response-status can be retrieved using
|
||||
* ne_get_status(). */
|
||||
int ne_request_dispatch(ne_request *req);
|
||||
|
||||
/* Returns a pointer to the response status information for the given
|
||||
* request; pointer is valid until request object is destroyed. */
|
||||
const ne_status *ne_get_status(const ne_request *req) ne_attribute((const));
|
||||
|
||||
/* Returns pointer to session associated with request. */
|
||||
ne_session *ne_get_session(const ne_request *req) ne_attribute((const));
|
||||
|
||||
/* Destroy memory associated with request pointer */
|
||||
void ne_request_destroy(ne_request *req);
|
||||
|
||||
/* "Caller-pulls" request interface. This is an ALTERNATIVE interface
|
||||
* to ne_request_dispatch: either use that, or do all this yourself:
|
||||
*
|
||||
* caller must call:
|
||||
* 1. ne_begin_request (fail if returns non-NE_OK)
|
||||
* 2. while(ne_read_response_block(...) > 0) ... loop ...;
|
||||
* (fail if ne_read_response_block returns <0)
|
||||
* 3. ne_end_request
|
||||
*
|
||||
* ne_end_request and ne_begin_request both return an NE_* code; if
|
||||
* ne_end_request returns NE_RETRY, you must restart the loop from (1)
|
||||
* above. */
|
||||
int ne_begin_request(ne_request *req);
|
||||
int ne_end_request(ne_request *req);
|
||||
|
||||
/* Read a block of the response into the passed buffer of size 'buflen'.
|
||||
*
|
||||
* Returns:
|
||||
* <0 - error, stop reading.
|
||||
* 0 - end of response
|
||||
* >0 - number of bytes read into buffer.
|
||||
*/
|
||||
ssize_t ne_read_response_block(ne_request *req, char *buffer, size_t buflen);
|
||||
|
||||
/* Read response blocks until end of response; exactly equivalent to
|
||||
* calling ne_read_response_block() until it returns 0. Returns
|
||||
* non-zero on error. */
|
||||
int ne_discard_response(ne_request *req);
|
||||
|
||||
/* Read response blocks until end of response, writing content to the
|
||||
* given file descriptor. Returns NE_ERROR on error. */
|
||||
int ne_read_response_to_fd(ne_request *req, int fd);
|
||||
|
||||
/* If 'flag' is non-zer, enable the HTTP/1.1 "Expect: 100-continue"
|
||||
* feature for the request, which allows the server to send an error
|
||||
* response before the request body is sent. This should only be used
|
||||
* if the server is known to support the feature (not all HTTP/1.1
|
||||
* servers do); the request will time out and fail otherwise. */
|
||||
void ne_set_request_expect100(ne_request *req, int flag);
|
||||
|
||||
/**** Request hooks handling *****/
|
||||
|
||||
typedef void (*ne_free_hooks)(void *cookie);
|
||||
|
||||
/* Hook called when a create is created; passed the request method,
|
||||
* and the string used as the Request-URI (which may be an abs_path,
|
||||
* or an absoluteURI, depending on whether an HTTP proxy is in
|
||||
* use). */
|
||||
typedef void (*ne_create_request_fn)(ne_request *req, void *userdata,
|
||||
const char *method, const char *requri);
|
||||
void ne_hook_create_request(ne_session *sess,
|
||||
ne_create_request_fn fn, void *userdata);
|
||||
|
||||
/* Hook called before the request is sent. 'header' is the raw HTTP
|
||||
* header before the trailing CRLF is added: add in more here. */
|
||||
typedef void (*ne_pre_send_fn)(ne_request *req, void *userdata,
|
||||
ne_buffer *header);
|
||||
void ne_hook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata);
|
||||
|
||||
/* Hook called after the request is dispatched (request sent, and
|
||||
* the entire response read). If an error occurred reading the response,
|
||||
* this hook will not run. May return:
|
||||
* NE_OK everything is okay
|
||||
* NE_RETRY try sending the request again.
|
||||
* anything else signifies an error, and the request is failed. The return
|
||||
* code is passed back the _dispatch caller, so the session error must
|
||||
* also be set appropriately (ne_set_error).
|
||||
*/
|
||||
typedef int (*ne_post_send_fn)(ne_request *req, void *userdata,
|
||||
const ne_status *status);
|
||||
void ne_hook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata);
|
||||
|
||||
/* Hook called when the function is destroyed. */
|
||||
typedef void (*ne_destroy_req_fn)(ne_request *req, void *userdata);
|
||||
void ne_hook_destroy_request(ne_session *sess,
|
||||
ne_destroy_req_fn fn, void *userdata);
|
||||
|
||||
typedef void (*ne_destroy_sess_fn)(void *userdata);
|
||||
/* Hook called when the session is destroyed. */
|
||||
void ne_hook_destroy_session(ne_session *sess,
|
||||
ne_destroy_sess_fn fn, void *userdata);
|
||||
|
||||
/* Store an opaque context for the request, 'priv' is returned by a
|
||||
* call to ne_request_get_private with the same ID. */
|
||||
void ne_set_request_private(ne_request *req, const char *id, void *priv);
|
||||
void *ne_get_request_private(ne_request *req, const char *id);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_REQUEST_H */
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
HTTP session handling
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
Portions are:
|
||||
Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifdef HAVE_ERRNO_H
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#include "ne_session.h"
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_utils.h"
|
||||
#include "ne_i18n.h"
|
||||
#include "ne_string.h"
|
||||
|
||||
#include "ne_private.h"
|
||||
|
||||
/* Destroy a a list of hooks. */
|
||||
static void destroy_hooks(struct hook *hooks)
|
||||
{
|
||||
struct hook *nexthk;
|
||||
|
||||
while (hooks) {
|
||||
nexthk = hooks->next;
|
||||
ne_free(hooks);
|
||||
hooks = nexthk;
|
||||
}
|
||||
}
|
||||
|
||||
void ne_session_destroy(ne_session *sess)
|
||||
{
|
||||
struct hook *hk;
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTP, "ne_session_destroy called.\n");
|
||||
|
||||
/* Run the destroy hooks. */
|
||||
for (hk = sess->destroy_sess_hooks; hk != NULL; hk = hk->next) {
|
||||
ne_destroy_sess_fn fn = (ne_destroy_sess_fn)hk->fn;
|
||||
fn(hk->userdata);
|
||||
}
|
||||
|
||||
destroy_hooks(sess->create_req_hooks);
|
||||
destroy_hooks(sess->pre_send_hooks);
|
||||
destroy_hooks(sess->post_send_hooks);
|
||||
destroy_hooks(sess->destroy_req_hooks);
|
||||
destroy_hooks(sess->destroy_sess_hooks);
|
||||
destroy_hooks(sess->private);
|
||||
|
||||
ne_free(sess->scheme);
|
||||
ne_free(sess->server.hostname);
|
||||
ne_free(sess->server.hostport);
|
||||
if (sess->server.address) ne_addr_destroy(sess->server.address);
|
||||
if (sess->proxy.address) ne_addr_destroy(sess->proxy.address);
|
||||
if (sess->proxy.hostname) ne_free(sess->proxy.hostname);
|
||||
if (sess->user_agent) ne_free(sess->user_agent);
|
||||
|
||||
if (sess->connected) {
|
||||
ne_close_connection(sess);
|
||||
}
|
||||
|
||||
#ifdef NE_HAVE_SSL
|
||||
if (sess->ssl_context)
|
||||
ne_ssl_context_destroy(sess->ssl_context);
|
||||
|
||||
if (sess->server_cert)
|
||||
ne_ssl_cert_free(sess->server_cert);
|
||||
|
||||
if (sess->client_cert)
|
||||
ne_ssl_clicert_free(sess->client_cert);
|
||||
#endif
|
||||
|
||||
ne_free(sess);
|
||||
}
|
||||
|
||||
int ne_version_pre_http11(ne_session *s)
|
||||
{
|
||||
return !s->is_http11;
|
||||
}
|
||||
|
||||
/* Stores the "hostname[:port]" segment */
|
||||
static void set_hostport(struct host_info *host, unsigned int defaultport)
|
||||
{
|
||||
size_t len = strlen(host->hostname);
|
||||
host->hostport = ne_malloc(len + 10);
|
||||
strcpy(host->hostport, host->hostname);
|
||||
if (host->port != defaultport)
|
||||
ne_snprintf(host->hostport + len, 9, ":%u", host->port);
|
||||
}
|
||||
|
||||
/* Stores the hostname/port in *info, setting up the "hostport"
|
||||
* segment correctly. */
|
||||
static void
|
||||
set_hostinfo(struct host_info *info, const char *hostname, unsigned int port)
|
||||
{
|
||||
info->hostname = ne_strdup(hostname);
|
||||
info->port = port;
|
||||
}
|
||||
|
||||
ne_session *ne_session_create(const char *scheme,
|
||||
const char *hostname, unsigned int port)
|
||||
{
|
||||
ne_session *sess = ne_calloc(sizeof *sess);
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTP, "HTTP session to %s://%s:%d begins.\n",
|
||||
scheme, hostname, port);
|
||||
|
||||
strcpy(sess->error, "Unknown error.");
|
||||
|
||||
/* use SSL if scheme is https */
|
||||
sess->use_ssl = !strcmp(scheme, "https");
|
||||
|
||||
/* set the hostname/port */
|
||||
set_hostinfo(&sess->server, hostname, port);
|
||||
set_hostport(&sess->server, sess->use_ssl?443:80);
|
||||
|
||||
|
||||
#ifdef NE_HAVE_SSL
|
||||
if (sess->use_ssl) {
|
||||
sess->ssl_context = ne_ssl_context_create(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
sess->scheme = ne_strdup(scheme);
|
||||
|
||||
return sess;
|
||||
}
|
||||
|
||||
void ne_session_proxy(ne_session *sess, const char *hostname,
|
||||
unsigned int port)
|
||||
{
|
||||
sess->use_proxy = 1;
|
||||
if (sess->proxy.hostname) ne_free(sess->proxy.hostname);
|
||||
set_hostinfo(&sess->proxy, hostname, port);
|
||||
}
|
||||
|
||||
void ne_set_addrlist(ne_session *sess, const ne_inet_addr **addrs, size_t n)
|
||||
{
|
||||
sess->addrlist = addrs;
|
||||
sess->numaddrs = n;
|
||||
}
|
||||
|
||||
void ne_set_error(ne_session *sess, const char *format, ...)
|
||||
{
|
||||
va_list params;
|
||||
|
||||
va_start(params, format);
|
||||
ne_vsnprintf(sess->error, sizeof sess->error, format, params);
|
||||
va_end(params);
|
||||
}
|
||||
|
||||
|
||||
void ne_set_progress(ne_session *sess,
|
||||
ne_progress progress, void *userdata)
|
||||
{
|
||||
sess->progress_cb = progress;
|
||||
sess->progress_ud = userdata;
|
||||
}
|
||||
|
||||
void ne_set_status(ne_session *sess,
|
||||
ne_notify_status status, void *userdata)
|
||||
{
|
||||
sess->notify_cb = status;
|
||||
sess->notify_ud = userdata;
|
||||
}
|
||||
|
||||
void ne_set_persist(ne_session *sess, int persist)
|
||||
{
|
||||
sess->no_persist = !persist;
|
||||
}
|
||||
|
||||
void ne_set_read_timeout(ne_session *sess, int timeout)
|
||||
{
|
||||
sess->rdtimeout = timeout;
|
||||
}
|
||||
|
||||
#define UAHDR "User-Agent: "
|
||||
#define AGENT " neon/" NEON_VERSION "\r\n"
|
||||
|
||||
void ne_set_useragent(ne_session *sess, const char *token)
|
||||
{
|
||||
if (sess->user_agent) ne_free(sess->user_agent);
|
||||
sess->user_agent = ne_malloc(strlen(UAHDR) + strlen(AGENT) +
|
||||
strlen(token) + 1);
|
||||
#ifdef HAVE_STPCPY
|
||||
strcpy(stpcpy(stpcpy(sess->user_agent, UAHDR), token), AGENT);
|
||||
#else
|
||||
strcat(strcat(strcpy(sess->user_agent, UAHDR), token), AGENT);
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *ne_get_server_hostport(ne_session *sess)
|
||||
{
|
||||
return sess->server.hostport;
|
||||
}
|
||||
|
||||
const char *ne_get_scheme(ne_session *sess)
|
||||
{
|
||||
return sess->scheme;
|
||||
}
|
||||
|
||||
void ne_fill_server_uri(ne_session *sess, ne_uri *uri)
|
||||
{
|
||||
uri->host = ne_strdup(sess->server.hostname);
|
||||
uri->port = sess->server.port;
|
||||
uri->scheme = ne_strdup(sess->scheme);
|
||||
}
|
||||
|
||||
const char *ne_get_error(ne_session *sess)
|
||||
{
|
||||
return sess->error;
|
||||
}
|
||||
|
||||
void ne_close_connection(ne_session *sess)
|
||||
{
|
||||
if (sess->connected) {
|
||||
NE_DEBUG(NE_DBG_SOCKET, "Closing connection.\n");
|
||||
ne_sock_close(sess->socket);
|
||||
sess->socket = NULL;
|
||||
NE_DEBUG(NE_DBG_SOCKET, "Connection closed.\n");
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_SOCKET, "(Not closing closed connection!).\n");
|
||||
}
|
||||
sess->connected = 0;
|
||||
}
|
||||
|
||||
void ne_ssl_set_verify(ne_session *sess, ne_ssl_verify_fn fn, void *userdata)
|
||||
{
|
||||
sess->ssl_verify_fn = fn;
|
||||
sess->ssl_verify_ud = userdata;
|
||||
}
|
||||
|
||||
void ne_ssl_provide_clicert(ne_session *sess,
|
||||
ne_ssl_provide_fn fn, void *userdata)
|
||||
{
|
||||
sess->ssl_provide_fn = fn;
|
||||
sess->ssl_provide_ud = userdata;
|
||||
}
|
||||
|
||||
void ne_ssl_trust_cert(ne_session *sess, const ne_ssl_certificate *cert)
|
||||
{
|
||||
#ifdef NE_HAVE_SSL
|
||||
ne_ssl_context_trustcert(sess->ssl_context, cert);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
HTTP session handling
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_SESSION_H
|
||||
#define NE_SESSION_H 1
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "ne_ssl.h"
|
||||
#include "ne_uri.h" /* for ne_uri */
|
||||
#include "ne_defs.h"
|
||||
#include "ne_socket.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
typedef struct ne_session_s ne_session;
|
||||
|
||||
/* Create a session to the given server, using the given scheme. If
|
||||
* "https" is passed as the scheme, SSL will be used to connect to the
|
||||
* server. */
|
||||
ne_session *ne_session_create(const char *scheme,
|
||||
const char *hostname, unsigned int port);
|
||||
|
||||
/* Finish an HTTP session */
|
||||
void ne_session_destroy(ne_session *sess);
|
||||
|
||||
/* Prematurely force the connection to be closed for the given
|
||||
* session. */
|
||||
void ne_close_connection(ne_session *sess);
|
||||
|
||||
/* Set the proxy server to be used for the session. */
|
||||
void ne_session_proxy(ne_session *sess,
|
||||
const char *hostname, unsigned int port);
|
||||
|
||||
/* Disable use of persistent connection if 'flag' is non-zero, else
|
||||
* enable (the default). */
|
||||
void ne_set_persist(ne_session *sess, int flag);
|
||||
|
||||
/* Bypass the normal name resolution; force the use of specific set of
|
||||
* addresses for this session, addrs[0]...addrs[n-1]. The addrs array
|
||||
* must remain valid until the session is destroyed. */
|
||||
void ne_set_addrlist(ne_session *sess, const ne_inet_addr **addrs, size_t n);
|
||||
|
||||
/* Progress callback. */
|
||||
typedef void (*ne_progress)(void *userdata, off_t progress, off_t total);
|
||||
|
||||
/* Set a progress callback for the session. */
|
||||
void ne_set_progress(ne_session *sess,
|
||||
ne_progress progress, void *userdata);
|
||||
|
||||
/* Store an opaque context for the session, 'priv' is returned by a
|
||||
* call to ne_session_get_private with the same ID. */
|
||||
void ne_set_session_private(ne_session *sess, const char *id, void *priv);
|
||||
void *ne_get_session_private(ne_session *sess, const char *id);
|
||||
|
||||
typedef enum {
|
||||
ne_conn_namelookup, /* lookup up hostname (info = hostname) */
|
||||
ne_conn_connecting, /* connecting to host (info = hostname) */
|
||||
ne_conn_connected, /* connected to host (info = hostname) */
|
||||
ne_conn_secure /* connection now secure (info = crypto level) */
|
||||
} ne_conn_status;
|
||||
|
||||
typedef void (*ne_notify_status)(void *userdata,
|
||||
ne_conn_status status,
|
||||
const char *info);
|
||||
|
||||
|
||||
/* Set a status notification callback for the session, to report
|
||||
* connection status. */
|
||||
void ne_set_status(ne_session *sess,
|
||||
ne_notify_status status, void *userdata);
|
||||
|
||||
/* Certificate verification failures.
|
||||
* The certificate is not yet valid: */
|
||||
#define NE_SSL_NOTYETVALID (0x01)
|
||||
/* The certificate has expired: */
|
||||
#define NE_SSL_EXPIRED (0x02)
|
||||
/* The hostname for which the certificate was issued does not
|
||||
* match the hostname of the server; this could mean that the
|
||||
* connection is being intercepted: */
|
||||
#define NE_SSL_IDMISMATCH (0x04)
|
||||
/* The certificate authority which signed the server certificate is
|
||||
* not trusted: there is no indicatation the server is who they claim
|
||||
* to be: */
|
||||
#define NE_SSL_UNTRUSTED (0x08)
|
||||
|
||||
/* The bitmask of known failure bits: if (failures & ~NE_SSL_FAILMASK)
|
||||
* is non-zero, an unrecognized failure is given, and the verification
|
||||
* should be failed. */
|
||||
#define NE_SSL_FAILMASK (0x0f)
|
||||
|
||||
/* A callback which is used when server certificate verification is
|
||||
* needed. The reasons for verification failure are given in the
|
||||
* 'failures' parameter, which is a binary OR of one or more of the
|
||||
* above NE_SSL_* values. failures is guaranteed to be non-zero. The
|
||||
* callback must return zero to accept the certificate: a non-zero
|
||||
* return value will fail the SSL negotiation. */
|
||||
typedef int (*ne_ssl_verify_fn)(void *userdata, int failures,
|
||||
const ne_ssl_certificate *cert);
|
||||
|
||||
/* Install a callback to handle server certificate verification. This
|
||||
* is required when the CA certificate is not known for the server
|
||||
* certificate, or the server cert has other verification problems. */
|
||||
void ne_ssl_set_verify(ne_session *sess, ne_ssl_verify_fn fn, void *userdata);
|
||||
|
||||
/* Use the given client certificate for the session. The client cert
|
||||
* MUST be in the decrypted state, otherwise behaviour is undefined. */
|
||||
void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *clicert);
|
||||
|
||||
/* Indicate that the certificate 'cert' is trusted; 'cert' is
|
||||
* duplicated internally and may be destroyed at will. */
|
||||
void ne_ssl_trust_cert(ne_session *sess, const ne_ssl_certificate *cert);
|
||||
|
||||
/* If the SSL library provided a default set of CA certificates, trust
|
||||
* this set of CAs. */
|
||||
void ne_ssl_trust_default_ca(ne_session *sess);
|
||||
|
||||
/* Callback used to load a client certificate on demand. If dncount
|
||||
* is > 0, the 'dnames' array dnames[0] through dnames[dncount-1]
|
||||
* gives the list of CA names which the server indicated were
|
||||
* acceptable. The callback should load an appropriate client
|
||||
* certificate and then pass it to 'ne_ssl_set_clicert'. */
|
||||
typedef void (*ne_ssl_provide_fn)(void *userdata, ne_session *sess,
|
||||
const ne_ssl_dname *const *dnames,
|
||||
int dncount);
|
||||
|
||||
/* Register a function to be called when the server requests a client
|
||||
* certificate. */
|
||||
void ne_ssl_provide_clicert(ne_session *sess,
|
||||
ne_ssl_provide_fn fn, void *userdata);
|
||||
|
||||
/* Set the timeout (in seconds) used when reading from a socket. The
|
||||
* timeout value must be greater than zero. */
|
||||
void ne_set_read_timeout(ne_session *sess, int timeout);
|
||||
|
||||
/* Sets the user-agent string. neon/VERSION will be appended, to make
|
||||
* the full header "User-Agent: product neon/VERSION".
|
||||
* If this function is not called, the User-Agent header is not sent.
|
||||
* The product string must follow the RFC2616 format, i.e.
|
||||
* product = token ["/" product-version]
|
||||
* product-version = token
|
||||
* where token is any alpha-numeric-y string [a-zA-Z0-9]* */
|
||||
void ne_set_useragent(ne_session *sess, const char *product);
|
||||
|
||||
/* Returns non-zero if next-hop server does not claim compliance to
|
||||
* HTTP/1.1 or later. */
|
||||
int ne_version_pre_http11(ne_session *sess);
|
||||
|
||||
/* Returns the 'hostport' URI segment for the end-server, e.g.
|
||||
* "my.server.com:8080". */
|
||||
const char *ne_get_server_hostport(ne_session *sess);
|
||||
|
||||
/* Returns the URL scheme being used for the current session, omitting
|
||||
* the trailing ':'; e.g. "http" or "https". */
|
||||
const char *ne_get_scheme(ne_session *sess);
|
||||
|
||||
/* Sets the host, scheme, and port fields (and no others) of the given
|
||||
* URI structure; host and scheme are malloc-allocated. */
|
||||
void ne_fill_server_uri(ne_session *sess, ne_uri *uri);
|
||||
|
||||
/* Set the error string for the session; takes printf-like format
|
||||
* string. */
|
||||
void ne_set_error(ne_session *sess, const char *format, ...)
|
||||
ne_attribute((format (printf, 2, 3)));
|
||||
|
||||
/* Retrieve the error string for the session */
|
||||
const char *ne_get_error(ne_session *sess);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_SESSION_H */
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
socket handling interface
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_SOCKET_H
|
||||
#define NE_SOCKET_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "ne_defs.h"
|
||||
#include "ne_ssl.h" /* for ne_ssl_context */
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
#define NE_SOCK_ERROR (-1)
|
||||
/* Read/Write timed out */
|
||||
#define NE_SOCK_TIMEOUT (-2)
|
||||
/* Socket was closed */
|
||||
#define NE_SOCK_CLOSED (-3)
|
||||
/* Connection was reset (e.g. server crashed) */
|
||||
#define NE_SOCK_RESET (-4)
|
||||
/* Secure connection was closed without proper SSL shutdown. */
|
||||
#define NE_SOCK_TRUNC (-5)
|
||||
|
||||
/* ne_socket represents a TCP socket. */
|
||||
typedef struct ne_socket_s ne_socket;
|
||||
|
||||
/* ne_sock_addr represents an address object. */
|
||||
typedef struct ne_sock_addr_s ne_sock_addr;
|
||||
|
||||
#ifndef NE_INET_ADDR_DEFINED
|
||||
typedef struct ne_inet_addr_s ne_inet_addr;
|
||||
#endif
|
||||
|
||||
/* While neon itself doesn't require per-process global
|
||||
* initialization, some platforms do, and so does the OpenSSL
|
||||
* library. */
|
||||
int ne_sock_init(void);
|
||||
|
||||
/* Shutdown any underlying libraries. */
|
||||
void ne_sock_exit(void);
|
||||
|
||||
/* Resolve the given hostname. 'flags' must be zero. Hex
|
||||
* string IPv6 addresses (e.g. `::1') may be enclosed in brackets
|
||||
* (e.g. `[::1]'). */
|
||||
ne_sock_addr *ne_addr_resolve(const char *hostname, int flags);
|
||||
|
||||
/* Returns zero if name resolution was successful, non-zero on
|
||||
* error. */
|
||||
int ne_addr_result(const ne_sock_addr *addr);
|
||||
|
||||
/* Returns the first network address associated with the 'addr'
|
||||
* object. Undefined behaviour if ne_addr_result returns non-zero for
|
||||
* 'addr'; otherwise, never returns NULL. */
|
||||
const ne_inet_addr *ne_addr_first(ne_sock_addr *addr);
|
||||
|
||||
/* Returns the next network address associated with the 'addr' object,
|
||||
* or NULL if there are no more. */
|
||||
const ne_inet_addr *ne_addr_next(ne_sock_addr *addr);
|
||||
|
||||
/* NB: the pointers returned by ne_addr_first and ne_addr_next are
|
||||
* valid until ne_addr_destroy is called for the corresponding
|
||||
* ne_sock_addr object. They must not be passed to ne_iaddr_free. */
|
||||
|
||||
/* If name resolution fails, copies the error string into 'buffer',
|
||||
* which is of size 'bufsiz'. 'buffer' is returned. */
|
||||
char *ne_addr_error(const ne_sock_addr *addr, char *buffer, size_t bufsiz);
|
||||
|
||||
/* Destroys an address object created by ne_addr_resolve. */
|
||||
void ne_addr_destroy(ne_sock_addr *addr);
|
||||
|
||||
/* Network address type; IPv4 or IPv6 */
|
||||
typedef enum {
|
||||
ne_iaddr_ipv4 = 0,
|
||||
ne_iaddr_ipv6
|
||||
} ne_iaddr_type;
|
||||
|
||||
/* Create a network address from raw byte representation (in network
|
||||
* byte order) of given type. 'raw' must be four bytes for an IPv4
|
||||
* address, 16 bytes for an IPv6 address. May return NULL if address
|
||||
* type is not supported. */
|
||||
ne_inet_addr *ne_iaddr_make(ne_iaddr_type type, const unsigned char *raw);
|
||||
|
||||
/* Compare two network addresses i1 and i2; return non-zero if they
|
||||
* are not equal. */
|
||||
int ne_iaddr_cmp(const ne_inet_addr *i1, const ne_inet_addr *i2);
|
||||
|
||||
/* Returns the type of the given network address. */
|
||||
ne_iaddr_type ne_iaddr_typeof(const ne_inet_addr *ia);
|
||||
|
||||
/* Prints the string representation of network address 'ia' into the
|
||||
* 'buffer', which is of size 'bufsiz'. Returns 'buffer'. */
|
||||
char *ne_iaddr_print(const ne_inet_addr *ia, char *buffer, size_t bufsiz);
|
||||
|
||||
/* Free a network address created using ne_iaddr_make. */
|
||||
void ne_iaddr_free(ne_inet_addr *addr);
|
||||
|
||||
/* Create a TCP socket; returns NULL on error. */
|
||||
ne_socket *ne_sock_create(void);
|
||||
|
||||
/* Connect the socket to server at address 'addr' on port 'port'.
|
||||
* Returns non-zero if a connection could not be established. */
|
||||
int ne_sock_connect(ne_socket *sock, const ne_inet_addr *addr,
|
||||
unsigned int port);
|
||||
|
||||
/* ne_sock_read reads up to 'count' bytes into 'buffer'.
|
||||
* Returns:
|
||||
* NE_SOCK_* on error,
|
||||
* >0 length of data read into buffer.
|
||||
*/
|
||||
ssize_t ne_sock_read(ne_socket *sock, char *buffer, size_t count);
|
||||
|
||||
/* ne_sock_peek reads up to 'count' bytes into 'buffer', but the data
|
||||
* will still be returned on a subsequent call to ne_sock_read or
|
||||
* ne_sock_peek.
|
||||
* Returns:
|
||||
* NE_SOCK_* on error,
|
||||
* >0 length of data read into buffer.
|
||||
*/
|
||||
ssize_t ne_sock_peek(ne_socket *sock, char *buffer, size_t count);
|
||||
|
||||
/* Block for up to 'n' seconds until data becomes available for reading
|
||||
* on the socket. Returns:
|
||||
* NE_SOCK_* on error,
|
||||
* NE_SOCK_TIMEOUT if no data arrives in 'n' seconds.
|
||||
* 0 if data arrived on the socket.
|
||||
*/
|
||||
int ne_sock_block(ne_socket *sock, int n);
|
||||
|
||||
/* Writes 'count' bytes of 'data' to the socket.
|
||||
* Returns 0 on success, NE_SOCK_* on error. */
|
||||
int ne_sock_fullwrite(ne_socket *sock, const char *data, size_t count);
|
||||
|
||||
/* Reads an LF-terminated line into 'buffer', and NUL-terminate it.
|
||||
* At most 'len' bytes are read (including the NUL terminator).
|
||||
* Returns:
|
||||
* NE_SOCK_* on error,
|
||||
* >0 number of bytes read (including NUL terminator)
|
||||
*/
|
||||
ssize_t ne_sock_readline(ne_socket *sock, char *buffer, size_t len);
|
||||
|
||||
/* Read exactly 'len' bytes into buffer; returns 0 on success,
|
||||
* NE_SOCK_* on error. */
|
||||
ssize_t ne_sock_fullread(ne_socket *sock, char *buffer, size_t len);
|
||||
|
||||
/* Accept a connection on listening socket 'fd'. */
|
||||
int ne_sock_accept(ne_socket *sock, int fd);
|
||||
|
||||
/* Returns the file descriptor used for socket 'sock'. */
|
||||
int ne_sock_fd(const ne_socket *sock);
|
||||
|
||||
/* Close the socket, and destroy the socket object. Returns non-zero
|
||||
* on error. */
|
||||
int ne_sock_close(ne_socket *sock);
|
||||
|
||||
/* Return current error string for socket. */
|
||||
const char *ne_sock_error(const ne_socket *sock);
|
||||
|
||||
/* Set read timeout for socket. */
|
||||
void ne_sock_read_timeout(ne_socket *sock, int timeout);
|
||||
|
||||
/* Negotiate an SSL connection on socket as an SSL server, using given
|
||||
* SSL context. */
|
||||
int ne_sock_accept_ssl(ne_socket *sock, ne_ssl_context *ctx);
|
||||
|
||||
/* Negotiate an SSL connection on socket as an SSL client, using given
|
||||
* SSL context. The 'userdata' parameter is associated with the
|
||||
* underlying SSL library's socket structure for use in callbacks.
|
||||
* Returns zero on success, or non-zero on error. */
|
||||
int ne_sock_connect_ssl(ne_socket *sock, ne_ssl_context *ctx,
|
||||
void *userdata);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_SOCKET_H */
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
SSL/TLS abstraction layer for neon
|
||||
Copyright (C) 2003-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
/* ne_ssl.h defines an interface for loading and accessing the
|
||||
* properties of SSL certificates. */
|
||||
|
||||
#ifndef NE_SSL_H
|
||||
#define NE_SSL_H 1
|
||||
|
||||
#include "ne_defs.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* A "distinguished name"; a unique name for some entity. */
|
||||
typedef struct ne_ssl_dname_s ne_ssl_dname;
|
||||
|
||||
/* Returns a single-line string representation of a distinguished
|
||||
* name, intended to be human-readable (e.g. "Acme Ltd., Norfolk,
|
||||
* GB"). Return value is a UTF-8-encoded malloc-allocated string and
|
||||
* must be free'd by the caller. */
|
||||
char *ne_ssl_readable_dname(const ne_ssl_dname *dn);
|
||||
|
||||
/* Returns zero if 'dn1' and 'dn2' refer to same name, or non-zero if
|
||||
* they are different. */
|
||||
int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2);
|
||||
|
||||
/* An SSL certificate. */
|
||||
typedef struct ne_ssl_certificate_s ne_ssl_certificate;
|
||||
|
||||
/* Read a certificate from a file in PEM format; returns NULL if the
|
||||
* certificate could not be parsed. */
|
||||
ne_ssl_certificate *ne_ssl_cert_read(const char *filename);
|
||||
|
||||
/* Write a certificate to a file in PEM format; returns non-zero if
|
||||
* the certificate could not be written. */
|
||||
int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename);
|
||||
|
||||
/* Export a certificate to a base64-encoded, NUL-terminated string.
|
||||
* The returned string is malloc-allocated and must be free()d by the
|
||||
* caller. */
|
||||
char *ne_ssl_cert_export(const ne_ssl_certificate *cert);
|
||||
|
||||
/* Import a certificate from a base64-encoded string as returned by
|
||||
* ne_ssl_cert_export(). Returns a certificate object or NULL if
|
||||
* 'data' was not valid. */
|
||||
ne_ssl_certificate *ne_ssl_cert_import(const char *data);
|
||||
|
||||
/* Returns the identity of the certificate, or NULL if none is given.
|
||||
* For a server certificate this will be the hostname of the server to
|
||||
* whom the cert was issued. String returned is UTF-8-encoded. */
|
||||
const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert);
|
||||
|
||||
/* Return the certificate of the entity which signed certificate
|
||||
* 'cert'. Returns NULL if 'cert' is self-signed or the issuer
|
||||
* certificate is not available. */
|
||||
const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert);
|
||||
|
||||
/* Returns the distinguished name of the certificate issuer. */
|
||||
const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert);
|
||||
|
||||
/* Returns the distinguished name of the certificate subject. */
|
||||
const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert);
|
||||
|
||||
#define NE_SSL_DIGESTLEN (60)
|
||||
|
||||
/* Calculate the certificate digest ("fingerprint") and format it as a
|
||||
* NUL-terminated hex string in 'digest', of the form "aa:bb:...:ff".
|
||||
* Returns zero on success or non-zero if there was an internal error
|
||||
* whilst calculating the digest. 'digest' must be at least
|
||||
* NE_SSL_DIGESTLEN bytes in length. */
|
||||
int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest);
|
||||
|
||||
#define NE_SSL_VDATELEN (30)
|
||||
|
||||
/* Copy the validity dates into buffers 'from' and 'until' as
|
||||
* NUL-terminated human-readable strings. The buffers must be at
|
||||
* least NE_SSL_VDATELEN bytes in length. */
|
||||
void ne_ssl_cert_validity(const ne_ssl_certificate *cert,
|
||||
char *from, char *until);
|
||||
|
||||
/* Returns zero if 'c1' and 'c2' refer to the same certificate, or
|
||||
* non-zero otherwise. */
|
||||
int ne_ssl_cert_cmp(const ne_ssl_certificate *c1,
|
||||
const ne_ssl_certificate *c2);
|
||||
|
||||
/* Deallocate memory associated with certificate. */
|
||||
void ne_ssl_cert_free(ne_ssl_certificate *cert);
|
||||
|
||||
/* A client certificate (and private key). */
|
||||
typedef struct ne_ssl_client_cert_s ne_ssl_client_cert;
|
||||
|
||||
/* Read a client certificate and private key from a PKCS12 file;
|
||||
* returns NULL if the file could not be parsed. If the client cert
|
||||
* is encrypted, it must be decrypted before use. */
|
||||
ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename);
|
||||
|
||||
/* Returns the "friendly name" given for the client cert, or NULL if
|
||||
* none given. This can be called before or after the client cert has
|
||||
* been decrypted. Returns a NUL-terminated, UTF-8-encoded string. */
|
||||
const char *ne_ssl_clicert_name(const ne_ssl_client_cert *ccert);
|
||||
|
||||
/* Returns non-zero if client cert is encrypted. */
|
||||
int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *ccert);
|
||||
|
||||
/* Decrypt the encrypted client cert using given password. Returns
|
||||
* non-zero on failure, in which case, the function can be called
|
||||
* again with a different password. For a ccert on which _encrypted()
|
||||
* returns 0, calling _decrypt results in undefined behaviour. */
|
||||
int ne_ssl_clicert_decrypt(ne_ssl_client_cert *ccert, const char *password);
|
||||
|
||||
/* Return the actual certificate part of the client certificate (never
|
||||
* returns NULL). */
|
||||
const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *ccert);
|
||||
|
||||
/* Deallocate memory associated with a client certificate. */
|
||||
void ne_ssl_clicert_free(ne_ssl_client_cert *ccert);
|
||||
|
||||
|
||||
/* SSL context object. The interfaces to manipulate an SSL context
|
||||
* are only needed when interfacing directly with ne_socket.h. */
|
||||
typedef struct ne_ssl_context_s ne_ssl_context;
|
||||
|
||||
/* Context creation modes: */
|
||||
#define NE_SSL_CTX_CLIENT (0) /* client context */
|
||||
#define NE_SSL_CTX_SERVER (1) /* default server context */
|
||||
#define NE_SSL_CTX_SERVERv2 (2) /* SSLv2-specific server context */
|
||||
|
||||
/* Create an SSL context. */
|
||||
ne_ssl_context *ne_ssl_context_create(int mode);
|
||||
|
||||
/* Client mode: trust the given certificate 'cert' in context 'ctx'. */
|
||||
void ne_ssl_context_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert);
|
||||
|
||||
/* Server mode: use given cert and key (filenames to PEM certificates). */
|
||||
int ne_ssl_context_keypair(ne_ssl_context *ctx,
|
||||
const char *cert, const char *key);
|
||||
|
||||
/* Server mode: set client cert verification options: required is non-zero if
|
||||
* a client cert is required, if ca_names is non-NULL it is a filename containing
|
||||
* a set of PEM certs from which CA names are sent in the ccert request. */
|
||||
int ne_ssl_context_set_verify(ne_ssl_context *ctx, int required,
|
||||
const char *ca_names, const char *verify_cas);
|
||||
|
||||
/* Destroy an SSL context. */
|
||||
void ne_ssl_context_destroy(ne_ssl_context *ctx);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,548 @@
|
||||
/*
|
||||
Microsoft SSPI based authentication routines
|
||||
Copyright (C) 2004-2005, Vladimir Berezniker @ http://public.xdi.org/=vmpn
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ne_utils.h"
|
||||
#include "ne_string.h"
|
||||
#include "ne_sspi.h"
|
||||
|
||||
#ifdef HAVE_SSPI
|
||||
|
||||
#define SEC_SUCCESS(Status) ((Status) >= 0)
|
||||
|
||||
struct SSPIContextStruct {
|
||||
CtxtHandle context;
|
||||
char *serverName;
|
||||
CredHandle credentials;
|
||||
int continueNeeded;
|
||||
char *mechanism;
|
||||
int ntlm;
|
||||
ULONG maxTokenSize;
|
||||
};
|
||||
|
||||
typedef struct SSPIContextStruct SSPIContext;
|
||||
|
||||
static ULONG negotiateMaxTokenSize = 0;
|
||||
static ULONG ntlmMaxTokenSize = 0;
|
||||
static HINSTANCE hSecDll = NULL;
|
||||
static PSecurityFunctionTable pSFT = NULL;
|
||||
static int initialized = 0;
|
||||
|
||||
/*
|
||||
* Query specified package for it's maximum token size.
|
||||
*/
|
||||
static int getMaxTokenSize(char *package, ULONG * maxTokenSize)
|
||||
{
|
||||
SECURITY_STATUS status;
|
||||
SecPkgInfo *packageSecurityInfo = NULL;
|
||||
|
||||
status = pSFT->QuerySecurityPackageInfo(package, &packageSecurityInfo);
|
||||
if (status == SEC_E_OK) {
|
||||
*maxTokenSize = packageSecurityInfo->cbMaxToken;
|
||||
if (pSFT->FreeContextBuffer(packageSecurityInfo) != SEC_E_OK) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Unable to free security package info.");
|
||||
}
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: QuerySecurityPackageInfo [failed] [%x].", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize all the SSPI data
|
||||
*/
|
||||
static void initDll(HINSTANCE hSecDll)
|
||||
{
|
||||
INIT_SECURITY_INTERFACE initSecurityInterface = NULL;
|
||||
|
||||
initSecurityInterface =
|
||||
(INIT_SECURITY_INTERFACE) GetProcAddress(hSecDll,
|
||||
SECURITY_ENTRYPOINT);
|
||||
|
||||
if (initSecurityInterface == NULL) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Obtaining security interface [fail].\n");
|
||||
initialized = -1;
|
||||
return;
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Obtaining security interface [ok].\n");
|
||||
}
|
||||
|
||||
pSFT = (initSecurityInterface) ();
|
||||
|
||||
if (pSFT == NULL) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Security Function Table [fail].\n");
|
||||
initialized = -2;
|
||||
return;
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Security Function Table [ok].\n");
|
||||
}
|
||||
|
||||
if (getMaxTokenSize("Negotiate", &negotiateMaxTokenSize)) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Unable to get negotiate maximum packet size");
|
||||
initialized = -3;
|
||||
}
|
||||
|
||||
if (getMaxTokenSize("NTLM", &ntlmMaxTokenSize)) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Unable to get negotiate maximum packet size");
|
||||
initialized = -3;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function needs to be called at least once before using any other.
|
||||
*/
|
||||
int ne_sspi_init(void)
|
||||
{
|
||||
if (initialized) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_SOCKET, "sspiInit\n");
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading security dll.\n");
|
||||
hSecDll = LoadLibrary("security.dll");
|
||||
|
||||
if (hSecDll == NULL) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading of security dll [fail].\n");
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Loading of security dll [ok].\n");
|
||||
initDll(hSecDll);
|
||||
if (initialized == 0) {
|
||||
initialized = 1;
|
||||
}
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: sspiInit [%d].\n", initialized);
|
||||
if (initialized < 0) {
|
||||
return initialized;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function can be called to free resources used by SSPI.
|
||||
*/
|
||||
int ne_sspi_deinit(void)
|
||||
{
|
||||
NE_DEBUG(NE_DBG_SOCKET, "sspi: DeInit\n");
|
||||
if (initialized <= 0) {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
pSFT = NULL;
|
||||
|
||||
if (hSecDll != NULL) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Unloading security dll.\n");
|
||||
if (FreeLibrary(hSecDll)) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Unloading of security dll [ok].\n");
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Unloading of security dll [fail].\n");
|
||||
return -1;
|
||||
}
|
||||
hSecDll = NULL;
|
||||
}
|
||||
|
||||
initialized = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Simplification wrapper arround AcquireCredentialsHandle as most of
|
||||
* the parameters do not change.
|
||||
*/
|
||||
static int acquireCredentialsHandle(CredHandle * credentials, char *package)
|
||||
{
|
||||
SECURITY_STATUS status;
|
||||
TimeStamp timestamp;
|
||||
|
||||
status =
|
||||
pSFT->AcquireCredentialsHandle(NULL, package, SECPKG_CRED_OUTBOUND,
|
||||
NULL, NULL, NULL, NULL, credentials,
|
||||
×tamp);
|
||||
|
||||
if (status != SEC_E_OK) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: AcquireCredentialsHandle [fail] [%x].\n", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrapper arround initializeSecurityContext. Supplies several
|
||||
* default parameters as well as logging in case of errors.
|
||||
*/
|
||||
static SECURITY_STATUS
|
||||
initializeSecurityContext(CredHandle * credentials, CtxtHandle * context,
|
||||
char *spn, ULONG contextReq,
|
||||
SecBufferDesc * inBuffer, CtxtHandle * newContext,
|
||||
SecBufferDesc * outBuffer)
|
||||
{
|
||||
ULONG contextAttributes;
|
||||
SECURITY_STATUS status;
|
||||
|
||||
status =
|
||||
pSFT->InitializeSecurityContext(credentials, context, spn, contextReq,
|
||||
0, SECURITY_NETWORK_DREP, inBuffer, 0,
|
||||
newContext, outBuffer,
|
||||
&contextAttributes, NULL);
|
||||
|
||||
if (!SEC_SUCCESS(status)) {
|
||||
if (status == SEC_E_INVALID_TOKEN) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"InitializeSecurityContext [fail] SEC_E_INVALID_TOKEN.\n");
|
||||
} else if (status == SEC_E_UNSUPPORTED_FUNCTION) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"InitializeSecurityContext [fail] SEC_E_UNSUPPORTED_FUNCTION.\n");
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"InitializeSecurityContext [fail] [%x].\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validates that the pointer is not NULL and converts it to its real type.
|
||||
*/
|
||||
static int getContext(void *context, SSPIContext **sspiContext)
|
||||
{
|
||||
if (!context) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*sspiContext = context;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that the buffer descriptor point only to one buffer and
|
||||
* returns the pointer to it.
|
||||
*/
|
||||
static int getSingleBufferDescriptor(SecBufferDesc *secBufferDesc,
|
||||
SecBuffer **secBuffer)
|
||||
{
|
||||
if (secBufferDesc->cBuffers != 1) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: fillBufferDescriptor "
|
||||
"[fail] numbers of descriptor buffers. 1 != [%d].\n",
|
||||
secBufferDesc->cBuffers);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*secBuffer = secBufferDesc->pBuffers;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decodes BASE64 string into SSPI SecBuffer
|
||||
*/
|
||||
static int base64ToBuffer(const char *token, SecBufferDesc * secBufferDesc)
|
||||
{
|
||||
SecBuffer *buffer;
|
||||
if (getSingleBufferDescriptor(secBufferDesc, &buffer)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buffer->BufferType = SECBUFFER_TOKEN;
|
||||
buffer->cbBuffer =
|
||||
ne_unbase64(token, &((unsigned char *) buffer->pvBuffer));
|
||||
|
||||
if (buffer->cbBuffer == 0) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: Unable to decode BASE64 SSPI token.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a SecBuffer of a specified size.
|
||||
*/
|
||||
static int makeBuffer(SecBufferDesc * secBufferDesc, ULONG size)
|
||||
{
|
||||
SecBuffer *buffer;
|
||||
if (getSingleBufferDescriptor(secBufferDesc, &buffer)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buffer->BufferType = SECBUFFER_TOKEN;
|
||||
buffer->cbBuffer = size;
|
||||
buffer->pvBuffer = ne_calloc(size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees data allocated in the buffer.
|
||||
*/
|
||||
static int freeBuffer(SecBufferDesc * secBufferDesc)
|
||||
{
|
||||
SecBuffer *buffer;
|
||||
if (getSingleBufferDescriptor(secBufferDesc, &buffer)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buffer->cbBuffer > 0 && buffer->pvBuffer) {
|
||||
ne_free(buffer->pvBuffer);
|
||||
buffer->cbBuffer = 0;
|
||||
buffer->pvBuffer = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns mechanism string for the specified context.
|
||||
*/
|
||||
int ne_sspi_get_mechanism(void *context, char const **mechanism)
|
||||
{
|
||||
int status;
|
||||
SSPIContext *sspiContext;
|
||||
|
||||
if (initialized <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = getContext(context, &sspiContext);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
*mechanism = sspiContext->mechanism;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a context to authenticate to specified server, using either
|
||||
* ntlm or negotiate.
|
||||
*/
|
||||
int ne_sspi_create_context(void **context, char *serverName, int ntlm)
|
||||
{
|
||||
SSPIContext *sspiContext;
|
||||
|
||||
if (initialized <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sspiContext = ne_calloc(sizeof(SSPIContext));
|
||||
sspiContext->continueNeeded = 0;
|
||||
|
||||
if (ntlm) {
|
||||
sspiContext->mechanism = "NTLM";
|
||||
sspiContext->serverName = ne_strdup(serverName);
|
||||
sspiContext->maxTokenSize = ntlmMaxTokenSize;
|
||||
} else {
|
||||
sspiContext->mechanism = "Negotiate";
|
||||
sspiContext->serverName = ne_concat("HTTP/", serverName, NULL);
|
||||
sspiContext->maxTokenSize = negotiateMaxTokenSize;
|
||||
}
|
||||
|
||||
sspiContext->ntlm = ntlm;
|
||||
*context = sspiContext;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Resets the context
|
||||
*/
|
||||
static void resetContext(SSPIContext * sspiContext)
|
||||
{
|
||||
pSFT->DeleteSecurityContext(&(sspiContext->context));
|
||||
pSFT->FreeCredentialHandle(&(sspiContext->credentials));
|
||||
sspiContext->continueNeeded = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes supplied SecBufferDesc to point to supplied SecBuffer
|
||||
* that is also initialized;
|
||||
*/
|
||||
static void
|
||||
initSingleEmptyBuffer(SecBufferDesc * bufferDesc, SecBuffer * buffer)
|
||||
{
|
||||
buffer->BufferType = SECBUFFER_EMPTY;
|
||||
buffer->cbBuffer = 0;
|
||||
buffer->pvBuffer = NULL;
|
||||
|
||||
bufferDesc->cBuffers = 1;
|
||||
bufferDesc->ulVersion = SECBUFFER_VERSION;
|
||||
bufferDesc->pBuffers = buffer;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroyes the supplied context.
|
||||
*/
|
||||
int ne_sspi_destroy_context(void *context)
|
||||
{
|
||||
|
||||
int status;
|
||||
SSPIContext *sspiContext;
|
||||
|
||||
if (initialized <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = getContext(context, &sspiContext);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
resetContext(sspiContext);
|
||||
if (sspiContext->serverName) {
|
||||
ne_free(sspiContext->serverName);
|
||||
sspiContext->serverName = NULL;
|
||||
}
|
||||
|
||||
ne_free(sspiContext);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Processes received authentication tokens as well as supplies the
|
||||
* response token.
|
||||
*/
|
||||
int ne_sspi_authenticate(void *context, const char *base64Token, char **responseToken)
|
||||
{
|
||||
SecBufferDesc outBufferDesc;
|
||||
SecBuffer outBuffer;
|
||||
int status;
|
||||
SECURITY_STATUS securityStatus;
|
||||
ULONG contextFlags;
|
||||
|
||||
SSPIContext *sspiContext;
|
||||
if (initialized <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = getContext(context, &sspiContext);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* TODO: Not sure what flags should be set. joe: this needs to be
|
||||
* driven by the ne_auth interface; the GSSAPI code needs similar
|
||||
* flags. */
|
||||
contextFlags = ISC_REQ_CONFIDENTIALITY | ISC_REQ_MUTUAL_AUTH;
|
||||
|
||||
initSingleEmptyBuffer(&outBufferDesc, &outBuffer);
|
||||
status = makeBuffer(&outBufferDesc, sspiContext->maxTokenSize);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (base64Token) {
|
||||
SecBufferDesc inBufferDesc;
|
||||
SecBuffer inBuffer;
|
||||
|
||||
if (!sspiContext->continueNeeded) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Got an unexpected token.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
initSingleEmptyBuffer(&inBufferDesc, &inBuffer);
|
||||
|
||||
status = base64ToBuffer(base64Token, &inBufferDesc);
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
securityStatus =
|
||||
initializeSecurityContext(&sspiContext->credentials,
|
||||
&(sspiContext->context),
|
||||
sspiContext->serverName, contextFlags,
|
||||
&inBufferDesc, &(sspiContext->context),
|
||||
&outBufferDesc);
|
||||
freeBuffer(&inBufferDesc);
|
||||
} else {
|
||||
if (sspiContext->continueNeeded) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: Expected a token from server.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Reset any existing context since we are starting over */
|
||||
resetContext(sspiContext);
|
||||
|
||||
if (acquireCredentialsHandle
|
||||
(&sspiContext->credentials, sspiContext->mechanism) != SEC_E_OK) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: acquireCredentialsHandle failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
securityStatus =
|
||||
initializeSecurityContext(&sspiContext->credentials, NULL,
|
||||
sspiContext->serverName, contextFlags,
|
||||
NULL, &(sspiContext->context),
|
||||
&outBufferDesc);
|
||||
}
|
||||
|
||||
if (securityStatus == SEC_I_COMPLETE_AND_CONTINUE
|
||||
|| securityStatus == SEC_I_COMPLETE_NEEDED) {
|
||||
SECURITY_STATUS compleStatus =
|
||||
pSFT->CompleteAuthToken(&(sspiContext->context), &outBufferDesc);
|
||||
|
||||
if (compleStatus != SEC_E_OK) {
|
||||
freeBuffer(&outBufferDesc);
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH, "sspi: CompleteAuthToken failed.\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (securityStatus == SEC_I_COMPLETE_AND_CONTINUE
|
||||
|| securityStatus == SEC_I_CONTINUE_NEEDED) {
|
||||
sspiContext->continueNeeded = 1;
|
||||
} else {
|
||||
sspiContext->continueNeeded = 0;
|
||||
}
|
||||
|
||||
if (!(securityStatus == SEC_I_COMPLETE_AND_CONTINUE
|
||||
|| securityStatus == SEC_I_COMPLETE_NEEDED
|
||||
|| securityStatus == SEC_I_CONTINUE_NEEDED
|
||||
|| securityStatus == SEC_E_OK)) {
|
||||
NE_DEBUG(NE_DBG_HTTPAUTH,
|
||||
"sspi: initializeSecurityContext [failed] [%x].\n",
|
||||
securityStatus);
|
||||
freeBuffer(&outBufferDesc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*responseToken = ne_base64(outBufferDesc.pBuffers->pvBuffer,
|
||||
outBufferDesc.pBuffers->cbBuffer);
|
||||
freeBuffer(&outBufferDesc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* HAVE_SSPI */
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Microsoft SSPI based authentication routines
|
||||
Copyright (C) 2004-2005, Vladimir Berezniker @ http://public.xdi.org/=vmpn
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_SSPI_H
|
||||
#define NE_SSPI_H
|
||||
|
||||
/* Win32 SSPI-based authentication interfaces. PRIVATE TO NEON -- NOT
|
||||
* PART OF THE EXTERNAL API. */
|
||||
|
||||
#ifdef HAVE_SSPI
|
||||
|
||||
#include <windows.h>
|
||||
#define SECURITY_WIN32
|
||||
#include <security.h>
|
||||
|
||||
int ne_sspi_init(void);
|
||||
int ne_sspi_deinit(void);
|
||||
|
||||
int ne_sspi_create_context(void **context, char * serverName, int ntlm);
|
||||
|
||||
int ne_sspi_destroy_context(void *context);
|
||||
|
||||
int ne_sspi_get_mechanism(void *context, char const **mechanism);
|
||||
|
||||
int ne_sspi_authenticate(void *context, const char *base64Token,
|
||||
char **responseToken);
|
||||
|
||||
#endif /* HAVE_SSPI */
|
||||
|
||||
#endif /* NE_SSPI_H */
|
||||
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
String utility functions
|
||||
Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <ctype.h> /* for isprint() etc in ne_strclean() */
|
||||
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_string.h"
|
||||
#include "ne_md5.h"
|
||||
|
||||
char *ne_token(char **str, char separator)
|
||||
{
|
||||
char *ret = *str, *pnt = strchr(*str, separator);
|
||||
|
||||
if (pnt) {
|
||||
*pnt = '\0';
|
||||
*str = pnt + 1;
|
||||
} else {
|
||||
/* no separator found: return end of string. */
|
||||
*str = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *ne_qtoken(char **str, char separator, const char *quotes)
|
||||
{
|
||||
char *pnt, *ret = NULL;
|
||||
|
||||
for (pnt = *str; *pnt != '\0'; pnt++) {
|
||||
char *quot = strchr(quotes, *pnt);
|
||||
|
||||
if (quot) {
|
||||
char *qclose = strchr(pnt+1, *quot);
|
||||
|
||||
if (!qclose) {
|
||||
/* no closing quote: invalid string. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pnt = qclose;
|
||||
} else if (*pnt == separator) {
|
||||
/* found end of token. */
|
||||
*pnt = '\0';
|
||||
ret = *str;
|
||||
*str = pnt + 1;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* no separator found: return end of string. */
|
||||
ret = *str;
|
||||
*str = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *ne_shave(char *str, const char *whitespace)
|
||||
{
|
||||
char *pnt, *ret = str;
|
||||
|
||||
while (*ret != '\0' && strchr(whitespace, *ret) != NULL) {
|
||||
ret++;
|
||||
}
|
||||
|
||||
/* pnt points at the NUL terminator. */
|
||||
pnt = &ret[strlen(ret)];
|
||||
|
||||
while (pnt > ret && strchr(whitespace, *(pnt-1)) != NULL) {
|
||||
pnt--;
|
||||
}
|
||||
|
||||
*pnt = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ne_buffer_clear(ne_buffer *buf)
|
||||
{
|
||||
memset(buf->data, 0, buf->length);
|
||||
buf->used = 1;
|
||||
}
|
||||
|
||||
/* Grows for given size, returns 0 on success, -1 on error. */
|
||||
void ne_buffer_grow(ne_buffer *buf, size_t newsize)
|
||||
{
|
||||
#define NE_BUFFER_GROWTH 512
|
||||
if (newsize > buf->length) {
|
||||
/* If it's not big enough already... */
|
||||
buf->length = ((newsize / NE_BUFFER_GROWTH) + 1) * NE_BUFFER_GROWTH;
|
||||
|
||||
/* Reallocate bigger buffer */
|
||||
buf->data = ne_realloc(buf->data, buf->length);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t count_concat(va_list *ap)
|
||||
{
|
||||
size_t total = 0;
|
||||
char *next;
|
||||
|
||||
while ((next = va_arg(*ap, char *)) != NULL)
|
||||
total += strlen(next);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static void do_concat(char *str, va_list *ap)
|
||||
{
|
||||
char *next;
|
||||
|
||||
while ((next = va_arg(*ap, char *)) != NULL) {
|
||||
#ifdef HAVE_STPCPY
|
||||
str = stpcpy(str, next);
|
||||
#else
|
||||
size_t len = strlen(next);
|
||||
memcpy(str, next, len);
|
||||
str += len;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ne_buffer_concat(ne_buffer *buf, ...)
|
||||
{
|
||||
va_list ap;
|
||||
ssize_t total;
|
||||
|
||||
va_start(ap, buf);
|
||||
total = buf->used + count_concat(&ap);
|
||||
va_end(ap);
|
||||
|
||||
/* Grow the buffer */
|
||||
ne_buffer_grow(buf, total);
|
||||
|
||||
va_start(ap, buf);
|
||||
do_concat(buf->data + buf->used - 1, &ap);
|
||||
va_end(ap);
|
||||
|
||||
buf->used = total;
|
||||
buf->data[total - 1] = '\0';
|
||||
}
|
||||
|
||||
char *ne_concat(const char *str, ...)
|
||||
{
|
||||
va_list ap;
|
||||
size_t total, slen = strlen(str);
|
||||
char *ret;
|
||||
|
||||
va_start(ap, str);
|
||||
total = slen + count_concat(&ap);
|
||||
va_end(ap);
|
||||
|
||||
ret = memcpy(ne_malloc(total + 1), str, slen);
|
||||
|
||||
va_start(ap, str);
|
||||
do_concat(ret + slen, &ap);
|
||||
va_end(ap);
|
||||
|
||||
ret[total] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Append zero-terminated string... returns 0 on success or -1 on
|
||||
* realloc failure. */
|
||||
void ne_buffer_zappend(ne_buffer *buf, const char *str)
|
||||
{
|
||||
ne_buffer_append(buf, str, strlen(str));
|
||||
}
|
||||
|
||||
void ne_buffer_append(ne_buffer *buf, const char *data, size_t len)
|
||||
{
|
||||
ne_buffer_grow(buf, buf->used + len);
|
||||
memcpy(buf->data + buf->used - 1, data, len);
|
||||
buf->used += len;
|
||||
buf->data[buf->used - 1] = '\0';
|
||||
}
|
||||
|
||||
ne_buffer *ne_buffer_create(void)
|
||||
{
|
||||
return ne_buffer_ncreate(512);
|
||||
}
|
||||
|
||||
ne_buffer *ne_buffer_ncreate(size_t s)
|
||||
{
|
||||
ne_buffer *buf = ne_malloc(sizeof(*buf));
|
||||
buf->data = ne_malloc(s);
|
||||
buf->data[0] = '\0';
|
||||
buf->length = s;
|
||||
buf->used = 1;
|
||||
return buf;
|
||||
}
|
||||
|
||||
void ne_buffer_destroy(ne_buffer *buf)
|
||||
{
|
||||
ne_free(buf->data);
|
||||
ne_free(buf);
|
||||
}
|
||||
|
||||
char *ne_buffer_finish(ne_buffer *buf)
|
||||
{
|
||||
char *ret = buf->data;
|
||||
ne_free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ne_buffer_altered(ne_buffer *buf)
|
||||
{
|
||||
buf->used = strlen(buf->data) + 1;
|
||||
}
|
||||
|
||||
static const char b64_alphabet[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/=";
|
||||
|
||||
char *ne_base64(const unsigned char *text, size_t inlen)
|
||||
{
|
||||
/* The tricky thing about this is doing the padding at the end,
|
||||
* doing the bit manipulation requires a bit of concentration only */
|
||||
char *buffer, *point;
|
||||
size_t outlen;
|
||||
|
||||
/* Use 'buffer' to store the output. Work out how big it should be...
|
||||
* This must be a multiple of 4 bytes */
|
||||
|
||||
outlen = (inlen*4)/3;
|
||||
if ((inlen % 3) > 0) /* got to pad */
|
||||
outlen += 4 - (inlen % 3);
|
||||
|
||||
buffer = ne_malloc(outlen + 1); /* +1 for the \0 */
|
||||
|
||||
/* now do the main stage of conversion, 3 bytes at a time,
|
||||
* leave the trailing bytes (if there are any) for later */
|
||||
|
||||
for (point=buffer; inlen>=3; inlen-=3, text+=3) {
|
||||
*(point++) = b64_alphabet[ (*text)>>2 ];
|
||||
*(point++) = b64_alphabet[ ((*text)<<4 & 0x30) | (*(text+1))>>4 ];
|
||||
*(point++) = b64_alphabet[ ((*(text+1))<<2 & 0x3c) | (*(text+2))>>6 ];
|
||||
*(point++) = b64_alphabet[ (*(text+2)) & 0x3f ];
|
||||
}
|
||||
|
||||
/* Now deal with the trailing bytes */
|
||||
if (inlen > 0) {
|
||||
/* We always have one trailing byte */
|
||||
*(point++) = b64_alphabet[ (*text)>>2 ];
|
||||
*(point++) = b64_alphabet[ (((*text)<<4 & 0x30) |
|
||||
(inlen==2?(*(text+1))>>4:0)) ];
|
||||
*(point++) = (inlen==1?'=':b64_alphabet[ (*(text+1))<<2 & 0x3c ]);
|
||||
*(point++) = '=';
|
||||
}
|
||||
|
||||
/* Null-terminate */
|
||||
*point = '\0';
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/* VALID_B64: fail if 'ch' is not a valid base64 character */
|
||||
#define VALID_B64(ch) (((ch) >= 'A' && (ch) <= 'Z') || \
|
||||
((ch) >= 'a' && (ch) <= 'z') || \
|
||||
((ch) >= '0' && (ch) <= '9') || \
|
||||
(ch) == '/' || (ch) == '+' || (ch) == '=')
|
||||
|
||||
/* DECODE_B64: decodes a valid base64 character. */
|
||||
#define DECODE_B64(ch) ((ch) >= 'a' ? ((ch) + 26 - 'a') : \
|
||||
((ch) >= 'A' ? ((ch) - 'A') : \
|
||||
((ch) >= '0' ? ((ch) + 52 - '0') : \
|
||||
((ch) == '+' ? 62 : 63))))
|
||||
|
||||
size_t ne_unbase64(const char *data, unsigned char **out)
|
||||
{
|
||||
size_t inlen = strlen(data);
|
||||
unsigned char *outp;
|
||||
const unsigned char *in;
|
||||
|
||||
if (inlen == 0 || (inlen % 4) != 0) return 0;
|
||||
|
||||
outp = *out = ne_malloc(inlen * 3 / 4);
|
||||
|
||||
for (in = (const unsigned char *)data; *in; in += 4) {
|
||||
unsigned int tmp;
|
||||
if (!VALID_B64(in[0]) || !VALID_B64(in[1]) || !VALID_B64(in[2]) ||
|
||||
!VALID_B64(in[3]) || in[0] == '=' || in[1] == '=' ||
|
||||
(in[2] == '=' && in[3] != '=')) {
|
||||
ne_free(*out);
|
||||
return 0;
|
||||
}
|
||||
tmp = (DECODE_B64(in[0]) & 0x3f) << 18 |
|
||||
(DECODE_B64(in[1]) & 0x3f) << 12;
|
||||
*outp++ = (tmp >> 16) & 0xff;
|
||||
if (in[2] != '=') {
|
||||
tmp |= (DECODE_B64(in[2]) & 0x3f) << 6;
|
||||
*outp++ = (tmp >> 8) & 0xff;
|
||||
if (in[3] != '=') {
|
||||
tmp |= DECODE_B64(in[3]) & 0x3f;
|
||||
*outp++ = tmp & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outp - *out;
|
||||
}
|
||||
/* Character map array; ascii_clean[n] = isprint(n) ? n : 0x20. Used
|
||||
* by ne_strclean as a locale-independent isprint(). */
|
||||
static const unsigned char ascii_clean[256] = {
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
||||
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
|
||||
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
|
||||
0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
|
||||
0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
|
||||
};
|
||||
|
||||
char *ne_strclean(char *str)
|
||||
{
|
||||
unsigned char *pnt;
|
||||
|
||||
for (pnt = (unsigned char *)str; *pnt; pnt++)
|
||||
*pnt = (char)ascii_clean[*pnt];
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
char *ne_strerror(int errnum, char *buf, size_t buflen)
|
||||
{
|
||||
#ifdef HAVE_STRERROR_R
|
||||
#ifdef STRERROR_R_CHAR_P
|
||||
/* glibc-style strerror_r which may-or-may-not use provided buffer. */
|
||||
char *ret = strerror_r(errnum, buf, buflen);
|
||||
if (ret != buf)
|
||||
ne_strnzcpy(buf, ret, buflen);
|
||||
#else /* POSIX-style strerror_r: */
|
||||
strerror_r(errnum, buf, buflen);
|
||||
#endif
|
||||
#else /* no strerror_r: */
|
||||
ne_strnzcpy(buf, strerror(errnum), buflen);
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/* Wrapper for ne_snprintf. */
|
||||
size_t ne_snprintf(char *str, size_t size, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
#ifdef HAVE_TRIO
|
||||
trio_vsnprintf(str, size, fmt, ap);
|
||||
#else
|
||||
vsnprintf(str, size, fmt, ap);
|
||||
#endif
|
||||
va_end(ap);
|
||||
str[size-1] = '\0';
|
||||
return strlen(str);
|
||||
}
|
||||
|
||||
/* Wrapper for ne_vsnprintf. */
|
||||
size_t ne_vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
|
||||
{
|
||||
#ifdef HAVE_TRIO
|
||||
trio_vsnprintf(str, size, fmt, ap);
|
||||
#else
|
||||
vsnprintf(str, size, fmt, ap);
|
||||
#endif
|
||||
str[size-1] = '\0';
|
||||
return strlen(str);
|
||||
}
|
||||
|
||||
void ne_md5_buffer(const char *buffer, char *digest)
|
||||
{
|
||||
unsigned char md5_buf[16];
|
||||
|
||||
struct ne_md5_ctx ctx;
|
||||
|
||||
ne_md5_init_ctx(&ctx);
|
||||
ne_md5_process_bytes(buffer, strlen(buffer), &ctx);
|
||||
|
||||
ne_md5_finish_ctx(&ctx, md5_buf);
|
||||
|
||||
ne_md5_to_ascii(md5_buf, digest);
|
||||
}
|
||||
|
||||
void ne_md5_buffer_bin(const char *buffer, char *digest)
|
||||
{
|
||||
unsigned char md5_buf[16];
|
||||
|
||||
struct ne_md5_ctx ctx;
|
||||
|
||||
ne_md5_init_ctx(&ctx);
|
||||
ne_md5_process_bytes(buffer, strlen(buffer), &ctx);
|
||||
|
||||
ne_md5_finish_ctx(&ctx, md5_buf);
|
||||
|
||||
memcpy(digest,md5_buf,16);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
String utility functions
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_STRING_H
|
||||
#define NE_STRING_H
|
||||
|
||||
#include "ne_defs.h"
|
||||
#include "ne_alloc.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* ne_token and ne_qtoken return the next token in *str between *str
|
||||
* and separator character 'sep' or the NUL terminator. ne_qtoken
|
||||
* skips over any parts quoted using a pair of any one of the
|
||||
* characters given in 'quotes'. After returning, *str will point to
|
||||
* the next character after the separator, or NULL if no more
|
||||
* separator characters were found.
|
||||
*
|
||||
* ne_qtoken may return NULL if unterminated quotes are found. */
|
||||
char *ne_token(char **str, char sep);
|
||||
char *ne_qtoken(char **str, char sep, const char *quotes);
|
||||
|
||||
/* Return portion of 'str' with any characters in 'whitespace' shaved
|
||||
* off the beginning and end. Modifies str. */
|
||||
char *ne_shave(char *str, const char *whitespace);
|
||||
|
||||
/* Cleanse 'str' of non-printable characters. 'str' is modified
|
||||
* in-place, and returned. */
|
||||
char *ne_strclean(char *str);
|
||||
|
||||
/* A base64 encoder: converts 'len' bytes of 'text' to base64.
|
||||
* Returns malloc-allocated buffer; caller must free(). */
|
||||
char *ne_base64(const unsigned char *text, size_t len);
|
||||
|
||||
/* Base64 decoder; decodes NUL-terminated base64-encoded string
|
||||
* 'data', places malloc-allocated raw data in '*out', returns length,
|
||||
* or zero on decode error (in which case *out is undefined). */
|
||||
size_t ne_unbase64(const char *data, unsigned char **out);
|
||||
|
||||
/* String buffer handling. (Strings are zero-terminated still). A
|
||||
* string buffer ne_buffer * which grows dynamically with the
|
||||
* string. */
|
||||
|
||||
typedef struct {
|
||||
char *data; /* contents: null-terminated string. */
|
||||
size_t used; /* used bytes in buffer */
|
||||
size_t length; /* length of buffer */
|
||||
} ne_buffer;
|
||||
|
||||
/* Returns size of data in buffer, equiv to strlen(ne_buffer_data(buf)) */
|
||||
#define ne_buffer_size(buf) ((buf)->used - 1)
|
||||
|
||||
/* Concatenate all given strings onto the end of the buffer. The
|
||||
* strings must all be NUL-terminated, and MUST be followed by a NULL
|
||||
* argument marking the end of the list. */
|
||||
void ne_buffer_concat(ne_buffer *buf, ...);
|
||||
|
||||
/* Create a new ne_buffer. */
|
||||
ne_buffer *ne_buffer_create(void);
|
||||
|
||||
/* Create a new ne_buffer of given minimum size. */
|
||||
ne_buffer *ne_buffer_ncreate(size_t size);
|
||||
|
||||
/* Destroys (deallocates) a buffer */
|
||||
void ne_buffer_destroy(ne_buffer *buf);
|
||||
|
||||
/* Append a NUL-terminated string 'str' to buf. */
|
||||
void ne_buffer_zappend(ne_buffer *buf, const char *str);
|
||||
|
||||
/* Append 'len' bytes of 'data' to buf. 'data' does not need to be
|
||||
* NUL-terminated. The resultant string will have a NUL-terminator,
|
||||
* either way. */
|
||||
void ne_buffer_append(ne_buffer *buf, const char *data, size_t len);
|
||||
|
||||
/* Append a literal constant string 'str' to buffer 'buf'. */
|
||||
#define ne_buffer_czappend(buf, str) \
|
||||
ne_buffer_append((buf), (str), sizeof((str)) - 1)
|
||||
|
||||
/* Empties the contents of buf; makes the buffer zero-length. */
|
||||
void ne_buffer_clear(ne_buffer *buf);
|
||||
|
||||
/* Grows the ne_buffer to a minimum size. */
|
||||
void ne_buffer_grow(ne_buffer *buf, size_t size);
|
||||
|
||||
void ne_buffer_altered(ne_buffer *buf);
|
||||
|
||||
/* Destroys a buffer, WITHOUT freeing the data, and returns the
|
||||
* data. */
|
||||
char *ne_buffer_finish(ne_buffer *buf);
|
||||
|
||||
/* Thread-safe strerror() wrapper; place system error for errno value
|
||||
* 'errnum' in 'buffer', which is of length 'buflen'. Returns
|
||||
* 'buffer'. */
|
||||
char *ne_strerror(int errnum, char *buffer, size_t buflen);
|
||||
|
||||
/* ne_strnzcpy copies at most 'n'-1 bytes of 'src' to 'dest', and
|
||||
* ensures that 'dest' is subsequently NUL-terminated. */
|
||||
#define ne_strnzcpy(dest, src, n) do { \
|
||||
strncpy(dest, src, n-1); dest[n-1] = '\0'; } while (0)
|
||||
|
||||
/* Return malloc-allocated concatenation of all NUL-terminated string
|
||||
* arguments, up to a terminating NULL. */
|
||||
char *ne_concat(const char *str, ...);
|
||||
|
||||
#define NE_ASC2HEX(x) (((x) <= '9') ? ((x) - '0') : (tolower((x)) + 10 - 'a'))
|
||||
#define NE_HEX2ASC(x) ((char) ((x) > 9 ? ((x) - 10 + 'a') : ((x) + '0')))
|
||||
|
||||
/* Wrapper for snprintf: always NUL-terminates returned buffer, and
|
||||
* returns strlen(str). */
|
||||
size_t ne_snprintf(char *str, size_t size, const char *fmt, ...)
|
||||
ne_attribute((format(printf, 3, 4)));
|
||||
|
||||
/* Wrapper for vsnprintf. */
|
||||
size_t ne_vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
|
||||
ne_attribute((format(printf, 3, 0)));
|
||||
|
||||
void ne_md5_buffer(const char *buffer, char *digest) ;
|
||||
void ne_md5_buffer_bin(const char *buffer, char *digest) ;
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_STRING_H */
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Stubs for SSL support when no SSL library has been configured
|
||||
Copyright (C) 2002-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h> /* for NULL */
|
||||
|
||||
#include "ne_ssl.h"
|
||||
#include "ne_session.h"
|
||||
|
||||
char *ne_ssl_readable_dname(const ne_ssl_dname *dn)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ne_ssl_certificate *ne_ssl_cert_read(const char *filename)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ne_ssl_cert_cmp(const ne_ssl_certificate *c1, const ne_ssl_certificate *c2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
const ne_ssl_certificate *ne_ssl_cert_signedby(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ne_ssl_cert_free(ne_ssl_certificate *cert) {}
|
||||
|
||||
ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const ne_ssl_certificate *ne_ssl_clicert_owner(const ne_ssl_client_cert *ccert)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *ccert)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ne_ssl_clicert_decrypt(ne_ssl_client_cert *ccert, const char *password)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ne_ssl_clicert_free(ne_ssl_client_cert *ccert) {}
|
||||
|
||||
void ne_ssl_trust_default_ca(ne_session *sess) {}
|
||||
|
||||
ne_ssl_context *ne_ssl_context_create(int mode)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ne_ssl_context_trustcert(ne_ssl_context *ctx, const ne_ssl_certificate *cert)
|
||||
{}
|
||||
|
||||
int ne_ssl_context_set_verify(ne_ssl_context *ctx,
|
||||
int required,
|
||||
const char *ca_names,
|
||||
const char *verify_cas)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ne_ssl_context_destroy(ne_ssl_context *ctx) {}
|
||||
|
||||
int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char digest[60])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ne_ssl_cert_validity(const ne_ssl_certificate *cert, char *from, char *until)
|
||||
{}
|
||||
|
||||
const char *ne_ssl_cert_identity(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
const char *ne_ssl_clicert_name(const ne_ssl_client_cert *ccert)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ne_ssl_dname_cmp(const ne_ssl_dname *dn1, const ne_ssl_dname *dn2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ne_ssl_cert_write(const ne_ssl_certificate *cert, const char *filename)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *ne_ssl_cert_export(const ne_ssl_certificate *cert)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ne_ssl_certificate *ne_ssl_cert_import(const char *data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ne_ssl_set_clicert(ne_session *sess, const ne_ssl_client_cert *cc)
|
||||
{}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
URI manipulation routines.
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRINGS_H
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "ne_string.h" /* for ne_buffer */
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_uri.h"
|
||||
|
||||
char *ne_path_parent(const char *uri)
|
||||
{
|
||||
size_t len = strlen(uri);
|
||||
const char *pnt = uri + len - 1;
|
||||
/* skip trailing slash (parent of "/foo/" is "/") */
|
||||
if (pnt >= uri && *pnt == '/')
|
||||
pnt--;
|
||||
/* find previous slash */
|
||||
while (pnt > uri && *pnt != '/')
|
||||
pnt--;
|
||||
if (pnt < uri || (pnt == uri && *pnt != '/'))
|
||||
return NULL;
|
||||
return ne_strndup(uri, pnt - uri + 1);
|
||||
}
|
||||
|
||||
int ne_path_has_trailing_slash(const char *uri)
|
||||
{
|
||||
size_t len = strlen(uri);
|
||||
return ((len > 0) &&
|
||||
(uri[len-1] == '/'));
|
||||
}
|
||||
|
||||
unsigned int ne_uri_defaultport(const char *scheme)
|
||||
{
|
||||
/* RFC2616/3.2.3 says use case-insensitive comparisons here. */
|
||||
if (strcasecmp(scheme, "http") == 0)
|
||||
return 80;
|
||||
else if (strcasecmp(scheme, "https") == 0)
|
||||
return 443;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: Also, maybe stop malloc'ing here, take a "char *" uri, modify
|
||||
* it in-place, and have fields point inside passed uri. More work
|
||||
* for the caller then though. */
|
||||
/* TODO: not a proper URI parser */
|
||||
int ne_uri_parse(const char *uri, ne_uri *parsed)
|
||||
{
|
||||
const char *pnt, *slash, *colon, *atsign, *openbk;
|
||||
|
||||
parsed->port = 0;
|
||||
parsed->host = NULL;
|
||||
parsed->path = NULL;
|
||||
parsed->scheme = NULL;
|
||||
parsed->authinfo = NULL;
|
||||
|
||||
if (uri[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pnt = strstr(uri, "://");
|
||||
if (pnt) {
|
||||
parsed->scheme = ne_strndup(uri, pnt - uri);
|
||||
pnt += 3; /* start of hostport segment */
|
||||
} else {
|
||||
pnt = uri;
|
||||
}
|
||||
|
||||
atsign = strchr(pnt, '@');
|
||||
slash = strchr(pnt, '/');
|
||||
openbk = strchr(pnt, '[');
|
||||
|
||||
/* Check for an authinfo segment in the hostport segment. */
|
||||
if (atsign != NULL && (slash == NULL || atsign < slash)) {
|
||||
parsed->authinfo = ne_strndup(pnt, atsign - pnt);
|
||||
pnt = atsign + 1;
|
||||
}
|
||||
|
||||
if (openbk && (!slash || openbk < slash)) {
|
||||
const char *closebk = strchr(openbk, ']');
|
||||
if (closebk == NULL)
|
||||
return -1;
|
||||
colon = strchr(closebk + 1, ':');
|
||||
} else {
|
||||
colon = strchr(pnt, ':');
|
||||
}
|
||||
|
||||
if (slash == NULL) {
|
||||
parsed->path = ne_strdup("/");
|
||||
if (colon == NULL) {
|
||||
parsed->host = ne_strdup(pnt);
|
||||
} else {
|
||||
parsed->port = atoi(colon+1);
|
||||
parsed->host = ne_strndup(pnt, colon - pnt);
|
||||
}
|
||||
} else {
|
||||
if (colon == NULL || colon > slash) {
|
||||
/* No port segment */
|
||||
if (slash != uri) {
|
||||
parsed->host = ne_strndup(pnt, slash - pnt);
|
||||
} else {
|
||||
/* No hostname segment. */
|
||||
}
|
||||
} else {
|
||||
/* Port segment */
|
||||
parsed->port = atoi(colon + 1);
|
||||
parsed->host = ne_strndup(pnt, colon - pnt);
|
||||
}
|
||||
parsed->path = ne_strdup(slash);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ne_uri_free(ne_uri *u)
|
||||
{
|
||||
if (u->host) ne_free(u->host);
|
||||
if (u->path) ne_free(u->path);
|
||||
if (u->scheme) ne_free(u->scheme);
|
||||
if (u->authinfo) ne_free(u->authinfo);
|
||||
memset(u, 0, sizeof *u);
|
||||
}
|
||||
|
||||
char *ne_path_unescape(const char *uri)
|
||||
{
|
||||
const char *pnt;
|
||||
char *ret, *retpos, buf[5] = { "0x00\0" };
|
||||
retpos = ret = ne_malloc(strlen(uri) + 1);
|
||||
for (pnt = uri; *pnt != '\0'; pnt++) {
|
||||
if (*pnt == '%') {
|
||||
if (!isxdigit((unsigned char) pnt[1]) ||
|
||||
!isxdigit((unsigned char) pnt[2])) {
|
||||
/* Invalid URI */
|
||||
ne_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
buf[2] = *++pnt; buf[3] = *++pnt; /* bit faster than memcpy */
|
||||
*retpos++ = (char)strtol(buf, NULL, 16);
|
||||
} else {
|
||||
*retpos++ = *pnt;
|
||||
}
|
||||
}
|
||||
*retpos = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ABNF definitions derived from RFC3986, except with "/" removed from
|
||||
* gen-delims since it's special: */
|
||||
|
||||
#define GD (1) /* gen-delims = ":" / "?" / "#" / "[" / "]" / "@" */
|
||||
#define SD (1) /* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
/ "*" / "+" / "," / ";" / "=" */
|
||||
|
||||
#define SL (0) /* forward-slash = "/" */
|
||||
#define UN (0) /* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" */
|
||||
|
||||
#define OT (1) /* others */
|
||||
|
||||
/* Lookup table for percent-encoding logic: value is non-zero if
|
||||
* character should be percent-encoded. */
|
||||
static const unsigned char uri_chars[128] = {
|
||||
/* 0xXX x0 x2 x4 x6 x8 xA xC xE */
|
||||
/* 0x */ OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
|
||||
/* 1x */ OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT, OT,
|
||||
/* 2x */ OT, SD, OT, GD, SD, OT, SD, SD, SD, SD, SD, SD, SD, UN, UN, SL,
|
||||
/* 3x */ UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, GD, SD, OT, SD, OT, GD,
|
||||
/* 4x */ GD, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN,
|
||||
/* 5x */ UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, GD, OT, GD, OT, OT,
|
||||
/* 6x */ OT, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN,
|
||||
/* 7x */ UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, UN, OT, OT, OT, UN, OT
|
||||
};
|
||||
|
||||
#undef SD
|
||||
#undef GD
|
||||
#undef SL
|
||||
#undef UN
|
||||
#undef OT
|
||||
|
||||
/* CH must be an unsigned char; evaluates to 1 if CH should be
|
||||
* percent-encoded. */
|
||||
#define path_escape_ch(ch) ((ch) > 127 || uri_chars[(ch)])
|
||||
|
||||
char *ne_path_escape(const char *path)
|
||||
{
|
||||
const unsigned char *pnt;
|
||||
char *ret, *p;
|
||||
size_t count = 0;
|
||||
|
||||
for (pnt = (const unsigned char *)path; *pnt != '\0'; pnt++) {
|
||||
count += path_escape_ch(*pnt);
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return ne_strdup(path);
|
||||
}
|
||||
|
||||
p = ret = ne_malloc(strlen(path) + 2 * count + 1);
|
||||
for (pnt = (const unsigned char *)path; *pnt != '\0'; pnt++) {
|
||||
if (path_escape_ch(*pnt)) {
|
||||
/* Escape it - %<hex><hex> */
|
||||
sprintf(p, "%%%02x", (unsigned char) *pnt);
|
||||
p += 3;
|
||||
} else {
|
||||
*p++ = *pnt;
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
#undef path_escape_ch
|
||||
|
||||
#define CASECMP(field) do { \
|
||||
n = strcasecmp(u1->field, u2->field); if (n) return n; } while(0)
|
||||
|
||||
#define CMP(field) do { \
|
||||
n = strcmp(u1->field, u2->field); if (n) return n; } while(0)
|
||||
|
||||
/* As specified by RFC 2616, section 3.2.3. */
|
||||
int ne_uri_cmp(const ne_uri *u1, const ne_uri *u2)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (u1->path[0] == '\0' && strcmp(u2->path, "/") == 0)
|
||||
return 0;
|
||||
if (u2->path[0] == '\0' && strcmp(u1->path, "/") == 0)
|
||||
return 0;
|
||||
|
||||
CMP(path);
|
||||
CASECMP(host);
|
||||
CASECMP(scheme);
|
||||
if (u1->port > u2->port)
|
||||
return 1;
|
||||
else if (u1->port < u2->port)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef CMP
|
||||
#undef CASECMP
|
||||
|
||||
#ifndef WIN32
|
||||
#undef min
|
||||
#define min(a,b) ((a)<(b)?(a):(b))
|
||||
#endif
|
||||
|
||||
/* TODO: implement properly */
|
||||
int ne_path_compare(const char *a, const char *b)
|
||||
{
|
||||
int ret = strcasecmp(a, b);
|
||||
if (ret) {
|
||||
/* This logic says: "If the lengths of the two URIs differ by
|
||||
* exactly one, and the LONGER of the two URIs has a trailing
|
||||
* slash and the SHORTER one DOESN'T, then..." */
|
||||
int traila = ne_path_has_trailing_slash(a),
|
||||
trailb = ne_path_has_trailing_slash(b),
|
||||
lena = strlen(a), lenb = strlen(b);
|
||||
if (traila != trailb && abs(lena - lenb) == 1 &&
|
||||
((traila && lena > lenb) || (trailb && lenb > lena))) {
|
||||
/* Compare them, ignoring the trailing slash on the longer
|
||||
* URI */
|
||||
if (strncasecmp(a, b, min(lena, lenb)) == 0)
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *ne_uri_unparse(const ne_uri *uri)
|
||||
{
|
||||
ne_buffer *buf = ne_buffer_create();
|
||||
|
||||
ne_buffer_concat(buf, uri->scheme, "://", uri->host, NULL);
|
||||
|
||||
if (uri->port > 0 && ne_uri_defaultport(uri->scheme) != uri->port) {
|
||||
char str[20];
|
||||
ne_snprintf(str, 20, ":%d", uri->port);
|
||||
ne_buffer_zappend(buf, str);
|
||||
}
|
||||
|
||||
ne_buffer_zappend(buf, uri->path);
|
||||
|
||||
return ne_buffer_finish(buf);
|
||||
}
|
||||
|
||||
/* Give it a path segment, it returns non-zero if child is
|
||||
* a child of parent. */
|
||||
int ne_path_childof(const char *parent, const char *child)
|
||||
{
|
||||
char *root = ne_strdup(child);
|
||||
int ret;
|
||||
if (strlen(parent) >= strlen(child)) {
|
||||
ret = 0;
|
||||
} else {
|
||||
/* root is the first of child, equal to length of parent */
|
||||
root[strlen(parent)] = '\0';
|
||||
ret = (ne_path_compare(parent, root) == 0);
|
||||
}
|
||||
ne_free(root);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
URI manipulation routines.
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_URI_H
|
||||
#define NE_URI_H
|
||||
|
||||
#include "ne_defs.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* Return a copy of a path string with anything other than
|
||||
* "unreserved" and the forward-slash character percent-encoded
|
||||
* according to the URI encoding rules. Returns a malloc-allocated
|
||||
* string and never NULL. */
|
||||
char *ne_path_escape(const char *path);
|
||||
|
||||
/* Return a decoded copy of a percent-encoded path string. Returns
|
||||
* malloc-allocated path on success, or NULL if the string contained
|
||||
* any syntactically invalid percent-encoding sequences. */
|
||||
char *ne_path_unescape(const char *epath);
|
||||
|
||||
/* Returns malloc-allocated parent of path, or NULL if path has no
|
||||
* parent (such as "/"). */
|
||||
char *ne_path_parent(const char *path);
|
||||
|
||||
/* Returns strcmp-like value giving comparison between p1 and p2,
|
||||
* ignoring trailing-slashes. */
|
||||
int ne_path_compare(const char *p1, const char *p2);
|
||||
|
||||
/* Returns non-zero if child is a child of parent */
|
||||
int ne_path_childof(const char *parent, const char *child);
|
||||
|
||||
/* Returns non-zero if path has a trailing slash character */
|
||||
int ne_path_has_trailing_slash(const char *path);
|
||||
|
||||
/* Return the default port for the given scheme, or 0 if none is
|
||||
* known. */
|
||||
unsigned int ne_uri_defaultport(const char *scheme);
|
||||
|
||||
typedef struct {
|
||||
char *scheme;
|
||||
char *host;
|
||||
unsigned int port;
|
||||
char *path;
|
||||
char *authinfo;
|
||||
} ne_uri;
|
||||
|
||||
/* Parse absoluteURI 'uri' and place parsed segments in *parsed.
|
||||
* Returns zero on success, non-zero on parse error. On successful or
|
||||
* error return, all the 'char *' fields of *parsed are either set to
|
||||
* NULL, or point to malloc-allocated NUL-terminated strings.
|
||||
* ne_uri_free can be used to free the structure after use.*/
|
||||
int ne_uri_parse(const char *uri, ne_uri *parsed);
|
||||
|
||||
/* Turns a URI structure back into a string. String is
|
||||
* malloc-allocated, and must be freed by the caller. */
|
||||
char *ne_uri_unparse(const ne_uri *uri);
|
||||
|
||||
/* Compares URIs u1 and u2, returns non-zero if they are found to be
|
||||
* non-equal. The sign of the return value is <0 if 'u1' is less than
|
||||
* 'u2', or >0 if 'u2' is greater than 'u1'. */
|
||||
int ne_uri_cmp(const ne_uri *u1, const ne_uri *u2);
|
||||
|
||||
/* Frees any non-NULL fields of parsed URI structure *parsed. All
|
||||
* fields are then zero-initialized. */
|
||||
void ne_uri_free(ne_uri *parsed);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_URI_H */
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
HTTP utility functions
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h> /* isdigit() for ne_parse_statusline */
|
||||
|
||||
#ifdef NE_HAVE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_OPENSSL
|
||||
#include <openssl/opensslv.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#endif
|
||||
|
||||
/* libxml2: pick up the version string. */
|
||||
#if defined(HAVE_LIBXML)
|
||||
#include <libxml/xmlversion.h>
|
||||
#elif defined(HAVE_EXPAT) && !defined(HAVE_XMLPARSE_H)
|
||||
#include <expat.h>
|
||||
#endif
|
||||
|
||||
#include "ne_utils.h"
|
||||
#include "ne_string.h" /* for ne_strdup */
|
||||
#include "ne_dates.h"
|
||||
|
||||
int ne_debug_mask = 0;
|
||||
FILE *ne_debug_stream = NULL;
|
||||
|
||||
void ne_debug_init(FILE *stream, int mask)
|
||||
{
|
||||
ne_debug_stream = stream;
|
||||
ne_debug_mask = mask;
|
||||
#if defined(HAVE_SETVBUF) && defined(_IONBF)
|
||||
/* If possible, turn off buffering on the debug log. this is very
|
||||
* helpful if debugging segfaults. */
|
||||
if (stream) setvbuf(stream, NULL, _IONBF, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ne_debug(int ch, const char *template, ...)
|
||||
{
|
||||
va_list params;
|
||||
if ((ch & ne_debug_mask) == 0) return;
|
||||
fflush(stdout);
|
||||
va_start(params, template);
|
||||
vfprintf(ne_debug_stream, template, params);
|
||||
va_end(params);
|
||||
if ((ch & NE_DBG_FLUSH) == NE_DBG_FLUSH)
|
||||
fflush(ne_debug_stream);
|
||||
}
|
||||
|
||||
#define NE_STRINGIFY(x) # x
|
||||
#define NE_EXPAT_VER(x,y,z) NE_STRINGIFY(x) "." NE_STRINGIFY(y) "." NE_STRINGIFY(z)
|
||||
|
||||
static const char version_string[] = "neon " NEON_VERSION ": "
|
||||
#ifdef NEON_IS_LIBRARY
|
||||
"Library build"
|
||||
#else
|
||||
"Bundled build"
|
||||
#endif
|
||||
#ifdef NE_HAVE_IPV6
|
||||
", IPv6"
|
||||
#endif
|
||||
#ifdef HAVE_EXPAT
|
||||
", Expat"
|
||||
/* expat >=1.95.2 exported the version */
|
||||
#ifdef XML_MAJOR_VERSION
|
||||
" " NE_EXPAT_VER(XML_MAJOR_VERSION, XML_MINOR_VERSION, XML_MICRO_VERSION)
|
||||
#endif
|
||||
#else /* !HAVE_EXPAT */
|
||||
#ifdef HAVE_LIBXML
|
||||
", libxml " LIBXML_DOTTED_VERSION
|
||||
#endif /* HAVE_LIBXML */
|
||||
#endif /* !HAVE_EXPAT */
|
||||
#if defined(NE_HAVE_ZLIB) && defined(ZLIB_VERSION)
|
||||
", zlib " ZLIB_VERSION
|
||||
#endif /* NE_HAVE_ZLIB && ... */
|
||||
#ifdef NE_HAVE_SOCKS
|
||||
", SOCKSv5"
|
||||
#endif
|
||||
#ifdef HAVE_OPENSSL
|
||||
#ifdef OPENSSL_VERSION_TEXT
|
||||
", " OPENSSL_VERSION_TEXT
|
||||
#else
|
||||
"OpenSSL (unknown version)"
|
||||
#endif /* OPENSSL_VERSION_TEXT */
|
||||
#endif /* HAVE_OPENSSL */
|
||||
#ifdef HAVE_GNUTLS
|
||||
", GNU TLS " LIBGNUTLS_VERSION
|
||||
#endif /* HAVE_GNUTLS */
|
||||
"."
|
||||
;
|
||||
|
||||
const char *ne_version_string(void)
|
||||
{
|
||||
return version_string;
|
||||
}
|
||||
|
||||
int ne_version_match(int major, int minor)
|
||||
{
|
||||
return NE_VERSION_MAJOR != major || NE_VERSION_MINOR < minor
|
||||
|| (NE_VERSION_MAJOR == 0 && NE_VERSION_MINOR != minor);
|
||||
}
|
||||
|
||||
int ne_has_support(int feature)
|
||||
{
|
||||
switch (feature) {
|
||||
#if defined(NE_HAVE_SSL) || defined(NE_HAVE_ZLIB) || defined(NE_HAVE_IPV6) \
|
||||
|| defined(NE_HAVE_SOCKS) || defined(NE_HAVE_LFS)
|
||||
#ifdef NE_HAVE_SSL
|
||||
case NE_FEATURE_SSL:
|
||||
#endif
|
||||
#ifdef NE_HAVE_ZLIB
|
||||
case NE_FEATURE_ZLIB:
|
||||
#endif
|
||||
#ifdef NE_HAVE_IPV6
|
||||
case NE_FEATURE_IPV6:
|
||||
#endif
|
||||
#ifdef NE_HAVE_SOCKS
|
||||
case NE_FEATURE_SOCKS:
|
||||
#endif
|
||||
#ifdef NE_HAVE_LFS
|
||||
case NE_FEATURE_LFS:
|
||||
#endif
|
||||
return 1;
|
||||
#endif /* NE_HAVE_* */
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ne_parse_statusline(const char *status_line, ne_status *st)
|
||||
{
|
||||
const char *part;
|
||||
int major, minor, status_code, klass;
|
||||
|
||||
/* skip leading garbage if any. */
|
||||
part = strstr(status_line, "HTTP/");
|
||||
if (part == NULL) return -1;
|
||||
|
||||
minor = major = 0;
|
||||
|
||||
/* Parse version string, skipping leading zeroes. */
|
||||
for (part += 5; *part != '\0' && isdigit(*part); part++)
|
||||
major = major*10 + (*part-'0');
|
||||
|
||||
if (*part++ != '.') return -1;
|
||||
|
||||
for (;*part != '\0' && isdigit(*part); part++)
|
||||
minor = minor*10 + (*part-'0');
|
||||
|
||||
if (*part != ' ') return -1;
|
||||
|
||||
/* Skip any spaces */
|
||||
for (; *part == ' '; part++) /* noop */;
|
||||
|
||||
/* Parse the Status-Code; part now points at the first Y in
|
||||
* "HTTP/x.x YYY". */
|
||||
if (!isdigit(part[0]) || !isdigit(part[1]) || !isdigit(part[2]) ||
|
||||
(part[3] != '\0' && part[3] != ' ')) return -1;
|
||||
status_code = 100*(part[0]-'0') + 10*(part[1]-'0') + (part[2]-'0');
|
||||
klass = part[0]-'0';
|
||||
|
||||
/* Skip whitespace between status-code and reason-phrase */
|
||||
for (part+=3; *part == ' ' || *part == '\t'; part++) /* noop */;
|
||||
|
||||
/* part now may be pointing to \0 if reason phrase is blank */
|
||||
|
||||
/* Fill in the results */
|
||||
st->major_version = major;
|
||||
st->minor_version = minor;
|
||||
st->reason_phrase = ne_strclean(ne_strdup(part));
|
||||
st->code = status_code;
|
||||
st->klass = klass;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
HTTP utility functions
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_UTILS_H
|
||||
#define NE_UTILS_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "ne_defs.h"
|
||||
|
||||
#ifdef NEON_TRIO
|
||||
/* no HAVE_TRIO_H check so this works from outside neon build tree. */
|
||||
#include <trio.h>
|
||||
#endif
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* Returns a human-readable version string like:
|
||||
* "neon 0.2.0: Library build, OpenSSL support"
|
||||
*/
|
||||
const char *ne_version_string(void);
|
||||
|
||||
/* Returns non-zero if library version is not of major version
|
||||
* 'major', or if minor version is not greater than or equal to
|
||||
* 'minor'. For neon versions with major == 0, all minor versions are
|
||||
* presumed to be incompatible. */
|
||||
int ne_version_match(int major, int minor);
|
||||
|
||||
#define NE_FEATURE_SSL (1) /* SSL/TLS support */
|
||||
#define NE_FEATURE_ZLIB (2) /* zlib compression in compress interface */
|
||||
#define NE_FEATURE_IPV6 (3) /* IPv6 is supported in resolver */
|
||||
#define NE_FEATURE_LFS (4) /* large file support */
|
||||
#define NE_FEATURE_SOCKS (5) /* SOCKSv5 support */
|
||||
|
||||
/* Returns non-zero if neon has support for given feature code
|
||||
* NE_FEATURE_*. */
|
||||
int ne_has_support(int feature);
|
||||
|
||||
/* CONSIDER: mutt has a nicer way of way of doing debugging output... maybe
|
||||
* switch to like that. */
|
||||
|
||||
#ifndef NE_DEBUGGING
|
||||
#define NE_DEBUG if (0) ne_debug
|
||||
#else /* DEBUGGING */
|
||||
#define NE_DEBUG ne_debug
|
||||
#endif /* DEBUGGING */
|
||||
|
||||
#define NE_DBG_SOCKET (1<<0)
|
||||
#define NE_DBG_HTTP (1<<1)
|
||||
#define NE_DBG_XML (1<<2)
|
||||
#define NE_DBG_HTTPAUTH (1<<3)
|
||||
#define NE_DBG_HTTPPLAIN (1<<4)
|
||||
#define NE_DBG_LOCKS (1<<5)
|
||||
#define NE_DBG_XMLPARSE (1<<6)
|
||||
#define NE_DBG_HTTPBODY (1<<7)
|
||||
#define NE_DBG_SSL (1<<8)
|
||||
#define NE_DBG_FLUSH (1<<30)
|
||||
|
||||
/* Send debugging output to 'stream', for all of the given debug
|
||||
* channels. To disable debugging, pass 'stream' as NULL and 'mask'
|
||||
* as 0. */
|
||||
void ne_debug_init(FILE *stream, int mask);
|
||||
|
||||
/* The current debug mask and stream set by the last call to
|
||||
* ne_debug_init. */
|
||||
extern int ne_debug_mask;
|
||||
extern FILE *ne_debug_stream;
|
||||
|
||||
/* Produce debug output if any of channels 'ch' is enabled for
|
||||
* debugging. */
|
||||
void ne_debug(int ch, const char *, ...) ne_attribute((format(printf, 2, 3)));
|
||||
|
||||
/* Storing an HTTP status result */
|
||||
typedef struct {
|
||||
int major_version;
|
||||
int minor_version;
|
||||
int code; /* Status-Code value */
|
||||
int klass; /* Class of Status-Code (1-5) */
|
||||
char *reason_phrase;
|
||||
} ne_status;
|
||||
|
||||
/* NB: couldn't use 'class' in ne_status because it would clash with
|
||||
* the C++ reserved word. */
|
||||
|
||||
/* Parser for strings which follow the Status-Line grammar from
|
||||
* RFC2616. s->reason_phrase is malloc-allocated if non-NULL, and
|
||||
* must be free'd by the caller.
|
||||
* Returns:
|
||||
* 0 on success, *s will be filled in.
|
||||
* -1 on parse error.
|
||||
*/
|
||||
int ne_parse_statusline(const char *status_line, ne_status *s);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_UTILS_H */
|
||||
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
Wrapper interface to XML parser
|
||||
Copyright (C) 1999-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRING_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRINGS_H
|
||||
#include <strings.h>
|
||||
#endif
|
||||
|
||||
#include "ne_i18n.h"
|
||||
|
||||
#include "ne_alloc.h"
|
||||
#include "ne_xml.h"
|
||||
#include "ne_utils.h"
|
||||
#include "ne_string.h"
|
||||
|
||||
#if defined(HAVE_EXPAT)
|
||||
/* expat support: */
|
||||
#ifdef HAVE_XMLPARSE_H
|
||||
#include "xmlparse.h"
|
||||
#else
|
||||
#include <expat.h>
|
||||
#endif
|
||||
typedef XML_Char ne_xml_char;
|
||||
|
||||
#if !defined(XML_MAJOR_VERSION) || (XML_MAJOR_VERSION < 2 \
|
||||
&& XML_MINOR_VERSION == 95 \
|
||||
&& XML_MICRO_VERSION < 2)
|
||||
#define NEED_BOM_HANDLING
|
||||
#endif
|
||||
|
||||
#elif defined(HAVE_LIBXML)
|
||||
/* libxml2 support: */
|
||||
#include <libxml/xmlversion.h>
|
||||
#include <libxml/parser.h>
|
||||
typedef xmlChar ne_xml_char;
|
||||
|
||||
#if LIBXML_VERSION < 20619
|
||||
/* 2.6.19 and earlier have broken BOM handling */
|
||||
#define NEED_BOM_HANDLING
|
||||
#endif
|
||||
#else /* not HAVE_LIBXML */
|
||||
# error need an XML parser
|
||||
#endif /* not HAVE_EXPAT */
|
||||
|
||||
/* Approx. one screen of text: */
|
||||
#define ERR_SIZE (2048)
|
||||
|
||||
struct handler {
|
||||
ne_xml_startelm_cb *startelm_cb; /* start-element callback */
|
||||
ne_xml_endelm_cb *endelm_cb; /* end-element callback */
|
||||
ne_xml_cdata_cb *cdata_cb; /* character-data callback. */
|
||||
void *userdata; /* userdata for the above. */
|
||||
struct handler *next; /* next handler in stack. */
|
||||
};
|
||||
|
||||
#ifdef HAVE_LIBXML
|
||||
static void sax_error(void *ctx, const char *msg, ...);
|
||||
#endif
|
||||
|
||||
struct element {
|
||||
const ne_xml_char *nspace;
|
||||
ne_xml_char *name;
|
||||
|
||||
int state; /* opaque state integer */
|
||||
|
||||
/* Namespaces declared in this element */
|
||||
ne_xml_char *default_ns; /* A default namespace */
|
||||
struct namespace *nspaces; /* List of other namespace scopes */
|
||||
|
||||
struct handler *handler; /* Handler for this element */
|
||||
|
||||
struct element *parent; /* parent element, or NULL */
|
||||
};
|
||||
|
||||
/* We pass around a ne_xml_parser as the userdata in the parsing
|
||||
* library. 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, up to
|
||||
* the root, but nothing other than that. */
|
||||
struct ne_xml_parser_s {
|
||||
struct element *root; /* the root of the document */
|
||||
struct element *current; /* current element in the branch */
|
||||
struct handler *top_handlers; /* always points at the
|
||||
* handler on top of the stack. */
|
||||
int failure; /* zero whilst parse should continue */
|
||||
int prune; /* if non-zero, depth within a dead branch */
|
||||
|
||||
#ifdef NEED_BOM_HANDLING
|
||||
int bom_pos;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_EXPAT
|
||||
XML_Parser parser;
|
||||
char *encoding;
|
||||
#else
|
||||
xmlParserCtxtPtr parser;
|
||||
#endif
|
||||
char error[ERR_SIZE];
|
||||
};
|
||||
|
||||
/* The callback handlers */
|
||||
static void start_element(void *userdata, const ne_xml_char *name, const ne_xml_char **atts);
|
||||
static void end_element(void *userdata, const ne_xml_char *name);
|
||||
static void char_data(void *userdata, const ne_xml_char *cdata, int len);
|
||||
static const char *resolve_nspace(const struct element *elm,
|
||||
const char *prefix, size_t pfxlen);
|
||||
|
||||
/* Linked list of namespace scopes */
|
||||
struct namespace {
|
||||
ne_xml_char *name;
|
||||
ne_xml_char *uri;
|
||||
struct namespace *next;
|
||||
};
|
||||
|
||||
#ifdef HAVE_LIBXML
|
||||
|
||||
/* Could be const as far as we care, but libxml doesn't want that */
|
||||
static xmlSAXHandler sax_handler = {
|
||||
NULL, /* internalSubset */
|
||||
NULL, /* isStandalone */
|
||||
NULL, /* hasInternalSubset */
|
||||
NULL, /* hasExternalSubset */
|
||||
NULL, /* resolveEntity */
|
||||
NULL, /* getEntity */
|
||||
NULL, /* entityDecl */
|
||||
NULL, /* notationDecl */
|
||||
NULL, /* attributeDecl */
|
||||
NULL, /* elementDecl */
|
||||
NULL, /* unparsedEntityDecl */
|
||||
NULL, /* setDocumentLocator */
|
||||
NULL, /* startDocument */
|
||||
NULL, /* endDocument */
|
||||
start_element, /* startElement */
|
||||
end_element, /* endElement */
|
||||
NULL, /* reference */
|
||||
char_data, /* characters */
|
||||
NULL, /* ignorableWhitespace */
|
||||
NULL, /* processingInstruction */
|
||||
NULL, /* comment */
|
||||
NULL, /* xmlParserWarning */
|
||||
sax_error, /* xmlParserError */
|
||||
sax_error, /* fatal error (never called by libxml2?) */
|
||||
NULL, /* getParameterEntity */
|
||||
char_data /* cdataBlock */
|
||||
};
|
||||
|
||||
/* empty attributes array to mimic expat behaviour */
|
||||
static const char *empty_atts[] = {NULL, NULL};
|
||||
|
||||
/* macro for determining the attributes array to pass */
|
||||
#define PASS_ATTS(atts) (atts ? (const char **)(atts) : empty_atts)
|
||||
|
||||
#else
|
||||
|
||||
#define PASS_ATTS(atts) ((const char **)(atts))
|
||||
|
||||
/* XML declaration callback for expat. */
|
||||
static void decl_handler(void *userdata,
|
||||
const XML_Char *version, const XML_Char *encoding,
|
||||
int standalone)
|
||||
{
|
||||
ne_xml_parser *p = userdata;
|
||||
if (encoding) p->encoding = ne_strdup(encoding);
|
||||
}
|
||||
|
||||
#endif /* HAVE_LIBXML */
|
||||
|
||||
int ne_xml_currentline(ne_xml_parser *p)
|
||||
{
|
||||
#ifdef HAVE_EXPAT
|
||||
return XML_GetCurrentLineNumber(p->parser);
|
||||
#else
|
||||
return p->parser->input->line;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *ne_xml_doc_encoding(const ne_xml_parser *p)
|
||||
{
|
||||
#ifdef HAVE_LIBXML
|
||||
return p->parser->encoding;
|
||||
#else
|
||||
return p->encoding;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The first character of the REC-xml-names "NCName" rule excludes
|
||||
* "Digit | '.' | '-' | '_' | CombiningChar | Extender"; the XML
|
||||
* parser will not enforce this rule in a namespace declaration since
|
||||
* it treats the entire attribute name as a REC-xml "Name" rule. It's
|
||||
* too hard to check for all of CombiningChar | Digit | Extender here,
|
||||
* but the valid_ncname_ch1 macro catches some of the rest. */
|
||||
|
||||
/* Return non-zero if 'ch' is an invalid start character for an NCName: */
|
||||
#define invalid_ncname_ch1(ch) ((ch) == '\0' || strchr("-.0123456789", (ch)) != NULL)
|
||||
|
||||
/* Subversion repositories have been deployed which use property names
|
||||
* marshalled as NCNames including a colon character; these should
|
||||
* also be rejected but will be allowed for the time being. */
|
||||
#define invalid_ncname(xn) (invalid_ncname_ch1((xn)[0]))
|
||||
|
||||
/* Extract the namespace prefix declarations from 'atts'. */
|
||||
static int declare_nspaces(ne_xml_parser *p, struct element *elm,
|
||||
const ne_xml_char **atts)
|
||||
{
|
||||
int n;
|
||||
|
||||
for (n = 0; atts && atts[n]; n += 2) {
|
||||
if (strcmp(atts[n], "xmlns") == 0) {
|
||||
/* New default namespace */
|
||||
elm->default_ns = ne_strdup(atts[n+1]);
|
||||
} else if (strncmp(atts[n], "xmlns:", 6) == 0) {
|
||||
struct namespace *ns;
|
||||
|
||||
/* Reject some invalid NCNames as namespace prefix, and an
|
||||
* empty URI as the namespace URI */
|
||||
if (invalid_ncname(atts[n] + 6) || atts[n+1][0] == '\0') {
|
||||
ne_snprintf(p->error, ERR_SIZE,
|
||||
("XML parse error at line %d: invalid namespace "
|
||||
"declaration"), ne_xml_currentline(p));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* New namespace scope */
|
||||
ns = ne_calloc(sizeof(*ns));
|
||||
ns->next = elm->nspaces;
|
||||
elm->nspaces = ns;
|
||||
ns->name = ne_strdup(atts[n]+6); /* skip the xmlns= */
|
||||
ns->uri = ne_strdup(atts[n+1]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Expand an XML qualified name, which may include a namespace prefix
|
||||
* as well as the local part. */
|
||||
static int expand_qname(ne_xml_parser *p, struct element *elm,
|
||||
const ne_xml_char *qname)
|
||||
{
|
||||
const ne_xml_char *pfx;
|
||||
|
||||
pfx = strchr(qname, ':');
|
||||
if (pfx == NULL) {
|
||||
struct element *e = elm;
|
||||
|
||||
/* Find default namespace; guaranteed to terminate as the root
|
||||
* element always has default_ns="". */
|
||||
while (e->default_ns == NULL)
|
||||
e = e->parent;
|
||||
|
||||
elm->name = ne_strdup(qname);
|
||||
elm->nspace = e->default_ns;
|
||||
} else if (invalid_ncname(pfx + 1) || qname == pfx) {
|
||||
ne_snprintf(p->error, ERR_SIZE,
|
||||
_("XML parse error at line %d: invalid element name"),
|
||||
ne_xml_currentline(p));
|
||||
return -1;
|
||||
} else {
|
||||
const char *uri = resolve_nspace(elm, qname, pfx-qname);
|
||||
|
||||
if (uri) {
|
||||
elm->name = ne_strdup(pfx+1);
|
||||
elm->nspace = uri;
|
||||
} else {
|
||||
ne_snprintf(p->error, ERR_SIZE,
|
||||
("XML parse error at line %d: undeclared namespace prefix"),
|
||||
ne_xml_currentline(p));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called with the start of a new element. */
|
||||
static void start_element(void *userdata, const ne_xml_char *name,
|
||||
const ne_xml_char **atts)
|
||||
{
|
||||
ne_xml_parser *p = userdata;
|
||||
struct element *elm;
|
||||
struct handler *hand;
|
||||
int state = NE_XML_DECLINE;
|
||||
|
||||
if (p->failure) return;
|
||||
|
||||
if (p->prune) {
|
||||
p->prune++;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a new element */
|
||||
elm = ne_calloc(sizeof *elm);
|
||||
elm->parent = p->current;
|
||||
p->current = elm;
|
||||
|
||||
if (declare_nspaces(p, elm, atts) || expand_qname(p, elm, name)) {
|
||||
p->failure = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find a handler which will accept this element (or abort the parse) */
|
||||
for (hand = elm->parent->handler; hand && state == NE_XML_DECLINE;
|
||||
hand = hand->next) {
|
||||
elm->handler = hand;
|
||||
state = hand->startelm_cb(hand->userdata, elm->parent->state,
|
||||
elm->nspace, elm->name, PASS_ATTS(atts));
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_XML, "XML: start-element (%d, {%s, %s}) => %d\n",
|
||||
elm->parent->state, elm->nspace, elm->name, state);
|
||||
|
||||
if (state > 0)
|
||||
elm->state = state;
|
||||
else if (state == NE_XML_DECLINE)
|
||||
/* prune this branch. */
|
||||
p->prune++;
|
||||
else /* state < 0 => abort parse */
|
||||
p->failure = state;
|
||||
}
|
||||
|
||||
/* Destroys an element structure. */
|
||||
static void destroy_element(struct element *elm)
|
||||
{
|
||||
struct namespace *this_ns, *next_ns;
|
||||
ne_free(elm->name);
|
||||
/* Free the namespaces */
|
||||
this_ns = elm->nspaces;
|
||||
while (this_ns != NULL) {
|
||||
next_ns = this_ns->next;
|
||||
ne_free(this_ns->name);
|
||||
ne_free(this_ns->uri);
|
||||
ne_free(this_ns);
|
||||
this_ns = next_ns;
|
||||
}
|
||||
if (elm->default_ns)
|
||||
ne_free(elm->default_ns);
|
||||
ne_free(elm);
|
||||
}
|
||||
|
||||
/* cdata SAX callback */
|
||||
static void char_data(void *userdata, const ne_xml_char *data, int len)
|
||||
{
|
||||
ne_xml_parser *p = userdata;
|
||||
struct element *elm = p->current;
|
||||
|
||||
if (p->failure || p->prune) return;
|
||||
|
||||
if (elm->handler->cdata_cb) {
|
||||
p->failure = elm->handler->cdata_cb(elm->handler->userdata, elm->state, data, len);
|
||||
NE_DEBUG(NE_DBG_XML, "XML: char-data (%d) returns %d\n",
|
||||
elm->state, p->failure);
|
||||
}
|
||||
}
|
||||
|
||||
/* Called with the end of an element */
|
||||
static void end_element(void *userdata, const ne_xml_char *name)
|
||||
{
|
||||
ne_xml_parser *p = userdata;
|
||||
struct element *elm = p->current;
|
||||
|
||||
if (p->failure) return;
|
||||
|
||||
if (p->prune) {
|
||||
if (p->prune-- > 1) return;
|
||||
} else if (elm->handler->endelm_cb) {
|
||||
p->failure = elm->handler->endelm_cb(elm->handler->userdata, elm->state,
|
||||
elm->nspace, elm->name);
|
||||
if (p->failure) {
|
||||
NE_DEBUG(NE_DBG_XML, "XML: end-element for %d failed with %d.\n",
|
||||
elm->state, p->failure);
|
||||
}
|
||||
}
|
||||
|
||||
NE_DEBUG(NE_DBG_XML, "XML: end-element (%d, {%s, %s})\n",
|
||||
elm->state, elm->nspace, elm->name);
|
||||
|
||||
/* move back up the tree */
|
||||
p->current = elm->parent;
|
||||
p->prune = 0;
|
||||
|
||||
destroy_element(elm);
|
||||
}
|
||||
|
||||
/* Find a namespace definition for 'prefix' in given element, where
|
||||
* length of prefix is 'pfxlen'. Returns the URI or NULL. */
|
||||
static const char *resolve_nspace(const struct element *elm,
|
||||
const char *prefix, size_t pfxlen)
|
||||
{
|
||||
const struct element *s;
|
||||
|
||||
/* Search up the tree. */
|
||||
for (s = elm; s != NULL; s = s->parent) {
|
||||
const struct namespace *ns;
|
||||
/* Iterate over defined spaces on this node. */
|
||||
for (ns = s->nspaces; ns != NULL; ns = ns->next) {
|
||||
if (strlen(ns->name) == pfxlen &&
|
||||
memcmp(ns->name, prefix, pfxlen) == 0)
|
||||
return ns->uri;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ne_xml_parser *ne_xml_create(void)
|
||||
{
|
||||
ne_xml_parser *p = ne_calloc(sizeof *p);
|
||||
/* Placeholder for the root element */
|
||||
p->current = p->root = ne_calloc(sizeof *p->root);
|
||||
p->root->default_ns = "";
|
||||
p->root->state = 0;
|
||||
strcpy(p->error, _("Unknown error"));
|
||||
#ifdef HAVE_EXPAT
|
||||
p->parser = XML_ParserCreate(NULL);
|
||||
if (p->parser == NULL) {
|
||||
abort();
|
||||
}
|
||||
XML_SetElementHandler(p->parser, start_element, end_element);
|
||||
XML_SetCharacterDataHandler(p->parser, char_data);
|
||||
XML_SetUserData(p->parser, (void *) p);
|
||||
XML_SetXmlDeclHandler(p->parser, decl_handler);
|
||||
#else
|
||||
p->parser = xmlCreatePushParserCtxt(&sax_handler,
|
||||
(void *)p, NULL, 0, NULL);
|
||||
if (p->parser == NULL) {
|
||||
abort();
|
||||
}
|
||||
p->parser->replaceEntities = 1;
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
|
||||
void ne_xml_push_handler(ne_xml_parser *p,
|
||||
ne_xml_startelm_cb *startelm_cb,
|
||||
ne_xml_cdata_cb *cdata_cb,
|
||||
ne_xml_endelm_cb *endelm_cb,
|
||||
void *userdata)
|
||||
{
|
||||
struct handler *hand = ne_calloc(sizeof(struct handler));
|
||||
|
||||
hand->startelm_cb = startelm_cb;
|
||||
hand->cdata_cb = cdata_cb;
|
||||
hand->endelm_cb = endelm_cb;
|
||||
hand->userdata = userdata;
|
||||
|
||||
/* If this is the first handler registered, update the
|
||||
* base pointer too. */
|
||||
if (p->top_handlers == NULL) {
|
||||
p->root->handler = hand;
|
||||
p->top_handlers = hand;
|
||||
} else {
|
||||
p->top_handlers->next = hand;
|
||||
p->top_handlers = hand;
|
||||
}
|
||||
}
|
||||
|
||||
int ne_xml_parse_v(void *userdata, const char *block, size_t len)
|
||||
{
|
||||
ne_xml_parser *p = userdata;
|
||||
return ne_xml_parse(p, (const ne_xml_char *)block, len);
|
||||
}
|
||||
|
||||
#define BOM_UTF8 "\xEF\xBB\xBF" /* UTF-8 BOM */
|
||||
|
||||
int ne_xml_parse(ne_xml_parser *p, const char *block, size_t len)
|
||||
{
|
||||
int ret, flag;
|
||||
/* duck out if it's broken */
|
||||
if (p->failure) {
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "XML: Failed; ignoring %" NE_FMT_SIZE_T
|
||||
" bytes.\n", len);
|
||||
return p->failure;
|
||||
}
|
||||
if (len == 0) {
|
||||
flag = -1;
|
||||
block = "";
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "XML: End of document.\n");
|
||||
} else {
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "XML: Parsing %" NE_FMT_SIZE_T " bytes.\n", len);
|
||||
flag = 0;
|
||||
}
|
||||
|
||||
#ifdef NEED_BOM_HANDLING
|
||||
if (p->bom_pos < 3) {
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "Checking for UTF-8 BOM.\n");
|
||||
while (len > 0 && p->bom_pos < 3 &&
|
||||
block[0] == BOM_UTF8[p->bom_pos]) {
|
||||
block++;
|
||||
len--;
|
||||
p->bom_pos++;
|
||||
}
|
||||
if (len == 0)
|
||||
return 0;
|
||||
if (p->bom_pos == 0) {
|
||||
p->bom_pos = 3; /* no BOM */
|
||||
} else if (p->bom_pos > 0 && p->bom_pos < 3) {
|
||||
strcpy(p->error, _("Invalid Byte Order Mark"));
|
||||
return p->failure = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Note, don't write a parser error if p->failure, since an error
|
||||
* will already have been written in that case. */
|
||||
#ifdef HAVE_EXPAT
|
||||
ret = XML_Parse(p->parser, block, len, flag);
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "XML: XML_Parse returned %d\n", ret);
|
||||
if (ret == 0 && p->failure == 0) {
|
||||
ne_snprintf(p->error, ERR_SIZE,
|
||||
"XML parse error at line %d: %s",
|
||||
XML_GetCurrentLineNumber(p->parser),
|
||||
XML_ErrorString(XML_GetErrorCode(p->parser)));
|
||||
p->failure = 1;
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "XML: Parse error: %s\n", p->error);
|
||||
}
|
||||
#else
|
||||
ret = xmlParseChunk(p->parser, block, len, flag);
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "XML: xmlParseChunk returned %d\n", ret);
|
||||
/* Parse errors are normally caught by the sax_error() callback,
|
||||
* which clears p->valid. */
|
||||
if (p->parser->errNo && p->failure == 0) {
|
||||
ne_snprintf(p->error, ERR_SIZE, "XML parse error at line %d.",
|
||||
ne_xml_currentline(p));
|
||||
p->failure = 1;
|
||||
NE_DEBUG(NE_DBG_XMLPARSE, "XML: Parse error: %s\n", p->error);
|
||||
}
|
||||
#endif
|
||||
return p->failure;
|
||||
}
|
||||
|
||||
int ne_xml_failed(ne_xml_parser *p)
|
||||
{
|
||||
return p->failure;
|
||||
}
|
||||
|
||||
void ne_xml_destroy(ne_xml_parser *p)
|
||||
{
|
||||
struct element *elm, *parent;
|
||||
struct handler *hand, *next;
|
||||
|
||||
/* Free up the handlers on the stack: the root element has the
|
||||
* pointer to the base of the handler stack. */
|
||||
for (hand = p->root->handler; hand!=NULL; hand=next) {
|
||||
next = hand->next;
|
||||
ne_free(hand);
|
||||
}
|
||||
|
||||
/* Clean up remaining elements */
|
||||
for (elm = p->current; elm != p->root; elm = parent) {
|
||||
parent = elm->parent;
|
||||
destroy_element(elm);
|
||||
}
|
||||
|
||||
/* free root element */
|
||||
ne_free(p->root);
|
||||
|
||||
#ifdef HAVE_EXPAT
|
||||
XML_ParserFree(p->parser);
|
||||
if (p->encoding) ne_free(p->encoding);
|
||||
#else
|
||||
xmlFreeParserCtxt(p->parser);
|
||||
#endif
|
||||
|
||||
ne_free(p);
|
||||
}
|
||||
|
||||
void ne_xml_set_error(ne_xml_parser *p, const char *msg)
|
||||
{
|
||||
ne_snprintf(p->error, ERR_SIZE, "%s", msg);
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBXML
|
||||
static void sax_error(void *ctx, const char *msg, ...)
|
||||
{
|
||||
ne_xml_parser *p = ctx;
|
||||
va_list ap;
|
||||
char buf[1024];
|
||||
|
||||
va_start(ap, msg);
|
||||
ne_vsnprintf(buf, 1024, msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (p->failure == 0) {
|
||||
ne_snprintf(p->error, ERR_SIZE,
|
||||
_("XML parse error at line %d: %s."),
|
||||
p->parser->input->line, buf);
|
||||
p->failure = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *ne_xml_get_error(ne_xml_parser *p)
|
||||
{
|
||||
return p->error;
|
||||
}
|
||||
|
||||
const char *
|
||||
ne_xml_get_attr(ne_xml_parser *p, const char **attrs,
|
||||
const char *nspace, const char *name)
|
||||
{
|
||||
int n;
|
||||
|
||||
for (n = 0; attrs[n] != NULL; n += 2) {
|
||||
char *pnt = strchr(attrs[n], ':');
|
||||
|
||||
if (!nspace && !pnt && strcmp(attrs[n], name) == 0) {
|
||||
return attrs[n+1];
|
||||
} else if (nspace && pnt) {
|
||||
/* If a namespace is given, and the local part matches,
|
||||
* then resolve the namespace and compare that too. */
|
||||
if (strcmp(pnt + 1, name) == 0) {
|
||||
const char *uri = resolve_nspace(p->current,
|
||||
attrs[n], pnt - attrs[n]);
|
||||
if (uri && strcmp(uri, nspace) == 0)
|
||||
return attrs[n+1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ne_xml_mapid(const struct ne_xml_idmap map[], size_t maplen,
|
||||
const char *nspace, const char *name)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
for (n = 0; n < maplen; n++)
|
||||
if (strcmp(name, map[n].name) == 0 &&
|
||||
strcmp(nspace, map[n].nspace) == 0)
|
||||
return map[n].id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
neon XML parser interface
|
||||
Copyright (C) 1999-2004, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_XML_H
|
||||
#define NE_XML_H
|
||||
|
||||
#include <sys/types.h> /* for size_t */
|
||||
|
||||
#include "ne_defs.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* The neon XML interface filters a streamed XML tree through a stack
|
||||
* of SAX "handlers". A handler is made up of three callbacks
|
||||
* (start-element, char-data, end-element). Each start-element event
|
||||
* is passed to each handler in the stack in turn until one until one
|
||||
* accepts the element. This handler then receives subsequent
|
||||
* char-data and end-element events for the element.
|
||||
*
|
||||
* For each new start-element event, the search up the handler stack
|
||||
* begins with the handler for the parent element (for the root
|
||||
* element, at the base of the stack).
|
||||
*
|
||||
* For each accepted element, a "state" integer is stored, which is
|
||||
* passed to the corresponding char-data and end-element callbacks for
|
||||
* the element. This integer is also passed to the start-element
|
||||
* callback of child elements so they can determine context.
|
||||
*
|
||||
* If no handler in the stack accepts a particular element, it (and
|
||||
* its children, if any) is ignored. */
|
||||
|
||||
#define NE_XML_DECLINE (0)
|
||||
#define NE_XML_ABORT (-1)
|
||||
|
||||
/* A start-element callback for element with given namespace/name.
|
||||
* The callback may return:
|
||||
* <0 => abort the parse (NE_XML_ABORT)
|
||||
* 0 => decline this element (NE_XML_DECLINE)
|
||||
* >0 => accept this element; value is state for this element.
|
||||
*
|
||||
* The 'parent' integer is the state returned by the handler of the
|
||||
* parent element. The attributes array gives name/value pairs
|
||||
* in atts[n] and atts[n+1] from n=0 up to atts[n]==NULL. */
|
||||
typedef int ne_xml_startelm_cb(void *userdata, int parent,
|
||||
const char *nspace, const char *name,
|
||||
const char **atts);
|
||||
|
||||
/* state for the root element */
|
||||
#define NE_XML_STATEROOT (0)
|
||||
|
||||
/* Character data callback; may return non-zero to abort the parse. */
|
||||
typedef int ne_xml_cdata_cb(void *userdata, int state,
|
||||
const char *cdata, size_t len);
|
||||
/* End element callback; may return non-zero to abort the parse. */
|
||||
typedef int ne_xml_endelm_cb(void *userdata, int state,
|
||||
const char *nspace, const char *name);
|
||||
|
||||
typedef struct ne_xml_parser_s ne_xml_parser;
|
||||
|
||||
/* Create an XML parser. */
|
||||
ne_xml_parser *ne_xml_create(void);
|
||||
|
||||
/* Push a new handler on the stack of parser 'p'. 'cdata' and/or
|
||||
* 'endelm' may be NULL; startelm must be non-NULL. */
|
||||
void ne_xml_push_handler(ne_xml_parser *p,
|
||||
ne_xml_startelm_cb *startelm,
|
||||
ne_xml_cdata_cb *cdata,
|
||||
ne_xml_endelm_cb *endelm,
|
||||
void *userdata);
|
||||
|
||||
/* ne_xml_failed returns non-zero if there was an error during
|
||||
* parsing, or zero if the parse completed successfully. The return
|
||||
* value is equal to that of the last ne_xml_parse call for this
|
||||
* parser. */
|
||||
int ne_xml_failed(ne_xml_parser *p);
|
||||
|
||||
/* Set error string for parser: the string may be truncated. */
|
||||
void ne_xml_set_error(ne_xml_parser *p, const char *msg);
|
||||
|
||||
/* Return the error string (never NULL). After ne_xml_failed returns
|
||||
* >0, this will describe the parse error. Otherwise it will be a
|
||||
* default error string. */
|
||||
const char *ne_xml_get_error(ne_xml_parser *p);
|
||||
|
||||
/* Destroy the parser object. */
|
||||
void ne_xml_destroy(ne_xml_parser *p);
|
||||
|
||||
/* Parse the given block of input of length len. Parser must be
|
||||
* called with len=0 to signify the end of the document (for that
|
||||
* case, the block argument is ignored). Returns zero on success, or
|
||||
* non-zero on error: for an XML syntax error, a positive number is
|
||||
* returned; if parsing is aborted by a caller-supplied callback, that
|
||||
* callback's return value is returned. */
|
||||
int ne_xml_parse(ne_xml_parser *p, const char *block, size_t len);
|
||||
|
||||
/* As above, casting (ne_xml_parser *)userdata internally.
|
||||
* (This function can be passed to ne_add_response_body_reader) */
|
||||
int ne_xml_parse_v(void *userdata, const char *block, size_t len);
|
||||
|
||||
/* Return current parse line for errors */
|
||||
int ne_xml_currentline(ne_xml_parser *p);
|
||||
|
||||
/* From a start_element callback which was passed 'attrs' using given
|
||||
* parser, return attribute of given name and namespace. If nspace is
|
||||
* NULL, no namespace resolution is performed. */
|
||||
const char *ne_xml_get_attr(ne_xml_parser *parser,
|
||||
const char **attrs, const char *nspace,
|
||||
const char *name);
|
||||
|
||||
/* Return the encoding of the document being parsed. May return NULL
|
||||
* if no encoding is defined or if the XML declaration has not yet
|
||||
* been parsed. */
|
||||
const char *ne_xml_doc_encoding(const ne_xml_parser *p);
|
||||
|
||||
/* A utility interface for mapping {nspace, name} onto an integer. */
|
||||
struct ne_xml_idmap {
|
||||
const char *nspace, *name;
|
||||
int id;
|
||||
};
|
||||
|
||||
/* Return the size of an idmap array */
|
||||
#define NE_XML_MAPLEN(map) (sizeof(map) / sizeof(struct ne_xml_idmap))
|
||||
|
||||
/* Return the 'id' corresponding to {nspace, name}, or zero. */
|
||||
int ne_xml_mapid(const struct ne_xml_idmap map[], size_t maplen,
|
||||
const char *nspace, const char *name);
|
||||
|
||||
/* media type, appropriate for adding to a Content-Type header */
|
||||
#define NE_XML_MEDIA_TYPE "application/xml"
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_XML_H */
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
XML/HTTP response handling
|
||||
Copyright (C) 2004-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ne_xmlreq.h"
|
||||
#include "ne_i18n.h"
|
||||
|
||||
/* Handle an XML response parse error, setting session error string
|
||||
* and closing the connection. */
|
||||
static int parse_error(ne_session *sess, ne_xml_parser *parser)
|
||||
{
|
||||
ne_set_error(sess, _("Could not parse response: %s"),
|
||||
ne_xml_get_error(parser));
|
||||
ne_close_connection(sess);
|
||||
return NE_ERROR;
|
||||
}
|
||||
|
||||
int ne_xml_parse_response(ne_request *req, ne_xml_parser *parser)
|
||||
{
|
||||
char buf[8000];
|
||||
ssize_t bytes;
|
||||
int ret = 0;
|
||||
|
||||
while ((bytes = ne_read_response_block(req, buf, sizeof buf)) > 0) {
|
||||
ret = ne_xml_parse(parser, buf, bytes);
|
||||
if (ret)
|
||||
return parse_error(ne_get_session(req), parser);
|
||||
}
|
||||
|
||||
if (bytes == 0) {
|
||||
/* Tell the parser that end of document was reached: */
|
||||
if (ne_xml_parse(parser, NULL, 0) == 0)
|
||||
return NE_OK;
|
||||
else
|
||||
return parse_error(ne_get_session(req), parser);
|
||||
} else {
|
||||
return NE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int ne_xml_dispatch_request(ne_request *req, ne_xml_parser *parser)
|
||||
{
|
||||
int ret;
|
||||
|
||||
do {
|
||||
ret = ne_begin_request(req);
|
||||
if (ret) break;
|
||||
|
||||
if (ne_get_status(req)->klass == 2)
|
||||
ret = ne_xml_parse_response(req, parser);
|
||||
else
|
||||
ret = ne_discard_response(req);
|
||||
|
||||
if (ret == NE_OK)
|
||||
ret = ne_end_request(req);
|
||||
} while (ret == NE_RETRY);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
XML/HTTP response handling
|
||||
Copyright (C) 2004-2005, Joe Orton <joe@manyfish.co.uk>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public
|
||||
License along with this library; if not, write to the Free
|
||||
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef NE_XMLREQ_H
|
||||
#define NE_XMLREQ_H
|
||||
|
||||
#include "ne_request.h"
|
||||
#include "ne_xml.h"
|
||||
|
||||
BEGIN_NEON_DECLS
|
||||
|
||||
/* Read the HTTP response body using calls to ne_read_response_block
|
||||
* (so must be enclosed by ne_begin_request/ne_end_request calls), and
|
||||
* parse it as an XML document, using the given parser. Returns NE_*
|
||||
* error codes. If an XML parse error occurs, the session error
|
||||
* string is set to the XML parser's error string, and NE_ERROR is
|
||||
* returned. */
|
||||
int ne_xml_parse_response(ne_request *req, ne_xml_parser *parser);
|
||||
|
||||
/* Dispatch the HTTP request, parsing the response body as an XML
|
||||
* document using * the given parser, if the response status class is
|
||||
* 2xx. Returns NE_* error codes. If an XML parse error occurs, the
|
||||
* session error string is set to the XML parser's error string, and
|
||||
* NE_ERROR is returned. */
|
||||
int ne_xml_dispatch_request(ne_request *req, ne_xml_parser *parser);
|
||||
|
||||
END_NEON_DECLS
|
||||
|
||||
#endif /* NE_XMLREQ_H */
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user