blob: 8880b79a9d19b587a5940270e5ce93a7a06455ca [file] [log] [blame]
Emeric Vigier2f625822012-08-06 11:09:52 -04001// Copyright (C) 2001-2005 Open Source Telecom Corporation.
2// Copyright (C) 2006-2010 David Sugar, Tycho Softworks.
3//
4// This program is free software; you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation; either version 2 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17//
18// As a special exception, you may use this file as part of a free software
19// library without restriction. Specifically, if other files instantiate
20// templates or use macros or inline functions from this file, or you compile
21// this file and link it with other files to produce an executable, this
22// file does not by itself cause the resulting executable to be covered by
23// the GNU General Public License. This exception does not however
24// invalidate any other reasons why the executable file might be covered by
25// the GNU General Public License.
26//
27// This exception applies only to the code released under the name GNU
28// Common C++. If you copy code from other releases into a copy of GNU
29// Common C++, as the General Public License permits, the exception does
30// not apply to the code that you add in this way. To avoid misleading
31// anyone as to the status of such modified files, you must delete
32// this exception notice from them.
33//
34// If you write modifications of your own for GNU Common C++, it is your choice
35// whether to permit this exception to apply to your modifications.
36// If you do not wish that, delete this exception notice.
37//
38
39#include <cc++/config.h>
40#ifdef CCXX_WITHOUT_EXTRAS
41#include <cc++/export.h>
42#endif
43#include <cc++/file.h>
44#include <cc++/thread.h>
45#include <cc++/exception.h>
46#include <cc++/address.h>
47#include <cc++/socket.h>
48#ifndef CCXX_WITHOUT_EXTRAS
49#include <cc++/export.h>
50#endif
51#include <cc++/url.h>
52
53#include <string>
54#include <cstdio>
55#include <cstdlib>
56#include <fcntl.h>
57#include <cerrno>
58#include <iostream>
59
60#ifdef WIN32
61#include <io.h>
62#endif
63
64#ifdef HAVE_SSTREAM
65#include <sstream>
66#else
67#include <strstream>
68#endif
69
70#include <cctype>
71
72#ifndef WIN32
73// cause problem on Solaris
74#if !defined(__sun) && !defined(__SUN__)
75#ifdef HAVE_NET_IF_H
76#include <net/if.h>
77#endif
78#endif
79#include <sys/ioctl.h>
80#endif
81
82#ifdef CCXX_NAMESPACES
83namespace ost {
84using namespace std;
85#endif
86
87URLStream::URLStream(Family fam, timeout_t to) :
88TCPStream(fam)
89{
90 persistent = false;
91 proxyPort = 0;
92 timeout = to;
93 protocol = protocolHttp1_0;
94 follow = true;
95 proxyAuth = authAnonymous;
96 encoding = encodingBinary;
97 proxyUser = proxyPasswd = NULL;
98 auth = authAnonymous;
99 cookie = agent = pragma = referer = user = password = NULL;
100 localif = NULL;
101 setError(false);
102}
103
104int URLStream::aRead(char *buffer, size_t len, timeout_t timer)
105{
106 return readData(buffer, len, 0, timer);
107}
108
109int URLStream::aWrite(char *buffer, size_t len, timeout_t timer)
110{
111 return writeData(buffer, len, timer);
112}
113
114void URLStream::httpHeader(const char *header, const char *value)
115{
116}
117
118char **URLStream::extraHeader(void)
119{
120 return NULL;
121}
122
123int URLStream::underflow(void)
124{
125 ssize_t len = 0, rlen;
126 char *buf;
127
128 if(bufsize == 1)
129 return TCPStream::underflow();
130
131 if(!gptr())
132 return EOF;
133
134 if(gptr() < egptr())
135 return (unsigned char)*gptr();
136
137 rlen = (ssize_t)((gbuf + bufsize) - eback());
138 if(encoding == encodingChunked) {
139 buf = (char *)eback();
140 *buf = '\n';
141 while(!chunk && (*buf == '\n' || *buf == '\r')) {
142 *buf = 0;
143 len = readLine(buf, rlen, timeout);
144 }
145 if(len) {
146 if(!chunk)
147 chunk = strtol(buf, NULL, 16);
148
149 if(rlen > (int)chunk)
150 rlen = chunk;
151 }
152 else
153 rlen = -1;
154 }
155
156 if(rlen > 0) {
157 if(Socket::state == STREAM)
158 rlen = aRead((char *)eback(), rlen, timeout);
159 else if(timeout) {
160 if(Socket::isPending(pendingInput, timeout))
161 rlen = readData(eback(), rlen);
162 else
163 rlen = -1;
164 }
165 else
166 rlen = readData(eback(), rlen);
167 }
168 if(encoding == encodingChunked && rlen > 0)
169 chunk -= rlen;
170
171 if(rlen < 1) {
172 if(rlen < 0)
173 clear(ios::failbit | rdstate());
174 return EOF;
175 }
176 setg(eback(), eback(), eback() + rlen);
177 return (unsigned char)*gptr();
178}
179
180void URLStream::setProxy(const char *host, tpport_t port)
181{
182 switch(family) {
183#ifdef CCXX_IPV6
184 case IPV6:
185 v6proxyHost = host;
186 break;
187#endif
188 case IPV4:
189 proxyHost = host;
190 break;
191 default:
192 proxyPort = 0;
193 return;
194 }
195 proxyPort = port;
196}
197
198URLStream::Error URLStream::submit(const char *path, const char **vars, size_t buf)
199{
200 Error status = errInvalid, saved;
201
202 if(!strnicmp(path, "http:", 5)) {
203 urlmethod = methodHttpGet;
204 path = strchr(path + 5, '/');
205 status = sendHTTPHeader(path, vars, buf);
206 }
207 if((status == errInvalid || status == errTimeout)) {
208 if(Socket::state != AVAILABLE)
209 close();
210 return status;
211 }
212 else {
213 saved = status;
214 status = getHTTPHeaders();
215 if(status == errSuccess)
216 return saved;
217 else if(status == errTimeout) {
218 if(Socket::state != AVAILABLE)
219 close();
220 }
221 return status;
222 }
223}
224
225URLStream::Error URLStream::post(const char *path, MIMEMultipartForm &form, size_t buf)
226{
227 Error status = errInvalid, saved;
228 if(!strnicmp(path, "http:", 5)) {
229 urlmethod = methodHttpPostMultipart;
230 path = strchr(path + 5, '/');
231 status = sendHTTPHeader(path, (const char **)form.getHeaders(), buf);
232 }
233
234 if(status == errInvalid || status == errTimeout) {
235 if(Socket::state != AVAILABLE)
236 close();
237 return status;
238 }
239 saved = status;
240 status = getHTTPHeaders();
241 if(status == errSuccess) {
242 form.body(dynamic_cast<std::ostream *>(this));
243 return saved;
244 }
245 if(status == errTimeout) {
246 if(Socket::state != AVAILABLE)
247 close();
248 }
249 return status;
250}
251
252URLStream::Error URLStream::post(const char *path, const char **vars, size_t buf)
253{
254 Error status = errInvalid, saved;
255
256 if(!strnicmp(path, "http:", 5)) {
257 urlmethod = methodHttpPost;
258 path = strchr(path + 5, '/');
259 status = sendHTTPHeader(path, vars, buf);
260 }
261
262 if((status == errInvalid || status == errTimeout)) {
263 if(Socket::state != AVAILABLE)
264 close();
265 return status;
266 }
267 saved = status;
268 status = getHTTPHeaders();
269 if(status == errSuccess)
270 return saved;
271 if(status == errTimeout) {
272 if(Socket::state != AVAILABLE)
273 close();
274 }
275 return status;
276}
277
278
279URLStream::Error URLStream::head(const char *path, size_t buf)
280{
281 Error status = errInvalid, saved;
282
283 if(!strnicmp(path, "http:", 5)) {
284 urlmethod = methodHttpGet;
285 path = strchr(path + 5, '/');
286 status = sendHTTPHeader(path, NULL, buf);
287 }
288
289 if((status == errInvalid || status == errTimeout)) {
290 if(Socket::state != AVAILABLE)
291 close();
292 return status;
293 }
294 else {
295 saved = status;
296 status = getHTTPHeaders();
297 if(status == errSuccess)
298 return saved;
299 else if(status == errTimeout) {
300 if(Socket::state != AVAILABLE)
301 close();
302 }
303 return status;
304 }
305}
306
307URLStream &URLStream::getline(char *buffer, size_t size)
308{
309 size_t len;
310
311 *buffer = 0;
312 // TODO: check, we mix use of streambuf with Socket::readLine...
313 iostream::getline(buffer, (unsigned long)size);
314 len = strlen(buffer);
315
316 while(len) {
317 if(buffer[len - 1] == '\r' || buffer[len - 1] == '\n')
318 buffer[len - 1] = 0;
319 else
320 break;
321 --len;
322 }
323 return *this;
324}
325
326URLStream::Error URLStream::get(size_t buffer)
327{
328 String path = String("http://") + m_host;
329
330 if ( m_address.operator[](0) != '/' )
331 path += "/";
332
333 path += m_address;
334
335 return get(path.c_str(), buffer);
336}
337
338URLStream::Error URLStream::get(const char *urlpath, size_t buf)
339{
340 const char *path = urlpath;
341 Error status = errInvalid, saved;
342
343 urlmethod = methodFileGet;
344
345 if(Socket::state != AVAILABLE)
346 close();
347
348
349 if(!strnicmp(path, "file:", 5)) {
350 urlmethod = methodFileGet;
351 path += 5;
352 }
353 else if(!strnicmp(path, "http:", 5)) {
354 urlmethod = methodHttpGet;
355 path = strchr(path + 5, '/');
356 }
357 switch(urlmethod) {
358 case methodHttpGet:
359 status = sendHTTPHeader(path, NULL, buf);
360 break;
361 case methodFileGet:
362 if(so != INVALID_SOCKET)
363 ::close((int)so);
364 so = ::open(path, O_RDWR);
365 if(so == INVALID_SOCKET)
366 so = ::open(path, O_RDONLY);
367 // FIXME: open return the same handle type as socket call ??
368 if(so == INVALID_SOCKET)
369 return errInvalid;
370 Socket::state = STREAM;
371 allocate(buf);
372 return errSuccess;
373 default:
374 break;
375 }
376
377
378 if((status == errInvalid || status == errTimeout)) {
379 if(Socket::state != AVAILABLE)
380 close();
381 return status;
382 }
383 else {
384 saved = status;
385 status = getHTTPHeaders();
386 if(status == errSuccess)
387 return saved;
388 else if(status == errTimeout) {
389 if(Socket::state != AVAILABLE)
390 close();
391 }
392 return status;
393 }
394}
395
396URLStream::Error URLStream::getHTTPHeaders()
397{
398 char buffer[512];
399 size_t buf = sizeof(buffer);
400 Error status = errSuccess;
401 char *cp, *ep;
402 ssize_t len = 1;
403 char nc = 0;
404
405 chunk = ((unsigned)-1) / 2;
406 encoding = encodingBinary;
407 while(len > 0) {
408 len = readLine(buffer, buf, timeout);
409 if(len < 1)
410 return errTimeout;
411
412 // FIXME: for multiline syntax ??
413 if(buffer[0] == ' ' || buffer[0] == '\r' || buffer[0] == '\n')
414 break;
415 cp = strchr(buffer, ':');
416 if(!cp)
417 continue;
418 *(cp++) = 0;
419 while(*cp == ' ' || *cp == '\t')
420 ++cp;
421 ep = strchr(cp, '\n');
422 if(!ep)
423 ep = &nc;
424 while(*ep == '\n' || *ep == '\r' || *ep == ' ') {
425 *ep = 0;
426 if((--ep) < cp)
427 break;
428 }
429 if(!stricmp(buffer, "Transfer-Encoding")) {
430 if(!stricmp(cp, "chunked")) {
431 chunk = 0;
432 encoding = encodingChunked;
433 }
434 }
435 httpHeader(buffer, cp);
436 }
437 return status;
438}
439
440
441void URLStream::close(void)
442{
443 if(Socket::state == AVAILABLE)
444 return;
445
446 endStream();
447 so = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
448 if(so != INVALID_SOCKET)
449 Socket::state = AVAILABLE;
450}
451
452URLStream::Error URLStream::sendHTTPHeader(const char *url, const char **vars, size_t buf)
453{
454 // TODO: implement authentication
455 char reloc[4096];
456 // "//" host ":" port == max 2 + 128 + 1 + 5 + 1(\0) = 137, rounded 140
457 char host[140];
458 // TODO: add support for //user:pass@host:port/ syntax
459#ifdef HAVE_SSTREAM
460 ostringstream str;
461#else
462 char buffer[4096];
463 strstream str(buffer, sizeof(buffer), ios::out);
464#endif
465 char *ref, *cp, *ep;
466 char *hp;
467 const char *uri = "/";
468 int count = 0;
469 size_t len = 0;
470 tpport_t port = 80;
471 const char **args = vars;
472 const char *var;
473 bool lasteq = true;
474
475 struct servent *svc;
476
477retry:
478#ifdef HAVE_SSTREAM
479 str.str("");
480#else
481 buffer[0] = 0;
482 str.seekp(0);
483#endif
484
485 setString(host, sizeof(host), url);
486reformat:
487 hp = strchr(host, '/');
488 if(!hp) {
489 host[0] = '/';
490 setString(host + 1, sizeof(host) - 1, url);
491 goto reformat;
492 }
493 while(*hp == '/')
494 ++hp;
495 cp = strchr(hp, '/');
496 if (cp) *cp = 0;
497 ep = strrchr(hp, ':');
498 if(ep) {
499 *ep = 0;
500 ++ep;
501 if(isdigit(*ep))
502 port = atoi(ep);
503 else {
504 Socket::mutex.enter();
505 svc = getservbyname(ep, "tcp");
506 if(svc)
507 port = ntohs(svc->s_port);
508 Socket::mutex.leave();
509 }
510 }
511
512 if(!proxyPort) {
513 const char* ep1 = url;
514 while(*ep1 == '/')
515 ++ep1;
516 ep1 = strchr(ep1, '/');
517 if(ep1)
518 uri = ep1;
519 }
520
521 switch(urlmethod) {
522 case methodHttpGet:
523 str << "GET ";
524 if(proxyPort) {
525 str << "http:" << url;
526 if(!cp) str << '/';
527 }
528 else
529 str << uri;
530 break;
531 case methodHttpPost:
532 case methodHttpPostMultipart:
533 str << "POST ";
534 if(proxyPort) {
535 str << "http:" << url;
536 if(!cp) str << '/';
537 }
538 else
539 str << uri;
540 break;
541 default:
542 return errInvalid;
543 }
544
545 if(vars && urlmethod == methodHttpGet) {
546 str << "?";
547 while(*vars) {
548 if(count++ && lasteq)
549 str << "&";
550 str << *vars;
551 if(!lasteq)
552 lasteq = true;
553 else if(strchr(*vars, '='))
554 lasteq = true;
555 else {
556 lasteq = false;
557 str << "=";
558 }
559 ++vars;
560 }
561 }
562
563 switch(protocol) {
564 case protocolHttp1_1:
565 str << " HTTP/1.1" << "\r\n";
566 break;
567 case protocolHttp1_0:
568 str << " HTTP/1.0" << "\r\n";
569 break;
570 }
571
572 if ( m_host.empty() )
573 m_host = hp;
574
575 str << "Host: " << hp << "\r\n";
576 if(agent)
577 str << "User-Agent: " << agent << "\r\n";
578
579 if(cookie)
580 str << "Cookie: " << cookie << "\r\n";
581
582 if(pragma)
583 str << "Pragma: " << pragma << "\r\n";
584
585 if(referer)
586 str << "Referer: " << referer << "\r\n";
587
588 switch(auth) {
589 case authBasic:
590 str << "Authorization: Basic ";
591 snprintf(reloc, 64, "%s:%s", user, password);
592 b64Encode(reloc, reloc + 64, 128);
593 str << reloc + 64 << "\r\n";
594 case authAnonymous:
595 break;
596 }
597
598 switch(proxyAuth) {
599 case authBasic:
600 str << "Proxy-Authorization: Basic ";
601 snprintf(reloc, 64, "%s:%s", proxyUser, proxyPasswd);
602 b64Encode(reloc, reloc + 64, 128);
603 str << reloc + 64 << "\r\n";
604 str << "Proxy-Connection: close" << "\r\n";
605 case authAnonymous:
606 break;
607 }
608
609
610 str << "Connection: close\r\n";
611 char **add = extraHeader();
612 if(add) {
613 while(*add) {
614 str << *(add++) << ": ";
615 str << *(add++) << "\r\n";
616 }
617 }
618 if(vars)
619 switch(urlmethod) {
620 case methodHttpPost:
621 while(*args) {
622 var = *args;
623 if(count++ || !strchr(var, '='))
624 len += strlen(var) + 1;
625 else
626 len = strlen(var);
627 ++args;
628 }
629 count = 0;
630 len += 2;
631 str << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
632 str << "Content-Length: " << (unsigned)len << "\r\n";
633 break;
634 case methodHttpPostMultipart:
635 while(*args)
636 str << *(args++) << "\r\n";
637 default:
638 break;
639 }
640
641 str << "\r\n";
642#ifdef HAVE_SSTREAM
643 // sstream does not want ends
644#else
645 str << ends;
646#endif
647
648 if(Socket::state != AVAILABLE)
649 close();
650#ifndef WIN32
651#ifdef SOICGIFINDEX
652 if (localif != NULL) {
653 struct ifreq ifr;
654
655 switch(family) {
656#ifdef CCXX_IPV6
657 case IPV6:
658 sockaddr_in6 source;
659 int alen = sizeof(source);
660
661 memset(&ifr, 0, sizeof(ifr));
662 setString(ifr.ifr_name, sizeof(ifr.ifr_name), localif);
663 if (ioctl(so, SIOCGIFINDEX, &ifr) < 0)
664 return errInterface;
665 else {
666 if (setsockopt(so, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1)
667 return errInterface;
668 else if(getsockname(so, (struct sockaddr*)&source,(socklen_t *) &alen) == -1)
669 return errInterface;
670 else if (bind(so, (struct sockaddr*)&source, sizeof(source)) == -1)
671 return errInterface;
672 else
673 source.sin6_port = 0;
674 }
675
676 break;
677#endif
678 case IPV4:
679 sockaddr_in source;
680 int alen = sizeof(source);
681
682 memset(&ifr, 0, sizeof(ifr));
683 setString(ifr.ifr_name, sizeof(ifr.ifr_name), localif);
684 if (ioctl(so, SIOCGIFINDEX, &ifr) < 0)
685 return errInterface;
686 else {
687 if (setsockopt(so, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1)
688 return errInterface;
689 else if(getsockname(so, (struct sockaddr*)&source,(socklen_t *) &alen) == -1)
690 return errInterface;
691 else if (bind(so, (struct sockaddr*)&source, sizeof(source)) == -1)
692 return errInterface;
693 else
694 source.sin_port = 0;
695 }
696 }
697
698 }
699#endif
700#endif
701
702 if(proxyPort) {
703 switch(family) {
704#ifdef CCXX_IPV6
705 case IPV6:
706 connect(v6proxyHost, proxyPort, (unsigned)buf);
707 break;
708#endif
709 case IPV4:
710 connect(proxyHost, proxyPort, (unsigned)buf);
711 break;
712 }
713 }
714 else {
715 switch(family) {
716#ifdef CCXX_IPV6
717 case IPV6:
718 connect(IPV6Host(hp), port, (unsigned)buf);
719 break;
720#endif
721 case IPV4:
722 connect(IPV4Host(hp), port, (unsigned)buf);
723 }
724 }
725
726 if(!isConnected())
727 return errUnreachable;
728
729 // FIXME: send (or write) can send less than len bytes
730 // use stream funcion ??
731#ifdef HAVE_SSTREAM
732 writeData(str.str().c_str(), _IOLEN64 str.str().length());
733#else
734 writeData(str.str().c_str(), _IOLEN64 str.str().length());
735#endif
736
737 if(urlmethod == methodHttpPost && vars) {
738#ifdef HAVE_SSTREAM
739 str.str() = "";
740#else
741 str.seekp(0);
742#endif
743 bool sep = false;
744 while(*vars) {
745 if(sep)
746 writeData("&", 1);
747 else
748 sep = true;
749 var = *vars;
750 if(!strchr(var, '=')) {
751 snprintf(reloc, sizeof(reloc), "%s=%s", var, *(++vars));
752 writeData(reloc, strlen(reloc));
753 }
754 else
755 writeData(var, strlen(var));
756 ++vars;
757 }
758 writeData("\r\n", 2);
759 }
760
761cont:
762#ifdef HAVE_SSTREAM
763 char buffer[4096];
764#else
765 // nothing here
766#endif
767
768 len = readLine(buffer, sizeof(buffer) - 1, timeout);
769 if(len < 1)
770 return errTimeout;
771
772 if(strnicmp(buffer, "HTTP/", 5))
773 return errInvalid;
774
775 ref = strchr(buffer, ' ');
776 while(*ref == ' ')
777 ++ref;
778
779 switch(atoi(ref)) {
780 default:
781 return errInvalid;
782 case 100:
783 goto cont;
784 case 200:
785 return errSuccess;
786 case 401:
787 return errUnauthorized;
788 case 403:
789 return errForbidden;
790 case 404:
791 return errMissing;
792 case 405:
793 return errDenied;
794 case 500:
795 case 501:
796 case 502:
797 case 503:
798 case 504:
799 case 505:
800 return errFailure;
801 case 300:
802 case 301:
803 case 302:
804 break;
805 }
806 if(!follow)
807 return errRelocated;
808 for(;;) {
809 len = readLine(reloc, sizeof(reloc), timeout);
810 if(len < 1)
811 return errTimeout;
812 if(!strnicmp(reloc, "Location: ", 10))
813 break;
814 }
815 if(!strnicmp(reloc + 10, "http:", 5)) {
816 url = strchr(reloc + 15, '/');
817 ep = (char *)(url + strlen(url) - 1);
818 while(*ep == '\r' || *ep == '\n')
819 *(ep--) = 0;
820 }
821 else
822 url = reloc + 10;
823 close();
824 goto retry;
825}
826
827void URLStream::setAuthentication(Authentication a, const char *value)
828{
829 auth = a;
830 if (auth != authAnonymous) {
831 if(!user)
832 user = "anonymous";
833 if(!password)
834 password = "";
835 }
836}
837
838void URLStream::setProxyAuthentication(Authentication a, const char *value)
839{
840 proxyAuth = a;
841 if (proxyAuth != authAnonymous) {
842 if(!proxyUser)
843 proxyUser = "anonymous";
844
845 if(!proxyPasswd)
846 proxyPasswd = "";
847 }
848}
849
850void URLStream::setReferer(const char *str)
851{
852 if(!str)
853 return;
854 referer = str;
855}
856
857#ifdef CCXX_NAMESPACES
858}
859#endif
860
861/** EMACS **
862 * Local variables:
863 * mode: c++
864 * c-basic-offset: 4
865 * End:
866 */