540 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
			
		
		
	
	
			540 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
#include <time.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <string.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <fcntl.h>
 | 
						|
 | 
						|
#ifndef AISL_WITHOUT_SSL
 | 
						|
#include <openssl/err.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#include <aisl/aisl.h>
 | 
						|
#include "debug.h"
 | 
						|
#include "stream.h"
 | 
						|
#include "http.h"
 | 
						|
#include "server.h"
 | 
						|
#include "instance.h"
 | 
						|
#include "client.h"
 | 
						|
 | 
						|
#define FLAG_KEEPALIVE (1<<0)
 | 
						|
#define FLAG_HANDSHAKE (1<<1)
 | 
						|
#define FLAG_CAN_READ  (1<<2)
 | 
						|
#define FLAG_CAN_WRITE (1<<3)
 | 
						|
 | 
						|
#define BUFFER_SIZE (16*1024)
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
aisl_client_close(AislClient client, AislStatus status)
 | 
						|
{
 | 
						|
  if (client->fd != -1)
 | 
						|
  {
 | 
						|
    aisl_raise(
 | 
						|
        client->server->instance
 | 
						|
      , (void *)client
 | 
						|
      , AISL_EVENT_CLIENT_DISCONNECT
 | 
						|
      , status
 | 
						|
    );
 | 
						|
 | 
						|
    close(client->fd);
 | 
						|
    shutdown(client->fd, SHUT_RDWR);
 | 
						|
    client->fd = -1;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static AislStatus
 | 
						|
aisl_client_parse(AislClient client, char * data, int32_t size)
 | 
						|
{
 | 
						|
  AislStatus    result = AISL_SUCCESS;
 | 
						|
  AislStream    s = client->stream;
 | 
						|
  ParserStatus    p = HTTP_PARSER_SUCCESS;
 | 
						|
 | 
						|
 | 
						|
  int32_t bytes_left = size;
 | 
						|
 | 
						|
  switch (client->http_version)
 | 
						|
  {
 | 
						|
    case AISL_HTTP_0_9:
 | 
						|
    case AISL_HTTP_1_0:
 | 
						|
    case AISL_HTTP_1_1:
 | 
						|
 | 
						|
      /* s = client->stream; */
 | 
						|
 | 
						|
      while ( p == HTTP_PARSER_SUCCESS )
 | 
						|
      {
 | 
						|
 | 
						|
        switch ( aisl_stream_get_state(s) )
 | 
						|
        {
 | 
						|
          case AISL_STREAM_STATE_IDLE:
 | 
						|
            p = http_10_parse_request(data, &size, client->stream);
 | 
						|
            break;
 | 
						|
 | 
						|
          case AISL_STREAM_STATE_WAIT_HEADER:
 | 
						|
            p = http_10_parse_header(data, &size, client->stream);
 | 
						|
            break;
 | 
						|
 | 
						|
          case AISL_STREAM_STATE_WAIT_BODY:
 | 
						|
            p = http_10_parse_body(data, &size, client->stream);
 | 
						|
            break;
 | 
						|
 | 
						|
          default: /* has input data, but request was already parsed */
 | 
						|
            p = HTTP_PARSER_ERROR;
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
        // size now has number of parsed bytes
 | 
						|
        data += size;
 | 
						|
        bytes_left -= size;
 | 
						|
        size = bytes_left;
 | 
						|
      }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
      break;
 | 
						|
 | 
						|
    case AISL_HTTP_2_0:
 | 
						|
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  switch(p)
 | 
						|
  {
 | 
						|
    case HTTP_PARSER_READY:
 | 
						|
      client->flags &= ~FLAG_CAN_READ;
 | 
						|
      client->flags |= FLAG_CAN_WRITE;
 | 
						|
 | 
						|
      aisl_raise(
 | 
						|
          client->server->instance
 | 
						|
        , (void *) s
 | 
						|
        , AISL_EVENT_STREAM_REQUEST
 | 
						|
        , result 
 | 
						|
      );
 | 
						|
      break;
 | 
						|
 | 
						|
    case HTTP_PARSER_ERROR:
 | 
						|
      /* reply Bad Request here */
 | 
						|
      client->stream->http_response = AISL_HTTP_BAD_REQUEST;
 | 
						|
 | 
						|
      aisl_raise(
 | 
						|
          client->server->instance
 | 
						|
        , (void *) s
 | 
						|
        , AISL_EVENT_STREAM_ERROR 
 | 
						|
        , result
 | 
						|
      );
 | 
						|
 | 
						|
      aisl_client_close(client, result);
 | 
						|
 | 
						|
      return result;
 | 
						|
 | 
						|
    default:
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  if (size)
 | 
						|
    buffer_shift(&client->in, client->in.used - size); /* reset buffer */
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* In HTTP 2.0 client->stream will be NULL if stream related data was completely
 | 
						|
 * parsed. If it is not NULL, then stream expects additional data -> same like
 | 
						|
 * in mono stream HTTP 1.0 
 | 
						|
 */
 | 
						|
static AislStatus
 | 
						|
aisl_client_input(AislClient client)
 | 
						|
{
 | 
						|
  int                l;
 | 
						|
 | 
						|
  char             * data = &client->in.data[ client->in.used ];
 | 
						|
  int32_t            size = client->in.size - client->in.used;
 | 
						|
 | 
						|
  #ifndef AISL_WITHOUT_SSL
 | 
						|
  if (client->ssl)
 | 
						|
  {
 | 
						|
    DPRINTF("SSL_read");
 | 
						|
    if ( !(client->flags & FLAG_HANDSHAKE) )
 | 
						|
    {
 | 
						|
      if ( (l = SSL_accept(client->ssl)) != 1 )
 | 
						|
      {
 | 
						|
        l = SSL_get_error(client->ssl, l);
 | 
						|
 | 
						|
        if (l == SSL_ERROR_WANT_READ || l == SSL_ERROR_WANT_WRITE)
 | 
						|
          return AISL_IDLE;
 | 
						|
 | 
						|
        DPRINTF("SSL handshake fail: %s\n", ERR_error_string(l, NULL) );
 | 
						|
 | 
						|
        aisl_client_close(client, AISL_EXTCALL_ERROR);
 | 
						|
        return AISL_EXTCALL_ERROR;
 | 
						|
      }
 | 
						|
 | 
						|
      client->flags &= ~FLAG_HANDSHAKE;
 | 
						|
    }
 | 
						|
 | 
						|
    l = SSL_read(client->ssl, data, size) ;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  #endif
 | 
						|
    l = recv( client->fd, data, size, 0);
 | 
						|
 | 
						|
  if (l > 0)
 | 
						|
  {
 | 
						|
    DPRINTF("%d bytes received from client", l);
 | 
						|
 | 
						|
    data = client->in.data;
 | 
						|
    size = client->in.used + l;
 | 
						|
 | 
						|
    client->in.used = size;
 | 
						|
 | 
						|
    return aisl_client_parse(client, data, size);
 | 
						|
  }
 | 
						|
  else if (l<0)
 | 
						|
  {
 | 
						|
    #ifndef AISL_WITHOUT_SSL
 | 
						|
    if (client->ssl)
 | 
						|
    {
 | 
						|
      if (SSL_get_error(client->ssl, l) == SSL_ERROR_WANT_READ)
 | 
						|
        return AISL_IDLE;
 | 
						|
    }
 | 
						|
    else
 | 
						|
    #endif
 | 
						|
    {
 | 
						|
      if(errno == EWOULDBLOCK)
 | 
						|
        return AISL_IDLE;
 | 
						|
 | 
						|
      DPRINTF("client - %s", strerror(errno));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* both: client disconnect + on read error  */
 | 
						|
  /* todo: raise client error here */
 | 
						|
  aisl_client_close(client, AISL_SYSCALL_ERROR);
 | 
						|
 | 
						|
  return AISL_SYSCALL_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static AislStatus 
 | 
						|
aisl_client_output(AislClient client)
 | 
						|
{
 | 
						|
  int l;
 | 
						|
  char * data;
 | 
						|
 | 
						|
  AislStream s = client->stream;
 | 
						|
 | 
						|
  /* while stream is not flushed, we should raise event */
 | 
						|
  if( aisl_get_output_event(s) )
 | 
						|
  {
 | 
						|
    /* in case of chunked output ( subscription for AISL_STREAM_OUTPUT event )
 | 
						|
     * stream buffer will be initialized with OUTPUT_BUFFER_SIZE size, but
 | 
						|
     * buffer->size will be used to carry amount of stored bytes
 | 
						|
     * */
 | 
						|
    l = aisl_stream_get_buffer_space(s);
 | 
						|
 | 
						|
    /*
 | 
						|
    if (bsz < OUTPUT_BUFFER_SIZE)
 | 
						|
    {
 | 
						|
      if (buffer_clear(s->buffer, OUTPUT_BUFFER_SIZE) == 0)
 | 
						|
        return false;
 | 
						|
 | 
						|
      s->buffer->size = bsz;
 | 
						|
      bsz = OUTPUT_BUFFER_SIZE;
 | 
						|
    }
 | 
						|
    */
 | 
						|
 | 
						|
    if ( !(l < aisl_stream_get_buffer_size(s) / 2) )
 | 
						|
    {
 | 
						|
      aisl_raise(
 | 
						|
          client->server->instance
 | 
						|
        , (void*)s
 | 
						|
        , AISL_EVENT_STREAM_OUTPUT
 | 
						|
        , AISL_SUCCESS
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  data = aisl_stream_get_data(s, &l);
 | 
						|
 | 
						|
  if ( !l )
 | 
						|
    return AISL_IDLE;
 | 
						|
 | 
						|
  #ifdef AISL_WITHOUT_SSL
 | 
						|
  l = send( client->fd,  data, l, 0);
 | 
						|
  #else
 | 
						|
  l = (client->ssl) ?
 | 
						|
        SSL_write(client->ssl, data, l) :
 | 
						|
        send(     client->fd,  data, l, 0);
 | 
						|
  #endif
 | 
						|
 | 
						|
  if (l > 0)
 | 
						|
  {
 | 
						|
    aisl_stream_shift(s, l);
 | 
						|
 | 
						|
    /*
 | 
						|
    if (s->state == STREAM_RESPONSE_READY && / * flushed * /
 | 
						|
        s->buffer->size == 0) / * all sent * /
 | 
						|
          */
 | 
						|
    if ( aisl_stream_is_done(s) )
 | 
						|
    {
 | 
						|
      /* buffer_clear(s->buffer, 0); */
 | 
						|
 | 
						|
      /* data has been sent */
 | 
						|
 | 
						|
      if (client->flags & FLAG_KEEPALIVE)
 | 
						|
      {
 | 
						|
        aisl_stream_free(s);
 | 
						|
 | 
						|
        client->stream = aisl_stream_new(client, client->next_id++);
 | 
						|
        if (client->stream != NULL )
 | 
						|
          return AISL_SUCCESS;
 | 
						|
 | 
						|
        /* in case of malloc error it will not be error as long as request was
 | 
						|
         * handled and we just close the connection.
 | 
						|
         */
 | 
						|
      }
 | 
						|
 | 
						|
      aisl_client_close(client, AISL_SUCCESS);
 | 
						|
    }
 | 
						|
 | 
						|
    return AISL_SUCCESS;
 | 
						|
  }
 | 
						|
 | 
						|
  /* l < 0 */
 | 
						|
  #ifndef AISL_WITHOUT_SSL
 | 
						|
  if (client->ssl)
 | 
						|
  {
 | 
						|
    if ( SSL_get_error(client->ssl, l) == SSL_ERROR_WANT_WRITE )
 | 
						|
      return AISL_IDLE;
 | 
						|
  }
 | 
						|
  else
 | 
						|
  #endif
 | 
						|
  {
 | 
						|
    if (errno == EWOULDBLOCK)
 | 
						|
      return AISL_IDLE;
 | 
						|
  }
 | 
						|
 | 
						|
  aisl_client_close(client, AISL_SYSCALL_ERROR);
 | 
						|
 | 
						|
  return AISL_SYSCALL_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
AislClient
 | 
						|
aisl_client_new( AislServer        server,
 | 
						|
                 int                  fd,
 | 
						|
                 struct sockaddr_in * addr )
 | 
						|
{
 | 
						|
  AislClient   client;
 | 
						|
  AislStream   stream;
 | 
						|
 | 
						|
  if ( (client = calloc(1, sizeof (struct aisl_client))) != NULL )
 | 
						|
  {
 | 
						|
    DPRINTF("client alocated");
 | 
						|
    memcpy(&client->address, addr, sizeof (struct sockaddr_in));
 | 
						|
    client->server     = server;
 | 
						|
    client->fd         = fd;
 | 
						|
    client->next_id    = 2;
 | 
						|
    client->http_version   = AISL_HTTP_1_0;
 | 
						|
    client->timestamp  = time(NULL);
 | 
						|
    client->flags      = FLAG_KEEPALIVE | FLAG_HANDSHAKE | FLAG_CAN_READ;
 | 
						|
 | 
						|
    if (buffer_init(&client->in, 2*BUFFER_SIZE) != -1)
 | 
						|
    {
 | 
						|
      DPRINTF("client buffer alocated");
 | 
						|
      memcpy(&client->out, &client->in, sizeof (struct buffer));
 | 
						|
 | 
						|
      stream = aisl_stream_new(client, 0);
 | 
						|
 | 
						|
      if (stream != NULL)
 | 
						|
      {
 | 
						|
        client->stream = stream;
 | 
						|
 | 
						|
        DPRINTF("client stream alocated");
 | 
						|
 | 
						|
        #ifdef AISL_WITHOUT_SSL
 | 
						|
 | 
						|
        return client;
 | 
						|
 | 
						|
        #else
 | 
						|
 | 
						|
        SSL_CTX * ssl_ctx;
 | 
						|
 | 
						|
        if ( !server->ssl )
 | 
						|
          return client;
 | 
						|
 | 
						|
        ssl_ctx = aisl_get_ssl_ctx(server->instance, NULL);
 | 
						|
 | 
						|
        if ((client->ssl = SSL_new(ssl_ctx)) != NULL )
 | 
						|
        {
 | 
						|
          SSL_set_fd(client->ssl, fd);
 | 
						|
          return client;
 | 
						|
        }
 | 
						|
 | 
						|
        #endif
 | 
						|
      }
 | 
						|
    }
 | 
						|
    aisl_client_free(client);
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void
 | 
						|
aisl_client_free(AislClient client)
 | 
						|
{
 | 
						|
  aisl_client_close(client, AISL_SUCCESS);
 | 
						|
 | 
						|
  #ifndef AISL_WITHOUT_SSL
 | 
						|
  if (client->ssl)
 | 
						|
    SSL_free(client->ssl);
 | 
						|
  #endif
 | 
						|
 | 
						|
  if (client->in.data)
 | 
						|
    free(client->in.data);
 | 
						|
 | 
						|
  /* out buffer is a shared part of input buffer, so no need to free it */
 | 
						|
 | 
						|
  if (client->stream)
 | 
						|
    aisl_stream_free(client->stream);
 | 
						|
 | 
						|
  free(client);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
AislStatus
 | 
						|
AislClientouch(AislClient client, int32_t timeout)
 | 
						|
{
 | 
						|
  AislStatus result = AISL_IDLE,
 | 
						|
                status = AISL_IDLE;
 | 
						|
 | 
						|
  /* input */
 | 
						|
  if (client->flags & FLAG_CAN_READ)
 | 
						|
  {
 | 
						|
    if ( (result = aisl_client_input(client)) < 0 )
 | 
						|
      return result;
 | 
						|
  }
 | 
						|
 | 
						|
  /* output */
 | 
						|
  if (client->flags & FLAG_CAN_WRITE)
 | 
						|
  {
 | 
						|
    if ( (status = aisl_client_output(client)) < 0 )
 | 
						|
      return status;
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /*
 | 
						|
  if ((client->http_version==AISL_HTTP_2_0 || s->state<STREAM_REQUEST_READY) &&
 | 
						|
      (client_input(client)) ) result = true;
 | 
						|
  */
 | 
						|
  /* output */
 | 
						|
  /*
 | 
						|
  s = list_index(client->streams, client->ostream);
 | 
						|
 | 
						|
  if ( s->flags & (STREAM_FLAG_OUTPUT_READY | STREAM_FLAG_OUTPUT_CHUNKED) )
 | 
						|
    result = client_output(client);
 | 
						|
  */
 | 
						|
  /* update timestamp */
 | 
						|
 | 
						|
  if (result == AISL_IDLE)
 | 
						|
    result = status;
 | 
						|
 | 
						|
  if (result == AISL_SUCCESS)
 | 
						|
    client->timestamp = time(NULL);
 | 
						|
  else
 | 
						|
  {
 | 
						|
    time_t now;
 | 
						|
    time(&now);
 | 
						|
 | 
						|
    if ( !(now - client->timestamp < timeout) )
 | 
						|
    {
 | 
						|
      aisl_client_close(client, result);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int
 | 
						|
aisl_client_get_socket(AislClient client)
 | 
						|
{
 | 
						|
  return client->fd;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
bool
 | 
						|
aisl_client_get_keepalive(AislClient client)
 | 
						|
{
 | 
						|
  return (client->flags & FLAG_KEEPALIVE);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void
 | 
						|
aisl_client_set_keepalive(AislClient client, bool value)
 | 
						|
{
 | 
						|
  if (value)
 | 
						|
    client->flags |= FLAG_KEEPALIVE;
 | 
						|
  else
 | 
						|
    client->flags &= ~FLAG_KEEPALIVE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* API Level ---------------------------------------------------------------- */
 | 
						|
 | 
						|
__attribute__ ((visibility ("default") ))
 | 
						|
AislServer
 | 
						|
aisl_client_get_server(AislClient client)
 | 
						|
{
 | 
						|
  return client->server;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
__attribute__ ((visibility ("default") ))
 | 
						|
bool
 | 
						|
aisl_client_is_secure(AislClient client)
 | 
						|
{
 | 
						|
  #ifdef AISL_WITHOUT_SSL
 | 
						|
  return false;
 | 
						|
  #else
 | 
						|
  return (client->ssl == NULL) ? false : true;
 | 
						|
  #endif
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
__attribute__ ((visibility ("default") ))
 | 
						|
bool
 | 
						|
aisl_client_is_online(AislClient client)
 | 
						|
{
 | 
						|
  return (client->fd == -1) ? false : true;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
__attribute__ ((visibility ("default") ))
 | 
						|
void
 | 
						|
aisl_client_disconnect(AislClient client)
 | 
						|
{
 | 
						|
  aisl_client_close(client, AISL_SUCCESS);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
__attribute__ ((visibility ("default") ))
 | 
						|
AislHttpVersion
 | 
						|
aisl_client_get_http_version(AislClient client)
 | 
						|
{
 | 
						|
  return client->http_version;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
__attribute__ ((visibility ("default") ))
 | 
						|
void
 | 
						|
aisl_client_get_address( AislClient client, struct sockaddr_in * address)
 | 
						|
{
 | 
						|
  memcpy(address, &client->address, sizeof (struct sockaddr_in));
 | 
						|
}
 |