pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
IRC: fill required command parameter counts (part 3)
release-2.x.y
2014-01-16, Tomasz Wasilczyk
6b0e0566af20
IRC: fill required command parameter counts (part 3)
/* $Id: http.c 1036 2010-12-15 00:02:28Z wojtekka $ */
/*
* (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License Version
* 2.1 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
* USA.
*/
/**
* \file http.c
*
* \brief Obsługa połączeń HTTP
*/
#include
<sys/types.h>
#ifndef _WIN32
#
include
<sys/socket.h>
#
include
<netinet/in.h>
#
include
<arpa/inet.h>
#endif
#include
"compat.h"
#include
"libgadu.h"
#include
"resolver.h"
#include
<ctype.h>
#include
<errno.h>
#ifndef _WIN32
#
include
<netdb.h>
#endif
#include
<signal.h>
#include
<stdarg.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<unistd.h>
#define GG_HTTP_MAX_LENGTH 1000000000
/**
* Rozpoczyna połączenie HTTP.
*
* Funkcja przeprowadza połączenie HTTP przy połączeniu synchronicznym,
* zwracając wynik w polach struktury \c gg_http, lub błąd, gdy sesja się
* nie powiedzie.
*
* Przy połączeniu asynchronicznym, funkcja rozpoczyna połączenie, a dalsze
* etapy będą przeprowadzane po wykryciu zmian (\c watch) na obserwowanym
* deskryptorze (\c fd) i wywołaniu funkcji \c gg_http_watch_fd().
*
* Po zakończeniu, należy zwolnić strukturę za pomocą funkcji
* \c gg_http_free(). Połączenie asynchroniczne można zatrzymać w każdej
* chwili za pomocą \c gg_http_stop().
*
* \param hostname Adres serwera
* \param port Port serwera
* \param async Flaga asynchronicznego połączenia
* \param method Metoda HTTP
* \param path Ścieżka do zasobu (musi być poprzedzona znakiem '/')
* \param header Nagłówek zapytania plus ewentualne dane dla POST
*
* \return Zaalokowana struktura \c gg_http lub NULL, jeśli wystąpił błąd.
*
* \ingroup http
*/
struct
gg_http
*
gg_http_connect
(
const
char
*
hostname
,
int
port
,
int
async
,
const
char
*
method
,
const
char
*
path
,
const
char
*
header
)
{
struct
gg_http
*
h
;
if
(
!
hostname
||
!
port
||
!
method
||
!
path
||
!
header
)
{
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_connect() invalid arguments
\n
"
);
errno
=
EFAULT
;
return
NULL
;
}
if
(
!
(
h
=
malloc
(
sizeof
(
*
h
))))
return
NULL
;
memset
(
h
,
0
,
sizeof
(
*
h
));
h
->
async
=
async
;
h
->
port
=
port
;
h
->
fd
=
-1
;
h
->
type
=
GG_SESSION_HTTP
;
gg_http_set_resolver
(
h
,
GG_RESOLVER_DEFAULT
);
if
(
gg_proxy_enabled
)
{
char
*
auth
=
gg_proxy_auth
();
h
->
query
=
gg_saprintf
(
"%s http://%s:%d%s HTTP/1.0
\r\n
%s%s"
,
method
,
hostname
,
port
,
path
,
(
auth
)
?
auth
:
""
,
header
);
hostname
=
gg_proxy_host
;
h
->
port
=
port
=
gg_proxy_port
;
free
(
auth
);
}
else
{
h
->
query
=
gg_saprintf
(
"%s %s HTTP/1.0
\r\n
%s"
,
method
,
path
,
header
);
}
if
(
!
h
->
query
)
{
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_connect() not enough memory for query
\n
"
);
free
(
h
);
errno
=
ENOMEM
;
return
NULL
;
}
gg_debug
(
GG_DEBUG_MISC
,
"=> -----BEGIN-HTTP-QUERY-----
\n
%s
\n
=> -----END-HTTP-QUERY-----
\n
"
,
h
->
query
);
if
(
async
)
{
if
(
h
->
resolver_start
(
&
h
->
fd
,
&
h
->
resolver
,
hostname
)
==
-1
)
{
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_connect() resolver failed
\n
"
);
gg_http_free
(
h
);
errno
=
ENOENT
;
return
NULL
;
}
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_connect() resolver = %p
\n
"
,
h
->
resolver
);
h
->
state
=
GG_STATE_RESOLVING
;
h
->
check
=
GG_CHECK_READ
;
h
->
timeout
=
GG_DEFAULT_TIMEOUT
;
}
else
{
struct
in_addr
*
addr_list
=
NULL
;
int
addr_count
;
if
(
gg_gethostbyname_real
(
hostname
,
&
addr_list
,
&
addr_count
,
0
)
==
-1
||
addr_count
==
0
)
{
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_connect() host not found
\n
"
);
gg_http_free
(
h
);
free
(
addr_list
);
errno
=
ENOENT
;
return
NULL
;
}
if
((
h
->
fd
=
gg_connect
(
&
addr_list
[
0
],
port
,
0
))
==
-1
)
{
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_connect() connection failed (errno=%d, %s)
\n
"
,
errno
,
strerror
(
errno
));
gg_http_free
(
h
);
free
(
addr_list
);
return
NULL
;
}
free
(
addr_list
);
h
->
state
=
GG_STATE_CONNECTING
;
while
(
h
->
state
!=
GG_STATE_ERROR
&&
h
->
state
!=
GG_STATE_PARSING
)
{
if
(
gg_http_watch_fd
(
h
)
==
-1
)
break
;
}
if
(
h
->
state
!=
GG_STATE_PARSING
)
{
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_connect() some strange error
\n
"
);
gg_http_free
(
h
);
return
NULL
;
}
}
h
->
callback
=
gg_http_watch_fd
;
h
->
destroy
=
gg_http_free
;
return
h
;
}
#ifndef DOXYGEN
#define gg_http_error(x) \
close(h->fd); \
h->fd = -1; \
h->state = GG_STATE_ERROR; \
h->error = x; \
return 0;
#endif
/* DOXYGEN */
/**
* Funkcja wywoływana po zaobserwowaniu zmian na deskryptorze połączenia.
*
* Operacja będzie zakończona, gdy pole \c state będzie równe
* \c GG_STATE_PARSING. W tym miejscu działanie przejmuje zwykle funkcja
* korzystająca z \c gg_http_watch_fd(). W przypadku błędu połączenia,
* pole \c state będzie równe \c GG_STATE_ERROR, a kod błędu znajdzie się
* w polu \c error.
*
* \param h Struktura połączenia
*
* \return \return 0 jeśli się powiodło, -1 w przypadku błędu
*
* \ingroup http
*/
int
gg_http_watch_fd
(
struct
gg_http
*
h
)
{
gg_debug
(
GG_DEBUG_FUNCTION
,
"** gg_http_watch_fd(%p);
\n
"
,
h
);
if
(
!
h
)
{
gg_debug
(
GG_DEBUG_MISC
,
"// gg_http_watch_fd() invalid arguments
\n
"
);
errno
=
EFAULT
;
return
-1
;
}
if
(
h
->
state
==
GG_STATE_RESOLVING
)
{
struct
in_addr
a
;
gg_debug
(
GG_DEBUG_MISC
,
"=> http, resolving done
\n
"
);
if
(
read
(
h
->
fd
,
&
a
,
sizeof
(
a
))
<
(
signed
)
sizeof
(
a
)
||
a
.
s_addr
==
INADDR_NONE
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, resolver thread failed
\n
"
);
gg_http_error
(
GG_ERROR_RESOLVING
);
}
close
(
h
->
fd
);
h
->
fd
=
-1
;
h
->
resolver_cleanup
(
&
h
->
resolver
,
0
);
gg_debug
(
GG_DEBUG_MISC
,
"=> http, connecting to %s:%d
\n
"
,
inet_ntoa
(
a
),
h
->
port
);
if
((
h
->
fd
=
gg_connect
(
&
a
,
h
->
port
,
h
->
async
))
==
-1
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, connection failed (errno=%d, %s)
\n
"
,
errno
,
strerror
(
errno
));
gg_http_error
(
GG_ERROR_CONNECTING
);
}
h
->
state
=
GG_STATE_CONNECTING
;
h
->
check
=
GG_CHECK_WRITE
;
h
->
timeout
=
GG_DEFAULT_TIMEOUT
;
return
0
;
}
if
(
h
->
state
==
GG_STATE_CONNECTING
)
{
int
res
=
0
;
socklen_t
res_size
=
sizeof
(
res
);
if
(
h
->
async
&&
(
getsockopt
(
h
->
fd
,
SOL_SOCKET
,
SO_ERROR
,
&
res
,
&
res_size
)
||
res
))
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, async connection failed (errno=%d, %s)
\n
"
,
(
res
)
?
res
:
errno
,
strerror
((
res
)
?
res
:
errno
));
close
(
h
->
fd
);
h
->
fd
=
-1
;
h
->
state
=
GG_STATE_ERROR
;
h
->
error
=
GG_ERROR_CONNECTING
;
if
(
res
)
errno
=
res
;
return
0
;
}
gg_debug
(
GG_DEBUG_MISC
,
"=> http, connected, sending request
\n
"
);
h
->
state
=
GG_STATE_SENDING_QUERY
;
}
if
(
h
->
state
==
GG_STATE_SENDING_QUERY
)
{
size_t
res
;
if
((
res
=
write
(
h
->
fd
,
h
->
query
,
strlen
(
h
->
query
)))
<
1
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, write() failed (len=%d, res=%d, errno=%d)
\n
"
,
strlen
(
h
->
query
),
res
,
errno
);
gg_http_error
(
GG_ERROR_WRITING
);
}
if
(
res
<
strlen
(
h
->
query
))
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, partial header sent (led=%d, sent=%d)
\n
"
,
strlen
(
h
->
query
),
res
);
memmove
(
h
->
query
,
h
->
query
+
res
,
strlen
(
h
->
query
)
-
res
+
1
);
h
->
state
=
GG_STATE_SENDING_QUERY
;
h
->
check
=
GG_CHECK_WRITE
;
h
->
timeout
=
GG_DEFAULT_TIMEOUT
;
}
else
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, request sent (len=%d)
\n
"
,
strlen
(
h
->
query
));
free
(
h
->
query
);
h
->
query
=
NULL
;
h
->
state
=
GG_STATE_READING_HEADER
;
h
->
check
=
GG_CHECK_READ
;
h
->
timeout
=
GG_DEFAULT_TIMEOUT
;
}
return
0
;
}
if
(
h
->
state
==
GG_STATE_READING_HEADER
)
{
char
buf
[
1024
],
*
tmp
;
int
res
;
if
((
res
=
read
(
h
->
fd
,
buf
,
sizeof
(
buf
)))
==
-1
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, reading header failed (errno=%d)
\n
"
,
errno
);
if
(
h
->
header
)
{
free
(
h
->
header
);
h
->
header
=
NULL
;
}
gg_http_error
(
GG_ERROR_READING
);
}
if
(
!
res
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, connection reset by peer
\n
"
);
if
(
h
->
header
)
{
free
(
h
->
header
);
h
->
header
=
NULL
;
}
gg_http_error
(
GG_ERROR_READING
);
}
gg_debug
(
GG_DEBUG_MISC
,
"=> http, read %d bytes of header
\n
"
,
res
);
if
(
!
(
tmp
=
realloc
(
h
->
header
,
h
->
header_size
+
res
+
1
)))
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, not enough memory for header
\n
"
);
free
(
h
->
header
);
h
->
header
=
NULL
;
gg_http_error
(
GG_ERROR_READING
);
}
h
->
header
=
tmp
;
memcpy
(
h
->
header
+
h
->
header_size
,
buf
,
res
);
h
->
header_size
+=
res
;
gg_debug
(
GG_DEBUG_MISC
,
"=> http, header_buf=%p, header_size=%d
\n
"
,
h
->
header
,
h
->
header_size
);
h
->
header
[
h
->
header_size
]
=
0
;
if
((
tmp
=
strstr
(
h
->
header
,
"
\r\n\r\n
"
))
||
(
tmp
=
strstr
(
h
->
header
,
"
\n\n
"
)))
{
int
sep_len
=
(
*
tmp
==
'\r'
)
?
4
:
2
;
unsigned
int
left
;
char
*
line
;
left
=
h
->
header_size
-
((
long
)(
tmp
)
-
(
long
)(
h
->
header
)
+
sep_len
);
gg_debug
(
GG_DEBUG_MISC
,
"=> http, got all header (%d bytes, %d left)
\n
"
,
h
->
header_size
-
left
,
left
);
/* HTTP/1.1 200 OK */
if
(
strlen
(
h
->
header
)
<
16
||
strncmp
(
h
->
header
+
9
,
"200"
,
3
))
{
gg_debug
(
GG_DEBUG_MISC
,
"=> -----BEGIN-HTTP-HEADER-----
\n
%s
\n
=> -----END-HTTP-HEADER-----
\n
"
,
h
->
header
);
gg_debug
(
GG_DEBUG_MISC
,
"=> http, didn't get 200 OK -- no results
\n
"
);
free
(
h
->
header
);
h
->
header
=
NULL
;
gg_http_error
(
GG_ERROR_CONNECTING
);
}
h
->
body_size
=
0
;
line
=
h
->
header
;
*
tmp
=
0
;
gg_debug
(
GG_DEBUG_MISC
,
"=> -----BEGIN-HTTP-HEADER-----
\n
%s
\n
=> -----END-HTTP-HEADER-----
\n
"
,
h
->
header
);
while
(
line
)
{
if
(
!
strncasecmp
(
line
,
"Content-length: "
,
16
))
{
h
->
body_size
=
atoi
(
line
+
16
);
}
line
=
strchr
(
line
,
'\n'
);
if
(
line
)
line
++
;
}
if
(
h
->
body_size
<=
0
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, content-length not found
\n
"
);
h
->
body_size
=
left
;
}
if
(
h
->
body_size
>
GG_HTTP_MAX_LENGTH
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, content-length too big
\n
"
);
h
->
body_size
=
GG_HTTP_MAX_LENGTH
;
}
if
(
left
>
h
->
body_size
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, oversized reply (%d bytes needed, %d bytes left)
\n
"
,
h
->
body_size
,
left
);
h
->
body_size
=
left
;
}
gg_debug
(
GG_DEBUG_MISC
,
"=> http, body_size=%d
\n
"
,
h
->
body_size
);
if
(
!
(
h
->
body
=
malloc
(
h
->
body_size
+
1
)))
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, not enough memory (%d bytes for body_buf)
\n
"
,
h
->
body_size
+
1
);
free
(
h
->
header
);
h
->
header
=
NULL
;
gg_http_error
(
GG_ERROR_READING
);
}
if
(
left
)
{
memcpy
(
h
->
body
,
tmp
+
sep_len
,
left
);
h
->
body_done
=
left
;
}
h
->
body
[
left
]
=
0
;
h
->
state
=
GG_STATE_READING_DATA
;
h
->
check
=
GG_CHECK_READ
;
h
->
timeout
=
GG_DEFAULT_TIMEOUT
;
}
return
0
;
}
if
(
h
->
state
==
GG_STATE_READING_DATA
)
{
char
buf
[
1024
];
int
res
;
if
((
res
=
read
(
h
->
fd
,
buf
,
sizeof
(
buf
)))
==
-1
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, reading body failed (errno=%d)
\n
"
,
errno
);
if
(
h
->
body
)
{
free
(
h
->
body
);
h
->
body
=
NULL
;
}
gg_http_error
(
GG_ERROR_READING
);
}
if
(
!
res
)
{
if
(
h
->
body_done
>=
h
->
body_size
)
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, we're done, closing socket
\n
"
);
h
->
state
=
GG_STATE_PARSING
;
close
(
h
->
fd
);
h
->
fd
=
-1
;
}
else
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, connection closed while reading (have %d, need %d)
\n
"
,
h
->
body_done
,
h
->
body_size
);
if
(
h
->
body
)
{
free
(
h
->
body
);
h
->
body
=
NULL
;
}
gg_http_error
(
GG_ERROR_READING
);
}
return
0
;
}
gg_debug
(
GG_DEBUG_MISC
,
"=> http, read %d bytes of body
\n
"
,
res
);
if
(
h
->
body_done
+
res
>
h
->
body_size
)
{
char
*
tmp
;
gg_debug
(
GG_DEBUG_MISC
,
"=> http, too much data (%d bytes, %d needed), enlarging buffer
\n
"
,
h
->
body_done
+
res
,
h
->
body_size
);
if
(
!
(
tmp
=
realloc
(
h
->
body
,
h
->
body_done
+
res
+
1
)))
{
gg_debug
(
GG_DEBUG_MISC
,
"=> http, not enough memory for data (%d needed)
\n
"
,
h
->
body_done
+
res
+
1
);
free
(
h
->
body
);
h
->
body
=
NULL
;
gg_http_error
(
GG_ERROR_READING
);
}
h
->
body
=
tmp
;
h
->
body_size
=
h
->
body_done
+
res
;
}
h
->
body
[
h
->
body_done
+
res
]
=
0
;
memcpy
(
h
->
body
+
h
->
body_done
,
buf
,
res
);
h
->
body_done
+=
res
;
gg_debug
(
GG_DEBUG_MISC
,
"=> body_done=%d, body_size=%d
\n
"
,
h
->
body_done
,
h
->
body_size
);
return
0
;
}
if
(
h
->
fd
!=
-1
)
close
(
h
->
fd
);
h
->
fd
=
-1
;
h
->
state
=
GG_STATE_ERROR
;
h
->
error
=
0
;
return
-1
;
}
/**
* Kończy asynchroniczne połączenie HTTP.
*
* Po zatrzymaniu należy zwolnić zasoby funkcją \c gg_http_free().
*
* \param h Struktura połączenia
*
* \ingroup http
*/
void
gg_http_stop
(
struct
gg_http
*
h
)
{
if
(
!
h
)
return
;
if
(
h
->
state
==
GG_STATE_ERROR
||
h
->
state
==
GG_STATE_DONE
)
return
;
if
(
h
->
fd
!=
-1
)
{
close
(
h
->
fd
);
h
->
fd
=
-1
;
}
h
->
resolver_cleanup
(
&
h
->
resolver
,
1
);
}
/**
* \internal Zwalnia pola struktury \c gg_http.
*
* Funkcja zwalnia same pola, nie zwalnia struktury.
*
* \param h Struktura połączenia
*/
void
gg_http_free_fields
(
struct
gg_http
*
h
)
{
if
(
!
h
)
return
;
if
(
h
->
body
)
{
free
(
h
->
body
);
h
->
body
=
NULL
;
}
if
(
h
->
query
)
{
free
(
h
->
query
);
h
->
query
=
NULL
;
}
if
(
h
->
header
)
{
free
(
h
->
header
);
h
->
header
=
NULL
;
}
}
/**
* Zwalnia zasoby po połączeniu HTTP.
*
* Jeśli połączenie nie zostało jeszcze zakończone, jest przerywane.
*
* \param h Struktura połączenia
*
* \ingroup http
*/
void
gg_http_free
(
struct
gg_http
*
h
)
{
if
(
!
h
)
return
;
gg_http_stop
(
h
);
gg_http_free_fields
(
h
);
free
(
h
);
}
/*
* Local variables:
* c-indentation-style: k&r
* c-basic-offset: 8
* indent-tabs-mode: notnil
* End:
*
* vim: shiftwidth=8:
*/