1 /* @source ajhttp *************************************************************
2 **
3 ** AJAX HTTP (database) functions
4 **
5 ** These functions control all aspects of AJAX http access
6 ** via SEND/GET/POST protocols
7 **
8 ** @author Copyright (C) 2010 Alan Bleasby
9 ** @version $Revision: 1.26 $
10 ** @modified $Date: 2012/12/07 10:15:39 $ by $Author: rice $
11 ** @@
12 **
13 ** This library is free software; you can redistribute it and/or
14 ** modify it under the terms of the GNU Lesser General Public
15 ** License as published by the Free Software Foundation; either
16 ** version 2.1 of the License, or (at your option) any later version.
17 **
18 ** This library is distributed in the hope that it will be useful,
19 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ** Lesser General Public License for more details.
22 **
23 ** You should have received a copy of the GNU Lesser General Public
24 ** License along with this library; if not, write to the Free Software
25 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 ** MA 02110-1301, USA.
27 **
28 ******************************************************************************/
29
30
31 #include "ajlib.h"
32
33 #include "ajhttp.h"
34 #include "ajsys.h"
35 #include "ajfile.h"
36 #include "ajreg.h"
37 #include "ajutil.h"
38 #include "ajnam.h"
39 #include "ajfileio.h"
40
41 #include <limits.h>
42 #include <stdarg.h>
43 #include <sys/types.h>
44 #ifndef WIN32
45 #include <sys/socket.h>
46 #include <netinet/in.h>
47 #include <arpa/inet.h>
48
49 #include <netdb.h>
50
51 #include <dirent.h>
52 #include <unistd.h>
53 #else
54 #include <winsock2.h>
55 #include <ws2tcpip.h>
56 #endif
57 #include <errno.h>
58 #include <signal.h>
59
60
61
62
63 static FILE* httpSend(const AjPStr dbname,
64 AjOSysSocket sock,
65 const AjPStr host, ajint iport,
66 const AjPStr proxyauth, const AjPStr proxycreds,
67 const AjPStr get);
68
69
70
71
72 /* @func ajHttpGetProxyinfo ***************************************************
73 **
74 ** Returns a proxy definition (if any). Any proxy string given as an
75 ** argument overrides any EMBOSS_PROXY definition. This
76 ** allows strings from DB definitions to override such an envvar.
77 **
78 ** @param [r] dbproxy [const AjPStr] Primary proxy string (if any)
79 ** @param [w] proxyport [ajint*] Proxy port
80 ** @param [w] proxyname [AjPStr*] Proxy name
81 ** @param [w] proxyauth [AjPStr*] Proxy authentication type (if any)
82 ** @param [w] proxycreds [AjPStr*] Proxy auth credentials (if any)
83 ** @return [AjBool] ajTrue if a proxy was defined
84 **
85 ** @release 6.4.0
86 ** @@
87 ******************************************************************************/
88
ajHttpGetProxyinfo(const AjPStr dbproxy,ajint * proxyport,AjPStr * proxyname,AjPStr * proxyauth,AjPStr * proxycreds)89 AjBool ajHttpGetProxyinfo(const AjPStr dbproxy, ajint* proxyport,
90 AjPStr* proxyname, AjPStr* proxyauth,
91 AjPStr* proxycreds)
92 {
93 AjPStr proxy = NULL;
94 AjPStr portStr = NULL;
95 const char *p = NULL;
96 const char *q = NULL;
97 AjPStrTok handle = NULL;
98
99 AjPStr serv = NULL;
100 AjPStr token = NULL;
101 AjBool ret = ajFalse;
102
103
104 ajStrDel(proxyname);
105 *proxyport = 0;
106 ajStrDel(proxyauth);
107 ajStrDel(proxycreds);
108
109
110 ajNamGetValueC("proxy", &proxy);
111
112 if(ajStrGetLen(dbproxy))
113 ajStrAssignS(&proxy, dbproxy);
114
115
116 token = ajStrNew();
117
118 ajStrTokenAssignC(&handle, proxy, " \n\r\t,");
119 ret = ajStrTokenNextParse(handle, &token);
120 if(!ret || ajStrMatchC(token, ":"))
121 {
122 ajStrDel(&proxy);
123 ajStrDel(&token);
124 ajStrTokenDel(&handle);
125
126 return ajFalse;
127 }
128
129
130 serv = ajStrNew();
131 portStr = ajStrNew();
132
133 ajStrAssignS(&serv, token);
134
135 if(ajStrMatchC(serv, ":"))
136 ajStrAssignClear(&serv);
137
138
139
140 p = ajStrGetPtr(serv);
141 if((q = strchr(p, (int)':')) && p != q)
142 {
143 ajStrAssignSubC(proxyname,p,0,q-p-1);
144 ajStrAssignC(&portStr,q+1);
145 }
146
147
148 if(!ajStrGetLen(*proxyname) || !ajStrGetLen(portStr))
149 {
150 ajStrTokenDel(&handle);
151 ajStrDel(&token);
152 ajStrDel(&serv);
153 ajStrDel(&proxy);
154 ajStrDel(&portStr);
155
156 return ajFalse;
157 }
158
159 ajStrToInt(portStr, proxyport);
160
161 ajStrDel(&portStr);
162 ajStrDel(&serv);
163
164 /* Check for authentication type */
165
166 ret = ajStrTokenNextParse(handle, &token);
167 if(!ret)
168 {
169 ajStrTokenDel(&handle);
170 ajStrDel(&token);
171 ajStrDel(&proxy);
172
173 return ajTrue;
174 }
175
176 ajStrAssignS(proxyauth, token);
177
178 /* Check for authentication credentials */
179
180 ret = ajStrTokenNextParse(handle, &token);
181 if(!ret)
182 {
183 ajWarn("ajHttpGetProxyinfo: No credentials specified in proxy "
184 "definition (%S)",proxy);
185 ajStrTokenDel(&handle);
186 ajStrDel(&token);
187 ajStrDel(&proxy);
188
189 return ajFalse;
190 }
191
192 ajStrAssignS(proxycreds,token);
193
194 ajStrTokenDel(&handle);
195 ajStrDel(&token);
196 ajStrDel(&proxy);
197
198 return ajTrue;
199 }
200
201
202
203
204 /* @func ajHttpGetVersion *****************************************************
205 **
206 ** Returns the HTTP version. Any supplied version takes precedence over
207 ** an EMBOSS_HTTPVERSION definition so allowing DB entries to
208 ** override such a setting.
209 **
210 ** @param [r] version [const AjPStr] Version or NULL (or zero-length string)
211 ** @param [w] httpver [AjPStr*] HTTP version
212 ** @return [AjBool] ajTrue if a version was defined
213 **
214 ** @release 6.4.0
215 ** @@
216 ******************************************************************************/
217
ajHttpGetVersion(const AjPStr version,AjPStr * httpver)218 AjBool ajHttpGetVersion(const AjPStr version, AjPStr* httpver)
219 {
220 ajNamGetValueC("httpversion", httpver);
221 ajDebug("httpver getValueC '%S'\n", *httpver);
222
223 if(ajStrGetLen(version))
224 ajStrAssignS(httpver, version);
225
226 ajDebug("httpver after qry '%S'\n", *httpver);
227
228 if(!ajStrGetLen(*httpver))
229 {
230 ajStrAssignC(httpver, "1.1");
231
232 return ajFalse;
233 }
234
235 if(!ajStrIsFloat(*httpver))
236 {
237 ajWarn("Invalid HTTPVERSION '%S', reset to 1.1", *httpver);
238 ajStrAssignC(httpver, "1.1");
239
240 return ajFalse;
241 }
242
243 ajDebug("httpver final '%S'\n", *httpver);
244
245 return ajTrue;
246 }
247
248
249
250
251 /* @func ajHttpOpen ***********************************************************
252 **
253 ** Opens an HTTP connection
254 **
255 ** @param [r] dbname [const AjPStr] Database name (for error reporting)
256 ** @param [r] host [const AjPStr] Host name
257 ** @param [r] iport [ajint] Port
258 ** @param [r] get [const AjPStr] GET string
259 ** @param [u] Psock [AjPSysSocket] Socket returned to caller
260 ** @return [FILE*] Open file on success, NULL on failure
261 **
262 ** @release 6.4.0
263 ** @@
264 ******************************************************************************/
265
ajHttpOpen(const AjPStr dbname,const AjPStr host,ajint iport,const AjPStr get,AjPSysSocket Psock)266 FILE* ajHttpOpen(const AjPStr dbname, const AjPStr host, ajint iport,
267 const AjPStr get, AjPSysSocket Psock)
268 {
269 FILE* fp;
270 struct addrinfo hints;
271 struct addrinfo *add = NULL;
272 struct addrinfo *addinit = NULL;
273
274 AjPStr portstr = NULL;
275
276 const char *phost = NULL;
277 const char *pport = NULL;
278
279 AjOSysSocket sock = *Psock;
280
281 AjPStr errstr = NULL;
282 int ret;
283
284 phost = ajStrGetPtr(host);
285 ajDebug("ajHttpOpen db: '%S' host '%S' port: %u get: '%S'\n",
286 dbname, host, iport, get);
287
288 memset(&hints,0,sizeof(hints));
289
290 hints.ai_family = AF_UNSPEC;
291 hints.ai_socktype = SOCK_STREAM;
292
293 portstr = ajStrNew();
294 ajFmtPrintS(&portstr,"%d",iport);
295 pport = ajStrGetPtr(portstr);
296
297 ret = getaddrinfo(phost, pport, &hints, &addinit);
298
299 ajStrDel(&portstr);
300
301 if(ret)
302 {
303 ajErr("[%s] Failed to find host '%S' for database '%S'",
304 gai_strerror(ret), host, dbname);
305
306 return NULL;
307 }
308
309 sock.sock = AJBADSOCK;
310
311 for(add = addinit; add; add = add->ai_next)
312 {
313 sock.sock = ajSysFuncSocket(add->ai_family, add->ai_socktype,
314 add->ai_protocol);
315
316 if(sock.sock == AJBADSOCK)
317 continue;
318
319 if(connect(sock.sock, add->ai_addr, add->ai_addrlen))
320 {
321 ajSysSocketclose(sock);
322 sock.sock = AJBADSOCK;
323 continue;
324 }
325
326 break;
327 }
328
329 freeaddrinfo(addinit);
330
331 if(sock.sock == AJBADSOCK)
332 {
333 ajDebug("Socket connect failed\n");
334 ajFmtPrintS(&errstr, "socket connect failed for database '%S': %s",
335 dbname, strerror(errno));
336 ajErr("%S", errstr);
337 ajStrDel(&errstr);
338
339 return NULL;
340 }
341
342 fp = httpSend(dbname, sock, host, iport, NULL, NULL, get);
343
344 return fp;
345 }
346
347
348
349
350 /* @func ajHttpOpenProxy ******************************************************
351 **
352 ** Opens an HTTP connection via a proxy
353 **
354 ** @param [r] dbname [const AjPStr] Databse name (for error reporting)
355 ** @param [r] proxyname [const AjPStr] Proxy name
356 ** @param [r] proxyport [ajint] Proxy port
357 ** @param [r] proxyauth [const AjPStr] Proxy auth type (if any)
358 ** @param [r] proxycreds [const AjPStr] Proxy auth credentials (if any)
359 ** @param [r] host [const AjPStr] Host name
360 ** @param [r] iport [ajint] Port
361 ** @param [r] get [const AjPStr] GET string
362 ** @param [u] Psock [AjPSysSocket] Socket returned to caller
363 ** @return [FILE*] Open file on success, NULL on failure
364 **
365 ** @release 6.4.0
366 ** @@
367 ******************************************************************************/
368
ajHttpOpenProxy(const AjPStr dbname,const AjPStr proxyname,ajint proxyport,const AjPStr proxyauth,const AjPStr proxycreds,const AjPStr host,ajint iport,const AjPStr get,AjPSysSocket Psock)369 FILE* ajHttpOpenProxy(const AjPStr dbname, const AjPStr proxyname,
370 ajint proxyport, const AjPStr proxyauth,
371 const AjPStr proxycreds, const AjPStr host,
372 ajint iport, const AjPStr get, AjPSysSocket Psock)
373 {
374 FILE* fp;
375 struct addrinfo hints;
376 struct addrinfo *add;
377 struct addrinfo *addinit;
378
379 AjPStr portstr = NULL;
380
381 const char *phost = NULL;
382 const char *pport = NULL;
383
384 AjOSysSocket sock = *Psock;
385
386 AjPStr errstr = NULL;
387 int ret;
388
389 phost = ajStrGetPtr(proxyname);
390 ajDebug("ajHttpOpenProxy db: '%S' host '%s' get: '%S'\n",
391 dbname, phost, get);
392
393 memset(&hints,0,sizeof(hints));
394
395 hints.ai_socktype = SOCK_STREAM;
396
397 portstr = ajStrNew();
398 ajFmtPrintS(&portstr,"%d",proxyport);
399 pport = ajStrGetPtr(portstr);
400
401 ret = getaddrinfo(phost, pport, &hints, &addinit);
402
403
404
405 if(ret)
406 {
407 ajErr("[%s] Failed to find host '%S' for service '%S'",
408 gai_strerror(ret), proxyname, portstr);
409 ajStrDel(&portstr);
410
411 return NULL;
412 }
413
414 ajStrDel(&portstr);
415
416 sock.sock = AJBADSOCK;
417
418 for(add = addinit; add; add = add->ai_next)
419 {
420 sock.sock = ajSysFuncSocket(add->ai_family, add->ai_socktype,
421 add->ai_protocol);
422
423 if(sock.sock == AJBADSOCK)
424 continue;
425
426 if(connect(sock.sock, add->ai_addr, add->ai_addrlen))
427 {
428 ajSysSocketclose(sock);
429 sock.sock = AJBADSOCK;
430 continue;
431 }
432
433 break;
434 }
435
436 freeaddrinfo(addinit);
437
438 if(sock.sock == AJBADSOCK)
439 {
440 ajDebug("Socket connect failed\n");
441 ajFmtPrintS(&errstr, "socket connect failed for database '%S': %s",
442 dbname, strerror(errno));
443 ajErr("%S", errstr);
444 ajStrDel(&errstr);
445
446 return NULL;
447 }
448
449 fp = httpSend(dbname, sock, host, iport, proxyauth, proxycreds, get);
450
451 return fp;
452 }
453
454
455
456
457 /* @funcstatic httpSend *******************************************************
458 **
459 ** Send HTTP GET request to an open socket
460 **
461 ** @param [r] dbname [const AjPStr] Database name (for error reporting)
462 ** @param [u] sock [AjOSysSocket] Socket structure
463 ** @param [r] host [const AjPStr] Host name for Host header line
464 ** @param [r] iport [ajint] Port for Host header line
465 ** @param [r] proxyauth [const AjPStr] Proxy auth type (if any)
466 ** @param [r] proxycreds [const AjPStr] Proxy auth credentials (if any)
467 ** @param [r] get [const AjPStr] GET string
468 ** @return [FILE*] Open file on success, NULL on failure
469 **
470 ** @release 6.4.0
471 ** @@
472 ******************************************************************************/
473
httpSend(const AjPStr dbname,AjOSysSocket sock,const AjPStr host,ajint iport,const AjPStr proxyauth,const AjPStr proxycreds,const AjPStr get)474 static FILE* httpSend(const AjPStr dbname,
475 AjOSysSocket sock,
476 const AjPStr host, ajint iport,
477 const AjPStr proxyauth, const AjPStr proxycreds,
478 const AjPStr get)
479 {
480 FILE* fp = NULL;
481 AjPStr gethead = NULL;
482 AjPStr cred = NULL;
483
484 ajint isendlen;
485
486 ajDebug("httpSend: Sending to socket\n");
487
488 gethead = ajStrNew();
489
490
491 isendlen = send(sock.sock, ajStrGetPtr(get), ajStrGetLen(get), 0);
492
493 if(isendlen < 0 || isendlen != (ajint) ajStrGetLen(get))
494 ajErr("send failure, expected %u bytes returned %d : %s",
495 ajStrGetLen(get), isendlen, ajMessGetSysmessageC());
496
497 ajDebug("sending: '%S'\n", get);
498 if(isendlen < 0)
499 ajDebug("send for GET errno %d msg '%s'\n",
500 errno, ajMessGetSysmessageC());
501
502 if(ajStrGetLen(proxyauth))
503 {
504 if(!ajStrMatchCaseC(proxyauth,"Basic"))
505 ajErr("Only 'Basic' proxy authentication currently implemented,\n"
506 "no 'Digest' or 'NTLM' [%S]",proxyauth);
507
508 cred = ajStrNew();
509
510 ajUtilBase64EncodeC(&cred, ajStrGetLen(proxycreds),
511 (const unsigned char *) ajStrGetPtr(proxycreds));
512
513 ajFmtPrintS(&gethead,"Proxy-Authorization: Basic %S\r\n",cred);
514
515 isendlen = send(sock.sock, ajStrGetPtr(gethead),
516 ajStrGetLen(gethead), 0);
517
518 if(isendlen < 0 || isendlen != (ajint) ajStrGetLen(gethead))
519 ajErr("send failure, expected %u bytes returned %d : %s",
520 ajStrGetLen(gethead), isendlen, ajMessGetSysmessageC());
521 ajDebug("sending: '%S'\n", gethead);
522 if(isendlen < 0)
523 ajDebug("send for host errno %d msg '%s'\n",
524 errno, ajMessGetSysmessageC());
525
526 ajStrDel(&cred);
527 }
528
529
530
531 ajFmtPrintS(&gethead, "User-Agent: EMBOSS/%S (%S)\r\n",
532 ajNamValueVersion(), ajNamValueSystem());
533 isendlen = send(sock.sock, ajStrGetPtr(gethead), ajStrGetLen(gethead), 0);
534
535 if(isendlen < 0 || isendlen != (ajint) ajStrGetLen(gethead))
536 ajErr("send failure, expected %u bytes returned %d : %s",
537 ajStrGetLen(gethead), isendlen, ajMessGetSysmessageC());
538 ajDebug("sending: '%S'\n", gethead);
539
540 ajFmtPrintS(&gethead, "Host: %S:%d\r\n", host, iport);
541 isendlen = send(sock.sock, ajStrGetPtr(gethead), ajStrGetLen(gethead), 0);
542
543 if(isendlen < 0 || isendlen != (ajint) ajStrGetLen(gethead))
544 ajErr("send failure, expected %u bytes returned %d : %s",
545 ajStrGetLen(gethead), isendlen, ajMessGetSysmessageC());
546 ajDebug("sending: '%S'\n", gethead);
547 if(isendlen < 0)
548 ajDebug("send for host errno %d msg '%s'\n",
549 errno, ajMessGetSysmessageC());
550
551 if(ajStrFindC(get,"HTTP/1.1") != -1)
552 {
553 ajFmtPrintS(&gethead, "Connection: close\r\n");
554 isendlen = send(sock.sock, ajStrGetPtr(gethead),ajStrGetLen(gethead),
555 0);
556
557 if(isendlen < 0 || isendlen != (ajint) ajStrGetLen(gethead))
558 ajErr("send failure, expected %u bytes returned %d : %s",
559 ajStrGetLen(gethead), isendlen, ajMessGetSysmessageC());
560
561 ajDebug("sending: '%S'\n", gethead);
562 if(isendlen < 0)
563 ajDebug("send for host errno %d msg '%s'\n",
564 errno, ajMessGetSysmessageC());
565 }
566
567 ajFmtPrintS(&gethead, "\r\n");
568 isendlen = send(sock.sock, ajStrGetPtr(gethead), ajStrGetLen(gethead), 0);
569
570 if(isendlen < 0 || isendlen != (ajint) ajStrGetLen(gethead))
571 ajErr("send failure, expected %u bytes returned %d : %s",
572 ajStrGetLen(gethead), isendlen, ajMessGetSysmessageC());
573 ajDebug("sending: '%S'\n", gethead);
574 if(isendlen < 0)
575 ajDebug("send for blankline errno %d msg '%s'\n",
576 errno, ajMessGetSysmessageC());
577
578 ajStrDel(&gethead);
579
580
581 fp = ajSysFdFromSocket(sock, "r");
582
583 if(!fp)
584 {
585 ajDebug("httpSend socket open failed\n");
586 ajErr("httpSend: socket open failed for database '%S'", dbname);
587
588 return NULL;
589 }
590
591 return fp;
592 }
593
594
595
596
597 /* @func ajHttpUrlrefNew ******************************************************
598 **
599 ** Initialise a URL components object
600 **
601 ** @return [AjPUrlref] URL Components
602 **
603 ** @release 6.4.0
604 ******************************************************************************/
605
ajHttpUrlrefNew(void)606 AjPUrlref ajHttpUrlrefNew(void)
607 {
608 AjPUrlref ret = NULL;
609
610 AJNEW0(ret);
611
612 ret->Method = ajStrNew();
613 ret->Host = ajStrNew();
614 ret->Port = ajStrNew();
615 ret->Absolute = ajStrNew();
616 ret->Relative = ajStrNew();
617 ret->Fragment = ajStrNew();
618 ret->Username = ajStrNew();
619 ret->Password = ajStrNew();
620
621 return ret;
622 }
623
624
625
626
627 /* @func ajHttpUrlrefDel ******************************************************
628 **
629 ** Delete URL components object
630 **
631 ** @param [u] thys [AjPUrlref*] URL components object
632 ** @return [void]
633 **
634 ** @release 6.4.0
635 ******************************************************************************/
636
ajHttpUrlrefDel(AjPUrlref * thys)637 void ajHttpUrlrefDel(AjPUrlref *thys)
638 {
639 AjPUrlref pthis = NULL;
640
641 if(!thys)
642 return;
643
644 if(!*thys)
645 return;
646
647 pthis = *thys;
648
649 ajStrDel(&pthis->Method);
650 ajStrDel(&pthis->Host);
651 ajStrDel(&pthis->Port);
652 ajStrDel(&pthis->Absolute);
653 ajStrDel(&pthis->Relative);
654 ajStrDel(&pthis->Fragment);
655 ajStrDel(&pthis->Username);
656 ajStrDel(&pthis->Password);
657
658 AJFREE(pthis);
659
660 *thys = NULL;
661
662 return;
663 }
664
665
666
667
668 /* @func ajHttpUrlrefParseC ***************************************************
669 **
670 ** Parse an IPV4/6 URL into its components
671 **
672 ** @param [u] parts [AjPUrlref*] URL components object
673 ** @param [u] url [const char*] URL
674 ** @return [void]
675 **
676 ** @release 6.4.0
677 ******************************************************************************/
678
ajHttpUrlrefParseC(AjPUrlref * parts,const char * url)679 void ajHttpUrlrefParseC(AjPUrlref *parts, const char *url)
680 {
681 char *ucopy = NULL;
682 char *p = NULL;
683 char *post = NULL;
684
685 char *dest = NULL;
686 char *src = NULL;
687
688 AjPUrlref comp = NULL;
689
690 char *pmethod = NULL;
691 char *phost = NULL;
692 char *pabs = NULL;
693 char *prel = NULL;
694
695 if(!parts || !url)
696 return;
697
698 if(!*parts)
699 return;
700
701 ucopy = ajCharNewC(url);
702
703 post = ucopy;
704 comp = *parts;
705
706 /* Get any fragment */
707 if ((p = strchr(ucopy, '#')))
708 {
709 *p++ = '\0';
710 ajStrAssignC(&comp->Fragment,p);
711 }
712
713 if((p = strchr(ucopy, ' ')))
714 *p++ = '\0';
715
716
717 for(p = ucopy; *p; ++p)
718 {
719 if (isspace((int) *p))
720 {
721 dest = p;
722 src = p+1;
723
724 while ((*dest++ = *src++));
725
726 p = p-1;
727 }
728
729 if (*p == '/' || *p == '#' || *p == '?')
730 break;
731
732 if (*p == ':')
733 {
734 *p = '\0';
735 pmethod = post;
736 post = p+1;
737
738 if(ajCharPrefixCaseC(pmethod,"URL"))
739 pmethod = NULL;
740 else
741 break;
742 }
743 }
744
745 p = post;
746
747 if(*p == '/')
748 {
749 if(p[1] == '/')
750 {
751 phost = p+2; /* There is a host */
752 *p = '\0';
753 p = strchr(phost,'/'); /* Find end of host */
754
755 if(p)
756 {
757 *p=0; /* and terminate it */
758 pabs = p+1; /* Path found */
759 }
760 }
761 else
762 pabs = p+1; /* Path found but not host */
763 }
764 else
765 prel = (*post) ? post : NULL;
766
767
768 if(pmethod)
769 ajStrAssignC(&comp->Method,pmethod);
770
771 if(phost)
772 ajStrAssignC(&comp->Host,phost);
773
774 if(pabs)
775 ajStrAssignC(&comp->Absolute,pabs);
776
777 if(prel)
778 ajStrAssignC(&comp->Relative,prel);
779
780 AJFREE(ucopy);
781
782 return;
783 }
784
785
786
787
788 /* @func ajHttpUrlrefSplitPort ************************************************
789 **
790 ** Separate any port from a host specification (IPV4/6)
791 **
792 ** @param [u] urli [AjPUrlref] URL components object
793 ** @return [void]
794 **
795 ** @release 6.4.0
796 ******************************************************************************/
797
ajHttpUrlrefSplitPort(AjPUrlref urli)798 void ajHttpUrlrefSplitPort(AjPUrlref urli)
799 {
800 const char *p = NULL;
801 const char *end = NULL;
802 const char *start = NULL;
803
804 ajint len;
805
806 if(!(len = ajStrGetLen(urli->Host)))
807 return;
808
809 end = (start = ajStrGetPtr(urli->Host)) + len - 1;
810
811 p = end;
812
813 if(!isdigit((int) *p))
814 return;
815
816 while(isdigit((int) *p) && p != start)
817 --p;
818
819 if(p == start)
820 return;
821
822 if(*p != ':')
823 return;
824
825 ajStrAssignC(&urli->Port,p+1);
826
827 ajStrAssignSubC(&urli->Host,start,0,p-start-1);
828
829 return;
830 }
831
832
833
834
835 /* @func ajHttpUrlrefParseS ***************************************************
836 **
837 ** Parse an IPV4/6 URL into its components
838 **
839 ** @param [u] parts [AjPUrlref*] URL components object
840 ** @param [r] surl [const AjPStr] URL
841 ** @return [void]
842 **
843 ** @release 6.5.0
844 ******************************************************************************/
845
ajHttpUrlrefParseS(AjPUrlref * parts,const AjPStr surl)846 void ajHttpUrlrefParseS(AjPUrlref *parts, const AjPStr surl)
847 {
848 ajHttpUrlrefParseC(parts, MAJSTRGETPTR(surl));
849 }
850
851
852
853
854 /* @func ajHttpUrlrefSplitUsername ********************************************
855 **
856 ** Separate any username[:password] from a host specification (IPV4/6)
857 **
858 ** @param [u] urli [AjPUrlref] URL components object
859 ** @return [void]
860 **
861 ** @release 6.4.0
862 ******************************************************************************/
863
ajHttpUrlrefSplitUsername(AjPUrlref urli)864 void ajHttpUrlrefSplitUsername(AjPUrlref urli)
865 {
866 const char *p = NULL;
867 const char *end = NULL;
868 AjPStr userpass = NULL;
869 AjPStr host = NULL;
870
871 ajint len;
872
873 if(!ajStrGetLen(urli->Host))
874 return;
875
876 if(!(end = strchr(ajStrGetPtr(urli->Host), (int)'@')))
877 return;
878
879 p = ajStrGetPtr(urli->Host);
880 len = end - p;
881
882 if(!len)
883 return;
884
885 userpass = ajStrNew();
886 ajStrAssignSubC(&userpass, p, 0, end - p - 1);
887
888 host = ajStrNew();
889 ajStrAssignC(&host,end + 1);
890 ajStrAssignS(&urli->Host,host);
891
892
893
894 if(!(end = strchr(ajStrGetPtr(userpass), (int)':')))
895 {
896 ajStrAssignS(&urli->Username,userpass);
897 ajStrDel(&userpass);
898 ajStrDel(&host);
899
900 return;
901 }
902
903 p = ajStrGetPtr(userpass);
904 len = end - p;
905
906 if(!len)
907 ajWarn("ajHttpUrlrefSplitUsername: Missing username in URL [%S@%S]",
908 userpass,host);
909 else
910 ajStrAssignSubC(&urli->Username,p,0,len - 1);
911
912 ajStrAssignC(&urli->Password, end + 1);
913
914 ajStrDel(&userpass);
915 ajStrDel(&host);
916
917 return;
918 }
919
920
921
922
923 /* @func ajHttpQueryUrl *******************************************************
924 **
925 ** Returns the components of a URL (IPV4/6)
926 ** An equivalent for seqHttpUrl().
927 **
928 ** @param [r] qry [const AjPQuery] Database query
929 ** @param [w] iport [ajint*] Port
930 ** @param [w] host [AjPStr*] Host name
931 ** @param [w] urlget [AjPStr*] URL for the HTTP header GET
932 ** @return [AjBool] ajTrue if the URL was parsed
933 **
934 ** @release 6.4.0
935 ** @@
936 ******************************************************************************/
937
ajHttpQueryUrl(const AjPQuery qry,ajint * iport,AjPStr * host,AjPStr * urlget)938 AjBool ajHttpQueryUrl(const AjPQuery qry, ajint* iport, AjPStr* host,
939 AjPStr* urlget)
940 {
941 const AjPStr url = NULL;
942 AjPUrlref uo = NULL;
943
944 url = qry->DbUrl;
945 if(!url)
946 {
947 ajErr("no URL defined for database %S", qry->DbName);
948
949 return ajFalse;
950 }
951
952 uo = ajHttpUrlrefNew();
953
954 ajHttpUrlrefParseC(&uo, ajStrGetPtr(url));
955 ajHttpUrlrefSplitPort(uo);
956
957 ajStrAssignS(host,uo->Host);
958 ajFmtPrintS(urlget,"/%S",uo->Absolute);
959
960 if(ajStrGetLen(uo->Port))
961 ajStrToInt(uo->Port,iport);
962
963 ajHttpUrlrefDel(&uo);
964
965 return ajTrue;
966 }
967
968
969
970
971 /* @func ajHttpUrlDeconstruct *************************************************
972 **
973 ** Deconstruct a URL (IPV4/6)
974 **
975 ** @param [r] url [const AjPStr] url
976 ** @param [w] iport [ajint*] Port
977 ** @param [w] host [AjPStr*] Host name
978 ** @param [w] urlget [AjPStr*] URL for the HTTP header GET
979 ** @return [void]
980 **
981 ** @release 6.4.0
982 ** @@
983 ******************************************************************************/
984
ajHttpUrlDeconstruct(const AjPStr url,ajint * iport,AjPStr * host,AjPStr * urlget)985 void ajHttpUrlDeconstruct(const AjPStr url, ajint* iport, AjPStr* host,
986 AjPStr* urlget)
987 {
988 AjPUrlref uo = NULL;
989
990 uo = ajHttpUrlrefNew();
991
992 ajHttpUrlrefParseC(&uo, ajStrGetPtr(url));
993 ajHttpUrlrefSplitPort(uo);
994
995 ajStrAssignS(host,uo->Host);
996 ajFmtPrintS(urlget,"/%S",uo->Absolute);
997
998 if(ajStrGetLen(uo->Port))
999 ajStrToInt(uo->Port,iport);
1000
1001 ajHttpUrlrefDel(&uo);
1002
1003 return;
1004 }
1005
1006
1007
1008
1009 /* @func ajHttpRedirect *******************************************************
1010 **
1011 ** Reads the header of http response in given buffer buff,
1012 ** if it includes a redirection response updates the host, port and get
1013 ** parameters using the 'Location' header
1014 **
1015 ** @param [u] buff [AjPFilebuff] file buffer
1016 ** @param [w] host [AjPStr*] Host name
1017 ** @param [w] port [ajint*] Port
1018 ** @param [w] path [AjPStr*] part of URL after port number
1019 ** @param [w] httpcode [ajuint*] HTTP protocol return code
1020 ** @return [AjBool] returns true if the header includes a redirection response
1021 **
1022 ** @release 6.4.0
1023 ** @@
1024 ******************************************************************************/
1025
ajHttpRedirect(AjPFilebuff buff,AjPStr * host,ajint * port,AjPStr * path,ajuint * httpcode)1026 AjBool ajHttpRedirect(AjPFilebuff buff, AjPStr* host, ajint* port, AjPStr* path,
1027 ajuint *httpcode)
1028 {
1029
1030 AjPRegexp httpexp = NULL;
1031 AjPRegexp nullexp = NULL;
1032 AjPRegexp redirexp = NULL;
1033
1034 AjPStr codestr = NULL;
1035 AjPStr newurl = NULL;
1036 AjPStr newhost = NULL;
1037 AjPStr currline = NULL;
1038
1039 AjBool isheader = ajFalse;
1040 AjBool ret = ajFalse;
1041
1042 httpexp = ajRegCompC("^HTTP/\\S+\\s+(\\d+)");
1043
1044 if(!buff->Size)
1045 return 0;
1046
1047 ajBuffreadLine(buff, &currline);
1048
1049 ajDebug("ajHttpRedirect: First line: '%S'\n", currline);
1050
1051 if(ajRegExec(httpexp, currline))
1052 {
1053 isheader = ajTrue;
1054 ajRegSubI(httpexp, 1, &codestr);
1055 ajStrToUint(codestr, httpcode);
1056 ajDebug("Header: codestr '%S' code '%u'\n", codestr, *httpcode);
1057 ajStrDel(&codestr);
1058 }
1059
1060 if(isheader)
1061 {
1062 if(*httpcode == 301 || *httpcode == 302 || *httpcode==307)
1063 {
1064 redirexp = ajRegCompC("^Location: (\\S+)");
1065 nullexp = ajRegCompC("^\r?\n?$");
1066
1067 while( ajBuffreadLine(buff, &currline) &&
1068 !ajRegExec(nullexp, currline))
1069 {
1070 ajDebug("ajHttpRedirect: header line: '%S'\n", currline);
1071
1072 if(ajRegExec(redirexp, currline))
1073 {
1074 ajRegSubI(redirexp, 1, &newurl);
1075 ajHttpUrlDeconstruct(newurl, port, &newhost, path);
1076
1077 if(ajStrGetLen(newhost))
1078 ajStrAssignS(host, newhost);
1079
1080 ajStrDel(&newurl);
1081 ajStrDel(&newhost);
1082 ret = ajTrue;
1083 break;
1084 }
1085 }
1086
1087 ajRegFree(&redirexp);
1088 ajRegFree(&nullexp);
1089 }
1090 }
1091
1092 if(!ret)
1093 ajFilebuffReset(buff);
1094
1095 ajRegFree(&httpexp);
1096 ajStrDel(&currline);
1097
1098 return ret;
1099 }
1100
1101
1102
1103
1104 /* @func ajHttpRead ***********************************************************
1105 **
1106 ** Reads the header of http response in given buffer buff,
1107 ** if it includes a redirection response updates the host, port and get
1108 ** parameters using the 'Location' header
1109 **
1110 ** @param [r] dbhttpver [const AjPStr] DB http version
1111 ** @param [r] dbname [const AjPStr] DB name
1112 ** @param [r] dbproxy [const AjPStr] DB proxy
1113 ** @param [r] host [const AjPStr] Host name
1114 ** @param [r] port [ajint] Port
1115 ** @param [r] dbpath [const AjPStr] part of URL after port number
1116 ** @return [AjPFilebuff] http response
1117 **
1118 ** @release 6.4.0
1119 ** @@
1120 ******************************************************************************/
1121
ajHttpRead(const AjPStr dbhttpver,const AjPStr dbname,const AjPStr dbproxy,const AjPStr host,ajint port,const AjPStr dbpath)1122 AjPFilebuff ajHttpRead(const AjPStr dbhttpver, const AjPStr dbname,
1123 const AjPStr dbproxy, const AjPStr host,
1124 ajint port, const AjPStr dbpath)
1125 {
1126 return ajHttpReadPos(dbhttpver, dbname, dbproxy, host, port, dbpath, 0L);
1127 }
1128
1129
1130
1131
1132 /* @func ajHttpReadPos ********************************************************
1133 **
1134 ** Reads the header of http response in given buffer buff,
1135 ** if it includes a redirection response updates the host, port and get
1136 ** parameters using the 'Location' header
1137 **
1138 ** @param [r] dbhttpver [const AjPStr] DB http version
1139 ** @param [r] dbname [const AjPStr] DB name
1140 ** @param [r] dbproxy [const AjPStr] DB proxy
1141 ** @param [r] host [const AjPStr] Host name
1142 ** @param [r] port [ajint] Port
1143 ** @param [r] dbpath [const AjPStr] part of URL after port number
1144 ** @param [r] fpos [ajlong] File start offset
1145 ** @return [AjPFilebuff] http response
1146 **
1147 ** @release 6.4.0
1148 ** @@
1149 ******************************************************************************/
1150
ajHttpReadPos(const AjPStr dbhttpver,const AjPStr dbname,const AjPStr dbproxy,const AjPStr host,ajint port,const AjPStr dbpath,ajlong fpos)1151 AjPFilebuff ajHttpReadPos(const AjPStr dbhttpver, const AjPStr dbname,
1152 const AjPStr dbproxy, const AjPStr host,
1153 ajint port, const AjPStr dbpath, ajlong fpos)
1154 {
1155 AjPStr get = NULL;
1156 AjPStr httpver = NULL;
1157 AjPStr proxyname = NULL;
1158 AjPStr proxyauth = NULL;
1159 AjPStr proxycred = NULL;
1160 AjPStr newhost = NULL;
1161 AjPStr path = NULL;
1162 AjPFilebuff buff = NULL;
1163 FILE *fp = NULL;
1164
1165 AjOSysSocket sock;
1166 AjOSysTimeout timo;
1167 ajint proxyport = 0;
1168 ajuint httpcode = 0;
1169
1170 httpver = ajStrNew();
1171 proxyname = ajStrNew();
1172 proxyauth = ajStrNew();
1173 proxycred = ajStrNew();
1174 get = ajStrNew();
1175 newhost = ajStrNew();
1176
1177 ajDebug("ajHttpRead db: '%S' host '%S' port: %u dbpath: '%S'\n",
1178 dbname, host, port, dbpath);
1179
1180 ajStrAssignS(&newhost,host);
1181 ajStrAssignS(&path, dbpath);
1182
1183
1184 ajHttpGetVersion(dbhttpver, &httpver);
1185
1186 ajHttpGetProxyinfo(dbproxy, &proxyport, &proxyname, &proxyauth, &proxycred);
1187
1188
1189 while (buff==NULL || ajHttpRedirect(buff, &newhost, &port, &path,
1190 &httpcode))
1191 {
1192 if(buff) /* means buff includes http redirect response*/
1193 ajFilebuffDel(&buff);
1194
1195 if(ajStrGetCharFirst(path)!='/')
1196 ajStrInsertK(&path, 0, '/');
1197
1198 if(ajStrGetLen(proxyname))
1199 {
1200 ajFmtPrintS(&get, "GET http://%S:%d%S HTTP/%S\r\n",
1201 host, port, path, httpver);
1202 fp = ajHttpOpenProxy(dbname, proxyname, proxyport, proxyauth,
1203 proxycred, newhost, port, get, &sock);
1204 }
1205 else
1206 {
1207 ajFmtPrintS(&get, "GET %S HTTP/%S\r\n", path, httpver);
1208 if(fpos)
1209 ajFmtPrintAppS(&get, "Range: bytes=%Ld-\r\n", fpos);
1210
1211 fp = ajHttpOpen(dbname, newhost, port, get, &sock);
1212 }
1213
1214 if(!fp)
1215 {
1216 ajErr("ajHttpRead: cannot open HTTP connection 'http://%S%S'",
1217 host, path);
1218 buff = NULL;
1219 break;
1220 }
1221
1222 buff = ajFilebuffNewFromCfile(fp);
1223
1224 if(!buff)
1225 {
1226 ajErr("ajHttpRead: socket buffer attach failed for host '%S'"
1227 ", HTTP get command was '%S'", host, get);
1228 break;
1229 }
1230
1231 timo.seconds = 180;
1232 ajSysTimeoutSet(&timo);
1233 ajFilebuffLoadAll(buff);
1234 ajSysTimeoutUnset(&timo);
1235 }
1236
1237 if(httpcode >= 400)
1238 {
1239 ajFilebuffDel(&buff);
1240 }
1241
1242 ajStrDel(&get);
1243 ajStrDel(&httpver);
1244 ajStrDel(&proxyname);
1245 ajStrDel(&newhost);
1246 ajStrDel(&path);
1247
1248 return buff;
1249 }
1250