/* helper function for reading data from a client */ int client_read_bytes (client_t *client, void *buf, unsigned len) { int (*con_read)(struct connection_tag *handle, void *buf, size_t len) = connection_read; int bytes; if (len == 0) return 0; if (client->refbuf && client->pos < client->refbuf->len) { unsigned remaining = client->refbuf->len - client->pos; if (remaining > len) remaining = len; memcpy (buf, client->refbuf->data + client->pos, remaining); client->pos += remaining; if (client->pos >= client->refbuf->len) client_set_queue (client, NULL); return remaining; } #ifdef HAVE_OPENSSL if (client->connection.ssl) con_read = connection_read_ssl; #endif bytes = con_read (&client->connection, buf, len); if (bytes == -1 && client->connection.error) DEBUG0 ("reading from connection has failed"); return bytes; }
int client_send_404 (client_t *client, const char *message) { int ret = -1; if (client->worker == NULL) /* client is not on any worker now */ { client_destroy (client); return 0; } client_set_queue (client, NULL); if (client->respcode) { worker_t *worker = client->worker; if (client->respcode >= 300) client->flags = client->flags & ~CLIENT_AUTHENTICATED; client->flags |= CLIENT_ACTIVE; worker_wakeup (worker); } else { if (client->parser->req_type == httpp_req_head || message == NULL) message = "Not Available"; ret = strlen (message); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 404 Not Available\r\n" "%s\r\nContent-Length: %d\r\nContent-Type: text/html\r\n\r\n" "%s", client_keepalive_header (client), ret, message ? message: ""); client->respcode = 404; client->refbuf->len = strlen (client->refbuf->data); ret = fserve_setup_client (client); } return ret; }
/* clients need to be start from somewhere in the queue so we will look for * a refbuf which has been previously marked as a sync point. */ static void find_client_start (source_t *source, client_t *client) { refbuf_t *refbuf = source->burst_point; /* we only want to attempt a burst at connection time, not midstream */ if (client->intro_offset == -1) refbuf = source->stream_data_tail; else { long size = 0; refbuf = source->burst_point; size = client->intro_offset; while (size > 0 && refbuf->next) { size -= refbuf->len; refbuf = refbuf->next; } } while (refbuf) { if (refbuf->sync_point) { client_set_queue (client, refbuf); client->check_buffer = format_advance_queue; client->write_to_client = source->format->write_buf_to_client; client->intro_offset = -1; break; } refbuf = refbuf->next; } }
static int fserve_move_listener (client_t *client) { fh_node *fh = client->shared_data; int ret = 0; fbinfo f; memset (&f, 0, sizeof (f)); client_set_queue (client, NULL); f.flags = fh->finfo.flags & (~FS_DELETE); f.limit = fh->finfo.limit; f.mount = fh->finfo.fallback; f.type = fh->finfo.type; if (move_listener (client, &f) < 0) { WARN1 ("moved failed, terminating listener on %s", fh->finfo.mount); ret = -1; } else { DEBUG3 ("moved %s from %s (%d)", client->connection.ip, fh->finfo.mount, fh->finfo.flags); ret = 0; remove_from_fh (fh, client); } return ret; }
static int fserve_move_listener (client_t *client) { fh_node *fh = client->shared_data; int ret = 0; fbinfo f; memset (&f, 0, sizeof (f)); if (client->refbuf && client->pos < client->refbuf->len) client->flags |= CLIENT_HAS_INTRO_CONTENT; // treat it as a partial write needing completion else client_set_queue (client, NULL); f.flags = fh->finfo.flags & (~FS_DELETE); f.limit = fh->finfo.limit; f.mount = fh->finfo.fallback; f.type = fh->finfo.type; if (move_listener (client, &f) < 0) { WARN1 ("moved failed, terminating listener on %s", fh->finfo.mount); ret = -1; } else { DEBUG3 ("moved %s from %s (%d)", client->connection.ip, fh->finfo.mount, fh->finfo.flags); ret = 0; remove_from_fh (fh, client); } return ret; }
static void file_release (client_t *client) { fh_node *fh = client->shared_data; int ret = -1; if (fh->finfo.flags & FS_FALLBACK) stats_event_dec (NULL, "listeners"); remove_from_fh (fh, client); client_set_queue (client, NULL); if (client->flags & CLIENT_AUTHENTICATED && client->parser->req_type == httpp_req_get) { const char *mount = httpp_getvar (client->parser, HTTPP_VAR_URI); ice_config_t *config = config_get_config (); mount_proxy *mountinfo = config_find_mount (config, mount); if (mountinfo && mountinfo->access_log.name) logging_access_id (&mountinfo->access_log, client); ret = auth_release_listener (client, mount, mountinfo); config_release_config(); } if (ret < 0) { client->flags &= ~CLIENT_AUTHENTICATED; client_destroy (client); } global_reduce_bitrate_sampling (global.out_bitrate); }
int client_send_404 (client_t *client, const char *message) { int ret = -1; if (client->worker == NULL) /* client is not on any worker now */ { client_destroy (client); return 0; } client_set_queue (client, NULL); if (client->respcode) { worker_t *worker = client->worker; if (client->respcode >= 300) client->flags = client->flags & ~CLIENT_AUTHENTICATED; client->flags |= CLIENT_ACTIVE; worker_wakeup (worker); } else { if (message == NULL) message = "Not Available"; client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 404 Not Available\r\n" "Content-Type: text/html\r\n\r\n" "<b>%s</b>\r\n", message); client->respcode = 404; client->refbuf->len = strlen (client->refbuf->data); ret = fserve_setup_client (client); } return ret; }
/* clients need to be start from somewhere in the queue so we will look for * a refbuf which has been previously marked as a sync point. */ static void find_client_start(source_t *source, client_t *client) { refbuf_t *refbuf = source->burst_point; /* we only want to attempt a burst at connection time, not midstream * however streams like theora may not have the most recent page marked as * a starting point, so look for one from the burst point */ if (client->intro_offset == -1 && source->stream_data_tail && source->stream_data_tail->sync_point) refbuf = source->stream_data_tail; else { size_t size = client->intro_offset; refbuf = source->burst_point; while (size > 0 && refbuf && refbuf->next) { size -= refbuf->len; refbuf = refbuf->next; } } while (refbuf) { if (refbuf->sync_point) { client_set_queue (client, refbuf); client->check_buffer = format_advance_queue; client->write_to_client = source->format->write_buf_to_client; client->intro_offset = -1; break; } refbuf = refbuf->next; } }
int command_list_mounts(client_t *client, int response) { DEBUG0("List mounts request"); client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); if (response == TEXT) { redirector_update (client); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"); client->refbuf->len = strlen (client->refbuf->data); client->respcode = 200; if (strcmp (httpp_getvar (client->parser, HTTPP_VAR_URI), "/admin/streams") == 0) client->refbuf->next = stats_get_streams (1); else client->refbuf->next = stats_get_streams (0); return fserve_setup_client (client); } else { xmlDocPtr doc; avl_tree_rlock (global.source_tree); doc = admin_build_sourcelist(NULL); avl_tree_unlock (global.source_tree); return admin_send_response (doc, client, response, "listmounts.xsl"); } }
int format_file_read (client_t *client, format_plugin_t *plugin, FILE *fp) { refbuf_t *refbuf = client->refbuf; size_t bytes = -1; int unprocessed = 0; do { if (refbuf == NULL) { if (fp == NULL) return -2; refbuf = client->refbuf = refbuf_new (4096); client->pos = refbuf->len; client->intro_offset = 0; client->queue_pos = 0; } if (client->pos < refbuf->len) break; if (fp == NULL || client->flags & CLIENT_HAS_INTRO_CONTENT) { if (refbuf->next) { //DEBUG1 ("next intro block is %d", refbuf->next->len); client->refbuf = refbuf->next; refbuf->next = NULL; refbuf_release (refbuf); client->pos = 0; return 0; } //DEBUG0 ("No more intro data "); client_set_queue (client, NULL); client->flags &= ~CLIENT_HAS_INTRO_CONTENT; client->intro_offset = client->connection.sent_bytes; refbuf = NULL; continue; } if (fseek (fp, client->intro_offset, SEEK_SET) < 0 || (bytes = fread (refbuf->data, 1, 4096, fp)) <= 0) { return bytes < 0 ? -2 : -1; } refbuf->len = bytes; client->pos = 0; if (plugin->align_buffer) { /* here the buffer may require truncating to keep the buffers aligned on * certain boundaries */ unprocessed = plugin->align_buffer (client, plugin); if (unprocessed < 0 || unprocessed >= bytes) unprocessed = 0; } client->intro_offset += (bytes - unprocessed); } while (1); return 0; }
void stats_callback (client_t *client, void *notused) { if (client->con->error) { client_destroy (client); return; } client_set_queue (client, NULL); thread_create("Stats Connection", stats_connection, (void *)client, THREAD_DETACHED); }
int client_send_416(client_t *client) { client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 416 Request Range Not Satisfiable\r\n\r\n"); client->respcode = 416; client->refbuf->len = strlen (client->refbuf->data); return fserve_setup_client (client); }
int client_send_501(client_t *client) { client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 501 Not Implemented\r\n\r\n"); client->respcode = 501; client->refbuf->len = strlen (client->refbuf->data); return fserve_setup_client (client); }
int client_send_400(client_t *client, const char *message) { client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 400 Bad Request\r\n" "Content-Type: text/html\r\n\r\n" "<b>%s</b>\r\n", message?message:""); client->respcode = 400; client->refbuf->len = strlen (client->refbuf->data); return fserve_setup_client (client); }
static int command_list_log (client_t *client, int response) { refbuf_t *content; const char *logname = httpp_get_query_param (client->parser, "log"); int log = -1; ice_config_t *config; if (logname == NULL) return client_send_400 (client, "No log specified"); config = config_get_config (); if (strcmp (logname, "errorlog") == 0) log = config->error_log.logid; else if (strcmp (logname, "accesslog") == 0) log = config->access_log.logid; else if (strcmp (logname, "playlistlog") == 0) log = config->playlist_log.logid; if (log < 0) { config_release_config(); WARN1 ("request to show unknown log \"%s\"", logname); return client_send_400 (client, "unknown"); } content = refbuf_new (0); log_contents (log, &content->data, &content->len); config_release_config(); if (response == XSLT) { xmlNodePtr xmlnode; xmlDocPtr doc; doc = xmlNewDoc(XMLSTR("1.0")); xmlnode = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL); xmlDocSetRootElement(doc, xmlnode); xmlNewTextChild (xmlnode, NULL, XMLSTR("log"), XMLSTR(content->data)); refbuf_release (content); return admin_send_response (doc, client, XSLT, "showlog.xsl"); } else { refbuf_t *http = refbuf_new (100); int len = snprintf (http->data, 100, "%s", "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n"); http->len = len; http->next = content; client->respcode = 200; client_set_queue (client, http); return fserve_setup_client (client); } }
int client_send_403(client_t *client, const char *reason) { if (reason == NULL) reason = "Forbidden"; client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 403 %s\r\n" "Content-Type: text/html\r\n\r\n", reason); client->respcode = 403; client->refbuf->len = strlen (client->refbuf->data); return fserve_setup_client (client); }
int client_send_302(client_t *client, const char *location) { client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 302 Temporarily Moved\r\n" "Content-Type: text/html\r\n" "Location: %s\r\n\r\n" "Moved <a href=\"%s\">here</a>\r\n", location, location); client->respcode = 302; client->refbuf->len = strlen (client->refbuf->data); return fserve_setup_client (client); }
int client_send_options(client_t *client) { client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.1 200 OK\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Headers: Origin, Accept, X-Requested-With, Content-Type\r\n" "Access-Control-Allow-Methods: GET, OPTIONS, STATS\r\n\r\n"); client->respcode = 200; client->refbuf->len = strlen (client->refbuf->data); return fserve_setup_client (client); }
/* clients need to be start from somewhere in the queue * * so we will look for a refbuf which has been previous * * marked as a sync point */ static void find_client_start (source_t *source, client_t *client) { refbuf_t *refbuf = source->burst_point; while (refbuf) { if (refbuf->sync_point) { client_set_queue (client, refbuf); break; } refbuf = refbuf->next; } }
void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client) { xmlDocPtr res; xsltStylesheetPtr cur; xmlChar *string; int len, problem = 0; xmlSetGenericErrorFunc ("", log_parse_failure); xsltSetGenericErrorFunc ("", log_parse_failure); thread_mutex_lock(&xsltlock); cur = xslt_get_stylesheet(xslfilename); if (cur == NULL) { thread_mutex_unlock(&xsltlock); ERROR1 ("problem reading stylesheet \"%s\"", xslfilename); client_send_404 (client, "Could not parse XSLT file"); return; } res = xsltApplyStylesheet(cur, doc, NULL); if (xsltSaveResultToString (&string, &len, res, cur) < 0) problem = 1; thread_mutex_unlock(&xsltlock); if (problem == 0) { const char *http = "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nContent-Length: "; int buf_len = strlen (http) + 20 + len; if (string == NULL) string = xmlStrdup (""); client->respcode = 200; client_set_queue (client, NULL); client->refbuf = refbuf_new (buf_len); len = snprintf (client->refbuf->data, buf_len, "%s%d\r\n\r\n%s", http, len, string); client->refbuf->len = len; fserve_add_client (client, NULL); xmlFree (string); } else { WARN1 ("problem applying stylesheet \"%s\"", xslfilename); client_send_404 (client, "XSLT problem"); } xmlFreeDoc(res); }
/* This is the commonly used for source streams, here we just progress to * the next buffer in the queue if there is no more left to be written from * the existing buffer. */ int format_advance_queue (source_t *source, client_t *client) { refbuf_t *refbuf = client->refbuf; if (refbuf == NULL) return -1; if (refbuf->next == NULL && client->pos == refbuf->len) return -1; /* move to the next buffer if we have finished with the current one */ if (refbuf->next && client->pos == refbuf->len) { client_set_queue (client, refbuf->next); refbuf = client->refbuf; } return 0; }
int admin_send_response (xmlDocPtr doc, client_t *client, admin_response_type response, const char *xslt_template) { int ret = -1; if (response == RAW) { xmlChar *buff = NULL; int len = 0; unsigned int buf_len; const char *http = "HTTP/1.0 200 OK\r\n" "Content-Type: text/xml\r\n" "Content-Length: "; xmlDocDumpFormatMemoryEnc (doc, &buff, &len, NULL, 1); buf_len = strlen (http) + len + 20; client_set_queue (client, NULL); client->refbuf = refbuf_new (buf_len); len = snprintf (client->refbuf->data, buf_len, "%s%d\r\n\r\n%s", http, len, buff); client->refbuf->len = len; xmlFree(buff); xmlFreeDoc (doc); client->respcode = 200; return fserve_setup_client (client); } if (response == XSLT) { char *fullpath_xslt_template; int fullpath_xslt_template_len; ice_config_t *config = config_get_config(); fullpath_xslt_template_len = strlen (config->adminroot_dir) + strlen(xslt_template) + 2; fullpath_xslt_template = malloc(fullpath_xslt_template_len); snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s", config->adminroot_dir, PATH_SEPARATOR, xslt_template); config_release_config(); DEBUG1("Sending XSLT (%s)", fullpath_xslt_template); ret = xslt_transform (doc, fullpath_xslt_template, client); free(fullpath_xslt_template); xmlFreeDoc(doc); } return ret; }
/* main client write routine for sending ogg data. Each refbuf has a * single page so we only need to determine if there are new headers */ static int write_buf_to_client (format_plugin_t *self, client_t *client) { refbuf_t *refbuf = client->refbuf; char *buf; unsigned len; struct ogg_client *client_data = client->format_data; int ret, written = 0; if (refbuf->next == NULL && client->pos == refbuf->len) return 0; if (refbuf->next && client->pos == refbuf->len) { client_set_queue (client, refbuf->next); refbuf = client->refbuf; } buf = refbuf->data + client->pos; len = refbuf->len - client->pos; do { if (client_data->headers != refbuf->associated) { ret = send_ogg_headers (client, refbuf->associated); if (client_data->headers_sent == 0) break; written += ret; } ret = client_send_bytes (client, buf, len); if (ret > 0) client->pos += ret; if (ret < (int)len) break; written += ret; /* we have now written the page(s) */ ret = 0; } while (0); if (ret > 0) written += ret; return written; }
int client_send_401 (client_t *client, const char *realm) { ice_config_t *config = config_get_config (); if (realm == NULL) realm = config->server_id; client_set_queue (client, NULL); client->refbuf = refbuf_new (500); snprintf (client->refbuf->data, 500, "HTTP/1.0 401 Authentication Required\r\n" "WWW-Authenticate: Basic realm=\"%s\"\r\n" "\r\n" "You need to authenticate\r\n", realm); config_release_config(); client->respcode = 401; client->refbuf->len = strlen (client->refbuf->data); return fserve_setup_client (client); }
int client_send_302(client_t *client, const char *location) { int len; char body [4096]; client_set_queue (client, NULL); client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); len = snprintf (body, sizeof body, "Moved <a href=\"%s\">here</a>\r\n", location); len = snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE, "HTTP/1.0 302 Temporarily Moved\r\n" "Content-Type: text/html\r\n" "Content-Length: %d\r\n" "Location: %s\r\n\r\n%s", len, location, body); client->respcode = 302; client->flags &= ~CLIENT_KEEPALIVE; client->refbuf->len = len; return fserve_setup_client (client); }
void stats_add_listener (client_t *client, int mask) { event_listener_t *listener = calloc (1, sizeof (event_listener_t)); listener->mask = mask; client->respcode = 200; client->ops = &stats_client_send_ops; client->shared_data = listener; client_set_queue (client, NULL); client->refbuf = refbuf_new (100); snprintf (client->refbuf->data, 100, "HTTP/1.0 200 OK\r\nCapability: streamlist stats\r\n\r\n"); client->refbuf->len = strlen (client->refbuf->data); listener->content_len = client->refbuf->len; listener->recent_block = client->refbuf; listener->client = client; _register_listener (client); client->flags |= CLIENT_ACTIVE; }
/* General listener client shutdown function. Here we free up the passed client but * if the client is authenticated and there's auth available then queue it. */ int auth_release_listener (client_t *client, const char *mount, mount_proxy *mountinfo) { if (client->flags & CLIENT_AUTHENTICATED) { client_set_queue (client, NULL); if (mount && mountinfo && mountinfo->auth && mountinfo->auth->release_listener) { auth_client *auth_user = auth_client_setup (mount, client); client->flags &= ~CLIENT_ACTIVE; if (client->worker) client->ops = &auth_release_ops; // put into a wait state auth_user->process = auth_remove_listener; queue_auth_client (auth_user, mountinfo); return 0; } client->flags &= ~CLIENT_AUTHENTICATED; } return client_send_404 (client, NULL); }
/* determine whether we need to process this client further. This * involves any auth exit, typically for external auth servers. */ int auth_release_listener (client_t *client) { if (client->authenticated) { const char *mount = httpp_getvar (client->parser, HTTPP_VAR_URI); /* drop any queue reference here, we do not want a race between the source thread * and the auth/fserve thread */ client_set_queue (client, NULL); if (mount && client->auth && client->auth->release_listener) { auth_client *auth_user = auth_client_setup (mount, client); auth_user->process = auth_remove_listener; queue_auth_client (auth_user, NULL); return 1; } client->authenticated = 0; } return 0; }
/* call to check the buffer contents for file reading. move the client * to right place in the queue at end of file else repeat file if queue * is not ready yet. */ int format_check_file_buffer (source_t *source, client_t *client) { refbuf_t *refbuf = client->refbuf; if (refbuf == NULL) { /* client refers to no data, must be from a move */ if (source->client->con) { find_client_start (source, client); return -1; } /* source -> file fallback, need a refbuf for data */ refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); client->refbuf = refbuf; client->pos = refbuf->len; client->intro_offset = 0; } if (client->pos == refbuf->len) { if (get_file_data (source->intro_file, client)) { client->pos = 0; client->intro_offset += refbuf->len; } else { if (source->stream_data_tail) { /* better find the right place in queue for this client */ client_set_queue (client, NULL); find_client_start (source, client); } else client->intro_offset = 0; /* replay intro file */ return -1; } } return 0; }
int xslt_transform (xmlDocPtr doc, const char *xslfilename, client_t *client) { xmlDocPtr res; xsltStylesheetPtr cur; int len; refbuf_t *content = NULL; char **params = NULL; xmlSetGenericErrorFunc ("", log_parse_failure); xsltSetGenericErrorFunc ("", log_parse_failure); thread_mutex_lock(&xsltlock); cur = xslt_get_stylesheet(xslfilename); if (cur == NULL) { thread_mutex_unlock(&xsltlock); ERROR1 ("problem reading stylesheet \"%s\"", xslfilename); return client_send_404 (client, "Could not parse XSLT file"); } if (client->parser->queryvars) { // annoying but we need to surround the args with ' when passing them in int i, arg_count = client->parser->queryvars->length * 2; avl_node *node = avl_get_first (client->parser->queryvars); params = calloc (arg_count+1, sizeof (char *)); for (i = 0; node && i < arg_count; node = avl_get_next (node)) { http_var_t *param = (http_var_t *)node->key; char *tmp = util_url_escape (param->value); params[i++] = param->name; // use alloca for now, should really url esc into a supplied buffer params[i] = (char*)alloca (strlen (tmp) + 3); sprintf (params[i++], "\'%s\'", tmp); free (tmp); } params[i] = NULL; } res = xsltApplyStylesheet (cur, doc, (const char **)params); free (params); if (res == NULL || xslt_SaveResultToBuf (&content, &len, res, cur) < 0) { thread_mutex_unlock (&xsltlock); xmlFreeDoc (res); WARN1 ("problem applying stylesheet \"%s\"", xslfilename); return client_send_404 (client, "XSLT problem"); } else { /* the 100 is to allow for the hardcoded headers */ refbuf_t *refbuf = refbuf_new (100); const char *mediatype = NULL; /* lets find out the content type to use */ if (cur->mediaType) mediatype = (char *)cur->mediaType; else { /* check method for the default, a missing method assumes xml */ if (cur->method && xmlStrcmp (cur->method, XMLSTR("html")) == 0) mediatype = "text/html"; else if (cur->method && xmlStrcmp (cur->method, XMLSTR("text")) == 0) mediatype = "text/plain"; else mediatype = "text/xml"; } snprintf (refbuf->data, 100, "HTTP/1.0 200 OK\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n", mediatype, len); thread_mutex_unlock (&xsltlock); client->respcode = 200; client_set_queue (client, NULL); client->refbuf = refbuf; refbuf->len = strlen (refbuf->data); refbuf->next = content; } xmlFreeDoc(res); return fserve_setup_client (client); }