2016-08-29 20:28:00 -07:00
# define EFL_NET_DIALER_WEBSOCKET_PROTECTED 1
# define EFL_NET_DIALER_PROTECTED 1
# define EFL_NET_SOCKET_PROTECTED 1
# define EFL_IO_READER_PROTECTED 1
# define EFL_IO_WRITER_PROTECTED 1
# define EFL_IO_CLOSER_PROTECTED 1
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include "Ecore.h"
# include "Ecore_Con.h"
# include "ecore_con_private.h"
# include "Emile.h"
/*
* NOTE :
*
* Test this code against Autobahnsuite : http : //autobahn.ws/testsuite/
* See src / examples / ecore / efl_net_dialer_websocket_autobahntestee
*
* Known failed tests :
*
* - Cases 6.4 . x : fail fast .
*
* STATUS : WONTFIX
*
* These are non - strict and requires UTF - 8 to be checked per frame ,
* something we do not do as it ' s left to the user and the user
* only gets full frames . At the end user will fail , but taking
* more data then it should .
*
*
* - Case 6.7 .1 : message is UTF - 8 null byte ( 1 byte = \ x00 ) .
*
* STATUS : WONTFIX
*
* We handle text messages as NULL terminated strings and when
* sending back , we do not include such null terminator in the
* payload . To do so we ' d need to use a cumbersome API that
* specifies the size of the string .
*
*/
/* just to check curl_version_info_data and warn on old versions */
# include "ecore_con_url_curl.h"
# define MY_CLASS EFL_NET_DIALER_WEBSOCKET_CLASS
typedef enum _Efl_Net_Dialer_Websocket_Opcode {
EFL_NET_DIALER_WEBSOCKET_OPCODE_CONTINUATION = 0x0 ,
EFL_NET_DIALER_WEBSOCKET_OPCODE_TEXT = 0x1 ,
EFL_NET_DIALER_WEBSOCKET_OPCODE_BINARY = 0x2 ,
EFL_NET_DIALER_WEBSOCKET_OPCODE_CLOSE = 0x8 ,
EFL_NET_DIALER_WEBSOCKET_OPCODE_PING = 0x9 ,
EFL_NET_DIALER_WEBSOCKET_OPCODE_PONG = 0xa ,
} Efl_Net_Dialer_Websocket_Opcode ;
static inline Eina_Bool
_efl_net_dialer_websocket_opcode_control_check ( Efl_Net_Dialer_Websocket_Opcode opcode )
{
switch ( opcode )
{
case EFL_NET_DIALER_WEBSOCKET_OPCODE_CONTINUATION :
case EFL_NET_DIALER_WEBSOCKET_OPCODE_TEXT :
case EFL_NET_DIALER_WEBSOCKET_OPCODE_BINARY :
return EINA_FALSE ;
default :
return EINA_TRUE ;
}
}
static inline Eina_Bool
_efl_net_dialer_websocket_close_reason_check ( Efl_Net_Dialer_Websocket_Close_Reason r )
{
switch ( r )
{
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_NORMAL :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_GOING_AWAY :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PROTOCOL_ERROR :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_UNEXPECTED_DATA :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_INCONSISTENT_DATA :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_POLICY_VIOLATION :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_TOO_BIG :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_MISSING_EXTENSION :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_SERVER_ERROR :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_IANA_REGISTRY_START :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_IANA_REGISTRY_END :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PRIVATE_START :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PRIVATE_END :
return EINA_TRUE ;
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_NO_REASON :
case EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_ABRUPTLY :
return EINA_FALSE ;
}
if ( ( r > = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_IANA_REGISTRY_START ) & &
( r < = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_IANA_REGISTRY_END ) )
return EINA_TRUE ;
if ( ( r > = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PRIVATE_START ) & &
( r < = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PRIVATE_END ) )
return EINA_TRUE ;
return EINA_FALSE ;
}
/*
* WebSocket is a framed protocol in the format :
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* + - + - + - + - + - - - - - - - + - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | F | R | R | R | opcode | M | Payload len | Extended payload length |
* | I | S | S | S | ( 4 ) | A | ( 7 ) | ( 16 / 64 ) |
* | N | V | V | V | | S | | ( if payload len = = 126 / 127 ) |
* | | 1 | 2 | 3 | | K | | |
* + - + - + - + - + - - - - - - - + - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - +
* | Extended payload length continued , if payload len = = 127 |
* + - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | | Masking - key , if MASK set to 1 |
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | Masking - key ( continued ) | Payload Data |
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* : Payload Data continued . . . :
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | Payload Data continued . . . |
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
*
* See https : //tools.ietf.org/html/rfc6455#section-5.2
*/
typedef struct _Efl_Net_Dialer_Websocket_Frame_Header {
/* first byte: fin + opcode */
uint8_t opcode : 4 ;
uint8_t _reserved : 3 ;
uint8_t fin : 1 ;
/* second byte: mask + payload length */
uint8_t payload_len : 7 ; /* if 126, uses extra 2 bytes (uint16_t)
* if 127 , uses extra 8 bytes ( uint64_t )
* if < = 125 is self - contained
*/
uint8_t mask : 1 ; /* if 1, uses 4 extra bytes */
} Efl_Net_Dialer_Websocket_Frame_Header ;
typedef struct _Efl_Net_Dialer_Websocket_Frame {
EINA_INLIST ;
size_t len ; /* total frame size to send */
Efl_Net_Dialer_Websocket_Frame_Header header ;
} Efl_Net_Dialer_Websocket_Frame ;
typedef struct _Efl_Net_Dialer_Websocket_Pending_Read {
EINA_INLIST ;
size_t len ;
uint8_t * bytes ;
} Efl_Net_Dialer_Websocket_Pending_Read ;
typedef struct _Efl_Net_Dialer_Websocket_Data {
Eo * http ;
2017-08-30 13:24:27 -07:00
Eina_Future * close_timeout ;
Eina_Future * job ;
2016-08-29 20:28:00 -07:00
Eina_Stringshare * address_dial ; /* must rewrite ws->http, wss->https */
Eina_Stringshare * address_remote ; /* must rewrite ws->http, wss->https */
struct {
Eina_List * requested ;
Eina_List * received ;
} protocols ;
char accept_key [ 29 ] ; /* 28 + \0 */
struct {
struct {
uint64_t total_len ;
uint64_t used_len ;
uint8_t * payload ;
Efl_Net_Dialer_Websocket_Opcode opcode ;
Eina_Bool fin ;
} current ;
struct {
uint64_t total_len ;
uint64_t used_len ;
uint8_t * payload ;
Efl_Net_Dialer_Websocket_Opcode opcode ;
} fragmented ;
Efl_Net_Dialer_Websocket_Pending_Read * pending_read ;
size_t pending_read_offset ;
/* for current frame */
uint8_t tmpbuf [ sizeof ( Efl_Net_Dialer_Websocket_Frame_Header ) + sizeof ( uint64_t ) ] ;
uint8_t done ; /* of tmpbuf, for header */
uint8_t needed ; /* of tmpbuf, for header */
} recv ;
struct {
Efl_Net_Dialer_Websocket_Frame * pending ;
size_t offset ;
} send ;
Efl_Net_Dialer_Websocket_Streaming_Mode streaming_mode ;
Eina_Bool connected ;
Eina_Bool close_requested ;
Eina_Bool can_read ;
Eina_Bool can_write ;
} Efl_Net_Dialer_Websocket_Data ;
static void _efl_net_dialer_websocket_job_schedule ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd ) ;
static void
_efl_net_dialer_websocket_send_pending_remove ( Efl_Net_Dialer_Websocket_Data * pd )
{
Efl_Net_Dialer_Websocket_Frame * f = pd - > send . pending ;
Eina_Inlist * lst = EINA_INLIST_GET ( f ) ;
lst = eina_inlist_remove ( lst , lst ) ;
pd - > send . pending = EINA_INLIST_CONTAINER_GET ( lst , Efl_Net_Dialer_Websocket_Frame ) ;
free ( f ) ;
pd - > send . offset = 0 ;
}
static uint8_t *
_efl_net_dialer_websocket_frame_bytes_get ( Efl_Net_Dialer_Websocket_Frame * f )
{
return ( uint8_t * ) & f - > header ;
}
static uint8_t *
_efl_net_dialer_websocket_frame_mask_get ( Efl_Net_Dialer_Websocket_Frame * f )
{
uint8_t * bytes = _efl_net_dialer_websocket_frame_bytes_get ( f )
+ sizeof ( Efl_Net_Dialer_Websocket_Frame_Header ) ;
if ( f - > header . payload_len = = 127 )
return bytes + sizeof ( uint64_t ) ;
else if ( f - > header . payload_len = = 126 )
return bytes + sizeof ( uint16_t ) ;
else
return bytes ;
}
static uint8_t *
_efl_net_dialer_websocket_frame_payload_get ( Efl_Net_Dialer_Websocket_Frame * f )
{
return _efl_net_dialer_websocket_frame_mask_get ( f ) + 4 ;
}
static Efl_Net_Dialer_Websocket_Frame *
_efl_net_dialer_websocket_send_pending_add ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Efl_Net_Dialer_Websocket_Opcode opcode , size_t payload_len )
{
uint8_t * bytes ;
size_t len = payload_len + 4 /* sizeof mask */ ;
Efl_Net_Dialer_Websocket_Frame * f ;
Eina_Inlist * lst ;
int i ;
if ( payload_len > UINT16_MAX ) len + = sizeof ( uint64_t ) ;
else if ( payload_len > 125 ) len + = sizeof ( uint16_t ) ;
f = malloc ( sizeof ( Efl_Net_Dialer_Websocket_Frame ) + len ) ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( f , NULL ) ;
f - > len = sizeof ( Efl_Net_Dialer_Websocket_Frame_Header ) + len ;
f - > header . opcode = opcode ;
f - > header . fin = 1 ;
f - > header . mask = 1 ;
f - > header . payload_len = ( ( payload_len > UINT16_MAX ) ? 127 :
( ( payload_len > 125 ) ? 126 : payload_len ) ) ;
bytes = _efl_net_dialer_websocket_frame_bytes_get ( f )
+ sizeof ( Efl_Net_Dialer_Websocket_Frame_Header ) ;
if ( f - > header . payload_len = = 127 )
{
uint64_t plen = payload_len ;
# ifndef WORDS_BIGENDIAN
plen = eina_swap64 ( plen ) ;
# endif
memcpy ( bytes , & plen , sizeof ( plen ) ) ;
bytes + = sizeof ( plen ) ;
}
else if ( f - > header . payload_len = = 126 )
{
uint16_t plen = payload_len ;
# ifndef WORDS_BIGENDIAN
plen = eina_swap16 ( plen ) ;
# endif
memcpy ( bytes , & plen , sizeof ( plen ) ) ;
bytes + = sizeof ( plen ) ;
}
for ( i = 0 ; i < 4 ; i + + , bytes + + )
* bytes = rand ( ) & 0xff ;
lst = EINA_INLIST_GET ( pd - > send . pending ) ;
lst = eina_inlist_append ( lst , EINA_INLIST_GET ( f ) ) ;
pd - > send . pending = EINA_INLIST_CONTAINER_GET ( lst , Efl_Net_Dialer_Websocket_Frame ) ;
DBG ( " opcode=%#x, payload_len=%zd, f=%p len=%zd " , opcode , payload_len , f , f - > len ) ;
/* say we can't write to throttle writers until we actually write */
efl_io_writer_can_write_set ( o , EINA_FALSE ) ;
if ( efl_io_writer_can_write_get ( pd - > http ) )
_efl_net_dialer_websocket_job_schedule ( o , pd ) ;
return f ;
}
static void
_efl_net_dialer_websocket_frame_write ( Efl_Net_Dialer_Websocket_Frame * f , size_t offset , const void * mem , size_t len )
{
uint8_t * mask = _efl_net_dialer_websocket_frame_mask_get ( f ) ;
uint8_t * payload = _efl_net_dialer_websocket_frame_payload_get ( f ) ;
const uint8_t * input = mem ;
uint8_t * o ;
for ( o = payload + offset ; o < payload + offset + len ; o + + , input + + )
* o = * input ^ mask [ ( o - payload ) & 0x3 ] ;
}
static void
_efl_net_dialer_websocket_send ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Efl_Net_Dialer_Websocket_Opcode opcode , const void * mem , size_t len )
{
Efl_Net_Dialer_Websocket_Frame * f ;
f = _efl_net_dialer_websocket_send_pending_add ( o , pd , opcode , len ) ;
EINA_SAFETY_ON_NULL_RETURN ( f ) ;
_efl_net_dialer_websocket_frame_write ( f , 0 , mem , len ) ;
}
static void
_efl_net_dialer_websocket_job_send ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
Eina_Slice slice = {
. bytes = _efl_net_dialer_websocket_frame_bytes_get ( pd - > send . pending ) + pd - > send . offset ,
. len = pd - > send . pending - > len - pd - > send . offset ,
} ;
Eina_Error err = efl_io_writer_write ( pd - > http , & slice , NULL ) ;
pd - > send . offset + = slice . len ;
if ( pd - > send . offset = = pd - > send . pending - > len )
{
_efl_net_dialer_websocket_send_pending_remove ( pd ) ;
if ( ! pd - > close_requested )
efl_io_writer_can_write_set ( o , EINA_TRUE ) ;
}
if ( ( err ) & & ( err ! = EAGAIN ) )
{
ERR ( " could not write to HTTP socket #%d '%s' " , err , eina_error_msg_get ( err ) ) ;
2019-03-08 07:47:32 -08:00
efl_event_callback_call ( o , EFL_NET_DIALER_EVENT_DIALER_ERROR , & err ) ;
2016-08-29 20:28:00 -07:00
}
}
static void
_efl_net_dialer_websocket_recv_frame_steal_and_queue ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , uint8_t * * p_payload , size_t len )
{
Efl_Net_Dialer_Websocket_Pending_Read * pr ;
Eina_Inlist * lst ;
if ( len = = 0 ) return ;
pr = malloc ( sizeof ( Efl_Net_Dialer_Websocket_Pending_Read ) ) ;
EINA_SAFETY_ON_NULL_RETURN ( pr ) ;
pr - > len = len ;
pr - > bytes = * p_payload ;
lst = EINA_INLIST_GET ( pd - > recv . pending_read ) ;
lst = eina_inlist_append ( lst , EINA_INLIST_GET ( pr ) ) ;
pd - > recv . pending_read = EINA_INLIST_CONTAINER_GET ( lst , Efl_Net_Dialer_Websocket_Pending_Read ) ;
* p_payload = NULL ;
efl_io_reader_can_read_set ( o , EINA_TRUE ) ;
}
# define _efl_net_dialer_websocket_close_protocol_error(o, message) \
do \
{ \
if ( ! efl_io_closer_closed_get ( o ) ) \
{ \
WRN ( " dialer=%p closing with PROTOCOL ERROR (%d) " , o , EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PROTOCOL_ERROR ) ; \
efl_net_dialer_websocket_close_request ( o , EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PROTOCOL_ERROR , message ) ; \
} \
} \
while ( 0 )
static Eina_Bool
_efl_net_dialer_websocket_job_dispatch_frame_validate ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
if ( ( pd - > close_requested ) & & ( pd - > recv . current . opcode ! = EFL_NET_DIALER_WEBSOCKET_OPCODE_CLOSE ) )
{
DBG ( " dialer=%p dropped frame opcode=%#x: close requested " , o , pd - > recv . current . opcode ) ;
return EINA_FALSE ;
}
if ( ( ! pd - > recv . current . fin ) & &
_efl_net_dialer_websocket_opcode_control_check ( pd - > recv . current . opcode ) )
WRN ( " dialer=%p server sent forbidden fragmented control frame opcode=%#x. " ,
o , pd - > recv . current . opcode ) ;
else if ( ( pd - > recv . current . opcode = = EFL_NET_DIALER_WEBSOCKET_OPCODE_CONTINUATION ) & &
( pd - > recv . fragmented . opcode = = 0 ) )
WRN ( " dialer=%p server sent continuation frame after non-fragmentable frame " , o ) ;
else
return EINA_TRUE ;
_efl_net_dialer_websocket_close_protocol_error ( o , NULL ) ;
return EINA_FALSE ;
}
static void
_efl_net_dialer_websocket_job_dispatch_frame_text ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , uint8_t * * p_payload , size_t len )
{
char * text = ( char * ) * p_payload ;
/* len == 0 may contain an old payload to be reused with realloc() */
if ( len = = 0 ) text = " " ;
efl_event_callback_call ( o , EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_TEXT , text ) ;
if ( pd - > streaming_mode = = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_TEXT )
_efl_net_dialer_websocket_recv_frame_steal_and_queue ( o , pd , p_payload , len ) ;
}
static void
_efl_net_dialer_websocket_job_dispatch_frame_binary ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , uint8_t * * p_payload , size_t len )
{
Eina_Slice slice = {
. bytes = * p_payload ,
. len = len ,
} ;
efl_event_callback_call ( o , EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_BINARY , & slice ) ;
if ( pd - > streaming_mode = = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_BINARY )
_efl_net_dialer_websocket_recv_frame_steal_and_queue ( o , pd , p_payload , len ) ;
}
static void
_efl_net_dialer_websocket_job_dispatch_frame ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
DBG ( " frame opcode=%#x fin=%d, length=% " PRIu64 ,
pd - > recv . current . opcode , pd - > recv . current . fin , pd - > recv . current . used_len ) ;
if ( ! _efl_net_dialer_websocket_job_dispatch_frame_validate ( o , pd ) )
return ;
switch ( pd - > recv . current . opcode )
{
case EFL_NET_DIALER_WEBSOCKET_OPCODE_CONTINUATION :
if ( pd - > recv . current . fin )
{
if ( pd - > recv . fragmented . opcode = = EFL_NET_DIALER_WEBSOCKET_OPCODE_TEXT )
_efl_net_dialer_websocket_job_dispatch_frame_text ( o , pd , & pd - > recv . current . payload , pd - > recv . current . used_len ) ;
else if ( pd - > recv . fragmented . opcode = = EFL_NET_DIALER_WEBSOCKET_OPCODE_BINARY )
_efl_net_dialer_websocket_job_dispatch_frame_binary ( o , pd , & pd - > recv . current . payload , pd - > recv . current . used_len ) ;
memset ( & pd - > recv . fragmented , 0 , sizeof ( pd - > recv . fragmented ) ) ;
}
else
{
pd - > recv . fragmented . payload = pd - > recv . current . payload ;
pd - > recv . fragmented . used_len = pd - > recv . current . used_len ;
pd - > recv . fragmented . total_len = pd - > recv . current . total_len ;
pd - > recv . current . payload = NULL ;
pd - > recv . current . used_len = 0 ;
pd - > recv . current . total_len = 0 ;
}
break ;
case EFL_NET_DIALER_WEBSOCKET_OPCODE_TEXT :
if ( pd - > recv . current . fin )
_efl_net_dialer_websocket_job_dispatch_frame_text ( o , pd , & pd - > recv . current . payload , pd - > recv . current . used_len ) ;
else
{
pd - > recv . fragmented . payload = pd - > recv . current . payload ;
pd - > recv . fragmented . used_len = pd - > recv . current . used_len ;
pd - > recv . fragmented . total_len = pd - > recv . current . total_len ;
pd - > recv . fragmented . opcode = pd - > recv . current . opcode ;
pd - > recv . current . payload = NULL ;
pd - > recv . current . used_len = 0 ;
pd - > recv . current . total_len = 0 ;
pd - > recv . current . opcode = 0 ;
pd - > recv . current . fin = 0 ;
}
break ;
case EFL_NET_DIALER_WEBSOCKET_OPCODE_BINARY :
if ( pd - > recv . current . fin )
_efl_net_dialer_websocket_job_dispatch_frame_binary ( o , pd , & pd - > recv . current . payload , pd - > recv . current . used_len ) ;
else
{
pd - > recv . fragmented . payload = pd - > recv . current . payload ;
pd - > recv . fragmented . used_len = pd - > recv . current . used_len ;
pd - > recv . fragmented . total_len = pd - > recv . current . total_len ;
pd - > recv . fragmented . opcode = pd - > recv . current . opcode ;
pd - > recv . current . payload = NULL ;
pd - > recv . current . used_len = 0 ;
pd - > recv . current . total_len = 0 ;
pd - > recv . current . opcode = 0 ;
pd - > recv . current . fin = 0 ;
}
break ;
case EFL_NET_DIALER_WEBSOCKET_OPCODE_CLOSE :
{
Efl_Net_Dialer_Websocket_Closed_Reason reason = {
. reason = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_NO_REASON ,
. message = " " ,
} ;
if ( pd - > recv . current . used_len > = sizeof ( uint16_t ) )
{
uint16_t r ;
memcpy ( & r , pd - > recv . current . payload , sizeof ( uint16_t ) ) ;
# ifndef WORDS_BIGENDIAN
r = eina_swap16 ( r ) ;
# endif
if ( ! _efl_net_dialer_websocket_close_reason_check ( r ) )
{
WRN ( " dialer=%p invalid CLOSE reason: %hu " , o , r ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " invalid close reason " ) ;
r = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PROTOCOL_ERROR ;
}
reason . reason = r ;
reason . message = ( const char * ) pd - > recv . current . payload + sizeof ( uint16_t ) ;
}
else if ( ( pd - > recv . current . used_len > 0 ) & &
( pd - > recv . current . used_len < sizeof ( uint16_t ) ) )
{
WRN ( " dialer=%p invalid CLOSE payload length: % " PRIu64 , o , pd - > recv . current . used_len ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " invalid close payload length " ) ;
}
efl_event_callback_call ( o , EFL_NET_DIALER_WEBSOCKET_EVENT_CLOSED_REASON , & reason ) ;
if ( ( ! pd - > recv . pending_read ) & &
( pd - > streaming_mode ! = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_DISABLED ) )
efl_event_callback_call ( o , EFL_IO_READER_EVENT_EOS , NULL ) ;
if ( ! pd - > close_requested )
efl_io_closer_close ( o ) ;
else
efl_event_callback_call ( o , EFL_IO_CLOSER_EVENT_CLOSED , NULL ) ;
2016-09-13 12:08:25 -07:00
if ( pd - > close_timeout )
2017-08-30 13:24:27 -07:00
eina_future_cancel ( pd - > close_timeout ) ;
2016-08-29 20:28:00 -07:00
break ;
}
case EFL_NET_DIALER_WEBSOCKET_OPCODE_PING :
DBG ( " got PING from server '%.*s', echo as PONG. " , ( int ) pd - > recv . current . used_len , pd - > recv . current . payload ) ;
_efl_net_dialer_websocket_send ( o , pd , EFL_NET_DIALER_WEBSOCKET_OPCODE_PONG , pd - > recv . current . payload , pd - > recv . current . used_len ) ;
break ;
case EFL_NET_DIALER_WEBSOCKET_OPCODE_PONG :
{
char * text = ( char * ) pd - > recv . current . payload ;
if ( pd - > recv . current . used_len = = 0 ) text = " " ;
efl_event_callback_call ( o , EFL_NET_DIALER_WEBSOCKET_EVENT_PONG , text ) ;
break ;
}
default :
WRN ( " dialer=%p unexpected WebSocket opcode: %#x. " , o , pd - > recv . current . opcode ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " unexpected opcode " ) ;
}
}
static void
_efl_net_dialer_websocket_job_receive ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
const uint8_t fh_len = sizeof ( Efl_Net_Dialer_Websocket_Frame_Header ) ;
Eina_Error err ;
while ( ( pd - > recv . done < pd - > recv . needed ) & &
( efl_io_reader_can_read_get ( pd - > http ) ) )
{
uint64_t frame_len ;
Eina_Rw_Slice rw_slice = {
. bytes = pd - > recv . tmpbuf + pd - > recv . done ,
. len = pd - > recv . needed - pd - > recv . done ,
} ;
err = efl_io_reader_read ( pd - > http , & rw_slice ) ;
EINA_SAFETY_ON_TRUE_GOTO ( err ! = 0 , error ) ;
pd - > recv . done + = rw_slice . len ;
if ( pd - > recv . done ! = pd - > recv . needed )
continue ;
if ( pd - > recv . needed = = fh_len )
{
Efl_Net_Dialer_Websocket_Frame_Header fh ;
memcpy ( & fh , pd - > recv . tmpbuf , pd - > recv . needed ) ;
pd - > recv . current . opcode = fh . opcode ;
pd - > recv . current . fin = fh . fin ;
if ( fh . _reserved )
{
WRN ( " dialer=%p server sent reserved bits %#x, opcode=%#x, but no extension was negotiated. " ,
o , fh . _reserved , fh . opcode ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " reserved bits not negotiated " ) ;
}
if ( fh . mask )
{
WRN ( " dialer=%p server masked frame opcode=%#x. " , o , fh . opcode ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " server sent masked frame " ) ;
}
if ( fh . payload_len = = 127 )
{
if ( _efl_net_dialer_websocket_opcode_control_check ( fh . opcode ) )
{
WRN ( " dialer=%p server sent forbidden 64-bit control frame %#x. " , o , fh . opcode ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " control frames are limited to 125 bytes " ) ;
}
pd - > recv . needed + = sizeof ( uint64_t ) ;
continue ;
}
else if ( fh . payload_len = = 126 )
{
if ( _efl_net_dialer_websocket_opcode_control_check ( fh . opcode ) )
{
WRN ( " dialer=%p server sent forbidden 16-bit control frame %#x. " , o , fh . opcode ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " control frames are limited to 125 bytes " ) ;
}
pd - > recv . needed + = sizeof ( uint16_t ) ;
continue ;
}
else
frame_len = fh . payload_len ;
}
else if ( pd - > recv . needed = = fh_len + sizeof ( uint64_t ) )
{
uint64_t len ;
memcpy ( & len , pd - > recv . tmpbuf + fh_len , sizeof ( len ) ) ;
# ifndef WORDS_BIGENDIAN
len = eina_swap64 ( len ) ;
# endif
frame_len = len ;
}
else if ( pd - > recv . needed = = fh_len + sizeof ( uint16_t ) )
{
uint16_t len ;
memcpy ( & len , pd - > recv . tmpbuf + fh_len , sizeof ( len ) ) ;
# ifndef WORDS_BIGENDIAN
len = eina_swap16 ( len ) ;
# endif
frame_len = len ;
}
else
{
CRI ( " shouldn't reach done=%u, needed=%u " , pd - > recv . done , pd - > recv . needed ) ;
frame_len = 0 ;
}
if ( pd - > recv . current . opcode = = EFL_NET_DIALER_WEBSOCKET_OPCODE_CONTINUATION )
{
if ( pd - > recv . fragmented . opcode = = 0 )
{
WRN ( " dialer=%p server sent continuation frame after non-fragmentable frame " , o ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " nothing to continue " ) ;
}
if ( pd - > recv . current . payload )
free ( pd - > recv . current . payload ) ;
pd - > recv . current . payload = pd - > recv . fragmented . payload ;
pd - > recv . current . used_len = pd - > recv . fragmented . used_len ;
pd - > recv . current . total_len = pd - > recv . fragmented . total_len ;
pd - > recv . fragmented . payload = NULL ;
pd - > recv . fragmented . used_len = 0 ;
pd - > recv . fragmented . total_len = 0 ;
}
else if ( ( ! _efl_net_dialer_websocket_opcode_control_check ( pd - > recv . current . opcode ) ) & &
( pd - > recv . fragmented . opcode ! = 0 ) )
{
WRN ( " dialer=%p server sent non-control frame opcode=%#x while continuation was expected " , o , pd - > recv . current . opcode ) ;
_efl_net_dialer_websocket_close_protocol_error ( o , " expected continuation or control frames " ) ;
}
if ( frame_len > 0 )
{
void * tmp = realloc ( pd - > recv . current . payload , pd - > recv . current . total_len + frame_len + 1 ) ;
err = errno ;
EINA_SAFETY_ON_NULL_GOTO ( tmp , error ) ;
pd - > recv . current . payload = tmp ;
pd - > recv . current . total_len + = frame_len ;
}
}
if ( ( ! efl_io_reader_can_read_get ( pd - > http ) ) & &
( pd - > recv . done < pd - > recv . needed ) )
return ; /* more header to do */
while ( ( pd - > recv . current . used_len < pd - > recv . current . total_len ) & &
( efl_io_reader_can_read_get ( pd - > http ) ) )
{
Eina_Rw_Slice rw_slice = {
. bytes = pd - > recv . current . payload + pd - > recv . current . used_len ,
. len = pd - > recv . current . total_len - pd - > recv . current . used_len ,
} ;
err = efl_io_reader_read ( pd - > http , & rw_slice ) ;
EINA_SAFETY_ON_TRUE_GOTO ( err ! = 0 , error ) ;
pd - > recv . current . used_len + = rw_slice . len ;
}
if ( pd - > recv . current . total_len > 0 )
pd - > recv . current . payload [ pd - > recv . current . used_len ] = ' \0 ' ;
if ( ( ! efl_io_reader_can_read_get ( pd - > http ) ) & &
( pd - > recv . current . used_len < pd - > recv . current . total_len ) )
return ; /* more payload to do */
_efl_net_dialer_websocket_job_dispatch_frame ( o , pd ) ;
pd - > recv . done = 0 ;
pd - > recv . needed = sizeof ( Efl_Net_Dialer_Websocket_Frame_Header ) ;
/* no need to free payload here, let realloc() reuse it */
pd - > recv . current . used_len = 0 ;
pd - > recv . current . total_len = 0 ;
return ;
error :
ERR ( " could not receive data header=%u/%u, payload=% " PRIu64 " /% " PRIu64 " : %s " ,
pd - > recv . done , pd - > recv . needed ,
pd - > recv . current . used_len , pd - > recv . current . total_len ,
eina_error_msg_get ( err ) ) ;
2016-09-12 09:44:35 -07:00
efl_ref ( o ) ;
efl_io_closer_close ( pd - > http ) ;
2019-03-08 07:47:32 -08:00
efl_event_callback_call ( o , EFL_NET_DIALER_EVENT_DIALER_ERROR , & err ) ;
2016-09-12 09:44:35 -07:00
efl_unref ( o ) ;
2016-08-29 20:28:00 -07:00
}
2017-08-30 13:24:27 -07:00
static Eina_Value
2018-12-07 03:15:16 -08:00
_efl_net_dialer_websocket_job ( Eo * o , void * data EINA_UNUSED , const Eina_Value v )
2016-08-29 20:28:00 -07:00
{
Efl_Net_Dialer_Websocket_Data * pd = efl_data_scope_get ( o , MY_CLASS ) ;
efl_ref ( o ) ;
if ( efl_io_reader_can_read_get ( pd - > http ) )
_efl_net_dialer_websocket_job_receive ( o , pd ) ;
if ( efl_io_writer_can_write_get ( pd - > http ) & & pd - > send . pending )
_efl_net_dialer_websocket_job_send ( o , pd ) ;
if ( efl_io_reader_can_read_get ( pd - > http ) )
_efl_net_dialer_websocket_job_schedule ( o , pd ) ;
efl_unref ( o ) ;
2017-08-30 13:24:27 -07:00
return v ;
2016-08-29 20:28:00 -07:00
}
static void
_efl_net_dialer_websocket_job_schedule ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
Eo * loop ;
if ( pd - > job ) return ;
2016-10-05 18:19:25 -07:00
loop = efl_loop_get ( o ) ;
2016-08-29 20:28:00 -07:00
if ( ! loop ) return ;
2017-08-30 13:24:27 -07:00
2018-11-21 17:33:10 -08:00
efl_future_then ( o , efl_loop_job ( loop ) ,
2018-12-07 03:15:16 -08:00
. success = _efl_net_dialer_websocket_job ,
. storage = & pd - > job ) ;
2016-08-29 20:28:00 -07:00
}
static void
_efl_net_dialer_websocket_http_can_read_changed ( void * data , const Efl_Event * event EINA_UNUSED )
{
Eo * o = data ;
Efl_Net_Dialer_Websocket_Data * pd = efl_data_scope_get ( o , MY_CLASS ) ;
if ( efl_io_reader_can_read_get ( pd - > http ) )
_efl_net_dialer_websocket_job_schedule ( o , pd ) ;
}
static void
_efl_net_dialer_websocket_http_can_write_changed ( void * data , const Efl_Event * event EINA_UNUSED )
{
Eo * o = data ;
Efl_Net_Dialer_Websocket_Data * pd = efl_data_scope_get ( o , MY_CLASS ) ;
if ( efl_io_writer_can_write_get ( pd - > http ) )
_efl_net_dialer_websocket_job_schedule ( o , pd ) ;
}
static void
_efl_net_dialer_websocket_http_closed ( void * data , const Efl_Event * event EINA_UNUSED )
{
Eo * o = data ;
Efl_Net_Dialer_Websocket_Data * pd = efl_data_scope_get ( o , MY_CLASS ) ;
2016-09-12 09:44:35 -07:00
efl_ref ( o ) ;
2016-08-29 20:28:00 -07:00
if ( ! pd - > close_requested )
{
Efl_Net_Dialer_Websocket_Closed_Reason reason = {
. reason = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_ABRUPTLY ,
. message = " " ,
} ;
pd - > close_requested = EINA_TRUE ;
efl_event_callback_call ( o , EFL_NET_DIALER_WEBSOCKET_EVENT_CLOSED_REASON , & reason ) ;
}
2016-09-12 09:44:35 -07:00
if ( ( ! pd - > recv . pending_read ) & &
( pd - > streaming_mode ! = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_DISABLED ) )
efl_event_callback_call ( o , EFL_IO_READER_EVENT_EOS , NULL ) ;
2016-08-29 20:28:00 -07:00
efl_event_callback_call ( o , EFL_IO_CLOSER_EVENT_CLOSED , NULL ) ;
2016-09-12 09:44:35 -07:00
efl_unref ( o ) ;
2016-08-29 20:28:00 -07:00
}
static void
_efl_net_dialer_websocket_http_error ( void * data , const Efl_Event * event )
{
Eo * o = data ;
Efl_Net_Dialer_Websocket_Data * pd = efl_data_scope_get ( o , MY_CLASS ) ;
Eina_Error * perr = event - > info ;
if ( ( pd - > close_requested ) & & ( * perr = = EFL_NET_HTTP_ERROR_RECV_ERROR ) )
return ;
2016-09-12 09:44:35 -07:00
efl_ref ( o ) ;
if ( ! efl_io_closer_closed_get ( o ) ) efl_io_closer_close ( o ) ;
2019-03-08 07:47:32 -08:00
efl_event_callback_call ( o , EFL_NET_DIALER_EVENT_DIALER_ERROR , perr ) ;
2016-09-12 09:44:35 -07:00
efl_unref ( o ) ;
2016-08-29 20:28:00 -07:00
}
static void
_efl_net_dialer_websocket_http_headers_done ( void * data , const Efl_Event * event EINA_UNUSED )
{
Eo * o = data ;
Efl_Net_Dialer_Websocket_Data * pd = efl_data_scope_get ( o , MY_CLASS ) ;
Eina_Bool accepted = EINA_FALSE ;
Eina_Bool upgraded = EINA_FALSE ;
Eina_Bool connection_websocket = EINA_FALSE ;
Efl_Net_Http_Status status ;
const Efl_Net_Http_Header * header ;
Eina_Stringshare * str ;
Eina_Iterator * itr ;
status = efl_net_dialer_http_response_status_get ( pd - > http ) ;
if ( status ! = EFL_NET_HTTP_STATUS_SWITCHING_PROTOCOLS )
{
2016-09-09 16:09:51 -07:00
Eina_Error err = EFL_NET_DIALER_ERROR_COULDNT_CONNECT ;
2016-08-29 20:28:00 -07:00
if ( ( status > = 300 ) & & ( status < 400 ) )
return ;
WRN ( " failed WebSocket handshake: HTTP status=%d, expected=%d " ,
status , EFL_NET_HTTP_STATUS_SWITCHING_PROTOCOLS ) ;
2016-09-12 09:44:35 -07:00
efl_ref ( o ) ;
efl_io_closer_close ( pd - > http ) ;
2019-03-08 07:47:32 -08:00
efl_event_callback_call ( o , EFL_NET_DIALER_EVENT_DIALER_ERROR , & err ) ;
2016-09-12 09:44:35 -07:00
efl_unref ( o ) ;
2016-08-29 20:28:00 -07:00
return ;
}
EINA_LIST_FREE ( pd - > protocols . received , str )
eina_stringshare_del ( str ) ;
itr = efl_net_dialer_http_response_headers_get ( pd - > http ) ;
EINA_ITERATOR_FOREACH ( itr , header )
{
if ( ! header - > key ) continue ;
if ( strcasecmp ( header - > key , " Connection " ) = = 0 )
upgraded = strcasecmp ( header - > value , " upgrade " ) = = 0 ;
else if ( strcasecmp ( header - > key , " Upgrade " ) = = 0 )
connection_websocket = strcasecmp ( header - > value , " websocket " ) = = 0 ;
else if ( strcasecmp ( header - > key , " Sec-WebSocket-Accept " ) = = 0 )
accepted = strcmp ( header - > value , pd - > accept_key ) = = 0 ; /* case matters */
else if ( strcasecmp ( header - > key , " Sec-WebSocket-Protocol " ) = = 0 )
{
char * * strv = eina_str_split ( header - > value , " , " , 0 ) ;
if ( strv )
{
int i ;
for ( i = 0 ; strv [ i ] ! = NULL ; i + + )
pd - > protocols . received = eina_list_append ( pd - > protocols . received , eina_stringshare_add ( strv [ i ] ) ) ;
free ( strv [ 0 ] ) ;
free ( strv ) ;
}
}
}
eina_iterator_free ( itr ) ;
efl_net_dialer_http_response_headers_clear ( pd - > http ) ;
if ( ( ! upgraded ) | | ( ! connection_websocket ) | | ( ! accepted ) )
{
2016-09-09 16:09:51 -07:00
Eina_Error err = EFL_NET_DIALER_ERROR_COULDNT_CONNECT ;
2016-08-29 20:28:00 -07:00
WRN ( " failed WebSocket handshake: upgraded=%d, connection_websocket=%d, accepted=%d " ,
upgraded , connection_websocket , accepted ) ;
2016-09-12 09:44:35 -07:00
efl_ref ( o ) ;
efl_io_closer_close ( pd - > http ) ;
2019-03-08 07:47:32 -08:00
efl_event_callback_call ( o , EFL_NET_DIALER_EVENT_DIALER_ERROR , & err ) ;
2016-09-12 09:44:35 -07:00
efl_unref ( o ) ;
2016-08-29 20:28:00 -07:00
return ;
}
str = efl_net_socket_address_remote_get ( pd - > http ) ;
if ( str )
{
char * tmp = NULL ;
/* if ws:// or wss:// were used, rewrite-back */
if ( strncasecmp ( pd - > address_dial , " ws:// " , strlen ( " ws:// " ) ) = = 0 )
{
tmp = malloc ( strlen ( str ) + strlen ( " ws:// " ) - strlen ( " http:// " ) + 1 ) ;
if ( tmp )
{
memcpy ( tmp , " ws:// " , strlen ( " ws:// " ) ) ;
memcpy ( tmp + strlen ( " ws:// " ) ,
str + strlen ( " http:// " ) ,
strlen ( str ) - strlen ( " http:// " ) + 1 ) ;
str = tmp ;
}
}
else if ( strncasecmp ( pd - > address_dial , " wss:// " , strlen ( " wss:// " ) ) = = 0 )
{
tmp = malloc ( strlen ( str ) + strlen ( " wss:// " ) - strlen ( " https:// " ) + 1 ) ;
if ( tmp )
{
memcpy ( tmp , " wss:// " , strlen ( " wss:// " ) ) ;
memcpy ( tmp + strlen ( " wss:// " ) ,
str + strlen ( " https:// " ) ,
strlen ( str ) - strlen ( " https:// " ) + 1 ) ;
str = tmp ;
}
}
efl_net_socket_address_remote_set ( o , str ) ;
free ( tmp ) ;
}
efl_net_dialer_connected_set ( o , EINA_TRUE ) ;
efl_io_writer_can_write_set ( o , EINA_TRUE ) ;
}
EFL_CALLBACKS_ARRAY_DEFINE ( _efl_net_dialer_websocket_http_cbs ,
{ EFL_IO_READER_EVENT_CAN_READ_CHANGED , _efl_net_dialer_websocket_http_can_read_changed } ,
{ EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED , _efl_net_dialer_websocket_http_can_write_changed } ,
{ EFL_IO_CLOSER_EVENT_CLOSED , _efl_net_dialer_websocket_http_closed } ,
2019-03-08 07:47:32 -08:00
{ EFL_NET_DIALER_EVENT_DIALER_ERROR , _efl_net_dialer_websocket_http_error } ,
2016-08-29 20:28:00 -07:00
{ EFL_NET_DIALER_HTTP_EVENT_HEADERS_DONE , _efl_net_dialer_websocket_http_headers_done } ) ;
EOLIAN static Efl_Object *
_efl_net_dialer_websocket_efl_object_constructor ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
pd - > recv . needed = sizeof ( Efl_Net_Dialer_Websocket_Frame_Header ) ;
o = efl_constructor ( efl_super ( o , MY_CLASS ) ) ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( o , NULL ) ;
pd - > http = efl_add ( EFL_NET_DIALER_HTTP_CLASS , o ,
2016-09-05 08:57:35 -07:00
efl_net_dialer_http_version_set ( efl_added , EFL_NET_HTTP_VERSION_V1_1 ) ,
efl_net_dialer_http_method_set ( efl_added , " GET " ) ,
efl_net_dialer_http_primary_mode_set ( efl_added , EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD ) ,
efl_event_callback_array_add ( efl_added , _efl_net_dialer_websocket_http_cbs ( ) , o ) ) ;
2016-08-29 20:28:00 -07:00
EINA_SAFETY_ON_NULL_RETURN_VAL ( pd - > http , NULL ) ;
return o ;
}
2018-04-09 17:01:21 -07:00
EOLIAN static void
_efl_net_dialer_websocket_efl_object_invalidate ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
pd - > http = NULL ;
efl_invalidate ( efl_super ( o , MY_CLASS ) ) ;
}
2016-08-29 20:28:00 -07:00
EOLIAN static void
_efl_net_dialer_websocket_efl_object_destructor ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
Eina_Stringshare * str ;
efl_event_callback_array_del ( pd - > http , _efl_net_dialer_websocket_http_cbs ( ) , o ) ;
2016-09-16 18:21:11 -07:00
2016-08-29 20:28:00 -07:00
efl_destructor ( efl_super ( o , MY_CLASS ) ) ;
eina_stringshare_replace ( & pd - > address_dial , NULL ) ;
eina_stringshare_replace ( & pd - > address_remote , NULL ) ;
EINA_LIST_FREE ( pd - > protocols . requested , str )
eina_stringshare_del ( str ) ;
EINA_LIST_FREE ( pd - > protocols . received , str )
eina_stringshare_del ( str ) ;
free ( pd - > recv . current . payload ) ;
pd - > recv . current . payload = NULL ;
free ( pd - > recv . fragmented . payload ) ;
pd - > recv . fragmented . payload = NULL ;
while ( pd - > send . pending )
_efl_net_dialer_websocket_send_pending_remove ( pd ) ;
}
static void
_efl_net_dialer_websocket_protocols_add ( Efl_Net_Dialer_Websocket_Data * pd )
{
const Eina_List * lst ;
Eina_Stringshare * str ;
char * protocols ;
size_t len = 0 ;
size_t offset = 0 ;
EINA_LIST_FOREACH ( pd - > protocols . requested , lst , str )
{
if ( len ) len + = strlen ( " , " ) ;
len + = eina_stringshare_strlen ( str ) ;
}
if ( len = = 0 ) return ;
protocols = malloc ( len + 1 ) ;
EINA_SAFETY_ON_NULL_RETURN ( protocols ) ;
EINA_LIST_FOREACH ( pd - > protocols . requested , lst , str )
{
if ( offset )
{
memcpy ( protocols + offset , " , " , strlen ( " , " ) ) ;
offset + = strlen ( " , " ) ;
}
memcpy ( protocols + offset , str , eina_stringshare_strlen ( str ) ) ;
offset + = eina_stringshare_strlen ( str ) ;
}
protocols [ offset ] = ' \0 ' ;
efl_net_dialer_http_request_header_add ( pd - > http , " Sec-WebSocket-Protocol " , protocols ) ;
free ( protocols ) ;
}
static void
_efl_net_dialer_websocket_key_add ( Efl_Net_Dialer_Websocket_Data * pd )
{
Eina_Binbuf * binbuf_key ;
Eina_Strbuf * strbuf_key_base64 ;
uint8_t key [ 16 ] ;
const Eina_Slice guid_slice = EINA_SLICE_STR_LITERAL ( " 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 " ) ;
Eina_Slice ro_slice ;
Eina_Rw_Slice rw_slice ;
uint8_t sha1hash [ 20 ] ;
Eina_Bool ret ;
size_t i ;
for ( i = 0 ; i < sizeof ( key ) ; i + + )
key [ i ] = rand ( ) & 0xff ;
binbuf_key = eina_binbuf_manage_new ( key , sizeof ( key ) , EINA_TRUE ) ;
EINA_SAFETY_ON_NULL_RETURN ( binbuf_key ) ;
strbuf_key_base64 = emile_base64_encode ( binbuf_key ) ;
eina_binbuf_free ( binbuf_key ) ;
EINA_SAFETY_ON_NULL_RETURN ( strbuf_key_base64 ) ;
efl_net_dialer_http_request_header_add ( pd - > http , " Sec-WebSocket-Key " , eina_strbuf_string_get ( strbuf_key_base64 ) ) ;
/* accept_key = base64(sha1(base64(random(16)) + guid)) */
/* 1) base64(random(16)) + guid */
eina_strbuf_append_slice ( strbuf_key_base64 , guid_slice ) ;
/* emile_binbuf_sha1() operates on binbuf, convert */
ro_slice = eina_strbuf_slice_get ( strbuf_key_base64 ) ;
binbuf_key = eina_binbuf_manage_new ( ro_slice . mem , ro_slice . len , EINA_TRUE ) ;
EINA_SAFETY_ON_NULL_GOTO ( binbuf_key , free_strbuf ) ;
/* 2) sha1(base64(random(16)) + guid) */
ret = emile_binbuf_sha1 ( binbuf_key , sha1hash ) ;
eina_binbuf_free ( binbuf_key ) ;
eina_strbuf_free ( strbuf_key_base64 ) ;
EINA_SAFETY_ON_FALSE_RETURN ( ret ) ;
/* 3) base64(sha1(base64(random(16)) + guid)) */
binbuf_key = eina_binbuf_manage_new ( sha1hash , sizeof ( sha1hash ) , EINA_TRUE ) ;
EINA_SAFETY_ON_NULL_RETURN ( binbuf_key ) ;
strbuf_key_base64 = emile_base64_encode ( binbuf_key ) ;
eina_binbuf_free ( binbuf_key ) ;
EINA_SAFETY_ON_NULL_RETURN ( strbuf_key_base64 ) ;
ro_slice = eina_strbuf_slice_get ( strbuf_key_base64 ) ;
rw_slice . mem = pd - > accept_key ;
rw_slice . len = sizeof ( pd - > accept_key ) - 1 ;
rw_slice = eina_rw_slice_copy ( rw_slice , ro_slice ) ;
rw_slice . bytes [ rw_slice . len ] = ' \0 ' ;
eina_strbuf_free ( strbuf_key_base64 ) ;
EINA_SAFETY_ON_TRUE_RETURN ( rw_slice . len < sizeof ( pd - > accept_key ) - 1 ) ;
return ;
free_strbuf :
eina_strbuf_free ( strbuf_key_base64 ) ;
}
static void
_efl_net_dialer_websocket_request_headers_websocket_add ( Efl_Net_Dialer_Websocket_Data * pd )
{
efl_net_dialer_http_request_header_add ( pd - > http , " Upgrade " , " websocket " ) ;
efl_net_dialer_http_request_header_add ( pd - > http , " Connection " , " Upgrade " ) ;
efl_net_dialer_http_request_header_add ( pd - > http , " Expect " , " 101 " ) ;
efl_net_dialer_http_request_header_add ( pd - > http , " Transfer-Encoding " , " " ) ;
efl_net_dialer_http_request_header_add ( pd - > http , " Sec-WebSocket-Version " , " 13 " ) ;
_efl_net_dialer_websocket_protocols_add ( pd ) ;
_efl_net_dialer_websocket_key_add ( pd ) ;
}
EOLIAN static Eina_Error
_efl_net_dialer_websocket_efl_net_dialer_dial ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , const char * address )
{
const char * http_url = address ;
char * tmp = NULL ;
Eina_Error err ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( address , EINVAL ) ;
if ( _c_init ( ) )
{
curl_version_info_data * cver = _c - > curl_version_info ( CURLVERSION_FOURTH ) ;
static Eina_Bool did_once = EINA_FALSE ;
/*
* CURL 7.50 .2 fixed bug
* https : //github.com/curl/curl/pull/899 that prevented
* WebSocket with initial small frames ( ie : 2 bytes ) to
* be processed , this was clear with autobahn test suite
* Case 1.1 .1
*
* commit 7 bda07b0466a192e082f32d363d1b1ce1881d483
* Author : Michael Kaufmann < mail @ michael - kaufmann . ch >
* Date : Tue Jun 28 10 : 57 : 30 2016 + 0200
*
* HTTP : stop parsing headers when switching to unknown protocols
*
* - unknown protocols probably won ' t send more headers ( e . g . WebSocket )
* - improved comments and moved them to the correct case statements
*
* Closes # 899
*/
if ( ( ! did_once ) & & ( cver ) & & ( cver - > version_num < 0x073202 ) )
{
WRN ( " Your version of CURL='%s' is too old. >=7.50.2 is required for WebSockets to work properly " , cver - > version ) ;
did_once = EINA_TRUE ;
}
}
efl_net_dialer_address_dial_set ( o , address ) ;
/* rewrite ws:// -> http://, wss:// -> https:// */
if ( strncasecmp ( address , " ws:// " , strlen ( " ws:// " ) ) = = 0 ) {
tmp = malloc ( strlen ( address ) + strlen ( " http:// " ) - strlen ( " ws:// " ) + 1 ) ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( tmp , ENOMEM ) ;
memcpy ( tmp , " http:// " , strlen ( " http:// " ) ) ;
memcpy ( tmp + strlen ( " http:// " ) ,
address + strlen ( " ws:// " ) ,
strlen ( address ) - strlen ( " ws:// " ) + 1 ) ;
http_url = tmp ;
} else if ( strncasecmp ( address , " wss:// " , strlen ( " wss:// " ) ) = = 0 ) {
tmp = malloc ( strlen ( address ) + strlen ( " https:// " ) - strlen ( " wss:// " ) + 1 ) ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( tmp , ENOMEM ) ;
memcpy ( tmp , " https:// " , strlen ( " https:// " ) ) ;
memcpy ( tmp + strlen ( " https:// " ) ,
address + strlen ( " wss:// " ) ,
strlen ( address ) - strlen ( " wss:// " ) + 1 ) ;
http_url = tmp ;
}
_efl_net_dialer_websocket_request_headers_websocket_add ( pd ) ;
err = efl_net_dialer_dial ( pd - > http , http_url ) ;
free ( tmp ) ;
return err ;
}
EOLIAN static void
_efl_net_dialer_websocket_efl_net_dialer_address_dial_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * address )
{
eina_stringshare_replace ( & pd - > address_dial , address ) ;
}
EOLIAN static const char *
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_net_dialer_address_dial_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return pd - > address_dial ;
}
EOLIAN static void
_efl_net_dialer_websocket_efl_net_dialer_connected_set ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Eina_Bool connected )
{
if ( pd - > connected = = connected ) return ;
pd - > connected = connected ;
if ( connected )
2019-03-08 07:47:32 -08:00
efl_event_callback_call ( o , EFL_NET_DIALER_EVENT_DIALER_CONNECTED , NULL ) ;
2016-08-29 20:28:00 -07:00
}
EOLIAN static Eina_Bool
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_net_dialer_connected_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return pd - > connected ;
}
EOLIAN static void
_efl_net_dialer_websocket_efl_net_dialer_proxy_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * proxy_url )
{
efl_net_dialer_proxy_set ( pd - > http , proxy_url ) ;
}
EOLIAN static const char *
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_net_dialer_proxy_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return efl_net_dialer_proxy_get ( pd - > http ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_efl_net_dialer_timeout_dial_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , double seconds )
{
efl_net_dialer_timeout_dial_set ( pd - > http , seconds ) ;
}
EOLIAN static double
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_net_dialer_timeout_dial_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return efl_net_dialer_timeout_dial_get ( pd - > http ) ;
}
EOLIAN static const char *
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_net_socket_address_local_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return efl_net_socket_address_local_get ( pd - > http ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_efl_net_socket_address_remote_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * address )
{
if ( eina_stringshare_replace ( & pd - > address_remote , address ) )
2019-03-08 07:47:32 -08:00
efl_event_callback_call ( o , EFL_NET_DIALER_EVENT_DIALER_RESOLVED , NULL ) ;
2016-08-29 20:28:00 -07:00
}
EOLIAN static const char *
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_net_socket_address_remote_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return pd - > address_remote ;
}
static void
_efl_net_dialer_websocket_recv_pending_read_remove ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
Efl_Net_Dialer_Websocket_Pending_Read * pr = pd - > recv . pending_read ;
Eina_Inlist * lst = EINA_INLIST_GET ( pr ) ;
lst = eina_inlist_remove ( lst , lst ) ;
pd - > recv . pending_read = EINA_INLIST_CONTAINER_GET ( lst , Efl_Net_Dialer_Websocket_Pending_Read ) ;
free ( pr - > bytes ) ;
free ( pr ) ;
pd - > recv . pending_read_offset = 0 ;
efl_io_reader_can_read_set ( o , ! ! pd - > recv . pending_read ) ;
}
EOLIAN static Eina_Error
_efl_net_dialer_websocket_efl_io_reader_read ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Eina_Rw_Slice * rw_slice )
{
size_t todo ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( rw_slice , EINVAL ) ;
if ( ! pd - > recv . pending_read ) return EAGAIN ;
todo = rw_slice - > len ;
while ( ( pd - > recv . pending_read ) & & ( todo > 0 ) )
{
Eina_Slice src = {
. bytes = pd - > recv . pending_read - > bytes + pd - > recv . pending_read_offset ,
. len = pd - > recv . pending_read - > len - pd - > recv . pending_read_offset ,
} ;
Eina_Rw_Slice dst = {
. bytes = rw_slice - > bytes + rw_slice - > len - todo ,
. len = todo ,
} ;
dst = eina_rw_slice_copy ( dst , src ) ;
todo - = dst . len ;
pd - > recv . pending_read_offset + = dst . len ;
if ( pd - > recv . pending_read_offset = = pd - > recv . pending_read - > len )
_efl_net_dialer_websocket_recv_pending_read_remove ( o , pd ) ;
}
rw_slice - > len - = todo ;
if ( ( pd - > close_requested ) & & ( ! pd - > recv . pending_read ) )
efl_event_callback_call ( o , EFL_IO_READER_EVENT_EOS , NULL ) ;
return 0 ;
}
EOLIAN static Eina_Bool
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_io_reader_can_read_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return pd - > can_read ;
}
EOLIAN static void
_efl_net_dialer_websocket_efl_io_reader_can_read_set ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Eina_Bool can_read )
{
if ( pd - > streaming_mode = = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_DISABLED ) return ;
if ( pd - > can_read = = can_read ) return ;
pd - > can_read = can_read ;
2019-02-22 05:50:02 -08:00
efl_event_callback_call ( o , EFL_IO_READER_EVENT_CAN_READ_CHANGED , ( void * ) ( uintptr_t ) can_read ) ;
2016-08-29 20:28:00 -07:00
}
2019-01-25 04:16:51 -08:00
EOLIAN static void
_efl_net_dialer_websocket_efl_io_reader_eos_set ( Eo * obj EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd EINA_UNUSED , Eina_Bool is_eos EINA_UNUSED )
{
/* NOP Does not need to be implemented, someone else cannot tell this class to be at the eos. This is done internally and decided within the state of the websocket.*/
}
2016-08-29 20:28:00 -07:00
EOLIAN static Eina_Bool
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_io_reader_eos_get ( const Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return ! pd - > can_read & & efl_io_closer_closed_get ( o ) ;
}
EOLIAN static Eina_Error
_efl_net_dialer_websocket_efl_io_writer_write ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Eina_Slice * slice , Eina_Slice * remaining )
{
Efl_Net_Dialer_Websocket_Opcode opcode ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( slice , EINVAL ) ;
EINA_SAFETY_ON_TRUE_RETURN_VAL ( efl_io_closer_closed_get ( o ) , EINVAL ) ;
EINA_SAFETY_ON_TRUE_RETURN_VAL ( pd - > streaming_mode = = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_DISABLED , EINVAL ) ;
if ( pd - > streaming_mode = = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_TEXT )
opcode = EFL_NET_DIALER_WEBSOCKET_OPCODE_TEXT ;
else
opcode = EFL_NET_DIALER_WEBSOCKET_OPCODE_BINARY ;
if ( remaining )
{
remaining - > mem = NULL ;
remaining - > len = 0 ;
}
_efl_net_dialer_websocket_send ( o , pd , opcode , slice - > mem , slice - > len ) ;
return 0 ;
}
EOLIAN static Eina_Bool
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_io_writer_can_write_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return pd - > can_write ;
}
EOLIAN static void
_efl_net_dialer_websocket_efl_io_writer_can_write_set ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Eina_Bool can_write )
{
if ( pd - > streaming_mode = = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_DISABLED ) return ;
if ( can_write & & efl_io_closer_closed_get ( o ) )
can_write = EINA_FALSE ;
if ( pd - > can_write = = can_write ) return ;
pd - > can_write = can_write ;
2019-02-22 05:50:02 -08:00
efl_event_callback_call ( o , EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED , ( void * ) ( uintptr_t ) can_write ) ;
2016-08-29 20:28:00 -07:00
}
EOLIAN static Eina_Error
_efl_net_dialer_websocket_efl_io_closer_close ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd )
{
if ( pd - > close_requested ) return 0 ;
efl_net_dialer_websocket_close_request ( o , EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_NORMAL , NULL ) ;
return 0 ;
}
EOLIAN static Eina_Bool
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_io_closer_closed_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return pd - > close_requested | | efl_io_closer_closed_get ( pd - > http ) ;
}
2016-09-12 08:23:29 -07:00
EOLIAN static Eina_Bool
_efl_net_dialer_websocket_efl_io_closer_close_on_exec_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , Eina_Bool close_on_exec )
{
return efl_io_closer_close_on_exec_set ( pd - > http , close_on_exec ) ;
}
EOLIAN static Eina_Bool
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_efl_io_closer_close_on_exec_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-09-12 08:23:29 -07:00
{
return efl_io_closer_close_on_exec_get ( pd - > http ) ;
}
EOLIAN static void
2018-04-17 16:17:29 -07:00
_efl_net_dialer_websocket_efl_io_closer_close_on_invalidate_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , Eina_Bool close_on_invalidate )
2016-09-12 08:23:29 -07:00
{
2018-04-17 16:17:29 -07:00
efl_io_closer_close_on_invalidate_set ( pd - > http , close_on_invalidate ) ;
2016-09-12 08:23:29 -07:00
}
EOLIAN static Eina_Bool
2018-04-17 16:17:29 -07:00
_efl_net_dialer_websocket_efl_io_closer_close_on_invalidate_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-09-12 08:23:29 -07:00
{
2018-04-17 16:17:29 -07:00
return efl_io_closer_close_on_invalidate_get ( pd - > http ) ;
2016-09-12 08:23:29 -07:00
}
2016-08-29 20:28:00 -07:00
EOLIAN static void
_efl_net_dialer_websocket_streaming_mode_set ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Efl_Net_Dialer_Websocket_Streaming_Mode streaming_mode )
{
if ( streaming_mode = = EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_DISABLED )
{
while ( pd - > recv . pending_read )
_efl_net_dialer_websocket_recv_pending_read_remove ( o , pd ) ;
}
pd - > streaming_mode = streaming_mode ;
}
EOLIAN static Efl_Net_Dialer_Websocket_Streaming_Mode
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_streaming_mode_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return pd - > streaming_mode ;
}
EOLIAN static void
_efl_net_dialer_websocket_user_agent_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * user_agent )
{
efl_net_dialer_http_user_agent_set ( pd - > http , user_agent ) ;
}
EOLIAN static const char *
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_user_agent_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return efl_net_dialer_http_user_agent_get ( pd - > http ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_authentication_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * username , const char * password , Efl_Net_Http_Authentication_Method method , Eina_Bool restricted )
{
efl_net_dialer_http_authentication_set ( pd - > http , username , password , method , restricted ) ;
}
EOLIAN static void
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_authentication_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * * username , const char * * password , Efl_Net_Http_Authentication_Method * method , Eina_Bool * restricted )
2016-08-29 20:28:00 -07:00
{
efl_net_dialer_http_authentication_get ( pd - > http , username , password , method , restricted ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_allow_redirects_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , Eina_Bool allow_redirects )
{
efl_net_dialer_http_allow_redirects_set ( pd - > http , allow_redirects ) ;
}
EOLIAN static Eina_Bool
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_allow_redirects_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return efl_net_dialer_http_allow_redirects_get ( pd - > http ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_cookie_jar_set ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * path )
{
efl_net_dialer_http_cookie_jar_set ( pd - > http , path ) ;
}
EOLIAN static const char *
2018-04-17 11:09:44 -07:00
_efl_net_dialer_websocket_cookie_jar_get ( const Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
2016-08-29 20:28:00 -07:00
{
return efl_net_dialer_http_cookie_jar_get ( pd - > http ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_ping ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , const char * reason )
{
size_t len ;
EINA_SAFETY_ON_TRUE_RETURN ( pd - > close_requested ) ;
if ( ! reason ) reason = " " ;
len = strlen ( reason ) ;
if ( len > 125 )
{
WRN ( " reason is over 125 bytes! chopped '%s' " , reason ) ;
len = 125 ;
}
_efl_net_dialer_websocket_send ( o , pd , EFL_NET_DIALER_WEBSOCKET_OPCODE_PING ,
reason , len ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_text_send ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , const char * text )
{
EINA_SAFETY_ON_TRUE_RETURN ( pd - > close_requested ) ;
if ( ! text ) text = " " ;
_efl_net_dialer_websocket_send ( o , pd , EFL_NET_DIALER_WEBSOCKET_OPCODE_TEXT ,
text , strlen ( text ) ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_binary_send ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , const Eina_Slice blob )
{
EINA_SAFETY_ON_TRUE_RETURN ( pd - > close_requested ) ;
_efl_net_dialer_websocket_send ( o , pd , EFL_NET_DIALER_WEBSOCKET_OPCODE_BINARY ,
blob . mem , blob . len ) ;
}
2017-08-30 13:24:27 -07:00
static Eina_Value
2018-12-07 03:15:16 -08:00
_efl_net_dialer_websocket_close_request_timeout ( Eo * o , void * data EINA_UNUSED , const Eina_Value v )
2016-08-29 20:28:00 -07:00
{
DBG ( " server did not close the TCP socket, timeout " ) ;
efl_event_callback_call ( o , EFL_IO_CLOSER_EVENT_CLOSED , NULL ) ;
2017-08-30 13:24:27 -07:00
return v ;
2016-08-29 20:28:00 -07:00
}
EOLIAN static void
_efl_net_dialer_websocket_close_request ( Eo * o , Efl_Net_Dialer_Websocket_Data * pd , Efl_Net_Dialer_Websocket_Close_Reason reason , const char * message )
{
Efl_Net_Dialer_Websocket_Frame * f ;
uint16_t r = reason ;
size_t len ;
EINA_SAFETY_ON_TRUE_RETURN ( pd - > close_requested ) ;
2016-09-13 12:08:25 -07:00
if ( pd - > close_timeout )
2017-08-30 13:24:27 -07:00
eina_future_cancel ( pd - > close_timeout ) ;
2016-09-13 12:08:25 -07:00
2018-11-21 17:33:10 -08:00
efl_future_then ( o , efl_loop_timeout ( efl_loop_get ( o ) , 2.0 ) ,
2018-12-07 03:15:16 -08:00
. success = _efl_net_dialer_websocket_close_request_timeout ,
. storage = & pd - > close_timeout ) ;
2016-09-13 12:08:25 -07:00
2016-08-29 20:28:00 -07:00
efl_io_writer_can_write_set ( o , EINA_FALSE ) ;
if ( ( ! reason ) | | ( reason = = EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_NO_REASON ) )
{
f = _efl_net_dialer_websocket_send_pending_add ( o , pd , EFL_NET_DIALER_WEBSOCKET_OPCODE_CLOSE , 0 ) ;
EINA_SAFETY_ON_NULL_RETURN ( f ) ;
pd - > close_requested = EINA_TRUE ;
return ;
}
if ( ! message ) message = " " ;
# ifndef WORDS_BIGENDIAN
r = eina_swap16 ( r ) ;
# endif
len = strlen ( message ) ;
if ( len > 123 ) /* 2 for code */
{
WRN ( " message is over 123 bytes! chopped '%s' " , message ) ;
len = 123 ;
}
f = _efl_net_dialer_websocket_send_pending_add ( o , pd , EFL_NET_DIALER_WEBSOCKET_OPCODE_CLOSE , sizeof ( r ) + len ) ;
EINA_SAFETY_ON_NULL_RETURN ( f ) ;
_efl_net_dialer_websocket_frame_write ( f , 0 , & r , sizeof ( r ) ) ;
_efl_net_dialer_websocket_frame_write ( f , sizeof ( r ) , message , len ) ;
pd - > close_requested = EINA_TRUE ;
}
typedef struct _Efl_Net_Dialer_Websocket_Blacklist_Header {
const char * header ;
size_t len ;
} Efl_Net_Dialer_Websocket_Blacklist_Header ;
static const Efl_Net_Dialer_Websocket_Blacklist_Header _efl_net_dialer_websocket_blacklisted_headers [ ] = {
# define _M(x) {x, sizeof(x) - 1}
_M ( " Upgrade " ) ,
_M ( " Connection " ) ,
_M ( " Expect " ) ,
_M ( " Transfer-Encoding " ) ,
_M ( " Sec-WebSocket-Key " ) ,
_M ( " Sec-WebSocket-Protocol " ) ,
_M ( " Sec-WebSocket-Version " ) ,
# undef _M
{ NULL , 0 }
} ;
static Eina_Bool
_efl_net_dialer_websocket_blacklisted_header_check ( const char * key )
{
const Efl_Net_Dialer_Websocket_Blacklist_Header * bh ;
size_t len ;
len = strlen ( key ) ;
for ( bh = _efl_net_dialer_websocket_blacklisted_headers ;
bh - > header ! = NULL ;
bh + + )
{
if ( ( bh - > len = = len ) & & ( memcmp ( bh - > header , key , len ) = = 0 ) )
return EINA_TRUE ;
}
return EINA_FALSE ;
}
EOLIAN static void
_efl_net_dialer_websocket_request_header_add ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * key , const char * value )
{
EINA_SAFETY_ON_NULL_RETURN ( key ) ;
EINA_SAFETY_ON_NULL_RETURN ( value ) ;
if ( _efl_net_dialer_websocket_blacklisted_header_check ( key ) )
{
ERR ( " HTTP header conflicts with WebSocket: %s: %s " , key , value ) ;
return ;
}
efl_net_dialer_http_request_header_add ( pd - > http , key , value ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_request_headers_clear ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
{
efl_net_dialer_http_request_headers_clear ( pd - > http ) ;
_efl_net_dialer_websocket_request_headers_websocket_add ( pd ) ;
}
typedef struct _Eina_Iterator_Filtered_Header
{
Eina_Iterator iterator ;
Eina_Iterator * full ;
} Eina_Iterator_Filtered_Header ;
static Eina_Bool
eina_iterator_filtered_header_next ( Eina_Iterator_Filtered_Header * it , void * * data )
{
Efl_Net_Http_Header * header ;
EINA_ITERATOR_FOREACH ( it - > full , header )
{
if ( ! header - > key ) continue ;
if ( ! _efl_net_dialer_websocket_blacklisted_header_check ( header - > key ) )
{
* data = header ;
return EINA_TRUE ;
}
}
return EINA_FALSE ;
}
static const struct filtered *
eina_iterator_filtered_header_get_container ( Eina_Iterator_Filtered_Header * it )
{
return eina_iterator_container_get ( it - > full ) ;
}
static void
eina_iterator_filtered_header_free ( Eina_Iterator_Filtered_Header * it )
{
eina_iterator_free ( it - > full ) ;
free ( it ) ;
}
EOLIAN static Eina_Iterator *
_efl_net_dialer_websocket_request_headers_get ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
{
Eina_Iterator_Filtered_Header * it ;
it = calloc ( 1 , sizeof ( Eina_Iterator_Filtered_Header ) ) ;
EINA_SAFETY_ON_NULL_RETURN_VAL ( it , NULL ) ;
it - > full = efl_net_dialer_http_request_headers_get ( pd - > http ) ;
EINA_SAFETY_ON_NULL_GOTO ( it - > full , error ) ;
EINA_MAGIC_SET ( & it - > iterator , EINA_MAGIC_ITERATOR ) ;
it - > iterator . version = EINA_ITERATOR_VERSION ;
it - > iterator . next = FUNC_ITERATOR_NEXT ( eina_iterator_filtered_header_next ) ;
it - > iterator . get_container = FUNC_ITERATOR_GET_CONTAINER ( eina_iterator_filtered_header_get_container ) ;
it - > iterator . free = FUNC_ITERATOR_FREE ( eina_iterator_filtered_header_free ) ;
return & it - > iterator ;
error :
free ( it ) ;
return NULL ;
}
EOLIAN static void
_efl_net_dialer_websocket_request_protocol_add ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd , const char * protocol )
{
EINA_SAFETY_ON_NULL_RETURN ( protocol ) ;
protocol = eina_stringshare_add ( protocol ) ;
pd - > protocols . requested = eina_list_append ( pd - > protocols . requested , protocol ) ;
}
EOLIAN static Eina_Iterator *
_efl_net_dialer_websocket_request_protocols_get ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
{
return eina_list_iterator_new ( pd - > protocols . requested ) ;
}
EOLIAN static void
_efl_net_dialer_websocket_request_protocols_clear ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
{
Eina_Stringshare * str ;
EINA_LIST_FREE ( pd - > protocols . requested , str )
eina_stringshare_del ( str ) ;
}
EOLIAN static Eina_Iterator *
_efl_net_dialer_websocket_response_protocols_get ( Eo * o EINA_UNUSED , Efl_Net_Dialer_Websocket_Data * pd )
{
return eina_list_iterator_new ( pd - > protocols . received ) ;
}
# include "efl_net_dialer_websocket.eo.c"