]> git.saurik.com Git - wxWidgets.git/blame - src/common/http.cpp
Fix wxHtmlHelpData::SetTempDir() to behave correctly without trailing slash.
[wxWidgets.git] / src / common / http.cpp
CommitLineData
f4ada568 1/////////////////////////////////////////////////////////////////////////////
670f9935 2// Name: src/common/http.cpp
f4ada568
GL
3// Purpose: HTTP protocol
4// Author: Guilhem Lavaux
dbccd1c2 5// Modified by: Simo Virokannas (authentication, Dec 2005)
f4ada568 6// Created: August 1997
f4ada568 7// Copyright: (c) 1997, 1998 Guilhem Lavaux
65571936 8// Licence: wxWindows licence
f4ada568
GL
9/////////////////////////////////////////////////////////////////////////////
10
fcc6dddd
JS
11// For compilers that support precompilation, includes "wx.h".
12#include "wx/wxprec.h"
13
14#ifdef __BORLANDC__
670f9935 15 #pragma hdrstop
fcc6dddd
JS
16#endif
17
a5d46b73 18#if wxUSE_PROTOCOL_HTTP
ce4169a4 19
f4ada568
GL
20#include <stdio.h>
21#include <stdlib.h>
a8d628fc
DS
22
23#ifndef WX_PRECOMP
670f9935
WS
24 #include "wx/string.h"
25 #include "wx/app.h"
a8d628fc
DS
26#endif
27
f4ada568
GL
28#include "wx/tokenzr.h"
29#include "wx/socket.h"
30#include "wx/protocol/protocol.h"
fae05df5 31#include "wx/url.h"
f4ada568
GL
32#include "wx/protocol/http.h"
33#include "wx/sckstrm.h"
204abcd4 34#include "wx/thread.h"
f4ada568 35
730b772b
FM
36
37// ----------------------------------------------------------------------------
38// wxHTTP
39// ----------------------------------------------------------------------------
40
f4ada568 41IMPLEMENT_DYNAMIC_CLASS(wxHTTP, wxProtocol)
5d3e7b52 42IMPLEMENT_PROTOCOL(wxHTTP, wxT("http"), wxT("80"), true)
f4ada568 43
f4ada568 44wxHTTP::wxHTTP()
df5168c4 45 : wxProtocol()
f4ada568 46{
48f7ffbe
WS
47 m_addr = NULL;
48 m_read = false;
49 m_proxy_mode = false;
48f7ffbe 50 m_http_response = 0;
f4ada568 51
48f7ffbe 52 SetNotify(wxSOCKET_LOST_FLAG);
f4ada568
GL
53}
54
55wxHTTP::~wxHTTP()
2fb203e6
VZ
56{
57 ClearHeaders();
58
59 delete m_addr;
60}
61
62void wxHTTP::ClearHeaders()
f4ada568 63{
730b772b 64 m_headers.clear();
f4ada568
GL
65}
66
906cb3f1
JS
67void wxHTTP::ClearCookies()
68{
69 m_cookies.clear();
70}
71
730b772b 72wxString wxHTTP::GetContentType() const
f4ada568 73{
48f7ffbe 74 return GetHeader(wxT("Content-Type"));
f4ada568
GL
75}
76
f61815af
GL
77void wxHTTP::SetProxyMode(bool on)
78{
48f7ffbe 79 m_proxy_mode = on;
f61815af
GL
80}
81
bdcade0a 82wxHTTP::wxHeaderIterator wxHTTP::FindHeader(const wxString& header)
71414756 83{
bdcade0a
MB
84 wxHeaderIterator it = m_headers.begin();
85 for ( wxHeaderIterator en = m_headers.end(); it != en; ++it )
86 {
86501081 87 if ( header.CmpNoCase(it->first) == 0 )
bdcade0a
MB
88 break;
89 }
71414756 90
bdcade0a
MB
91 return it;
92}
93
94wxHTTP::wxHeaderConstIterator wxHTTP::FindHeader(const wxString& header) const
95{
96 wxHeaderConstIterator it = m_headers.begin();
97 for ( wxHeaderConstIterator en = m_headers.end(); it != en; ++it )
71414756 98 {
86501081 99 if ( header.CmpNoCase(it->first) == 0 )
71414756
VZ
100 break;
101 }
102
103 return it;
104}
105
906cb3f1
JS
106wxHTTP::wxCookieIterator wxHTTP::FindCookie(const wxString& cookie)
107{
108 wxCookieIterator it = m_cookies.begin();
109 for ( wxCookieIterator en = m_cookies.end(); it != en; ++it )
110 {
111 if ( cookie.CmpNoCase(it->first) == 0 )
112 break;
113 }
114
115 return it;
116}
117
118wxHTTP::wxCookieConstIterator wxHTTP::FindCookie(const wxString& cookie) const
119{
120 wxCookieConstIterator it = m_cookies.begin();
121 for ( wxCookieConstIterator en = m_cookies.end(); it != en; ++it )
122 {
123 if ( cookie.CmpNoCase(it->first) == 0 )
124 break;
125 }
126
127 return it;
128}
129
f4ada568
GL
130void wxHTTP::SetHeader(const wxString& header, const wxString& h_data)
131{
48f7ffbe
WS
132 if (m_read) {
133 ClearHeaders();
134 m_read = false;
135 }
f4ada568 136
48f7ffbe
WS
137 wxHeaderIterator it = FindHeader(header);
138 if (it != m_headers.end())
139 it->second = h_data;
140 else
141 m_headers[header] = h_data;
f4ada568
GL
142}
143
71414756 144wxString wxHTTP::GetHeader(const wxString& header) const
f4ada568 145{
bdcade0a 146 wxHeaderConstIterator it = FindHeader(header);
f4ada568 147
01482482 148 return it == m_headers.end() ? wxGetEmptyString() : it->second;
f4ada568
GL
149}
150
906cb3f1
JS
151wxString wxHTTP::GetCookie(const wxString& cookie) const
152{
153 wxCookieConstIterator it = FindCookie(cookie);
154
155 return it == m_cookies.end() ? wxGetEmptyString() : it->second;
156}
157
dbccd1c2
JS
158wxString wxHTTP::GenerateAuthString(const wxString& user, const wxString& pass) const
159{
e97cbf83
VZ
160 // TODO: Use wxBase64Encode() now that we have it instead of reproducing it
161
dbccd1c2
JS
162 static const char *base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
163
164 wxString buf;
165 wxString toencode;
166
167 buf.Printf(wxT("Basic "));
168
169 toencode.Printf(wxT("%s:%s"),user.c_str(),pass.c_str());
170
670f9935 171 size_t len = toencode.length();
dbccd1c2
JS
172 const wxChar *from = toencode.c_str();
173 while (len >= 3) { // encode full blocks first
174 buf << wxString::Format(wxT("%c%c"), base64[(from[0] >> 2) & 0x3f], base64[((from[0] << 4) & 0x30) | ((from[1] >> 4) & 0xf)]);
175 buf << wxString::Format(wxT("%c%c"), base64[((from[1] << 2) & 0x3c) | ((from[2] >> 6) & 0x3)], base64[from[2] & 0x3f]);
176 from += 3;
177 len -= 3;
178 }
179 if (len > 0) { // pad the remaining characters
180 buf << wxString::Format(wxT("%c"), base64[(from[0] >> 2) & 0x3f]);
181 if (len == 1) {
182 buf << wxString::Format(wxT("%c="), base64[(from[0] << 4) & 0x30]);
183 } else {
886f61ca 184 buf << wxString::Format(wxT("%c%c"), base64[((from[0] << 4) & 0x30) | ((from[1] >> 4) & 0xf)], base64[(from[1] << 2) & 0x3c]);
dbccd1c2 185 }
2523e9b7 186 buf << wxT("=");
dbccd1c2
JS
187 }
188
189 return buf;
190}
191
f9133b32
VZ
192void wxHTTP::SetPostBuffer(const wxString& post_buf)
193{
ab9d6a4c
VZ
194 // Use To8BitData() for backwards compatibility in this deprecated method.
195 // The new code should use the other overload or SetPostText() and specify
196 // the encoding to use for the text explicitly.
197 wxScopedCharBuffer scb = post_buf.To8BitData();
198 if ( scb.length() )
199 {
200 m_postBuffer.Clear();
201 m_postBuffer.AppendData(scb.data(), scb.length());
202 }
203}
204
205bool
206wxHTTP::SetPostBuffer(const wxString& contentType,
207 const wxMemoryBuffer& data)
208{
209 m_postBuffer = data;
210 m_contentType = contentType;
211
212 return !m_postBuffer.IsEmpty();
213}
214
215bool
216wxHTTP::SetPostText(const wxString& contentType,
217 const wxString& data,
218 const wxMBConv& conv)
219{
c84ef5b9 220#if wxUSE_UNICODE
ab9d6a4c 221 wxScopedCharBuffer scb = data.mb_str(conv);
c84ef5b9
VZ
222 const size_t len = scb.length();
223 const char* const buf = scb.data();
224#else // !wxUSE_UNICODE
225 const size_t len = data.length();
226 const char* const buf = data.mb_str(conv);
227#endif // wxUSE_UNICODE/!wxUSE_UNICODE
228
229 if ( !len )
ab9d6a4c
VZ
230 return false;
231
232 m_postBuffer.Clear();
c84ef5b9 233 m_postBuffer.AppendData(buf, len);
ab9d6a4c
VZ
234 m_contentType = contentType;
235
236 return true;
f9133b32
VZ
237}
238
f4ada568
GL
239void wxHTTP::SendHeaders()
240{
48f7ffbe
WS
241 typedef wxStringToStringHashMap::iterator iterator;
242 wxString buf;
f4ada568 243
48f7ffbe
WS
244 for (iterator it = m_headers.begin(), en = m_headers.end(); it != en; ++it )
245 {
246 buf.Printf(wxT("%s: %s\r\n"), it->first.c_str(), it->second.c_str());
53c6e7cc 247
48f7ffbe
WS
248 const wxWX2MBbuf cbuf = buf.mb_str();
249 Write(cbuf, strlen(cbuf));
250 }
f4ada568
GL
251}
252
253bool wxHTTP::ParseHeaders()
254{
48f7ffbe
WS
255 wxString line;
256 wxStringTokenizer tokenzr;
f4ada568 257
48f7ffbe 258 ClearHeaders();
906cb3f1 259 ClearCookies();
48f7ffbe 260 m_read = true;
f4ada568 261
a09f8377 262 for ( ;; )
48f7ffbe 263 {
730b772b
FM
264 m_lastError = ReadLine(this, line);
265 if (m_lastError != wxPROTO_NOERR)
48f7ffbe
WS
266 return false;
267
6636ef8d 268 if ( line.empty() )
48f7ffbe
WS
269 break;
270
271 wxString left_str = line.BeforeFirst(':');
906cb3f1
JS
272 if(!left_str.CmpNoCase("Set-Cookie"))
273 {
274 wxString cookieName = line.AfterFirst(':').Strip(wxString::both).BeforeFirst('=');
275 wxString cookieValue = line.AfterFirst(':').Strip(wxString::both).AfterFirst('=').BeforeFirst(';');
276 m_cookies[cookieName] = cookieValue;
277
278 // For compatibility
279 m_headers[left_str] = line.AfterFirst(':').Strip(wxString::both);
280 }
281 else
282 {
283 m_headers[left_str] = line.AfterFirst(':').Strip(wxString::both);
284 }
48f7ffbe
WS
285 }
286 return true;
f4ada568
GL
287}
288
f9133b32 289bool wxHTTP::Connect(const wxString& host, unsigned short port)
f4ada568 290{
48f7ffbe 291 wxIPV4address *addr;
f4ada568 292
48f7ffbe 293 if (m_addr) {
5276b0a5 294 wxDELETE(m_addr);
48f7ffbe
WS
295 Close();
296 }
f4ada568 297
48f7ffbe 298 m_addr = addr = new wxIPV4address();
f4ada568 299
48f7ffbe 300 if (!addr->Hostname(host)) {
5276b0a5 301 wxDELETE(m_addr);
730b772b 302 m_lastError = wxPROTO_NETERR;
48f7ffbe
WS
303 return false;
304 }
f4ada568 305
48f7ffbe
WS
306 if ( port )
307 addr->Service(port);
308 else if (!addr->Service(wxT("http")))
309 addr->Service(80);
ce22d615 310
1c7a6772
VZ
311 wxString hostHdr = host;
312 if ( port && port != 80 )
313 hostHdr << wxT(":") << port;
314 SetHeader(wxT("Host"), hostHdr);
f4ada568 315
730b772b 316 m_lastError = wxPROTO_NOERR;
48f7ffbe 317 return true;
f4ada568
GL
318}
319
ddc7f0c9 320bool wxHTTP::Connect(const wxSockAddress& addr, bool WXUNUSED(wait))
f4ada568 321{
48f7ffbe
WS
322 if (m_addr) {
323 delete m_addr;
324 Close();
325 }
f4ada568 326
48f7ffbe 327 m_addr = addr.Clone();
acd15a3f 328
48f7ffbe 329 wxIPV4address *ipv4addr = wxDynamicCast(&addr, wxIPV4address);
1c7a6772
VZ
330 if ( ipv4addr )
331 {
332 wxString hostHdr = ipv4addr->OrigHostname();
333 unsigned short port = ipv4addr->Service();
334 if ( port && port != 80 )
335 hostHdr << wxT(":") << port;
336 SetHeader(wxT("Host"), hostHdr);
337 }
5b96a71a 338
730b772b 339 m_lastError = wxPROTO_NOERR;
48f7ffbe 340 return true;
f4ada568
GL
341}
342
6535170f 343bool wxHTTP::BuildRequest(const wxString& path, const wxString& method)
f4ada568 344{
6535170f
VZ
345 // Use the data in the post buffer, if any.
346 if ( !m_postBuffer.IsEmpty() )
48f7ffbe 347 {
6535170f
VZ
348 wxString len;
349 len << m_postBuffer.GetDataLen();
350
351 // Content length must be correct, so always set, possibly
352 // overriding the value set explicitly by a previous call to
353 // SetHeader("Content-Length").
354 SetHeader(wxS("Content-Length"), len);
355
356 // However if the user had explicitly set the content type, don't
357 // override it with the content type passed to SetPostText().
358 if ( !m_contentType.empty() && GetContentType().empty() )
359 SetHeader(wxS("Content-Type"), m_contentType);
48f7ffbe
WS
360 }
361
362 m_http_response = 0;
363
364 // If there is no User-Agent defined, define it.
6636ef8d 365 if ( GetHeader(wxT("User-Agent")).empty() )
48f7ffbe
WS
366 SetHeader(wxT("User-Agent"), wxT("wxWidgets 2.x"));
367
dbccd1c2 368 // Send authentication information
670f9935 369 if (!m_username.empty() || !m_password.empty()) {
dbccd1c2
JS
370 SetHeader(wxT("Authorization"), GenerateAuthString(m_username, m_password));
371 }
372
48f7ffbe
WS
373 SaveState();
374
375 // we may use non blocking sockets only if we can dispatch events from them
d15e514e
VZ
376 int flags = wxIsMainThread() && wxApp::IsMainLoopRunning() ? wxSOCKET_NONE
377 : wxSOCKET_BLOCK;
378 // and we must use wxSOCKET_WAITALL to ensure that all data is sent
379 flags |= wxSOCKET_WAITALL;
380 SetFlags(flags);
48f7ffbe
WS
381 Notify(false);
382
383 wxString buf;
6535170f 384 buf.Printf(wxT("%s %s HTTP/1.0\r\n"), method, path);
86501081
VS
385 const wxWX2MBbuf pathbuf = buf.mb_str();
386 Write(pathbuf, strlen(pathbuf));
48f7ffbe
WS
387 SendHeaders();
388 Write("\r\n", 2);
389
6535170f
VZ
390 if ( !m_postBuffer.IsEmpty() ) {
391 Write(m_postBuffer.GetData(), m_postBuffer.GetDataLen());
ab9d6a4c
VZ
392
393 m_postBuffer.Clear();
48f7ffbe
WS
394 }
395
396 wxString tmp_str;
730b772b
FM
397 m_lastError = ReadLine(this, tmp_str);
398 if (m_lastError != wxPROTO_NOERR) {
48f7ffbe
WS
399 RestoreState();
400 return false;
401 }
402
403 if (!tmp_str.Contains(wxT("HTTP/"))) {
404 // TODO: support HTTP v0.9 which can have no header.
405 // FIXME: tmp_str is not put back in the in-queue of the socket.
730b772b 406 m_lastError = wxPROTO_NOERR;
48f7ffbe
WS
407 SetHeader(wxT("Content-Length"), wxT("-1"));
408 SetHeader(wxT("Content-Type"), wxT("none/none"));
409 RestoreState();
410 return true;
411 }
f4ada568 412
48f7ffbe
WS
413 wxStringTokenizer token(tmp_str,wxT(' '));
414 wxString tmp_str2;
415 bool ret_value;
416
417 token.NextToken();
418 tmp_str2 = token.NextToken();
419
420 m_http_response = wxAtoi(tmp_str2);
421
c9f78968 422 switch ( tmp_str2[0u].GetValue() )
48f7ffbe
WS
423 {
424 case wxT('1'):
425 /* INFORMATION / SUCCESS */
426 break;
427
428 case wxT('2'):
429 /* SUCCESS */
430 break;
431
432 case wxT('3'):
433 /* REDIRECTION */
434 break;
435
436 default:
730b772b 437 m_lastError = wxPROTO_NOFILE;
48f7ffbe
WS
438 RestoreState();
439 return false;
440 }
441
730b772b 442 m_lastError = wxPROTO_NOERR;
48f7ffbe
WS
443 ret_value = ParseHeaders();
444 RestoreState();
445 return ret_value;
f4ada568
GL
446}
447
730b772b
FM
448bool wxHTTP::Abort(void)
449{
450 return wxSocketClient::Close();
451}
452
453// ----------------------------------------------------------------------------
454// wxHTTPStream and wxHTTP::GetInputStream
455// ----------------------------------------------------------------------------
456
fc4b32c2
GRG
457class wxHTTPStream : public wxSocketInputStream
458{
f4ada568 459public:
48f7ffbe
WS
460 wxHTTP *m_http;
461 size_t m_httpsize;
462 unsigned long m_read_bytes;
9a1b2c28 463
f6687493
VZ
464 wxHTTPStream(wxHTTP *http) : wxSocketInputStream(*http)
465 {
466 m_http = http;
467 m_httpsize = 0;
468 m_read_bytes = 0;
469 }
470
48f7ffbe
WS
471 size_t GetSize() const { return m_httpsize; }
472 virtual ~wxHTTPStream(void) { m_http->Abort(); }
a324a7bc
GL
473
474protected:
48f7ffbe 475 size_t OnSysRead(void *buffer, size_t bufsize);
22f3361e 476
c0c133e1 477 wxDECLARE_NO_COPY_CLASS(wxHTTPStream);
f4ada568
GL
478};
479
a324a7bc
GL
480size_t wxHTTPStream::OnSysRead(void *buffer, size_t bufsize)
481{
ccc3a25d 482 if (m_read_bytes >= m_httpsize)
32013d27
VZ
483 {
484 m_lasterror = wxSTREAM_EOF;
485 return 0;
486 }
a324a7bc 487
32013d27
VZ
488 size_t ret = wxSocketInputStream::OnSysRead(buffer, bufsize);
489 m_read_bytes += ret;
a324a7bc 490
8b6b8e21
SC
491 if (m_httpsize==(size_t)-1 && m_lasterror == wxSTREAM_READ_ERROR )
492 {
493 // if m_httpsize is (size_t) -1 this means read until connection closed
494 // which is equivalent to getting a READ_ERROR, for clients however this
495 // must be translated into EOF, as it is the expected way of signalling
496 // end end of the content
730b772b 497 m_lasterror = wxSTREAM_EOF;
8b6b8e21
SC
498 }
499
32013d27 500 return ret;
a324a7bc
GL
501}
502
f4ada568
GL
503wxInputStream *wxHTTP::GetInputStream(const wxString& path)
504{
48f7ffbe 505 wxHTTPStream *inp_stream;
8f5bda17 506
48f7ffbe 507 wxString new_path;
f4ada568 508
730b772b 509 m_lastError = wxPROTO_CONNERR; // all following returns share this type of error
48f7ffbe
WS
510 if (!m_addr)
511 return NULL;
f4ada568 512
48f7ffbe 513 // We set m_connected back to false so wxSocketBase will know what to do.
ad8b8498 514#ifdef __WXMAC__
48f7ffbe
WS
515 wxSocketClient::Connect(*m_addr , false );
516 wxSocketClient::WaitOnConnect(10);
ad8b8498
SC
517
518 if (!wxSocketClient::IsConnected())
519 return NULL;
520#else
48f7ffbe
WS
521 if (!wxProtocol::Connect(*m_addr))
522 return NULL;
ad8b8498 523#endif
f4ada568 524
6535170f
VZ
525 // Use the user-specified method if any or determine the method to use
526 // automatically depending on whether we have anything to post or not.
527 wxString method = m_method;
528 if (method.empty())
529 method = m_postBuffer.IsEmpty() ? wxS("GET"): wxS("POST");
530
531 if (!BuildRequest(path, method))
48f7ffbe 532 return NULL;
f4ada568 533
48f7ffbe 534 inp_stream = new wxHTTPStream(this);
8f5bda17 535
48f7ffbe 536 if (!GetHeader(wxT("Content-Length")).empty())
86501081 537 inp_stream->m_httpsize = wxAtoi(GetHeader(wxT("Content-Length")));
48f7ffbe
WS
538 else
539 inp_stream->m_httpsize = (size_t)-1;
a324a7bc 540
48f7ffbe 541 inp_stream->m_read_bytes = 0;
a324a7bc 542
48f7ffbe
WS
543 Notify(false);
544 SetFlags(wxSOCKET_BLOCK | wxSOCKET_WAITALL);
9a1b2c28 545
730b772b
FM
546 // no error; reset m_lastError
547 m_lastError = wxPROTO_NOERR;
48f7ffbe 548 return inp_stream;
f4ada568 549}
35a4dab7 550
a5d46b73 551#endif // wxUSE_PROTOCOL_HTTP