1 /* HTInet.c
2 ** GENERIC INTERNET UTILITIES
3 **
4 ** (c) COPYRIGHT MIT 1995.
5 ** Please first read the full copyright statement in the file COPYRIGH.
6 ** @(#) $Id$
7 **
8 ** This code is in common between client and server sides.
9 **
10 ** 16 Mar 96 HFN Spawned off from HTTCP.c
11 */
12
13 /* Library include files */
14 #include "wwwsys.h"
15 #include "WWWUtil.h"
16 #include "HTParse.h"
17 #include "HTAlert.h"
18 #include "HTError.h"
19 #include "HTNetMan.h"
20 #include "HTDNS.h"
21 #include "HTInet.h" /* Implemented here */
22
23 #ifndef DEFAULT_NEWS_HOST
24 #define DEFAULT_NEWS_HOST "news"
25 #endif
26
27 #ifndef SERVER_FILE
28 #define SERVER_FILE "/usr/local/lib/rn/server"
29 #endif
30
31 /* ------------------------------------------------------------------------- */
32
33 /*
34 ** Returns the string equivalent to the errno passed in the argument.
35 ** We can't use errno directly as we have both errno and socerrno. The
36 ** result is a dynamic string that must be freed by the caller.
37 */
HTErrnoString(int errornumber)38 PUBLIC char * HTErrnoString (int errornumber)
39 {
40 char * msg = NULL;
41 #ifdef _WINSOCKAPI_
42 if ((msg = (char *) HT_MALLOC(64)) == NULL)
43 HT_OUTOFMEM("HTErrnoString");
44 *msg = '\0';
45 sprintf(msg, "WinSock reported error=%ld", WSAGetLastError());
46 #else
47 #ifdef HAVE_STRERROR
48 StrAllocCopy(msg, strerror(errornumber));
49 #else
50 #ifdef HAVE_SYS_ERRLIST
51 #ifdef HAVE_SYS_NERR
52 if (errno < sys_nerr)
53 StrAllocCopy(msg, sys_errlist[errno]);
54 else
55 StrAllocCopy(msg, "Unknown error");
56 #else
57 StrAllocCopy(msg, sys_errlist[errno]);
58 #endif /* HAVE_SYS_NERR */
59 #else
60 #ifdef VMS
61 if ((msg = (char *) HT_MALLOC(64)) == NULL)
62 HT_OUTOFMEM("HTErrnoString");
63 *msg = '\0';
64 sprintf(msg, "Unix errno=%ld dec, VMS error=%lx hex", errornumber,
65 vaxc$errno);
66 #else
67 StrAllocCopy(msg, "Error number not translated!");
68 #endif /* _WINSOCKAPI_ */
69 #endif /* VMS */
70 #endif /* HAVE_SYS_ERRLIST */
71 #endif /* HAVE_STRERROR */
72 return msg;
73 }
74
75
76 /* Debug error message
77 */
HTInetStatus(int errnum,char * where)78 PUBLIC int HTInetStatus (int errnum, char * where)
79 {
80 #ifdef VMS
81 HTTRACE(CORE_TRACE, "System Error Unix = %ld dec\n" _ errno);
82 HTTRACE(CORE_TRACE, "System Error VMS = %lx hex\n" _ vaxc$errno);
83 return (-vaxc$errno);
84 #else
85 #ifdef _WINSOCKAPI_
86 HTTRACE(CORE_TRACE, "System Error Unix = %ld dec\n" _ errno);
87 HTTRACE(CORE_TRACE, "System Error WinSock error=%lx hex\n" _
88 WSAGetLastError());
89 return (-errnum);
90 #else
91 #ifdef HTDEBUG
92 if (CORE_TRACE) {
93 char * errmsg = HTErrnoString(errnum);
94 HTTRACE(CORE_TRACE, "System Error %d after call to %s() failed\n............ %s\n" _
95 errno _ where _ errmsg);
96 HT_FREE(errmsg);
97 }
98 #endif /* HTDEBUG */
99 return (-errnum);
100 #endif /* _WINSOCKAPI_ */
101 #endif /* VMS */
102 }
103
104
105 /* Parse a cardinal value parse_cardinal()
106 ** ----------------------
107 **
108 ** On entry,
109 ** *pp points to first character to be interpreted, terminated by
110 ** non 0:9 character.
111 ** *pstatus points to status already valid
112 ** maxvalue gives the largest allowable value.
113 **
114 ** On exit,
115 ** *pp points to first unread character
116 ** *pstatus points to status updated iff bad
117 */
118
HTCardinal(int * pstatus,char ** pp,unsigned int max_value)119 PUBLIC unsigned int HTCardinal (int * pstatus,
120 char ** pp,
121 unsigned int max_value)
122 {
123 unsigned int n=0;
124 if ( (**pp<'0') || (**pp>'9')) { /* Null string is error */
125 *pstatus = -3; /* No number where one expeceted */
126 return 0;
127 }
128 while ((**pp>='0') && (**pp<='9')) n = n*10 + *((*pp)++) - '0';
129
130 if (n>max_value) {
131 *pstatus = -4; /* Cardinal outside range */
132 return 0;
133 }
134
135 return n;
136 }
137
138 /* ------------------------------------------------------------------------- */
139 /* SIGNAL HANDLING */
140 /* ------------------------------------------------------------------------- */
141
142 #ifdef WWWLIB_SIG
143 /* HTSetSignal
144 ** This function sets up signal handlers. This might not be necessary to
145 ** call if the application has its own handlers.
146 */
147 #include <signal.h>
148
HTSetSignal(void)149 PUBLIC void HTSetSignal (void)
150 {
151 /* On some systems (SYSV) it is necessary to catch the SIGPIPE signal
152 ** when attemting to connect to a remote host where you normally should
153 ** get `connection refused' back
154 */
155 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
156 HTTRACE(CORE_TRACE, "HTSignal.... Can't catch SIGPIPE\n");
157 } else {
158 HTTRACE(CORE_TRACE, "HTSignal.... Ignoring SIGPIPE\n");
159 }
160 }
161 #else /* WWWLIB_SIG */
162
HTSetSignal(void)163 PUBLIC void HTSetSignal (void) { }
164
165 #endif /* WWWLIB_SIG */
166
167 /* ------------------------------------------------------------------------- */
168 /* HOST NAME FUNCTIONS */
169 /* ------------------------------------------------------------------------- */
170
171 /* Produce a string for an Internet address
172 ** ----------------------------------------
173 **
174 ** On exit,
175 ** returns a pointer to a static string which must be copied if
176 ** it is to be kept.
177 */
HTInetString(SockA * sin)178 PUBLIC const char * HTInetString (SockA * sin)
179 {
180 #ifndef DECNET /* Function only used below for a trace message */
181 #if 0
182 /* This dumps core on some Sun systems :-(. The problem is now, that
183 the current implememtation only works for IP-addresses and not in
184 other address spaces. */
185 return inet_ntoa(sin->sin_addr);
186 #endif
187 static char string[16];
188 sprintf(string, "%d.%d.%d.%d",
189 (int)*((unsigned char *)(&sin->sin_addr)+0),
190 (int)*((unsigned char *)(&sin->sin_addr)+1),
191 (int)*((unsigned char *)(&sin->sin_addr)+2),
192 (int)*((unsigned char *)(&sin->sin_addr)+3));
193 return string;
194 #else
195 return "";
196 #endif /* Decnet */
197 }
198
199 /* Parse a network node address and port
200 ** -------------------------------------
201 ** It is assumed that any portnumber and numeric host address
202 ** is given in decimal notation. Separation character is '.'
203 ** Any port number gets chopped off
204 ** Returns:
205 ** >0 Number of homes
206 ** 0 Wait for persistent socket
207 ** -1 Error
208 */
HTParseInet(HTHost * host,char * hostname,HTRequest * request)209 PUBLIC int HTParseInet (HTHost * host, char * hostname, HTRequest * request)
210 {
211 int status = 1;
212 SockA *sin = &host->sock_addr;
213
214 #ifdef DECNET
215 /* read Decnet node name. @@ Should know about DECnet addresses, but it's
216 probably worth waiting until the Phase transition from IV to V. */
217
218 sin->sdn_nam.n_len = min(DN_MAXNAML, strlen(hostname)); /* <=6 in phase 4 */
219 strncpy (sin->sdn_nam.n_name, hostname, sin->sdn_nam.n_len + 1);
220
221 HTTRACE(CORE_TRACE, "DECnet: Parsed address as object number %d on host %.6s...\n" _
222 sin->sdn_objnum _ hostname);
223 #else /* Internet */
224 {
225 char *strptr = hostname;
226 while (*strptr) {
227 if (*strptr == ':') {
228 *strptr = '\0'; /* Don't want port number in numeric host */
229 break;
230 }
231 if (!isdigit((int) *strptr) && *strptr != '.')
232 break;
233 strptr++;
234 }
235 if (!*strptr) {
236 #ifdef GUSI
237 sin->sin_addr = inet_addr(hostname); /* See netinet/in.h */
238 #else
239 sin->sin_addr.s_addr = inet_addr(hostname); /* See arpa/inet.h */
240 #endif
241 } else {
242 char * port = strchr(hostname, ':'); /* Chop port */
243 if (port) *port = '\0';
244 status = HTGetHostByName(host, hostname, request);
245 }
246 #ifdef HTDEBUG
247 if (status > 0)
248 HTTRACE(CORE_TRACE, "ParseInet... as port %d on %s with %d homes\n" _
249 (int) ntohs(sin->sin_port) _ HTInetString(sin) _ status);
250 #endif /* HTDEBUG */
251 }
252 #endif /* Internet vs. Decnet */
253 return status;
254 }
255
256
257 #if 0
258 /* HTGetDomainName
259 ** Returns the current domain name without the local host name.
260 ** The response is pointing to a static area that might be changed
261 ** using HTSetHostName().
262 **
263 ** Returns NULL on error, "" if domain name is not found
264 */
265 PRIVATE char * HTGetDomainName (void)
266 {
267 char * host = HTGetHostName();
268 char * domain;
269 if (host && *host) {
270 if ((domain = strchr(host, '.')) != NULL)
271 return ++domain;
272 else
273 return "";
274 } else
275 return NULL;
276 }
277 #endif
278
279 /* HTGetHostName
280 ** Returns the name of this host. It uses the following algoritm:
281 **
282 ** 1) gethostname()
283 ** 2) if the hostname doesn't contain any '.' try to read
284 ** /etc/resolv.conf. If there is no domain line in this file then
285 ** 3) Try getdomainname and do as the man pages say for resolv.conf (sun)
286 ** If there is no domain line in this file, then it is derived
287 ** from the domain name set by the domainname(1) command, usually
288 ** by removing the first component. For example, if the domain-
289 ** name is set to ``foo.podunk.edu'' then the default domain name
290 ** used will be ``pudunk.edu''.
291 **
292 ** This is the same procedure as used by res_init() and sendmail.
293 **
294 ** Return: hostname on success else NULL
295 */
HTGetHostName(void)296 PUBLIC char * HTGetHostName (void)
297 {
298 char * hostname = NULL;
299 int fqdn = 0; /* 0=no, 1=host, 2=fqdn */
300 char name[MAXHOSTNAMELEN+1];
301 *(name+MAXHOSTNAMELEN) = '\0';
302
303 #if defined(HAVE_SYSINFO) && defined(SI_HOSTNAME)
304 if (!fqdn && sysinfo(SI_HOSTNAME, name, MAXHOSTNAMELEN) > 0) {
305 char * dot = strchr(name, '.');
306 HTTRACE(CORE_TRACE, "HostName.... sysinfo says `%s\'\n" _ name);
307 StrAllocCopy(hostname, name);
308 fqdn = dot ? 2 : 1;
309 }
310 #endif /* HAVE_SYSINFO */
311
312 #ifdef HAVE_GETHOSTNAME
313 if (!fqdn && gethostname(name, MAXHOSTNAMELEN) == 0) {
314 char * dot = strchr(name, '.');
315 HTTRACE(CORE_TRACE, "HostName.... gethostname says `%s\'\n" _ name);
316 StrAllocCopy(hostname, name);
317 fqdn = dot ? 2 : 1;
318 }
319 #endif /* HAVE_GETHOSTNAME */
320
321 #ifdef RESOLV_CONF
322 /* Now try the resolver config file */
323 {
324 FILE *fp;
325 if (fqdn==1 && (fp = fopen(RESOLV_CONF, "r")) != NULL) {
326 char buffer[80];
327 *(buffer+79) = '\0';
328 while (fgets(buffer, 79, fp)) {
329 if (!strncasecomp(buffer, "domain", 6) ||
330 !strncasecomp(buffer, "search", 6)) {
331 char *domainstr = buffer+6;
332 char *end;
333 while (*domainstr == ' ' || *domainstr == '\t')
334 domainstr++;
335 end = domainstr;
336 while (*end && !isspace((int) *end))
337 end++;
338 *end = '\0';
339 if (*domainstr) {
340 StrAllocCat(hostname, ".");
341 StrAllocCat(hostname, domainstr);
342 fqdn = 2;
343 break;
344 }
345 }
346 }
347 fclose(fp);
348 }
349 }
350 #endif /* RESOLV_CONF */
351
352 #ifdef HAVE_GETDOMAINNAME
353 /* If everything else has failed then try getdomainname */
354 if (fqdn==1) {
355 if (getdomainname(name, MAXHOSTNAMELEN)) {
356 HTTRACE(CORE_TRACE, "HostName.... Can't get domain name\n");
357 StrAllocCopy(hostname, "");
358 return NULL;
359 }
360
361 /* If the host name and the first part of the domain name are different
362 then use the former as it is more exact (I guess) */
363 if (strncmp(name, hostname, (int) strlen(hostname))) {
364 char *domain = strchr(name, '.');
365 if (!domain)
366 domain = name;
367 StrAllocCat(hostname, domain);
368 }
369 }
370 #endif /* HAVE_GETDOMAINNAME */
371
372 if (hostname) {
373 char *strptr = hostname;
374 while (*strptr) {
375 *strptr = TOLOWER(*strptr);
376 strptr++;
377 }
378 if (*(hostname+strlen(hostname)-1) == '.') /* Remove trailing dot */
379 *(hostname+strlen(hostname)-1) = '\0';
380 HTTRACE(CORE_TRACE, "HostName.... FQDN is `%s\'\n" _ hostname);
381 }
382 return hostname;
383 }
384
385 /* HTGetMailAddress
386 **
387 ** Get the mail address of the current user on the current host. The
388 ** domain name used is the one initialized in HTSetHostName or
389 ** HTGetHostName. The login name is determined using (ordered):
390 **
391 ** getlogin
392 ** getpwuid(getuid())
393 **
394 ** The weakness about the last attempt is if the user has multiple
395 ** login names each with the same user ID. If this fails as well then:
396 **
397 ** LOGNAME environment variable
398 ** USER environment variable
399 **
400 ** Returns NULL or string to be freed by caller
401 */
HTGetMailAddress(void)402 PUBLIC char * HTGetMailAddress (void)
403 {
404 #ifdef HT_REENTRANT
405 char name[HT_LOGNAME_MAX]; /* For getlogin_r or getUserName */
406 int result;
407 #endif
408 #ifdef WWW_MSWINDOWS/* what was the plan for this under windows? - EGP */
409 char name[256]; /* For getlogin_r or getUserName */
410 unsigned int bufSize = sizeof(name);
411 #endif
412 #ifdef HAVE_PWD_H
413 struct passwd * pw_info = NULL;
414 #endif
415 char * login = NULL;
416
417 #ifdef WWW_MSWINDOWS
418 if (!login && GetUserName(name, &bufSize) != TRUE)
419 HTTRACE(CORE_TRACE, "MailAddress. GetUsername returns NO\n");
420 #endif /* WWW_MSWINDOWS */
421
422 #ifdef HAVE_CUSERID
423 if (!login && (login = (char *) cuserid(NULL)) == NULL)
424 HTTRACE(CORE_TRACE, "MailAddress. cuserid returns NULL\n");
425 #endif /* HAVE_CUSERID */
426
427 #ifdef HAVE_GETLOGIN
428 #ifdef GETLOGIN_R_RETURNS_POINTER
429 if (!login && (login = (char *) getlogin_r(name, HT_LOGNAME_MAX)) == NULL)
430 #elif defined(GETLOGIN_R_RETURNS_INT)
431 if (!login && (result = getlogin_r(name, HT_LOGNAME_MAX)) == 0)
432 {
433 login = &name[0];
434 }
435 else
436 #else
437 if (!login && (login = (char *) getlogin()) == NULL)
438 #endif /* HT_REENTRANT */
439 HTTRACE(CORE_TRACE, "MailAddress. getlogin returns NULL\n");
440 #endif /* HAVE_GETLOGIN */
441
442 #ifdef HAVE_PWD_H
443 if (!login && (pw_info = getpwuid(getuid())) != NULL)
444 login = pw_info->pw_name;
445 #endif /* HAVE_PWD_H */
446
447 if (!login && (login = getenv("LOGNAME")) == NULL)
448 HTTRACE(CORE_TRACE, "MailAddress. LOGNAME not found\n");
449
450 if (!login && (login = getenv("USER")) == NULL)
451 HTTRACE(CORE_TRACE, "MailAddress. USER not found\n");
452
453 if (!login) login = HT_DEFAULT_LOGIN;
454
455 if (login) {
456 char * domain = NULL;
457 char * mailaddress = NULL;
458 StrAllocCopy(mailaddress, login);
459 StrAllocCat(mailaddress, "@");
460 if ((domain = HTGetHostName()) != NULL) {
461 StrAllocCat(mailaddress, domain);
462 HT_FREE(domain);
463 }
464 return mailaddress;
465 }
466 return NULL;
467 }
468
469 /*
470 ** Except on the NeXT, we pick up the NewsHost name from
471 **
472 ** 1. Environment variable NNTPSERVER
473 ** 2. File SERVER_FILE
474 ** 3. Compilation time macro DEFAULT_NEWS_HOST
475 **
476 ** On the NeXT, we pick up the NewsHost name from, in order:
477 **
478 ** 1. WorldWideWeb default "NewsHost"
479 ** 2. News default "NewsHost"
480 ** 3. Compilation time macro DEFAULT_NEWS_HOST
481 **
482 ** Returns NULL or string to be freed by caller
483 */
HTGetNewsServer(void)484 PUBLIC char * HTGetNewsServer (void)
485 {
486 char * newshost = NULL;
487 char buffer[80];
488
489 #ifdef NeXTStep
490 if ((newshost = NXGetDefaultValue("WorldWideWeb","NewsHost")) == 0)
491 if ((newshost = NXGetDefaultValue("News","NewsHost")) == 0)
492 newshost = DEFAULT_NEWS_HOST;
493 #else
494 if ((newshost = (char *) getenv("NNTPSERVER")) == NULL) {
495 FILE *fp = fopen(SERVER_FILE, "r");
496 *(buffer+79) = '\0';
497 if (fp) {
498 if (fgets(buffer, 79, fp)) {
499 char *end;
500 newshost = buffer;
501 while (*newshost == ' ' || *newshost == '\t')
502 newshost++;
503 end = newshost;
504 while (*end && !isspace((int) *end))
505 end++;
506 *end = '\0';
507 }
508 fclose(fp);
509 }
510 }
511 #endif /* NestStep */
512
513 /* Last resort */
514 if (!newshost || !*newshost) newshost = DEFAULT_NEWS_HOST;
515
516 /* Canonicalize host name */
517 {
518 char * result = NULL;
519 StrAllocCopy(result, newshost);
520 {
521 char * strptr = result;
522 while (*strptr) {
523 *strptr = TOLOWER(*strptr);
524 strptr++;
525 }
526 }
527 return result;
528 }
529 }
530
531 /* Timezone Offset
532 ** ---------------
533 ** Calculates the offset from GMT in seconds
534 */
HTGetTimeZoneOffset(void)535 PUBLIC time_t HTGetTimeZoneOffset (void)
536 {
537 static time_t HTTimeZone = -1; /* Invalid timezone offset */
538 if (HTTimeZone != -1) return HTTimeZone; /* Already done */
539 #ifdef HAVE_TIMEZONE
540 {
541 time_t cur_t = time(NULL);
542 #ifdef HT_REENTRANT
543 struct tm loctime;
544 struct tm *local = (struct tm *) localtime_r(&cur_t, &loctime);
545 #else
546 struct tm *local = localtime(&cur_t);
547 #endif /* HT_REENTRANT */
548 #ifdef HAVE_DAYLIGHT
549 if (daylight && local->tm_isdst>0) { /* daylight time? */
550 #else
551 if (local->tm_isdst>0) { /* daylight time? */
552 #endif /* HAVE_DAYLIGHT */
553 #ifdef HAVE_ALTZONE
554 HTTimeZone = altzone;
555 #else
556 /* Assumes a fixed DST offset of 1 hour, which is probably wrong */
557 #ifdef __CYGWIN__
558 HTTimeZone = _timezone - 3600;
559 #else
560 HTTimeZone = timezone - 3600;
561 #endif
562 #endif /* HAVE_ALTZONE */
563 } else { /* no */
564 #ifdef __CYGWIN__
565 HTTimeZone = _timezone;
566 #else
567 HTTimeZone = timezone;
568 #endif
569 }
570 HTTimeZone = -HTTimeZone;
571 HTTRACE(CORE_TRACE, "TimeZone.... GMT + (%02d) hours (including DST)\n" _
572 (int) HTTimeZone/3600);
573 }
574 #else
575 #ifdef HAVE_TM_GMTOFF
576 {
577 time_t cur_t = time(NULL);
578 #ifdef HT_REENTRANT
579 struct tm loctime;
580 localtime_r(&cur_t, &loctime);
581 #else
582 struct tm * local = localtime(&cur_t);
583 #endif /* HT_REENTRANT */
584 HTTimeZone = local->tm_gmtoff;
585 HTTRACE(CORE_TRACE, "TimeZone.... GMT + (%02d) hours (including DST)\n" _
586 (int)local->tm_gmtoff / 3600);
587 }
588 #else
589 HTTRACE(CORE_TRACE, "TimeZone.... Not defined\n");
590 #endif /* HAVE_TM_GMTOFF */
591 #endif /* HAVE_TIMEZONE */
592 return HTTimeZone;
593 }
594
595 /*
596 ** Finds a temporary name in in the directory given. If the directory
597 ** is NULL then don't prepend anything.
598 ** If success, the result must be freed by caller, else we return NULL
599 */
600 PUBLIC char * HTGetTmpFileName (const char * abs_dir)
601 {
602 char * result = NULL;
603 #ifdef HAVE_TEMPNAM
604 static char * envtmpdir = NULL;
605 size_t len = 0;
606 if (abs_dir && *abs_dir) {
607 char * tmpdir = getenv("TMPDIR");
608 if (tmpdir)
609 len = strlen(tmpdir);
610 if (len) {
611 if (!(envtmpdir = (char *) HT_REALLOC(envtmpdir, len + 8)))
612 HT_OUTOFMEM("HTGetTmpFileName");
613 strcpy(envtmpdir, "TMPDIR=");
614 strcpy(envtmpdir + 7, tmpdir);
615 putenv("TMPDIR=");
616 }
617 }
618 #ifdef __CYGWIN__
619 result = tempnam(abs_dir, "");
620 #else
621 result = tempnam(abs_dir, NULL);
622 #endif /* __CYGWIN__ */
623 if (len)
624 putenv(envtmpdir);
625 #else
626 /*
627 ** This is only approx. as we don't know if this file exists or not.
628 ** Hopefully, tempnam() exists on enough platforms so that this is not
629 ** a problem.
630 */
631 char * offset = NULL;
632 if (!(result = (char *) HT_MALLOC((abs_dir ? strlen(abs_dir) : 0) +
633 HT_MAX_TMPNAM + 2)))
634 HT_OUTOFMEM("HTGetTmpFileName");
635
636 #ifdef WWW_MSWINDOWS
637 if (abs_dir) {
638 #else
639 if (abs_dir && *abs_dir==DIR_SEPARATOR_CHAR) {
640 #endif /* WWW_MSWINDOWS */
641 strcpy(result, abs_dir);
642 offset = result+strlen(result);
643 if (*(offset-1) != DIR_SEPARATOR_CHAR) *offset++ = DIR_SEPARATOR_CHAR;
644
645 #ifdef HT_REENTRANT
646 tmpnam_r(offset);
647 #else
648 tmpnam(offset);
649 #endif
650
651 {
652 char * orig = strrchr(offset, DIR_SEPARATOR_CHAR);
653 char * dest = offset;
654 if (orig++) while ((*dest++ = *orig++));
655 }
656 } else {
657 offset = result;
658 #ifdef HT_REENTRANT
659 tmpnam_r(offset);
660 #else
661 tmpnam(offset);
662 #endif
663 offset = result;
664 }
665 #endif /* HAVE_TEMPNAM */
666 return result;
667 }
668
669 /*
670 ** Copied from X utilities
671 */
672 PUBLIC ms_t HTGetTimeInMillis (void)
673 {
674 #ifdef WWW_MSWINDOWS
675 return GetTickCount();
676 #else /* WWW_MSWINDOWS */
677 #ifdef HAVE_GETTIMEOFDAY
678 struct timeval tp;
679 gettimeofday(&tp, NULL);
680 return(tp.tv_sec * 1000) + (tp.tv_usec / 1000);
681 #else
682 return((ms_t) 0);
683 #endif
684 #endif /* !WWW_MSWINDOWS */
685 }
686