3 * centericq HTTP protocol handling class
4 * $Id: HTTPClient.cc,v 1.19 2005/01/18 23:20:17 konst Exp $
6 * Copyright (C) 2003-2004 by Konstantin Klyagin <konst@konst.org.ua>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
25 #include "HTTPClient.h"
32 HTTPClient::HTTPClient() : m_state(NOT_CONNECTED
), m_recv(),
33 m_proxy_port(8080), m_timeout(50) {
34 m_socket
= new TCPSocket();
38 HTTPClient::~HTTPClient() {
39 if(m_socket
->getSocketHandle() > -1) SignalRemoveSocket(m_socket
->getSocketHandle());
43 void HTTPClient::Init() {
46 void HTTPClient::Connect() {
51 HTTPRequestEvent
*ev
= m_queue
.front();
52 m_hostname
= !m_redirect
.empty() ? m_redirect
: ev
->getURL();
56 m_content
= m_redirect
= "";
59 if(up(m_hostname
.substr(0, 7)) == "HTTP://") {
60 m_hostname
.erase(0, 7);
63 if((pos
= m_hostname
.find("@")) != -1) {
64 ev
->setAuth(HTTPRequestEvent::Basic
, m_hostname
.substr(0, pos
), "");
65 m_hostname
.erase(0, pos
+1);
67 if((pos
= ev
->m_user
.find(":")) != -1) {
68 ev
->m_pass
= ev
->m_user
.substr(pos
+1);
69 ev
->m_user
.erase(pos
);
73 if((pos
= m_hostname
.find("/")) != -1) {
74 m_resource
= m_hostname
.substr(pos
);
75 m_hostname
.erase(pos
);
78 if((pos
= m_hostname
.find(":")) != -1) {
79 m_port
= strtoul(m_hostname
.substr(pos
+1).c_str(), 0, 0);
80 m_hostname
.erase(pos
);
84 if(ev
->connectTries
++ > 10) {
85 ev
->setHTTPResp("too many connect attempts");
86 throw DisconnectedException("Too many connect attempts");
89 if(m_proxy_hostname
.empty()) {
90 m_socket
->setRemoteHost(m_hostname
.c_str());
91 m_socket
->setRemotePort(m_port
);
93 m_socket
->setRemoteHost(m_proxy_hostname
.c_str());
94 m_socket
->setRemotePort(m_proxy_port
);
97 m_socket
->setBindHost(m_bindhost
.c_str());
98 m_socket
->setBlocking(false);
100 SignalAddSocket(m_socket
->getSocketHandle(), SocketEvent::WRITE
);
102 time(&m_last_operation
);
103 m_state
= WAITING_FOR_CONNECT
;
105 } catch(DisconnectedException e
) {
106 SignalLog(LogEvent::WARN
, e
.what());
109 } catch(SocketException e
) {
110 SignalLog(LogEvent::WARN
, e
.what());
114 SignalLog(LogEvent::WARN
, "Uncaught HTTP connect exception");
119 if(m_state
== NOT_CONNECTED
) {
120 MessageEvent
*ev
= m_queue
.front();
122 ev
->setDelivered(false);
123 ev
->setFinished(true);
124 ev
->setDeliveryFailureReason(MessageEvent::Failed
);
132 void HTTPClient::Parse() {
134 string response
, head
, val
;
135 HTTPRequestEvent
*ev
= m_queue
.front();
137 npos
= m_recv
.size();
141 m_recv
.UnpackCRLFString(response
);
142 if(response
.empty()) break;
144 while(!response
.empty()) {
145 if(response
.substr(response
.size()-1).find_first_of("\r\n") != -1)
146 response
.erase(response
.size()-1); else break;
149 time(&m_last_operation
);
151 string log
= "Received HTTP response from " +
152 IPtoString(m_socket
->getRemoteIP()) + ":" +
153 i2str(m_socket
->getRemotePort()) + "\n" + response
;
155 SignalLog(LogEvent::DIRECTPACKET
, log
);
157 if(m_state
== WAITING_FOR_HEADER
) {
160 if((npos
= response
.find(" ")) != -1) {
161 m_code
= strtoul(response
.substr(npos
+1).c_str(), 0, 0);
171 ev
->setHTTPResp(response
.substr(npos
+1));
172 throw HTTPException("Didn't receive HTTP OK");
175 m_state
= RECEIVING_HEADER
;
177 } else if(m_state
== RECEIVING_HEADER
) {
178 if((npos
= response
.find(" ")) != -1)
179 head
= up(response
.substr(0, npos
));
181 if(head
== "CONTENT-LENGTH:") {
182 m_length
= strtoul(response
.substr(npos
+1).c_str(), 0, 0);
188 if(head
== "LOCATION:") {
189 m_redirect
= response
.substr(npos
+1);
190 m_socket
->Disconnect();
191 SignalRemoveSocket(m_socket
->getSocketHandle());
192 m_state
= NOT_CONNECTED
;
198 if(head
== "WWW-AUTHENTICATE:") {
199 if(ev
->authTries
++) {
200 ev
->setHTTPResp("HTTP auth failed");
201 throw HTTPException("Authentification failed");
204 response
.erase(0, npos
+1);
206 if((npos
= response
.find(" ")) != -1) {
207 head
= up(response
.substr(0, npos
));
208 response
.erase(0, npos
+1);
211 ev
->authmethod
= HTTPRequestEvent::Digest
;
213 while(!response
.empty()) {
214 if((npos
= response
.find("=")) != -1) {
215 head
= response
.substr(0, npos
);
216 response
.erase(0, npos
+1);
218 if(response
.substr(0, 1) == "\"") {
219 response
.erase(0, 1);
220 if((npos
= response
.find("\"")) != -1)
221 val
= response
.substr(0, npos
);
223 if((npos
= response
.find(",")) != -1)
224 val
= response
.substr(0, npos
);
227 ev
->authparams
[head
] = val
;
228 response
.erase(0, npos
+1);
230 while(response
.substr(0, 1).find_first_of(", \t") != -1)
231 response
.erase(0, 1);
236 m_socket
->Disconnect();
237 SignalRemoveSocket(m_socket
->getSocketHandle());
238 m_state
= NOT_CONNECTED
;
245 if(response
.empty()) m_state
= RECEIVING_CONTENT
;
247 } else if(m_state
== RECEIVING_CONTENT
) {
248 m_content
+= response
+ "\n";
254 void HTTPClient::Send(Buffer
&b
) {
258 log
= "Sending HTTP command to " + IPtoString(m_socket
->getRemoteIP()) + ":" +
259 i2str(m_socket
->getRemotePort());
261 SignalLog(LogEvent::DIRECTPACKET
, log
);
264 } catch(SocketException e
) {
265 log
= (string
) "Failed to send: " + e
.what();
266 m_state
= NOT_CONNECTED
;
267 throw DisconnectedException(log
);
271 string
HTTPClient::strMethod(HTTPRequestEvent::RequestMethod m
) {
275 case HTTPRequestEvent::GET
: r
= "GET"; break;
276 case HTTPRequestEvent::POST
: r
= "POST"; break;
282 static string
getMD5(const string
&s
) {
284 md5_byte_t digest
[16];
290 md5_append(&state
, (md5_byte_t
*) s
.c_str(), s
.size());
291 md5_finish(&state
, digest
);
293 for(a
= 0; a
< 16; a
++) {
294 sprintf(hexdigest
, "%02x", digest
[a
]);
301 void HTTPClient::SendRequest() {
303 HTTPRequestEvent
*ev
= m_queue
.front();
307 rq
= strMethod(ev
->getMethod());
309 if(m_proxy_hostname
.empty()) {
310 b
.Pack(rq
+ " " + m_resource
+ " HTTP/1.0\r\n");
311 b
.Pack((string
) "Host: " + m_hostname
);
312 if(m_port
!= 80) b
.Pack((string
) ":" + i2str(m_port
));
315 b
.Pack(rq
+ " http://" + m_hostname
+ m_resource
+ " HTTP/1.0\r\n");
318 if(!m_proxy_user
.empty() && !m_proxy_hostname
.empty()) {
319 auto_ptr
<char> ba(cw_base64_encode((m_proxy_user
+ ":" + m_proxy_passwd
).c_str()));
320 b
.Pack((string
) "Proxy-Authorization: Basic " + ba
.get() + "\r\n");
323 // b.Pack((string) "Connection: keep-alive\r\n");
324 b
.Pack((string
) "User-Agent: " + PACKAGE
+ "/" + VERSION
+ "\r\n");
326 if(!ev
->m_user
.empty()) {
329 switch(ev
->authmethod
) {
330 case HTTPRequestEvent::Basic
: {
331 auto_ptr
<char> ba(cw_base64_encode((ev
->m_user
+ ":" + ev
->m_pass
).c_str()));
332 rq
= (string
) "Basic " + ba
.get();
336 case HTTPRequestEvent::User
:
337 rq
= (string
) "User " + ev
->m_user
+ ":" + ev
->m_pass
;
340 case HTTPRequestEvent::Digest
:
341 if(!ev
->authparams
.empty()) {
344 for(i
= 0; i
< 8; i
++) {
345 char c
= randlimit(0, 15);
346 if(c
< 10) c
+= 48; else c
+= 87;
350 ev
->authparams
["username"] = ev
->m_user
;
351 ev
->authparams
["uri"] = ev
->m_url
;
352 ev
->authparams
["nc"] = "00000001";
353 ev
->authparams
["cnonce"] = cnonce
;
355 string a1
= ev
->m_user
+ ":" + ev
->authparams
["realm"] + ":" + ev
->m_pass
;
356 string a2
= strMethod(ev
->getMethod()) + ":" + ev
->m_url
;
358 string mid
= (string
) ":" + ev
->authparams
["nonce"] + ":" +
359 ev
->authparams
["nc"] + ":" + cnonce
+ ":" +
360 ev
->authparams
["qop"] + ":";
362 ev
->authparams
["response"] = getMD5(getMD5(a1
) + mid
+ getMD5(a2
));
364 map
<string
, string
>::const_iterator ia
= ev
->authparams
.begin();
365 while(ia
!= ev
->authparams
.end()) {
366 if(ia
!= ev
->authparams
.begin()) rq
+= ", ";
367 rq
+= ia
->first
+ "=\"" + ia
->second
+ "\"";
376 b
.Pack((string
) "Authorization: " + rq
+ "\r\n");
380 if(ev
->getMethod() == HTTPRequestEvent::POST
) {
382 vector
<pair
<string
, string
> >::const_iterator ip
= ev
->pbegin();
384 b
.Pack("Content-type: application/x-www-form-urlencoded\r\n");
386 while(ip
!= ev
->pend()) {
387 len
+= ip
->first
.size() + ip
->second
.size() + 2;
392 b
.Pack((string
) "Content-length: " + i2str(len
) + "\r\n\r\n");
394 for(ip
= ev
->pbegin(); ip
!= ev
->pend(); ++ip
) {
395 if(ip
!= ev
->pbegin()) b
.Pack("&");
406 m_state
= WAITING_FOR_HEADER
;
409 void HTTPClient::Disconnect() {
410 SignalLog(LogEvent::WARN
, "HTTP disconnected");
411 m_socket
->Disconnect();
412 m_state
= NOT_CONNECTED
;
413 if(m_socket
->getSocketHandle() > -1) SignalRemoveSocket(m_socket
->getSocketHandle());
416 void HTTPClient::FinishNonBlockingConnect() {
420 void HTTPClient::Recv() {
424 while(m_socket
->connected()) {
425 if(!m_socket
->Recv(m_recv
)) break;
427 if(m_length
&& m_recv
.size() >= m_length
) break;
430 } catch(SocketException e
) {
432 throw DisconnectedException((string
) "Failed on recv: " + e
.what());
434 } catch(ParseException e
) {
436 throw DisconnectedException((string
) "Failed parsing: " + e
.what());
438 } catch(HTTPException e
) {
439 MessageEvent
*ev
= *m_queue
.begin();
440 ev
->setDelivered(false);
441 ev
->setFinished(true);
442 ev
->setDeliveryFailureReason(MessageEvent::Failed
);
449 SignalLog(LogEvent::ERROR
, e
.what());
453 if(m_length
&& m_recv
.size() >= m_length
) {
455 throw DisconnectedException("Content-Length reached");
459 void HTTPClient::check_timeout() {
460 time_t now
= time(0);
462 if(now
-m_last_operation
> m_timeout
) {
463 MessageEvent
*ev
= *m_queue
.begin();
464 ev
->setDelivered(false);
465 ev
->setFinished(true);
466 dynamic_cast<HTTPRequestEvent
*>(ev
)->setHTTPResp("Timed out");
467 ev
->setDeliveryFailureReason(MessageEvent::Failed
);
476 void HTTPClient::setTimeout(time_t t
) { m_timeout
= t
; }
477 time_t HTTPClient::getTimeout() const { return m_timeout
; }
479 void HTTPClient::clearoutMessagesPoll() {
480 if(!m_queue
.empty()) {
481 if(m_state
== NOT_CONNECTED
) {
489 void HTTPClient::SendEvent(MessageEvent
* ev
) {
490 HTTPRequestEvent
*rev
= dynamic_cast<HTTPRequestEvent
*>(ev
);
492 list
<HTTPRequestEvent
*>::const_iterator ir
= m_queue
.begin();
493 while(ir
!= m_queue
.end()) {
501 m_queue
.push_back(rev
);
503 if(m_state
== NOT_CONNECTED
) {
509 // ----------------------------------------------------------------------------
511 void HTTPClient::socket_cb(int fd
, SocketEvent::Mode m
) {
512 TCPSocket
*sock
= getSocket();
514 if(sock
->getState() == TCPSocket::NONBLOCKING_CONNECT
&& (m
& SocketEvent::WRITE
)) {
516 sock
->FinishNonBlockingConnect();
518 } catch(SocketException e
) {
519 SignalLog(LogEvent::ERROR
, (string
) "Failed on non-blocking connect for direct connection: " + e
.what());
520 SignalRemoveSocket(fd
);
525 SignalRemoveSocket(fd
);
526 SignalAddSocket(fd
, SocketEvent::READ
);
529 FinishNonBlockingConnect();
531 } catch(DisconnectedException e
) {
532 SignalLog(LogEvent::WARN
, e
.what());
533 SignalRemoveSocket(fd
);
536 } else if(sock
->getState() == TCPSocket::CONNECTED
&& (m
& SocketEvent::READ
)) {
540 } catch(DisconnectedException e
) {
541 SignalLog(LogEvent::WARN
, e
.what());
542 SignalRemoveSocket(fd
);
544 if(!m_recv
.empty()) {
546 m_recv
.Unpack(remains
, m_recv
.size());
547 m_content
+= remains
;
550 HTTPRequestEvent
*ev
= *m_queue
.begin();
551 ev
->setDelivered(true);
552 ev
->setFinished(true);
553 ev
->setContent(m_content
);
562 SignalLog(LogEvent::ERROR
, "Direct Connection socket in inconsistent state!");
563 SignalRemoveSocket(fd
);