Annotation of Amaya/amaya/AHTBridge.c, revision 1.9
1.9 ! cvs 1: /*
! 2: *
! 3: * (c) COPYRIGHT MIT and INRIA, 1996.
! 4: * Please first read the full copyright statement in file COPYRIGHT.
! 5: *
! 6: */
! 7:
1.4 cvs 8: /* AHTBridge.c
9: * INTERFACE BRIDGE TO XT, LIBWWW, and AMAYA
10: *
11: * (c) COPYRIGHT
12: * Please first read the full copyright statement in the file COPYRIGH.
13: *
14: * This module implments the callback setup and handlers between
15: * the Xt, libwww, and Amaya procedures
16: *
17: * History:
18: * May 02 96 JK First semi-stable version, Jose Kahan
19: * June 01 96 JK First almost-complete version, Jose Kahan
1.7 cvs 20: */
1.4 cvs 21:
22: /* Unix/C/X */
23: #include "thot_gui.h"
24: #include "thot_sys.h"
25: #include "message.h"
26: #include "dialog.h"
27: #include "application.h"
28: #include "content.h"
29: #include "view.h"
30: #include "interface.h"
31: #include "amaya.h"
32:
33: #if 0
34: #include "AHTCommon.h"
35: #include "query.h"
36: #endif
37:
1.7 cvs 38: #include "AHTBridge.h" /* implemented here */
1.4 cvs 39:
40: #ifdef WWW_XWINDOWS
41: /* Amaya's X appcontext */
42: extern XtAppContext app_cont;
1.7 cvs 43:
1.4 cvs 44: #endif
45:
46:
47: #ifndef HACK_WWW
1.7 cvs 48: extern PUBLIC HTEventCallback *HTEvent_Retrieve (SOCKET, SockOps, HTRequest ** arp);
49:
1.4 cvs 50: #endif
51:
52: /*
1.7 cvs 53: * Private functions
54: */
1.4 cvs 55: #ifdef __STDC__
1.7 cvs 56: static void RequestKillReadXtevent (AHTReqContext *);
57: static void RequestKillWriteXtevent (AHTReqContext *);
58: static void RequestKillExceptXtevent (AHTReqContext *);
59:
1.4 cvs 60: #else
1.7 cvs 61: static void RequestKillReadXtevent ();
62: static void RequestKillWriteXtevent ();
63: static void RequestKillExceptXtevent ();
64:
65: #endif
1.4 cvs 66:
67: /*
68: * Private variables
69: */
70:
71: /*
72: * this set of SockOps map our WinSock "socket event SockOps" into
73: * our read and write sets. Note that under the canonical Unix model,
74: * a non-blocking socket passed to an accept() call will appear as readable,
75: * whilst a non-blocking call to connect() will appear as writeable. In add.
76: * if the connection has been closed, the socket will appear readable under
77: * BSD Unix semantics
78: */
1.7 cvs 79: PRIVATE const SockOps ReadBits = FD_READ | FD_ACCEPT | FD_CLOSE;
80: PRIVATE const SockOps WriteBits = FD_WRITE | FD_CONNECT;
81: PRIVATE const SockOps ExceptBits = FD_OOB;
1.4 cvs 82:
83: /*
84: * Private functions
85: */
86:
87: /*
88: * Callback that acts as a bridge between X and wwwlib.
89: * This function is equivalent to the library's __DoCallback()
90: * function, but with a different API, to conform to Xt's event loop
91: * specifications. For more info, cf. the library's HTEvntrg.c module.
92: */
93:
94: #ifdef WWW_XWINDOWS
95: #ifdef __STDC__
1.7 cvs 96: XtInputCallbackProc AHTCallback_bridge (caddr_t cd, int *s, XtInputId * id)
97: #else
1.4 cvs 98: XtInputCallbackProc AHTCallback_bridge (cd, s, id)
1.7 cvs 99: caddr_t cd;
100: int *s;
101: XtInputId *id;
102:
1.4 cvs 103: #endif /* __STDC__ */
104:
1.7 cvs 105: #else /* WWW_XWINDOWS */
1.4 cvs 106: /* some winproc someday? */
1.7 cvs 107: LONG AHTCallback_bridge (caddr_t cd, int *s)
108: #endif /* !WWW_XWINDOWS */
1.4 cvs 109: {
1.7 cvs 110: int status;
111: HTRequest *rqp = NULL;
112: AHTReqContext *me;
113: SOCKET sock;
114: SockOps ops; /* what value goes here ? Ask eric */
1.4 cvs 115:
116: /* Libwww 4.1 does not take into account the third parameter
117: for this function call */
118:
119: #ifdef HACK_WWW
1.7 cvs 120: HTEventCallback *cbf;
121:
1.4 cvs 122: #else
1.7 cvs 123: HTEventCallback *cbf = (HTEventCallback *) __RetrieveCBF (*s, ops, &rqp);
124:
1.4 cvs 125: #endif
1.7 cvs 126: me = HTRequest_context (rqp);
1.4 cvs 127:
128: #if 0
1.7 cvs 129: switch ((XtInputId) cd)
130: {
131: case XtInputReadMask:
132: ops = me->read_ops;
133: if (me->read_xtinput_id)
134: {
135: XtRemoveInput (me->read_xtinput_id);
136: if (THD_TRACE)
137: fprintf (stderr, "(BT) removing Xtinput %lu R (AHTBridge before cbf), sock %d\n", me->read_xtinput_id, *s);
138: me->read_xtinput_id = 0;
139: }
140: break;
141: case XtInputWriteMask:
142: ops = me->write_ops;
143: if (me->write_xtinput_id)
144: {
145: XtRemoveInput (me->write_xtinput_id);
146: if (THD_TRACE)
147: fprintf (stderr, "(BT) removing Xtinput %lu W (AHTBridge before cbf), sock %d\n", me->write_xtinput_id, *s);
148: me->write_xtinput_id = 0;
149: }
150: break;
151: case XtInputExceptMask:
152: ops = me->except_ops;
153: if (me->except_xtinput_id)
154: {
155: XtRemoveInput (me->except_xtinput_id);
156: if (THD_TRACE)
157: fprintf (stderr, "(BT) removing Xtinput %lu E (AHTBridge before cbf), sock %d\n", me->except_xtinput_id, *s);
158: me->except_xtinput_id = 0;
159: }
160: break;
161: } /* switch */
1.4 cvs 162: #endif
163:
1.7 cvs 164: if (THD_TRACE)
1.4 cvs 165: fprintf (stderr, "AHTBridge: Processing url %s \n", me->urlName);
166:
167:
168: #ifdef WWW_XWINDOWS
1.7 cvs 169: switch ((XtInputId) cd)
170: {
171: case XtInputReadMask:
172: ops = me->read_ops;
173: ops = FD_READ;
174: break;
175: case XtInputWriteMask:
176: ops = me->write_ops;
177: ops = FD_WRITE;
178: break;
179: case XtInputExceptMask:
180: ops = me->except_ops;
181: ops = FD_OOB;
182: break;
183: } /* switch */
184: #endif /* WWW_XWINDOWS */
185:
186: /*
187: * Liberate the input, so that when a pending socket is activated,
188: * the socket status will be ... available
189: *
190: * verify if I can CHKR_LIMIT this to the unregister function
191: * does not look so
192: *
193: * although it makes no sense, callbacks can be null
194: */
1.4 cvs 195:
1.7 cvs 196: if (!cbf || !rqp || rqp->priority == HT_PRIORITY_OFF)
197: {
1.4 cvs 198: if (THD_TRACE)
1.7 cvs 199: HTTrace ("Callback.... No callback found\n");
1.4 cvs 200: /* put some more code to correctly destroy this request */
201: return (0);
1.7 cvs 202: }
203:
204: me->reqStatus = HT_BUSY;
1.4 cvs 205:
1.7 cvs 206: if ((status = (*cbf) (*s, rqp, ops)) != HT_OK)
207: HTTrace ("Callback.... received != HT_OK");
1.4 cvs 208:
1.7 cvs 209: /* Several states can happen after this callback. They
210: * are indicated by the me->reqStatus structure member and
211: * the fds external variables. The following lines examine
212: * the states and correspondly update the Xt event register
213: *
214: * Regarding the me->reqStatus member, we have the following
215: * possible states:
216: *
217: * HT_BUSY: Request has blocked
218: * HT_WAITING: Request has been reissued
219: * HT_ABORT: Request has been stopped
220: * HT_END: Request has ended
221: */
222:
223: /* Has the request been stopped?
1.4 cvs 224:
1.7 cvs 225: * we verify if the request exists. If it has ended, me will have
226: * a reqStatus with an HT_END value */
1.4 cvs 227:
1.7 cvs 228: /* Was the stop button pressed? */
1.4 cvs 229:
1.7 cvs 230: #ifdef WWW_XWINDOWS
231: if (me->reqStatus == HT_ABORT)
232: {
1.4 cvs 233: me->reqStatus = HT_WAITING;
234: StopRequest (me->docid);
1.7 cvs 235: if (THD_TRACE)
236: fprintf (stderr, "(BF) removing Xtinput %lu !RWE (Stop buttonl), sock %d\n", me->read_xtinput_id, sock);
237: return (0);
238: }
239: #endif /* WWW_XWINDOWS */
240:
241: /* the request is being reissued */
242:
243: if (me->reqStatus == HT_WAITING)
244: {
245: /*
246: * (1) The old request has ended and the library
247: * assigned the old socket number to a pending
248: * request.
249: *
250: * (2) The request has been reissued after an
251: * authentication or redirection directive and
252: * we are using the same old socket number.
253: */
1.4 cvs 254:
1.7 cvs 255: if (THD_TRACE)
256: fprintf (stderr, "*** detected a reissue of request \n");
257: return (0);
258: }
1.4 cvs 259:
1.7 cvs 260:
261: /* verify if the request is still alive !! */
262:
263: if ((me->request->net == (HTNet *) NULL) || (me->reqStatus == HT_END || me->reqStatus == HT_ERR))
264: {
265: /* the socket is now being used by a different request, so the request has ended */
1.4 cvs 266: #ifdef WWW_XWINDOWS
1.7 cvs 267: if (THD_TRACE)
268: fprintf (stderr, "(BF) removing Xtinput %lu !RWE, sock %d (Request has ended)\n", *id, *s);
1.4 cvs 269: #endif
1.7 cvs 270: if ((me->mode & AMAYA_ASYNC) || (me->mode & AMAYA_IASYNC))
271: {
272: AHTPrintPendingRequestStatus (me->docid, YES);
273: AHTReqContext_delete (me);
274: }
275: else if (me->reqStatus != HT_END && HTError_hasSeverity (HTRequest_error (me->request), ERR_NON_FATAL))
1.4 cvs 276: me->reqStatus = HT_ERR;
1.7 cvs 277: return (0);
278: }
279: me->reqStatus = HT_WAITING;
280: return (0);
1.4 cvs 281: }
282:
283:
284: /*
285: * This function is called whenever a socket is available
286: * for a request. It the necessary events to the Xt
287: * A small interface to the HTLoadAnchor libwww function.
288: * It prepares Xt to handle the asynchronous data requests.
289: */
290:
291: #ifdef __STDC__
1.7 cvs 292: int Add_NewSocket_to_Loop (HTRequest * request, HTAlertOpcode op, int msgnum, const char *dfault, void *input, HTAlertPar * reply)
1.4 cvs 293: #else
1.7 cvs 294: int Add_NewSocket_to_Loop (request, op, msgnum, dfault, input, reply)
295: HTRequest *request;
296: HTAlertOpcode op;
297: int msgnum;
298: const char *dfault;
299: void *input;
300: HTAlertPar *reply;
301:
302: #endif /* __STDC__ */
1.4 cvs 303: {
1.7 cvs 304: SOCKET req_socket;
305: AHTReqContext *me = HTRequest_context (request);
1.4 cvs 306:
1.7 cvs 307: /* AmayaOpenRequests *reqState; */
1.4 cvs 308:
1.7 cvs 309: if (me->reqStatus == HT_NEW_PENDING)
310: {
311: /* we are opening a pending request */
312: if ((me->output = fopen (me->outputfile, "w")) == NULL)
313: {
314: me->outputfile[0] = '\0'; /* file could not be opened */
315: TtaSetStatus (me->docid, 1, TtaGetMessage (AMAYA, AM_CANNOT_CREATE_FILE),
316: me->outputfile);
317: me->reqStatus = HT_ERR;
318: return (HT_ERROR);
319: }
320: if (THD_TRACE)
321: fprintf (stderr, "Add_NewSocket_to_Loop: Activating pending %s . Open fd %d\n", me->urlName, (int) me->output);
322: HTRequest_setOutputStream (me->request,
323: AHTFWriter_new (me->request, me->output, YES));
324: }
1.4 cvs 325:
1.7 cvs 326: me->reqStatus = HT_WAITING;
1.4 cvs 327:
1.7 cvs 328: if (THD_TRACE)
329: fprintf (stderr, "(Activating a pending request\n");
1.4 cvs 330:
1.7 cvs 331: /* reusing this function to save on file descriptors */
1.4 cvs 332:
333:
1.7 cvs 334: return (HT_OK);
1.4 cvs 335:
336: /***
337: if(me->method == METHOD_PUT || me->method == METHOD_POST)
338: return (HT_OK);
339: ***/
340:
1.7 cvs 341: /* get the socket number associated to the request */
1.4 cvs 342:
1.7 cvs 343: req_socket = HTNet_socket (request->net);
344:
345: if (req_socket == INVSOC)
346: {
1.4 cvs 347: /* this should never be true */
348: return (HT_ERROR);
1.7 cvs 349: }
1.4 cvs 350:
1.7 cvs 351: /* add the input */
1.4 cvs 352:
353: #ifdef WWW_XWINDOWS
1.7 cvs 354: me->write_xtinput_id =
355: XtAppAddInput (app_cont, req_socket, (XtPointer) XtInputWriteMask,
1.4 cvs 356: (XtInputCallbackProc) AHTCallback_bridge, NULL);
1.7 cvs 357: if (THD_TRACE)
358: fprintf (stderr, "(BT) adding Xtinput %lu Socket %d W \n", me->write_xtinput_id, req_socket);
359:
360: if (me->write_xtinput_id == (XtInputId) NULL)
361: {
362: TtaSetStatus (me->docid, 1, TtaGetMessage (AMAYA, AM_XT_ERROR), me->urlName);
363:
364: /* I still need to add some error treatment here, to liberate memory */
365: return (HT_ERROR);
366: }
1.4 cvs 367:
368: #endif /* WWW_XWINDOWS */
1.7 cvs 369: /* To speed up the stop performances, we move the active requests to the top of the Amaya list */
1.4 cvs 370:
1.7 cvs 371: /*
372: reqState = DocRequestState(Amaya->open_requests, me->docid);
1.4 cvs 373:
1.7 cvs 374: if(reqState->counter > 1) {
1.4 cvs 375: HTList_removeObject (Amaya->reqlist, (void *) me);
376: HTList_addObject (Amaya->reqlist, (void *) me);
1.7 cvs 377: }
1.4 cvs 378: */
1.7 cvs 379: return (HT_OK);
1.4 cvs 380: }
381:
382:
383: /*
384: * This function is called whenever a socket is available
385: * for a request. It the necessary events to the Xt
386: * A small interface to the HTLoadAnchor libwww function.
387: * It prepares Xt to handle the asynchronous data requests.
388: */
389:
390: #ifdef __STDC__
1.7 cvs 391: int AHTEvent_register (SOCKET sock, HTRequest * rqp, SockOps ops, HTEventCallback * cbf, HTPriority p)
1.4 cvs 392: #else
1.7 cvs 393: int AHTEvent_register (sock, rqp, ops, cbf, p)
394: SOCKET sock;
395: HTRequest *rqp;
396: SockOps ops;
397: HTEventCallback *cbf;
398: HTPriority p;
399:
400: #endif /* __STDC__ */
1.4 cvs 401: {
1.7 cvs 402: AHTReqContext *me;
403: int status;
404:
405: if (sock == INVSOC)
1.4 cvs 406: return (0);
407:
1.7 cvs 408: /* get the request associated to the socket number */
409:
410: if ((status = HTEventrg_register (sock, rqp, ops,
411: cbf, p)) != HT_OK)
1.4 cvs 412: return (status);
413:
1.7 cvs 414: if (rqp)
415: {
1.4 cvs 416:
1.7 cvs 417: me = HTRequest_context (rqp);
1.4 cvs 418:
1.7 cvs 419: /* verify if we need to open the fd */
1.4 cvs 420:
1.7 cvs 421: if (me->reqStatus == HT_NEW_PENDING)
422: {
423: /* we are opening a pending request */
424: if ((me->output = fopen (me->outputfile, "w")) == NULL)
425: {
426: me->outputfile[0] = '\0'; /* file could not be opened */
427: TtaSetStatus (me->docid, 1, TtaGetMessage (AMAYA, AM_CANNOT_CREATE_FILE),
428: me->outputfile);
429: me->reqStatus = HT_ERR;
430: return (HT_ERROR);
431: }
432: HTRequest_setOutputStream (me->request,
433: AHTFWriter_new (me->request, me->output, YES));
434: me->reqStatus = HT_WAITING;
435:
436: if (THD_TRACE)
437: fprintf (stderr, "AHTEvent_register: Activating pending request url %s, fd %d\n", me->urlName, (int) me->output);
438: }
1.4 cvs 439:
1.7 cvs 440: if (THD_TRACE)
441: fprintf (stderr, "AHTEvent_register: url %s, sock %d, ops %lu \n",
442: me->urlName, sock, ops);
1.4 cvs 443:
1.7 cvs 444: /* add the input */
445: if (me->reqStatus == HT_NEW)
446: me->reqStatus = HT_WAITING;
447:
448: if (ops & ReadBits)
449: {
450: me->read_ops = ops;
451:
452: #ifdef WWW_XWINDOWS
453: if (me->read_xtinput_id)
454: XtRemoveInput (me->read_xtinput_id);
455: me->read_xtinput_id =
456: XtAppAddInput (app_cont,
457: sock,
458: (XtPointer) XtInputReadMask,
459: (XtInputCallbackProc) AHTCallback_bridge,
460: (XtPointer) XtInputReadMask);
461: if (THD_TRACE)
462: fprintf (stderr, "(BT) adding Xtinput %lu Socket %d R\n",
463: me->read_xtinput_id, sock);
464: #endif /* WWW_XWINDOWS */
465: }
466:
467: if (ops & WriteBits)
468: {
469: me->write_ops = ops;
470: #ifdef WWW_XWINDOWS
471: if (me->write_xtinput_id)
472: XtRemoveInput (me->write_xtinput_id);
473: me->write_xtinput_id = XtAppAddInput (app_cont, sock,
474: (XtPointer) XtInputWriteMask,
475: (XtInputCallbackProc) AHTCallback_bridge,
476: (XtPointer) XtInputWriteMask);
477: if (THD_TRACE)
478: fprintf (stderr, "(BT) adding Xtinput %lu Socket %d W\n",
479: me->write_xtinput_id, sock);
480: #endif /* WWW_XWINDOWS */
481: }
482:
483: if (ops & ExceptBits)
484: {
485: me->except_ops = ops;
486: #ifdef WWW_XWINDOWS
487: if (me->except_xtinput_id)
488: XtRemoveInput (me->except_xtinput_id);
489:
490: me->except_xtinput_id = XtAppAddInput (app_cont, sock,
491: (XtPointer) XtInputExceptMask,
492: (XtInputCallbackProc) AHTCallback_bridge,
493: (XtPointer) XtInputExceptMask);
494: if (THD_TRACE)
495: fprintf (stderr, "(BT) adding Xtinput %lu Socket %d E\n", me->except_xtinput_id, sock);
1.4 cvs 496: #endif /* WWW_XWINDOWS */
1.7 cvs 497: }
498: }
1.4 cvs 499:
500:
501:
502: #if 0
1.7 cvs 503: if (me->xtinput_id == (XtInputId) NULL)
504: {
505: TtaSetStatus (me->docid, 1, TtaGetMessage (AMAYA, AM_XT_ERROR), me->urlName);
506:
507: /* I still need to add some error treatment here, to liberate memory */
508: return (HT_ERROR);
509: }
1.4 cvs 510:
511: #endif
512:
1.7 cvs 513: return (status);
1.4 cvs 514: }
515:
516: #ifdef __STDC__
1.7 cvs 517: int AHTEvent_unregister (SOCKET sock, SockOps ops)
1.4 cvs 518: #else
1.7 cvs 519: int AHTEvent_unregister (sock, ops)
520: SOCKET sock;
521: SockOps ops;
522:
523: #endif /* __STDC__ */
1.4 cvs 524: {
1.7 cvs 525: int status;
526:
527: HTRequest *rqp = NULL;
528: AHTReqContext *me;
1.4 cvs 529:
530: /* Libwww 4.1 does not take into account the third parameter
1.7 cvs 531: ** for this function call */
1.4 cvs 532:
1.7 cvs 533: HTEventCallback *cbf = (HTEventCallback *) __RetrieveCBF (sock, (SockOps) NULL, &rqp);
1.4 cvs 534:
1.8 cvs 535: #ifdef WWW_XWINDOWS
1.7 cvs 536: if (cbf)
537: {
538: if (rqp)
539: {
540: me = HTRequest_context (rqp);
541:
542: if (ops & ReadBits)
543: RequestKillReadXtevent (me);
1.4 cvs 544:
1.7 cvs 545: if (ops & WriteBits)
546: RequestKillWriteXtevent (me);
547:
548: if (ops & ExceptBits)
549: RequestKillExceptXtevent (me);
550:
551:
552: }
553: }
554:
555: status = HTEventrg_unregister (sock, ops);
1.8 cvs 556: #endif /* WWW_XWINDOWS */
1.7 cvs 557: return (status);
1.4 cvs 558: }
559:
560: #ifdef __STDC__
1.7 cvs 561: void RequestKillAllXtevents (AHTReqContext * me)
1.4 cvs 562: #else
1.7 cvs 563: void RequestKillAllXtevents (me)
564: AHTReqContext *me;
565:
566: #endif /* __STDC__ */
1.4 cvs 567: {
568: #ifdef WWW_XWINDOWS
1.7 cvs 569: if (THD_TRACE)
570: fprintf (stderr, "Request_kill: Clearing Xtinputs\n");
1.4 cvs 571:
1.7 cvs 572: RequestKillReadXtevent (me);
573: RequestKillWriteXtevent (me);
574: RequestKillExceptXtevent (me);
1.4 cvs 575: #endif /* WWW_XWINDOWS */
576: }
577:
578: #ifdef __STDC__
1.7 cvs 579: static void RequestKillReadXtevent (AHTReqContext * me)
1.4 cvs 580: #else
1.7 cvs 581: static void RequestKillReadXtevent (me)
582: AHTReqContext *me;
583:
584: #endif /* __STDC__ */
1.4 cvs 585: {
586: #ifdef WWW_XWINDOWS
1.7 cvs 587: if (me->read_xtinput_id)
588: {
589: if (THD_TRACE)
590: fprintf (stderr, "Request_kill: Clearing Read Xtinputs%lu\n", me->read_xtinput_id);
591: XtRemoveInput (me->read_xtinput_id);
592: me->read_xtinput_id = (XtInputId) NULL;
593: }
1.4 cvs 594: #endif /* WWW_XWINDOWS */
595: }
596:
597: #ifdef __STDC__
1.7 cvs 598: static void RequestKillWriteXtevent (AHTReqContext * me)
1.4 cvs 599: #else
1.7 cvs 600: static void RequestKillWriteXtevent (me)
601: AHTReqContext *me;
602:
603: #endif /* __STDC__ */
1.4 cvs 604: {
605: #ifdef WWW_XWINDOWS
1.7 cvs 606: if (me->write_xtinput_id)
607: {
608: if (THD_TRACE)
609: fprintf (stderr, "Request_kill: Clearing Write Xtinputs %lu\n", me->write_xtinput_id);
610: XtRemoveInput (me->write_xtinput_id);
611: me->write_xtinput_id = (XtInputId) NULL;
612: }
1.4 cvs 613: #endif /* WWW_XWINDOWS */
614: }
615:
616: #ifdef __STDC__
1.7 cvs 617: static void RequestKillExceptXtevent (AHTReqContext * me)
1.4 cvs 618: #else
1.7 cvs 619: static void RequestKillExceptXtevent (me)
620: AHTReqContext *me;
621:
622: #endif /* __STDC__ */
1.4 cvs 623: {
624: #ifdef WWW_XWINDOWS
1.7 cvs 625: if (me->except_xtinput_id)
626: {
627: if (THD_TRACE)
628: fprintf (stderr, "Request_kill: Clearing Except Xtinputs %lu\n", me->except_xtinput_id);
629: XtRemoveInput (me->except_xtinput_id);
630: me->except_xtinput_id = (XtInputId) NULL;
631: }
1.4 cvs 632: #endif /* WWW_XWINDOWS */
633: }
Webmaster