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