]> git.saurik.com Git - wxWidgets.git/blob - src/common/http.cpp
Fix wxHtmlHelpData::SetTempDir() to behave correctly without trailing slash.
[wxWidgets.git] / src / common / http.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/http.cpp
3 // Purpose: HTTP protocol
4 // Author: Guilhem Lavaux
5 // Modified by: Simo Virokannas (authentication, Dec 2005)
6 // Created: August 1997
7 // Copyright: (c) 1997, 1998 Guilhem Lavaux
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_PROTOCOL_HTTP
19
20 #include <stdio.h>
21 #include <stdlib.h>
22
23 #ifndef WX_PRECOMP
24 #include "wx/string.h"
25 #include "wx/app.h"
26 #endif
27
28 #include "wx/tokenzr.h"
29 #include "wx/socket.h"
30 #include "wx/protocol/protocol.h"
31 #include "wx/url.h"
32 #include "wx/protocol/http.h"
33 #include "wx/sckstrm.h"
34 #include "wx/thread.h"
35
36
37 // ----------------------------------------------------------------------------
38 // wxHTTP
39 // ----------------------------------------------------------------------------
40
41 IMPLEMENT_DYNAMIC_CLASS(wxHTTP, wxProtocol)
42 IMPLEMENT_PROTOCOL(wxHTTP, wxT("http"), wxT("80"), true)
43
44 wxHTTP::wxHTTP()
45 : wxProtocol()
46 {
47 m_addr = NULL;
48 m_read = false;
49 m_proxy_mode = false;
50 m_http_response = 0;
51
52 SetNotify(wxSOCKET_LOST_FLAG);
53 }
54
55 wxHTTP::~wxHTTP()
56 {
57 ClearHeaders();
58
59 delete m_addr;
60 }
61
62 void wxHTTP::ClearHeaders()
63 {
64 m_headers.clear();
65 }
66
67 void wxHTTP::ClearCookies()
68 {
69 m_cookies.clear();
70 }
71
72 wxString wxHTTP::GetContentType() const
73 {
74 return GetHeader(wxT("Content-Type"));
75 }
76
77 void wxHTTP::SetProxyMode(bool on)
78 {
79 m_proxy_mode = on;
80 }
81
82 wxHTTP::wxHeaderIterator wxHTTP::FindHeader(const wxString& header)
83 {
84 wxHeaderIterator it = m_headers.begin();
85 for ( wxHeaderIterator en = m_headers.end(); it != en; ++it )
86 {
87 if ( header.CmpNoCase(it->first) == 0 )
88 break;
89 }
90
91 return it;
92 }
93
94 wxHTTP::wxHeaderConstIterator wxHTTP::FindHeader(const wxString& header) const
95 {
96 wxHeaderConstIterator it = m_headers.begin();
97 for ( wxHeaderConstIterator en = m_headers.end(); it != en; ++it )
98 {
99 if ( header.CmpNoCase(it->first) == 0 )
100 break;
101 }
102
103 return it;
104 }
105
106 wxHTTP::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
118 wxHTTP::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
130 void wxHTTP::SetHeader(const wxString& header, const wxString& h_data)
131 {
132 if (m_read) {
133 ClearHeaders();
134 m_read = false;
135 }
136
137 wxHeaderIterator it = FindHeader(header);
138 if (it != m_headers.end())
139 it->second = h_data;
140 else
141 m_headers[header] = h_data;
142 }
143
144 wxString wxHTTP::GetHeader(const wxString& header) const
145 {
146 wxHeaderConstIterator it = FindHeader(header);
147
148 return it == m_headers.end() ? wxGetEmptyString() : it->second;
149 }
150
151 wxString wxHTTP::GetCookie(const wxString& cookie) const
152 {
153 wxCookieConstIterator it = FindCookie(cookie);
154
155 return it == m_cookies.end() ? wxGetEmptyString() : it->second;
156 }
157
158 wxString wxHTTP::GenerateAuthString(const wxString& user, const wxString& pass) const
159 {
160 // TODO: Use wxBase64Encode() now that we have it instead of reproducing it
161
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
171 size_t len = toencode.length();
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 {
184 buf << wxString::Format(wxT("%c%c"), base64[((from[0] << 4) & 0x30) | ((from[1] >> 4) & 0xf)], base64[(from[1] << 2) & 0x3c]);
185 }
186 buf << wxT("=");
187 }
188
189 return buf;
190 }
191
192 void wxHTTP::SetPostBuffer(const wxString& post_buf)
193 {
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
205 bool
206 wxHTTP::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
215 bool
216 wxHTTP::SetPostText(const wxString& contentType,
217 const wxString& data,
218 const wxMBConv& conv)
219 {
220 #if wxUSE_UNICODE
221 wxScopedCharBuffer scb = data.mb_str(conv);
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 )
230 return false;
231
232 m_postBuffer.Clear();
233 m_postBuffer.AppendData(buf, len);
234 m_contentType = contentType;
235
236 return true;
237 }
238
239 void wxHTTP::SendHeaders()
240 {
241 typedef wxStringToStringHashMap::iterator iterator;
242 wxString buf;
243
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());
247
248 const wxWX2MBbuf cbuf = buf.mb_str();
249 Write(cbuf, strlen(cbuf));
250 }
251 }
252
253 bool wxHTTP::ParseHeaders()
254 {
255 wxString line;
256 wxStringTokenizer tokenzr;
257
258 ClearHeaders();
259 ClearCookies();
260 m_read = true;
261
262 for ( ;; )
263 {
264 m_lastError = ReadLine(this, line);
265 if (m_lastError != wxPROTO_NOERR)
266 return false;
267
268 if ( line.empty() )
269 break;
270
271 wxString left_str = line.BeforeFirst(':');
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 }
285 }
286 return true;
287 }
288
289 bool wxHTTP::Connect(const wxString& host, unsigned short port)
290 {
291 wxIPV4address *addr;
292
293 if (m_addr) {
294 wxDELETE(m_addr);
295 Close();
296 }
297
298 m_addr = addr = new wxIPV4address();
299
300 if (!addr->Hostname(host)) {
301 wxDELETE(m_addr);
302 m_lastError = wxPROTO_NETERR;
303 return false;
304 }
305
306 if ( port )
307 addr->Service(port);
308 else if (!addr->Service(wxT("http")))
309 addr->Service(80);
310
311 wxString hostHdr = host;
312 if ( port && port != 80 )
313 hostHdr << wxT(":") << port;
314 SetHeader(wxT("Host"), hostHdr);
315
316 m_lastError = wxPROTO_NOERR;
317 return true;
318 }
319
320 bool wxHTTP::Connect(const wxSockAddress& addr, bool WXUNUSED(wait))
321 {
322 if (m_addr) {
323 delete m_addr;
324 Close();
325 }
326
327 m_addr = addr.Clone();
328
329 wxIPV4address *ipv4addr = wxDynamicCast(&addr, wxIPV4address);
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 }
338
339 m_lastError = wxPROTO_NOERR;
340 return true;
341 }
342
343 bool wxHTTP::BuildRequest(const wxString& path, const wxString& method)
344 {
345 // Use the data in the post buffer, if any.
346 if ( !m_postBuffer.IsEmpty() )
347 {
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);
360 }
361
362 m_http_response = 0;
363
364 // If there is no User-Agent defined, define it.
365 if ( GetHeader(wxT("User-Agent")).empty() )
366 SetHeader(wxT("User-Agent"), wxT("wxWidgets 2.x"));
367
368 // Send authentication information
369 if (!m_username.empty() || !m_password.empty()) {
370 SetHeader(wxT("Authorization"), GenerateAuthString(m_username, m_password));
371 }
372
373 SaveState();
374
375 // we may use non blocking sockets only if we can dispatch events from them
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);
381 Notify(false);
382
383 wxString buf;
384 buf.Printf(wxT("%s %s HTTP/1.0\r\n"), method, path);
385 const wxWX2MBbuf pathbuf = buf.mb_str();
386 Write(pathbuf, strlen(pathbuf));
387 SendHeaders();
388 Write("\r\n", 2);
389
390 if ( !m_postBuffer.IsEmpty() ) {
391 Write(m_postBuffer.GetData(), m_postBuffer.GetDataLen());
392
393 m_postBuffer.Clear();
394 }
395
396 wxString tmp_str;
397 m_lastError = ReadLine(this, tmp_str);
398 if (m_lastError != wxPROTO_NOERR) {
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.
406 m_lastError = wxPROTO_NOERR;
407 SetHeader(wxT("Content-Length"), wxT("-1"));
408 SetHeader(wxT("Content-Type"), wxT("none/none"));
409 RestoreState();
410 return true;
411 }
412
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
422 switch ( tmp_str2[0u].GetValue() )
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:
437 m_lastError = wxPROTO_NOFILE;
438 RestoreState();
439 return false;
440 }
441
442 m_lastError = wxPROTO_NOERR;
443 ret_value = ParseHeaders();
444 RestoreState();
445 return ret_value;
446 }
447
448 bool wxHTTP::Abort(void)
449 {
450 return wxSocketClient::Close();
451 }
452
453 // ----------------------------------------------------------------------------
454 // wxHTTPStream and wxHTTP::GetInputStream
455 // ----------------------------------------------------------------------------
456
457 class wxHTTPStream : public wxSocketInputStream
458 {
459 public:
460 wxHTTP *m_http;
461 size_t m_httpsize;
462 unsigned long m_read_bytes;
463
464 wxHTTPStream(wxHTTP *http) : wxSocketInputStream(*http)
465 {
466 m_http = http;
467 m_httpsize = 0;
468 m_read_bytes = 0;
469 }
470
471 size_t GetSize() const { return m_httpsize; }
472 virtual ~wxHTTPStream(void) { m_http->Abort(); }
473
474 protected:
475 size_t OnSysRead(void *buffer, size_t bufsize);
476
477 wxDECLARE_NO_COPY_CLASS(wxHTTPStream);
478 };
479
480 size_t wxHTTPStream::OnSysRead(void *buffer, size_t bufsize)
481 {
482 if (m_read_bytes >= m_httpsize)
483 {
484 m_lasterror = wxSTREAM_EOF;
485 return 0;
486 }
487
488 size_t ret = wxSocketInputStream::OnSysRead(buffer, bufsize);
489 m_read_bytes += ret;
490
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
497 m_lasterror = wxSTREAM_EOF;
498 }
499
500 return ret;
501 }
502
503 wxInputStream *wxHTTP::GetInputStream(const wxString& path)
504 {
505 wxHTTPStream *inp_stream;
506
507 wxString new_path;
508
509 m_lastError = wxPROTO_CONNERR; // all following returns share this type of error
510 if (!m_addr)
511 return NULL;
512
513 // We set m_connected back to false so wxSocketBase will know what to do.
514 #ifdef __WXMAC__
515 wxSocketClient::Connect(*m_addr , false );
516 wxSocketClient::WaitOnConnect(10);
517
518 if (!wxSocketClient::IsConnected())
519 return NULL;
520 #else
521 if (!wxProtocol::Connect(*m_addr))
522 return NULL;
523 #endif
524
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))
532 return NULL;
533
534 inp_stream = new wxHTTPStream(this);
535
536 if (!GetHeader(wxT("Content-Length")).empty())
537 inp_stream->m_httpsize = wxAtoi(GetHeader(wxT("Content-Length")));
538 else
539 inp_stream->m_httpsize = (size_t)-1;
540
541 inp_stream->m_read_bytes = 0;
542
543 Notify(false);
544 SetFlags(wxSOCKET_BLOCK | wxSOCKET_WAITALL);
545
546 // no error; reset m_lastError
547 m_lastError = wxPROTO_NOERR;
548 return inp_stream;
549 }
550
551 #endif // wxUSE_PROTOCOL_HTTP