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