This commit is contained in:
biosvos
2026-08-07 17:38:18 +09:00
commit 873193a243
9613 changed files with 2755992 additions and 0 deletions
@@ -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
@@ -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,
&timestamp);
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