534 lines
13 KiB
C++
534 lines
13 KiB
C++
/*
|
|
* File: dpip.c
|
|
*
|
|
* Copyright 2005-2015 Jorge Arellano Cid <jcid@dillo.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h> /* for close */
|
|
#include <fcntl.h> /* for fcntl */
|
|
|
|
#include "dpip.hh"
|
|
#include "d_size.h"
|
|
|
|
#define RBUF_SZ 16*1024
|
|
//#define RBUF_SZ 1
|
|
|
|
#define DPIP_TAG_END " '>"
|
|
#define DPIP_MODE_SWITCH_TAG "cmd='start_send_page' "
|
|
#define MSG_ERR(...) fprintf(stderr, "[dpip]: " __VA_ARGS__)
|
|
|
|
/*
|
|
* Local variables
|
|
*/
|
|
static const char Quote = '\'';
|
|
|
|
/** @file
|
|
* Library for dealing with dpip tags (dillo plugin protocol tags).
|
|
*
|
|
* Basically the syntax of a dpip tag is:
|
|
*
|
|
* @code
|
|
* "<"[*alpha] *(<name>"="Quote<escaped_value>Quote) " "Quote">"
|
|
* @endcode
|
|
* 1. No space is allowed around the "=" sign between a name and its value.
|
|
* 2. The Quote character is not allowed in <name>.
|
|
* 3. Attribute values stuff Quote as QuoteQuote.
|
|
*
|
|
* e.g. (with ' as Quote):
|
|
*
|
|
* @code
|
|
* <a='b' b='c' '> OK
|
|
* <dpi a='b i' b='12' '> OK
|
|
* <a='>' '> OK
|
|
* <a='ain''t no doubt' '> OK
|
|
* <a='ain''t b=''no'' b='' doubt' '> OK
|
|
* <a = '>' '> Wrong
|
|
* @endcode
|
|
*
|
|
* Notes:
|
|
*
|
|
* - Restriction #1 is for easy finding of end of tag (EOT=Space+Quote+>).
|
|
* - Restriction #2 can be removed, but what for? ;)
|
|
* - The functions here provide for this functionality.
|
|
*/
|
|
|
|
typedef enum {
|
|
SEEK_NAME,
|
|
MATCH_NAME,
|
|
SKIP_VALUE,
|
|
SKIP_QUOTE,
|
|
FOUND
|
|
} DpipTagParsingState;
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* Printf like function for building dpip commands.
|
|
* It takes care of dpip escaping of its arguments.
|
|
* NOTE : It ONLY accepts string parameters, and
|
|
* only one %s per parameter.
|
|
*/
|
|
char *a_Dpip_build_cmd(const char *format, ...)
|
|
{
|
|
va_list argp;
|
|
char *p, *q, *s;
|
|
Dstr *cmd;
|
|
|
|
/* Don't allow Quote characters in attribute names */
|
|
if (strchr(format, Quote))
|
|
return NULL;
|
|
|
|
cmd = dStr_sized_new(64);
|
|
dStr_append_c(cmd, '<');
|
|
va_start(argp, format);
|
|
for (p = q = (char*)format; *q; ) {
|
|
p = strstr(q, "%s");
|
|
if (!p) {
|
|
dStr_append(cmd, q);
|
|
break;
|
|
} else {
|
|
/* Copy format's part */
|
|
while (q != p)
|
|
dStr_append_c(cmd, *q++);
|
|
q += 2;
|
|
|
|
dStr_append_c(cmd, Quote);
|
|
/* Stuff-copy of argument */
|
|
s = va_arg (argp, char *);
|
|
for ( ; *s; ++s) {
|
|
dStr_append_c(cmd, *s);
|
|
if (*s == Quote)
|
|
dStr_append_c(cmd, *s);
|
|
}
|
|
dStr_append_c(cmd, Quote);
|
|
}
|
|
}
|
|
va_end(argp);
|
|
dStr_append_c(cmd, ' ');
|
|
dStr_append_c(cmd, Quote);
|
|
dStr_append_c(cmd, '>');
|
|
|
|
p = cmd->str;
|
|
dStr_free(cmd, FALSE);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Task: given a tag, its size and an attribute name, return the
|
|
* attribute value (stuffing of ' is removed here).
|
|
*
|
|
* Return value: the attribute value, or NULL if not present or malformed.
|
|
*/
|
|
char *a_Dpip_get_attr_l(const char *tag, size_t tagsize, const char *attrname)
|
|
{
|
|
uint_t i, n = 0, found = 0;
|
|
const char *p, *q, *start;
|
|
char *r, *s, *val = NULL;
|
|
DpipTagParsingState state = SEEK_NAME;
|
|
|
|
if (!tag || !tagsize || !attrname || !*attrname)
|
|
return NULL;
|
|
|
|
for (i = 1; i < tagsize && !found; ++i) {
|
|
switch (state) {
|
|
case SEEK_NAME:
|
|
if (tag[i] == attrname[0] && (tag[i-1] == ' ' || tag[i-1] == '<')) {
|
|
n = 1;
|
|
state = MATCH_NAME;
|
|
} else if (tag[i] == Quote && tag[i-1] == '=')
|
|
state = SKIP_VALUE;
|
|
break;
|
|
case MATCH_NAME:
|
|
if (tag[i] == attrname[n])
|
|
++n;
|
|
else if (tag[i] == '=' && !attrname[n])
|
|
state = FOUND;
|
|
else
|
|
state = SEEK_NAME;
|
|
break;
|
|
case SKIP_VALUE:
|
|
if (tag[i] == Quote)
|
|
state = (tag[i+1] == Quote) ? SKIP_QUOTE : SEEK_NAME;
|
|
break;
|
|
case SKIP_QUOTE:
|
|
state = SKIP_VALUE;
|
|
break;
|
|
case FOUND:
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
p = start = tag + i;
|
|
while ((q = strchr(p, Quote)) && q[1] == Quote)
|
|
p = q + 2;
|
|
if (q && q[1] == ' ') {
|
|
val = dStrndup(start, (uint_t)(q - start));
|
|
for (r = s = val; (*r = *s); ++r, ++s)
|
|
if (s[0] == Quote && s[0] == s[1])
|
|
++s;
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Task: given a tag and an attribute name, return its value.
|
|
* Return value: the attribute value, or NULL if not present or malformed.
|
|
*/
|
|
char *a_Dpip_get_attr(const char *tag, const char *attrname)
|
|
{
|
|
return (tag ? a_Dpip_get_attr_l(tag, strlen(tag), attrname) : NULL);
|
|
}
|
|
|
|
/**
|
|
* Check whether the given 'auth' string equals what dpid saved.
|
|
* Return value: 1 if equal, -1 otherwise
|
|
*/
|
|
int a_Dpip_check_auth(const char *auth_tag)
|
|
{
|
|
char SharedSecret[32];
|
|
FILE *In;
|
|
char *fname, *rcline = NULL, *tail, *cmd, *msg;
|
|
int i, port, ret = -1;
|
|
|
|
/* sanity checks */
|
|
if (!auth_tag ||
|
|
!(cmd = a_Dpip_get_attr(auth_tag, "cmd")) || strcmp(cmd, "auth") ||
|
|
!(msg = a_Dpip_get_attr(auth_tag, "msg"))) {
|
|
return ret;
|
|
}
|
|
|
|
fname = dStrconcat(dGethomedir(), "/.flenser/dpid_comm_keys", NULL);
|
|
if ((In = fopen(fname, "r")) == NULL) {
|
|
MSG_ERR("[a_Dpip_check_auth] %s\n", dStrerror(errno));
|
|
} else if ((rcline = dGetline_unsafe(In)) == NULL) {
|
|
MSG_ERR("[a_Dpip_check_auth] empty file: %s\n", fname);
|
|
} else {
|
|
port = strtol(rcline, &tail, 10);
|
|
if (tail && port != 0) {
|
|
for (i = 0; *tail && isxdigit(tail[i+1]); ++i)
|
|
SharedSecret[i] = tail[i+1];
|
|
SharedSecret[i] = 0;
|
|
if (strcmp(msg, SharedSecret) == 0)
|
|
ret = 1;
|
|
}
|
|
}
|
|
if (In)
|
|
fclose(In);
|
|
dFree(rcline);
|
|
dFree(fname);
|
|
dFree(msg);
|
|
dFree(cmd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
* Dpip socket API ----------------------------------------------------------
|
|
*/
|
|
|
|
/**
|
|
* Create and initialize a dpip socket handler.
|
|
*/
|
|
Dsh *a_Dpip_dsh_new(int fd_in, int fd_out, int flush_sz)
|
|
{
|
|
Dsh *dsh = dNew(Dsh, 1);
|
|
|
|
/* init descriptors and streams */
|
|
dsh->fd_in = fd_in;
|
|
dsh->fd_out = fd_out;
|
|
|
|
/* init buffer */
|
|
dsh->wrbuf = dStr_sized_new(8 *1024);
|
|
dsh->rdbuf = dStr_sized_new(8 *1024);
|
|
dsh->flush_sz = flush_sz;
|
|
dsh->mode = DPIP_TAG;
|
|
if (fcntl(dsh->fd_in, F_GETFL) & O_NONBLOCK)
|
|
dsh->mode |= DPIP_NONBLOCK;
|
|
dsh->status = 0;
|
|
|
|
return dsh;
|
|
}
|
|
|
|
/*
|
|
* Return value: 1..DataSize sent, -1 eagain, or -3 on big Error
|
|
*/
|
|
static int Dpip_dsh_write(Dsh *dsh, int nb, const char *Data, int DataSize)
|
|
{
|
|
int req_mode, old_flags = 0, st, ret = -3, sent = 0;
|
|
|
|
req_mode = (nb) ? DPIP_NONBLOCK : 0;
|
|
if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
|
|
/* change mode temporarily... */
|
|
old_flags = fcntl(dsh->fd_out, F_GETFL);
|
|
fcntl(dsh->fd_out, F_SETFL,
|
|
(nb) ? O_NONBLOCK | old_flags : old_flags & ~O_NONBLOCK);
|
|
}
|
|
|
|
while (1) {
|
|
st = write(dsh->fd_out, Data + sent, DataSize - sent);
|
|
if (st < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
} else if (errno == EAGAIN) {
|
|
dsh->status = DPIP_EAGAIN;
|
|
ret = -1;
|
|
break;
|
|
} else {
|
|
MSG_ERR("[Dpip_dsh_write] %s\n", dStrerror(errno));
|
|
dsh->status = DPIP_ERROR;
|
|
break;
|
|
}
|
|
} else {
|
|
sent += st;
|
|
if (nb || sent == DataSize) {
|
|
ret = sent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
|
|
/* restore old mode */
|
|
fcntl(dsh->fd_out, F_SETFL, old_flags);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Streamed write to socket.
|
|
* Return: 0 on success, 1 on error.
|
|
*/
|
|
int a_Dpip_dsh_write(Dsh *dsh, int flush, const char *Data, int DataSize)
|
|
{
|
|
int ret = 1;
|
|
|
|
/* append to buf */
|
|
dStr_append_l(dsh->wrbuf, Data, DataSize);
|
|
|
|
if (!flush || dsh->wrbuf->len == 0)
|
|
return 0;
|
|
|
|
ret = Dpip_dsh_write(dsh, 0, dsh->wrbuf->str, dsh->wrbuf->len);
|
|
if (ret == dsh->wrbuf->len) {
|
|
dStr_truncate(dsh->wrbuf, 0);
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Return value: 0 on success or empty buffer,
|
|
* 1..DataSize sent, -1 eagain, or -3 on big Error
|
|
*/
|
|
int a_Dpip_dsh_tryflush(Dsh *dsh)
|
|
{
|
|
int st;
|
|
|
|
if (dsh->wrbuf->len == 0) {
|
|
st = 0;
|
|
} else {
|
|
st = Dpip_dsh_write(dsh, 1, dsh->wrbuf->str, dsh->wrbuf->len);
|
|
if (st > 0) {
|
|
/* update internal buffer */
|
|
dStr_erase(dsh->wrbuf, 0, st);
|
|
}
|
|
}
|
|
return (dsh->wrbuf->len == 0) ? 0 : st;
|
|
}
|
|
|
|
/*
|
|
* Return value: 1..DataSize sent, -1 eagain, or -3 on big Error
|
|
*/
|
|
int a_Dpip_dsh_trywrite(Dsh *dsh, const char *Data, int DataSize)
|
|
{
|
|
int st;
|
|
|
|
if ((st = Dpip_dsh_write(dsh, 1, Data, DataSize)) > 0) {
|
|
/* update internal buffer */
|
|
if (st < DataSize)
|
|
dStr_append_l(dsh->wrbuf, Data + st, DataSize - st);
|
|
}
|
|
return st;
|
|
}
|
|
|
|
/**
|
|
* Convenience function.
|
|
*/
|
|
int a_Dpip_dsh_write_str(Dsh *dsh, int flush, const char *str)
|
|
{
|
|
return a_Dpip_dsh_write(dsh, flush, str, (int)strlen(str));
|
|
}
|
|
|
|
/**
|
|
* Read raw data from the socket into our buffer in
|
|
* either BLOCKING or NONBLOCKING mode.
|
|
*/
|
|
static void Dpip_dsh_read(Dsh *dsh, int blocking)
|
|
{
|
|
char buf[RBUF_SZ];
|
|
int req_mode, old_flags = 0, st, nb = !blocking;
|
|
|
|
dReturn_if (dsh->status == DPIP_ERROR || dsh->status == DPIP_EOF);
|
|
|
|
req_mode = (nb) ? DPIP_NONBLOCK : 0;
|
|
if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
|
|
/* change mode temporarily... */
|
|
old_flags = fcntl(dsh->fd_in, F_GETFL);
|
|
fcntl(dsh->fd_in, F_SETFL,
|
|
(nb) ? O_NONBLOCK | old_flags : old_flags & ~O_NONBLOCK);
|
|
}
|
|
|
|
while (1) {
|
|
st = read(dsh->fd_in, buf, RBUF_SZ);
|
|
if (st < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
} else if (errno == EAGAIN) {
|
|
dsh->status = DPIP_EAGAIN;
|
|
break;
|
|
} else {
|
|
MSG_ERR("[Dpip_dsh_read] %s\n", dStrerror(errno));
|
|
dsh->status = DPIP_ERROR;
|
|
break;
|
|
}
|
|
} else if (st == 0) {
|
|
dsh->status = DPIP_EOF;
|
|
break;
|
|
} else {
|
|
/* append to buf */
|
|
dStr_append_l(dsh->rdbuf, buf, st);
|
|
if (blocking)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
|
|
/* restore old mode */
|
|
fcntl(dsh->fd_out, F_SETFL, old_flags);
|
|
}
|
|
|
|
/* assert there's no more data in the wire...
|
|
* (st < buf upon interrupt || st == buf and no more data) */
|
|
if (blocking)
|
|
Dpip_dsh_read(dsh, 0);
|
|
}
|
|
|
|
/**
|
|
* Return a newlly allocated string with the next dpip token in the socket.
|
|
* Return value: token string and length on success, NULL otherwise.
|
|
* (useful for handling null characters in the data stream)
|
|
*/
|
|
char *a_Dpip_dsh_read_token2(Dsh *dsh, int blocking, int *DataSize)
|
|
{
|
|
char *p, *ret = NULL;
|
|
*DataSize = 0;
|
|
|
|
/* Read all available data without blocking */
|
|
Dpip_dsh_read(dsh, 0);
|
|
|
|
/* switch mode upon request */
|
|
if (dsh->mode & DPIP_LAST_TAG)
|
|
dsh->mode = DPIP_RAW;
|
|
|
|
if (blocking) {
|
|
if (dsh->mode & DPIP_TAG) {
|
|
/* Only wait for data when the tag is incomplete */
|
|
if (!strstr(dsh->rdbuf->str, DPIP_TAG_END)) {
|
|
do {
|
|
Dpip_dsh_read(dsh, 1);
|
|
p = strstr(dsh->rdbuf->str, DPIP_TAG_END);
|
|
} while (!p && dsh->status == EAGAIN);
|
|
}
|
|
|
|
} else if (dsh->mode & DPIP_RAW) {
|
|
/* Wait for data when the buffer is empty and there's no ERR/EOF */
|
|
while (dsh->rdbuf->len == 0 &&
|
|
dsh->status != DPIP_ERROR && dsh->status != DPIP_EOF)
|
|
Dpip_dsh_read(dsh, 1);
|
|
}
|
|
}
|
|
|
|
if (dsh->mode & DPIP_TAG) {
|
|
/* return a full tag */
|
|
if ((p = strstr(dsh->rdbuf->str, DPIP_TAG_END))) {
|
|
ret = dStrndup(dsh->rdbuf->str, p - dsh->rdbuf->str + 3);
|
|
*DataSize = p - dsh->rdbuf->str + 3;
|
|
dStr_erase(dsh->rdbuf, 0, p - dsh->rdbuf->str + 3);
|
|
if (strstr(ret, DPIP_MODE_SWITCH_TAG))
|
|
dsh->mode |= DPIP_LAST_TAG;
|
|
}
|
|
} else {
|
|
/* raw mode, return what we have "as is" */
|
|
if (dsh->rdbuf->len > 0) {
|
|
ret = dStrndup(dsh->rdbuf->str, dsh->rdbuf->len);
|
|
*DataSize = dsh->rdbuf->len;
|
|
dStr_truncate(dsh->rdbuf, 0);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Return a newlly allocated string with the next dpip token in the socket.
|
|
* Return value: token string on success, NULL otherwise
|
|
*/
|
|
char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking)
|
|
{
|
|
int token_size;
|
|
|
|
return a_Dpip_dsh_read_token2(dsh, blocking, &token_size);
|
|
}
|
|
|
|
/**
|
|
* Close this socket for reading and writing.
|
|
* (flush pending data)
|
|
*/
|
|
void a_Dpip_dsh_close(Dsh *dsh)
|
|
{
|
|
int st;
|
|
|
|
/* flush internal buffer */
|
|
a_Dpip_dsh_write(dsh, 1, "", 0);
|
|
|
|
/* close fds */
|
|
st = dClose(dsh->fd_in);
|
|
if (st < 0)
|
|
MSG_ERR("[a_Dpip_dsh_close] close: %s\n", dStrerror(errno));
|
|
if (dsh->fd_out != dsh->fd_in) {
|
|
st = dClose(dsh->fd_out);
|
|
if (st < 0)
|
|
MSG_ERR("[a_Dpip_dsh_close] close: %s\n", dStrerror(errno));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Free the SockHandler structure
|
|
*/
|
|
void a_Dpip_dsh_free(Dsh *dsh)
|
|
{
|
|
dReturn_if (dsh == NULL);
|
|
|
|
dStr_free(dsh->wrbuf, 1);
|
|
dStr_free(dsh->rdbuf, 1);
|
|
dFree(dsh);
|
|
}
|
|
|