blob: e745afbf260b8d3961733650b0a8463b8eb97b5c [file] [log] [blame]
adamdunkelsca9ddcb2003-03-19 14:13:31 +00001/*
2 * Copyright (c) 2002, Adam Dunkels.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following
12 * disclaimer in the documentation and/or other materials provided
13 * with the distribution.
adamdunkels06f897e2004-06-06 05:59:20 +000014 * 3. The name of the author may not be used to endorse or promote
adamdunkelsca9ddcb2003-03-19 14:13:31 +000015 * products derived from this software without specific prior
16 * written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
19 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 * This file is part of the Contiki desktop environment
31 *
oliverschmidt453510a2005-01-21 14:25:27 +000032 * $Id: www.c,v 1.26 2005/01/21 14:25:27 oliverschmidt Exp $
adamdunkelsca9ddcb2003-03-19 14:13:31 +000033 *
34 */
35
adamdunkelsdb300d22004-02-24 09:57:49 +000036#include <string.h>
adamdunkelsca9ddcb2003-03-19 14:13:31 +000037
adamdunkelsf2f8cb22004-07-04 11:35:07 +000038#include "ek.h"
adamdunkelsca9ddcb2003-03-19 14:13:31 +000039#include "ctk.h"
adamdunkelsca9ddcb2003-03-19 14:13:31 +000040#include "webclient.h"
41#include "htmlparser.h"
42#include "http-strings.h"
43#include "resolv.h"
44
45#include "petsciiconv.h"
46
adamdunkels8bb5cca2003-08-24 22:41:31 +000047#include "program-handler.h"
48
adamdunkelsdb300d22004-02-24 09:57:49 +000049#include "uiplib.h"
adamdunkels8bb5cca2003-08-24 22:41:31 +000050
adamdunkelsf2f8cb22004-07-04 11:35:07 +000051#include "tcpip.h"
52
adamdunkels8af703e2003-04-08 11:50:20 +000053#include "loader.h"
54
adamdunkelsca9ddcb2003-03-19 14:13:31 +000055#include "www-conf.h"
56
adamdunkels30c449e2003-07-31 23:12:05 +000057#if 1
adamdunkelsca9ddcb2003-03-19 14:13:31 +000058#define PRINTF(x)
59#else
60#include <stdio.h>
61#define PRINTF(x) printf x
62#endif
63
64
65/* The array that holds the current URL. */
66static char url[WWW_CONF_MAX_URLLEN + 1];
67static char tmpurl[WWW_CONF_MAX_URLLEN + 1];
68
69/* The array that holds the web page text. */
adamdunkels89c1f4e2003-08-09 13:33:25 +000070static char webpage[WWW_CONF_WEBPAGE_WIDTH *
71 WWW_CONF_WEBPAGE_HEIGHT + 1];
adamdunkelsca9ddcb2003-03-19 14:13:31 +000072
adamdunkels269d7be2004-09-03 09:55:22 +000073
adamdunkelsca9ddcb2003-03-19 14:13:31 +000074/* The CTK widgets for the main window. */
75static struct ctk_window mainwindow;
76
77static struct ctk_button backbutton =
78 {CTK_BUTTON(0, 0, 4, "Back")};
79static struct ctk_button downbutton =
80 {CTK_BUTTON(10, 0, 4, "Down")};
81static struct ctk_button stopbutton =
adamdunkels89c1f4e2003-08-09 13:33:25 +000082 {CTK_BUTTON(WWW_CONF_WEBPAGE_WIDTH - 16, 0, 4, "Stop")};
adamdunkelsca9ddcb2003-03-19 14:13:31 +000083static struct ctk_button gobutton =
adamdunkels89c1f4e2003-08-09 13:33:25 +000084 {CTK_BUTTON(WWW_CONF_WEBPAGE_WIDTH - 4, 0, 2, "Go")};
adamdunkelsca9ddcb2003-03-19 14:13:31 +000085
86static struct ctk_separator sep1 =
adamdunkels89c1f4e2003-08-09 13:33:25 +000087 {CTK_SEPARATOR(0, 2, WWW_CONF_WEBPAGE_WIDTH)};
adamdunkelsca9ddcb2003-03-19 14:13:31 +000088
89static char editurl[WWW_CONF_MAX_URLLEN + 1];
90static struct ctk_textentry urlentry =
adamdunkels89c1f4e2003-08-09 13:33:25 +000091 {CTK_TEXTENTRY(0, 1, WWW_CONF_WEBPAGE_WIDTH - 2,
92 1, editurl, WWW_CONF_MAX_URLLEN)};
adamdunkelsca9ddcb2003-03-19 14:13:31 +000093static struct ctk_label webpagelabel =
adamdunkels89c1f4e2003-08-09 13:33:25 +000094 {CTK_LABEL(0, 3, WWW_CONF_WEBPAGE_WIDTH,
95 WWW_CONF_WEBPAGE_HEIGHT, webpage)};
adamdunkelsca9ddcb2003-03-19 14:13:31 +000096
adamdunkels89c1f4e2003-08-09 13:33:25 +000097static char statustexturl[WWW_CONF_WEBPAGE_WIDTH];
adamdunkelsca9ddcb2003-03-19 14:13:31 +000098static struct ctk_label statustext =
adamdunkels89c1f4e2003-08-09 13:33:25 +000099 {CTK_LABEL(0, WWW_CONF_WEBPAGE_HEIGHT + 4,
100 WWW_CONF_WEBPAGE_WIDTH, 1, "")};
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000101static struct ctk_separator sep2 =
adamdunkels89c1f4e2003-08-09 13:33:25 +0000102 {CTK_SEPARATOR(0, WWW_CONF_WEBPAGE_HEIGHT + 3,
103 WWW_CONF_WEBPAGE_WIDTH)};
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000104
adamdunkelsf6eea752003-08-22 19:24:40 +0000105static struct ctk_window wgetdialog;
106static struct ctk_label wgetlabel1 =
adamdunkels77b7a692003-08-29 20:36:23 +0000107 {CTK_LABEL(1, 1, 34, 1, "This web page cannot be displayed.")};
adamdunkelsf6eea752003-08-22 19:24:40 +0000108static struct ctk_label wgetlabel2 =
adamdunkels77b7a692003-08-29 20:36:23 +0000109 {CTK_LABEL(1, 3, 35, 1, "Would you like to download instead?")};
adamdunkelsf6eea752003-08-22 19:24:40 +0000110static struct ctk_button wgetnobutton =
adamdunkels77b7a692003-08-29 20:36:23 +0000111 {CTK_BUTTON(1, 5, 6, "Cancel")};
adamdunkelsf6eea752003-08-22 19:24:40 +0000112static struct ctk_button wgetyesbutton =
adamdunkels77b7a692003-08-29 20:36:23 +0000113 {CTK_BUTTON(11, 5, 24, "Close browser & download")};
adamdunkelsf6eea752003-08-22 19:24:40 +0000114
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000115/* The char arrays that hold the history of visited URLs. */
adamdunkels17e84a32003-04-02 09:54:39 +0000116static char history[WWW_CONF_HISTORY_SIZE][WWW_CONF_MAX_URLLEN];
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000117static char history_last, history_first;
118
119
120/* The CTK widget definitions for the hyperlinks and the char arrays
121 that hold the link URLs. */
122struct formattribs {
123 char formaction[WWW_CONF_MAX_FORMACTIONLEN];
124 char formname[WWW_CONF_MAX_FORMNAMELEN];
125#define FORMINPUTTYPE_SUBMITBUTTON 1
126#define FORMINPUTTYPE_INPUTFIELD 2
127 unsigned char inputtype;
128 char inputname[WWW_CONF_MAX_INPUTNAMELEN];
129 char *inputvalue;
130};
131
132union pagewidgetattrib {
133 char url[WWW_CONF_MAX_URLLEN];
adamdunkelsd59dadf2003-08-15 18:48:35 +0000134#if WWW_CONF_FORMS
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000135 struct formattribs form;
adamdunkelsd59dadf2003-08-15 18:48:35 +0000136#endif /* WWW_CONF_FORMS */
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000137};
adamdunkels17e84a32003-04-02 09:54:39 +0000138static struct ctk_widget pagewidgets[WWW_CONF_MAX_NUMPAGEWIDGETS];
139static union pagewidgetattrib pagewidgetattribs[WWW_CONF_MAX_NUMPAGEWIDGETS];
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000140static unsigned char pagewidgetptr;
141
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000142#if WWW_CONF_RENDERSTATE
143static unsigned char renderstate;
144#endif /* WWW_CONF_RENDERSTATE */
145
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000146#define ISO_nl 0x0a
147#define ISO_space 0x20
148#define ISO_ampersand 0x26
149#define ISO_plus 0x2b
150#define ISO_slash 0x2f
151#define ISO_eq 0x3d
152#define ISO_questionmark 0x3f
153
154/* The state of the rendering code. */
adamdunkels269d7be2004-09-03 09:55:22 +0000155static char *webpageptr;
156static unsigned char x, y;
157static unsigned char loading;
158static unsigned short firsty, pagey;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000159
160static unsigned char count;
161static char receivingmsgs[4][23] = {
162 "Receiving web page ...",
163 "Receiving web page. ..",
164 "Receiving web page.. .",
165 "Receiving web page... "
166};
167
168
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000169EK_EVENTHANDLER(www_eventhandler, ev, data);
170EK_PROCESS(p, "Web browser", EK_PRIO_NORMAL,
171 www_eventhandler, NULL, NULL);
172static ek_id_t id = EK_ID_NONE;
173
174/*static DISPATCHER_SIGHANDLER(www_sighandler, s, data);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000175static struct dispatcher_proc p =
adamdunkels78c03dc2003-04-09 13:45:05 +0000176 {DISPATCHER_PROC("Web browser", NULL, www_sighandler, webclient_appcall)};
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000177 static ek_id_t id;*/
178
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000179
180
181static void formsubmit(struct formattribs *attribs);
182/*-----------------------------------------------------------------------------------*/
183/* make_window()
184 *
185 * Creates the web browser's window.
186 */
187static void
188make_window(void)
189{
190
191 CTK_WIDGET_ADD(&mainwindow, &backbutton);
192 CTK_WIDGET_ADD(&mainwindow, &downbutton);
193 CTK_WIDGET_ADD(&mainwindow, &stopbutton);
194 CTK_WIDGET_ADD(&mainwindow, &gobutton);
195 CTK_WIDGET_ADD(&mainwindow, &urlentry);
196 CTK_WIDGET_ADD(&mainwindow, &sep1);
197 CTK_WIDGET_ADD(&mainwindow, &webpagelabel);
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000198 CTK_WIDGET_SET_FLAG(&webpagelabel, CTK_WIDGET_FLAG_MONOSPACE);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000199 CTK_WIDGET_ADD(&mainwindow, &sep2);
200 CTK_WIDGET_ADD(&mainwindow, &statustext);
201
202 CTK_WIDGET_FOCUS(&mainwindow, &stopbutton);
203
204 pagewidgetptr = 0;
205}
206/*-----------------------------------------------------------------------------------*/
207/* redraw_window():
208 *
209 * Convenience function that calls upon CTK to redraw the browser
210 * window. */
211static void
212redraw_window(void)
213{
214 ctk_window_redraw(&mainwindow);
215}
216/*-----------------------------------------------------------------------------------*/
217/* www_init();
218 *
219 * Initializes and starts the web browser. Called either at startup or
220 * to open the browser window.
221 */
adamdunkels8bb5cca2003-08-24 22:41:31 +0000222LOADER_INIT_FUNC(www_init, arg)
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000223{
adamdunkels8bb5cca2003-08-24 22:41:31 +0000224 arg_free(arg);
225
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000226 if(id == EK_ID_NONE) {
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000227 /* id = dispatcher_start(&p);*/
228 id = ek_start(&p);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000229
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000230 }
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000231}
232/*-----------------------------------------------------------------------------------*/
233static void
234clear_page(void)
235{
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000236 ctk_window_clear(&mainwindow);
237 make_window();
adamdunkels269d7be2004-09-03 09:55:22 +0000238 redraw_window();
adamdunkels17e84a32003-04-02 09:54:39 +0000239 memset(webpage, 0, WWW_CONF_WEBPAGE_WIDTH * WWW_CONF_WEBPAGE_HEIGHT);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000240}
241/*-----------------------------------------------------------------------------------*/
242static void
243show_url(void)
244{
245 memcpy(editurl, url, WWW_CONF_MAX_URLLEN);
246 strncpy(editurl, "http://", 7);
247 petsciiconv_topetscii(editurl + 7, WWW_CONF_MAX_URLLEN - 7);
248 CTK_WIDGET_REDRAW(&urlentry);
249}
adamdunkels269d7be2004-09-03 09:55:22 +0000250static void
251start_loading(void)
252{
253 loading = 1;
254 x = y = 0;
255 pagey = 0;
256 webpageptr = webpage;
257
258 clear_page();
259}
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000260/*-----------------------------------------------------------------------------------*/
261static void
262show_statustext(char *text)
263{
264 ctk_label_set_text(&statustext, text);
265 CTK_WIDGET_REDRAW(&statustext);
266}
267/*-----------------------------------------------------------------------------------*/
268/* open_url():
269 *
270 * Called when the URL present in the global "url" variable should be
271 * opened. It will call the hostname resolver as well as the HTTP
272 * client requester.
273 */
274static void
275open_url(void)
276{
277 unsigned char i;
278 static char host[32];
279 char *file;
280 register char *urlptr;
281 unsigned short port;
adamdunkels75c276c2003-09-04 19:35:32 +0000282 static u16_t addr[2];
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000283
284 /* Trim off any spaces in the end of the url. */
285 urlptr = url + strlen(url) - 1;
286 while(*urlptr == ' ' && urlptr > url) {
287 *urlptr = 0;
288 --urlptr;
289 }
290
291 /* Don't even try to go further if the URL is empty. */
292 if(urlptr == url) {
293 return;
294 }
295
296 /* See if the URL starts with http://, otherwise prepend it. */
297 if(strncmp(url, http_http, 7) != 0) {
298 while(urlptr >= url) {
299 *(urlptr + 7) = *urlptr;
300 --urlptr;
301 }
302 strncpy(url, http_http, 7);
303 }
304
305 /* Find host part of the URL. */
306 urlptr = &url[7];
307 for(i = 0; i < sizeof(host); ++i) {
308 if(*urlptr == 0 ||
309 *urlptr == '/' ||
310 *urlptr == ' ' ||
311 *urlptr == ':') {
312 host[i] = 0;
313 break;
314 }
315 host[i] = *urlptr;
316 ++urlptr;
317 }
318
319 /* XXX: Here we should find the port part of the URL, but this isn't
320 currently done because of laziness from the programmer's side
321 :-) */
322
323 /* Find file part of the URL. */
324 while(*urlptr != '/' && *urlptr != 0) {
325 ++urlptr;
326 }
327 if(*urlptr == '/') {
328 file = urlptr;
329 } else {
330 file = "/";
331 }
332
333 /* Try to lookup the hostname. If it fails, we initiate a hostname
334 lookup and print out an informative message on the statusbar. */
adamdunkelsdb300d22004-02-24 09:57:49 +0000335 if(uiplib_ipaddrconv(host, (unsigned char *)addr) == 0) {
adamdunkels75c276c2003-09-04 19:35:32 +0000336 if(resolv_lookup(host) == NULL) {
337 resolv_query(host);
338 show_statustext("Resolving host...");
339 return;
340 }
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000341 }
342
343 /* The hostname we present in the hostname table, so we send out the
344 initial GET request. */
345 if(webclient_get(host, 80, file) == 0) {
346 show_statustext("Out of memory error.");
347 } else {
348 show_statustext("Connecting...");
349 }
350 redraw_window();
351}
352/*-----------------------------------------------------------------------------------*/
353/* open_link(link):
354 *
355 * Will format a link from the current web pages so that it suits the
356 * open_url() function and finally call it to open the requested URL.
357 */
358static void
359open_link(char *link)
360{
361 char *urlptr;
362
363 if(strncmp(link, http_http, 7) == 0) {
364 /* The link starts with http://. We just copy the contents of the
365 link into the url string and jump away. */
366 strncpy(url, link, WWW_CONF_MAX_URLLEN);
367 } else if(*link == ISO_slash &&
368 *(link + 1) == ISO_slash) {
369 /* The link starts with //, so we'll copy it into the url
370 variable, starting after the http (which already is present in
371 the url variable since we were able to open the web page on
372 which this link was found in the first place). */
373 strncpy(&url[5], link, WWW_CONF_MAX_URLLEN);
374 } else if(*link == ISO_slash) {
375 /* The link starts with a slash, so it is a non-relative link
376 within the same web site. We find the start of the filename of
377 the current URL and paste the contents of this link there, and
378 head off to the new URL. */
379 for(urlptr = &url[7];
380 *urlptr != 0 && *urlptr != ISO_slash;
381 ++urlptr);
382 strncpy(urlptr, link, WWW_CONF_MAX_URLLEN - (urlptr - url));
383 } else {
384 /* A fully relative link is found. We find the last slash in the
385 current URL and paste the link there. */
386
387 /* XXX: we should really parse any ../ in the link as well. */
388 for(urlptr = url + strlen(url);
389 urlptr != url && *urlptr != ISO_slash;
390 --urlptr);
391 ++urlptr;
392 strncpy(urlptr, link, WWW_CONF_MAX_URLLEN - (urlptr - url));
393 }
394
395 /* Open the URL. */
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000396 show_url();
397 open_url();
adamdunkels269d7be2004-09-03 09:55:22 +0000398
399
400 start_loading();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000401}
402/*-----------------------------------------------------------------------------------*/
403/* log_back():
404 *
405 * Copies the current URL from the url variable and into the log for
406 * the back button.
407 */
408static void
409log_back(void)
410{
adamdunkels89c1f4e2003-08-09 13:33:25 +0000411 if(strncmp(url, history[(int)history_last], WWW_CONF_MAX_URLLEN) != 0) {
412 memcpy(history[(int)history_last], url, WWW_CONF_MAX_URLLEN);
413 ++history_last;
414 if(history_last >= WWW_CONF_HISTORY_SIZE) {
415 history_last = 0;
416 }
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000417 }
418}
419/*-----------------------------------------------------------------------------------*/
adamdunkelsf6eea752003-08-22 19:24:40 +0000420static void
421quit(void)
422{
423 ctk_window_close(&mainwindow);
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000424 ek_exit();
adamdunkelsf6eea752003-08-22 19:24:40 +0000425 id = EK_ID_NONE;
426 LOADER_UNLOAD();
427}
428/*-----------------------------------------------------------------------------------*/
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000429/* www_dispatcher():
430 *
431 * The program's signal dispatcher function. Is called by the ek
432 * dispatcher whenever a signal arrives.
433 */
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000434/*static
435 DISPATCHER_SIGHANDLER(www_sighandler, s, data)*/
436EK_EVENTHANDLER(www_eventhandler, ev, data)
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000437{
438 static struct ctk_widget *w;
439 static unsigned char i;
adamdunkels8bb5cca2003-08-24 22:41:31 +0000440 static char *argptr;
441
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000442 /* DISPATCHER_SIGHANDLER_ARGS(s, data);*/
443 EK_EVENTHANDLER_ARGS(ev, data);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000444
445
446 w = (struct ctk_widget *)data;
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000447
448 if(ev == tcpip_event) {
449 webclient_appcall(data);
450 } else if(ev == EK_EVENT_INIT) {
451 /* Create the main window. */
452 memset(webpage, 0, sizeof(webpage));
453 ctk_window_new(&mainwindow, WWW_CONF_WEBPAGE_WIDTH,
454 WWW_CONF_WEBPAGE_HEIGHT+5, "Web browser");
455 make_window();
456#ifdef WWW_CONF_HOMEPAGE
457 strncpy(editurl, WWW_CONF_HOMEPAGE, sizeof(editurl));
458#endif /* WWW_CONF_HOMEPAGE */
459 CTK_WIDGET_FOCUS(&mainwindow, &urlentry);
460
461 /* Create download dialog.*/
462 ctk_dialog_new(&wgetdialog, 38, 7);
463 CTK_WIDGET_ADD(&wgetdialog, &wgetlabel1);
464 CTK_WIDGET_ADD(&wgetdialog, &wgetlabel2);
465 CTK_WIDGET_ADD(&wgetdialog, &wgetnobutton);
466 CTK_WIDGET_ADD(&wgetdialog, &wgetyesbutton);
467
468 ctk_window_open(&mainwindow);
469
470 } else if(ev == ctk_signal_widget_activate) {
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000471 if(w == (struct ctk_widget *)&backbutton) {
adamdunkels269d7be2004-09-03 09:55:22 +0000472 firsty = 0;
473 start_loading();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000474
475 --history_last;
adamdunkels17e84a32003-04-02 09:54:39 +0000476 if(history_last > WWW_CONF_HISTORY_SIZE) {
477 history_last = WWW_CONF_HISTORY_SIZE - 1;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000478 }
adamdunkels30c449e2003-07-31 23:12:05 +0000479 memcpy(url, history[(int)history_last], WWW_CONF_MAX_URLLEN);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000480 open_url();
481 CTK_WIDGET_FOCUS(&mainwindow, &backbutton);
482 } else if(w == (struct ctk_widget *)&downbutton) {
adamdunkels269d7be2004-09-03 09:55:22 +0000483 firsty = pagey + WWW_CONF_WEBPAGE_HEIGHT - 4;
484 start_loading();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000485 open_url();
486 CTK_WIDGET_FOCUS(&mainwindow, &downbutton);
adamdunkels328e5352003-08-20 20:54:46 +0000487 } else if(w == (struct ctk_widget *)&gobutton ||
488 w == (struct ctk_widget *)&urlentry) {
adamdunkels269d7be2004-09-03 09:55:22 +0000489 start_loading();
490 firsty = 0;
adamdunkels30c449e2003-07-31 23:12:05 +0000491
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000492 log_back();
493 memcpy(url, editurl, WWW_CONF_MAX_URLLEN);
494 petsciiconv_toascii(url, WWW_CONF_MAX_URLLEN);
495 open_url();
496 CTK_WIDGET_FOCUS(&mainwindow, &gobutton);
497 } else if(w == (struct ctk_widget *)&stopbutton) {
adamdunkels269d7be2004-09-03 09:55:22 +0000498 loading = 0;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000499 webclient_close();
adamdunkelsf6eea752003-08-22 19:24:40 +0000500 } else if(w == (struct ctk_widget *)&wgetnobutton) {
501 ctk_dialog_close();
502 } else if(w == (struct ctk_widget *)&wgetyesbutton) {
adamdunkels8bb5cca2003-08-24 22:41:31 +0000503 ctk_dialog_close();
adamdunkelsf6eea752003-08-22 19:24:40 +0000504 quit();
adamdunkels8bb5cca2003-08-24 22:41:31 +0000505 argptr = arg_alloc(WWW_CONF_MAX_URLLEN);
506 if(argptr != NULL) {
507 strncpy(argptr, url, WWW_CONF_MAX_URLLEN);
508 }
509 program_handler_load("wget.prg", argptr);
adamdunkelsf6eea752003-08-22 19:24:40 +0000510
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000511#if WWW_CONF_FORMS
512 } else {
513 /* Check form buttons */
514 for(i = 0; i < pagewidgetptr; ++i) {
515 if(&pagewidgets[i] == w) {
516 formsubmit(&pagewidgetattribs[i].form);
517 /* show_statustext(pagewidgetattribs[i].form.formaction);*/
518 /* PRINTF(("Formaction %s formname %s inputname %s\n",
519 pagewidgetattribs[i].form.formaction,
520 pagewidgetattribs[i].form.formname,
521 pagewidgetattribs[i].form.inputname));*/
522 break;
523 }
524 }
525#endif /* WWW_CONF_FORMS */
526 }
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000527 } else if(ev == ctk_signal_hyperlink_activate) {
adamdunkels269d7be2004-09-03 09:55:22 +0000528 firsty = 0;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000529 log_back();
530 open_link(w->widget.hyperlink.url);
531 CTK_WIDGET_FOCUS(&mainwindow, &stopbutton);
adamdunkelsf6eea752003-08-22 19:24:40 +0000532 /* ctk_window_open(&mainwindow);*/
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000533 } else if(ev == ctk_signal_hyperlink_hover) {
adamdunkels89c1f4e2003-08-09 13:33:25 +0000534 if(CTK_WIDGET_TYPE((struct ctk_widget *)data) ==
535 CTK_WIDGET_HYPERLINK) {
adamdunkelsc4bf5ca2003-04-18 00:20:22 +0000536 strncpy(statustexturl, w->widget.hyperlink.url,
537 sizeof(statustexturl));
538 petsciiconv_topetscii(statustexturl, sizeof(statustexturl));
539 show_statustext(statustexturl);
540 }
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000541 } else if(ev == resolv_event_found) {
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000542 /* Either found a hostname, or not. */
543 if((char *)data != NULL &&
544 resolv_lookup((char *)data) != NULL) {
545 open_url();
546 } else {
547 show_statustext("Host not found.");
548 }
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000549 } else if(ev == ctk_signal_window_close ||
550 ev == EK_EVENT_REQUEST_EXIT) {
adamdunkelsf6eea752003-08-22 19:24:40 +0000551 quit();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000552 }
553}
554/*-----------------------------------------------------------------------------------*/
555/* set_url():
556 *
557 * Constructs an URL from the arguments and puts it into the global
558 * "url" variable and the visible "editurl" (which is shown in the URL
559 * text entry widget in the browser window).
560 */
561static void
562set_url(char *host, u16_t port, char *file)
563{
564 char *urlptr;
565
566 memset(url, 0, WWW_CONF_MAX_URLLEN);
567
568 if(strncmp(file, http_http, 7) == 0) {
569 strncpy(url, file, sizeof(url));
570 } else {
571 strncpy(url, http_http, 7);
572 urlptr = url + 7;
573 strcpy(urlptr, host);
574 urlptr += strlen(host);
575 strcpy(urlptr, file);
576 }
577
578 show_url();
579}
580/*-----------------------------------------------------------------------------------*/
581/* webclient_aborted():
582 *
583 * Callback function. Called from the webclient when the HTTP
584 * connection was abruptly aborted.
585 */
586void
587webclient_aborted(void)
588{
589 show_statustext("Connection reset by peer");
590}
591/*-----------------------------------------------------------------------------------*/
592/* webclient_timedout():
593 *
594 * Callback function. Called from the webclient when the HTTP
595 * connection timed out.
596 */
597void
598webclient_timedout(void)
599{
600 show_statustext("Connection timed out");
601}
602/*-----------------------------------------------------------------------------------*/
603/* webclient_closed():
604 *
605 * Callback function. Called from the webclient when the HTTP
606 * connection was closed after a request from the "webclient_close()"
607 * function. .
608 */
609void
610webclient_closed(void)
611{
612 show_statustext("Stopped.");
adamdunkels17e84a32003-04-02 09:54:39 +0000613 petsciiconv_topetscii(&webpage[(WWW_CONF_WEBPAGE_HEIGHT - 1) *
614 WWW_CONF_WEBPAGE_WIDTH], WWW_CONF_WEBPAGE_WIDTH);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000615 redraw_window();
616}
617/*-----------------------------------------------------------------------------------*/
618/* webclient_closed():
619 *
620 * Callback function. Called from the webclient when the HTTP
621 * connection is connected.
622 */
623void
624webclient_connected(void)
625{
adamdunkels269d7be2004-09-03 09:55:22 +0000626 start_loading();
627
628 clear_page();
adamdunkels30c449e2003-07-31 23:12:05 +0000629
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000630 show_statustext("Request sent...");
631 set_url(webclient_hostname(), webclient_port(), webclient_filename());
632
633#if WWW_CONF_RENDERSTATE
634 renderstate = HTMLPARSER_RENDERSTATE_NONE;
635#endif /* WWW_CONF_RENDERSTATE */
636 htmlparser_init();
637}
638/*-----------------------------------------------------------------------------------*/
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000639/* webclient_datahandler():
640 *
641 * Callback function. Called from the webclient module when HTTP data
642 * has arrived.
643 */
644void
645webclient_datahandler(char *data, u16_t len)
646{
647 if(len > 0) {
adamdunkelsf6eea752003-08-22 19:24:40 +0000648 if(strcmp(webclient_mimetype(), http_texthtml) == 0) {
649 count = (count + 1) & 3;
650 show_statustext(receivingmsgs[count]);
651 htmlparser_parse(data, len);
adamdunkels269d7be2004-09-03 09:55:22 +0000652 redraw_window();
adamdunkelsf6eea752003-08-22 19:24:40 +0000653 } else {
adamdunkels8bb5cca2003-08-24 22:41:31 +0000654 uip_abort();
adamdunkelsf6eea752003-08-22 19:24:40 +0000655 ctk_dialog_open(&wgetdialog);
656 }
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000657 } else {
658 /* Clear remaining parts of page. */
adamdunkels269d7be2004-09-03 09:55:22 +0000659 loading = 0;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000660 }
661
662 if(data == NULL) {
adamdunkels269d7be2004-09-03 09:55:22 +0000663 loading = 0;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000664 show_statustext("Done.");
adamdunkels17e84a32003-04-02 09:54:39 +0000665 petsciiconv_topetscii(&webpage[(WWW_CONF_WEBPAGE_HEIGHT - 1) *
666 WWW_CONF_WEBPAGE_WIDTH], WWW_CONF_WEBPAGE_WIDTH);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000667 redraw_window();
668 }
669}
670/*-----------------------------------------------------------------------------------*/
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000671static void *
adamdunkels269d7be2004-09-03 09:55:22 +0000672add_pagewidget(char *text, unsigned char len, unsigned char type,
673 unsigned char border)
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000674{
adamdunkelsd59dadf2003-08-15 18:48:35 +0000675 register struct ctk_widget *lptr;
adamdunkels269d7be2004-09-03 09:55:22 +0000676 register unsigned char *wptr;
677 static unsigned char maxwidth;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000678 static void *dataptr;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000679
adamdunkels269d7be2004-09-03 09:55:22 +0000680 if(!loading) {
681 return NULL;
682 }
683
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000684 if(len + border == 0) {
685 return NULL;
686 }
687
adamdunkels17e84a32003-04-02 09:54:39 +0000688 maxwidth = WWW_CONF_WEBPAGE_WIDTH - (1 + 2 * border);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000689
690 /* If the text of the link is too long so that it does not fit into
691 the width of the current window, counting from the current x
692 coordinate, we first try to jump to the next line. */
693 if(len + x > maxwidth) {
adamdunkels269d7be2004-09-03 09:55:22 +0000694 htmlparser_newline();
695 if(!loading) {
696 return NULL;
697 }
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000698 }
699
700 /* If the text of the link still is too long, we just chop it off!
701 XXX: this is not really the right thing to do, we should probably
adamdunkels269d7be2004-09-03 09:55:22 +0000702 either make a link into a multiline link, or add multiple
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000703 buttons. But this will do for now. */
704 if(len > maxwidth) {
705 text[maxwidth] = 0;
706 len = maxwidth;
707 }
708
709 dataptr = NULL;
710
adamdunkels269d7be2004-09-03 09:55:22 +0000711 if(firsty == pagey) {
712 wptr = webpageptr;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000713 /* To save memory, we'll copy the widget text to the web page
714 drawing area and reference it from there. */
adamdunkels269d7be2004-09-03 09:55:22 +0000715 wptr[0] = 0;
716 wptr += border;
717 memcpy(wptr, text, len);
718 wptr[len] = 0;
719 wptr[len + border] = ' ';
adamdunkels17e84a32003-04-02 09:54:39 +0000720 if(pagewidgetptr < WWW_CONF_MAX_NUMPAGEWIDGETS) {
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000721 dataptr = &pagewidgetattribs[pagewidgetptr];
722 lptr = &pagewidgets[pagewidgetptr];
723
724 switch(type) {
725 case CTK_WIDGET_HYPERLINK:
726 CTK_HYPERLINK_NEW((struct ctk_hyperlink *)lptr, x,
adamdunkels269d7be2004-09-03 09:55:22 +0000727 y + 3, len,
728 wptr, dataptr);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000729 break;
730 case CTK_WIDGET_BUTTON:
731 CTK_BUTTON_NEW((struct ctk_button *)lptr, x,
adamdunkels269d7be2004-09-03 09:55:22 +0000732 y + 3, len,
733 wptr);
734 ((struct formattribs *)dataptr)->inputvalue = wptr;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000735 break;
736 case CTK_WIDGET_TEXTENTRY:
adamdunkels269d7be2004-09-03 09:55:22 +0000737 CTK_TEXTENTRY_NEW((struct ctk_textentry *)lptr, x,
738 y + 3, len, 1,
739 wptr, len);
740 ((struct formattribs *)dataptr)->inputvalue = wptr;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000741 break;
742 }
adamdunkelsf2f8cb22004-07-04 11:35:07 +0000743 CTK_WIDGET_SET_FLAG(lptr, CTK_WIDGET_FLAG_MONOSPACE);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000744 CTK_WIDGET_ADD(&mainwindow, lptr);
745
746 ++pagewidgetptr;
747 }
748 }
749 /* Increase the x coordinate with the length of the link text plus
750 the extra space behind it and the CTK button markers. */
adamdunkels269d7be2004-09-03 09:55:22 +0000751 len = len + 1 + 2 * border;
752 x += len;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000753
adamdunkels269d7be2004-09-03 09:55:22 +0000754 if(firsty == pagey) {
755 webpageptr += len;
756 }
757
758 if(x == WWW_CONF_WEBPAGE_WIDTH) {
759 htmlparser_newline();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000760 }
761
762 return dataptr;
763}
764/*-----------------------------------------------------------------------------------*/
adamdunkels269d7be2004-09-03 09:55:22 +0000765#if WWW_CONF_RENDERSTATE
766static void
767centerline(char *wptr)
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000768{
adamdunkels269d7be2004-09-03 09:55:22 +0000769 static char tmpcenterline[WWW_CONF_WEBPAGE_WIDTH];
770 unsigned char spaces, i;
771 char *cptr;
772 register struct ctk_widget *linksptr;
773
774 cptr = wptr + WWW_CONF_WEBPAGE_WIDTH - 1;
775 for(spaces = 0; spaces < WWW_CONF_WEBPAGE_WIDTH; ++spaces) {
776 if(*cptr-- != 0) {
777 break;
778 }
779 }
780
781 spaces = spaces / 2;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000782
adamdunkels269d7be2004-09-03 09:55:22 +0000783 memcpy(tmpcenterline,
784 wptr,
785 WWW_CONF_WEBPAGE_WIDTH);
786 memcpy(wptr + spaces,
787 tmpcenterline,
788 WWW_CONF_WEBPAGE_WIDTH - spaces);
789 memset(wptr, ' ', spaces);
790
791 linksptr = pagewidgets;
792
793 for(i = 0; i < pagewidgetptr; ++i) {
794 if(CTK_WIDGET_YPOS(linksptr) == y + 2) {
795 linksptr->x += spaces;
796 linksptr->widget.hyperlink.text += spaces;
797 }
798 ++linksptr;
799 }
800}
801#endif /* WWW_CONF_RENDERSTATE */
802/*-----------------------------------------------------------------------------------*/
803void
804htmlparser_newline(void)
805{
806 char *wptr;
807
808 if(pagey < firsty) {
809 ++pagey;
810 x = 0;
811 return;
812 }
813
oliverschmidt453510a2005-01-21 14:25:27 +0000814 if(!loading) {
815 return;
816 }
817
adamdunkels269d7be2004-09-03 09:55:22 +0000818 webpageptr += (WWW_CONF_WEBPAGE_WIDTH - x);
819 ++y;
820 x = 0;
821
822 wptr = webpageptr - WWW_CONF_WEBPAGE_WIDTH;
823 petsciiconv_topetscii(wptr,
824 WWW_CONF_WEBPAGE_WIDTH);
825#if WWW_CONF_RENDERSTATE
826 if(renderstate & HTMLPARSER_RENDERSTATE_CENTER) {
827 centerline(wptr);
828 }
829#endif /* WWW_CONF_RENDERSTATE */
830
831 if(y == WWW_CONF_WEBPAGE_HEIGHT) {
832 loading = 0;
833 webclient_close();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000834 }
835}
836/*-----------------------------------------------------------------------------------*/
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000837void
adamdunkels269d7be2004-09-03 09:55:22 +0000838htmlparser_word(char *word, unsigned char wordlen)
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000839{
adamdunkels269d7be2004-09-03 09:55:22 +0000840
841 if(loading) {
842 if(wordlen + 1 > WWW_CONF_WEBPAGE_WIDTH - x) {
843 htmlparser_newline();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000844 }
adamdunkels269d7be2004-09-03 09:55:22 +0000845
846 if(loading) {
847 if(pagey == firsty) {
848 memcpy(webpageptr, word, wordlen);
849 webpageptr += wordlen;
850 *webpageptr = ' ';
851 ++webpageptr;
852 }
853 x += wordlen + 1;
854 if(x == WWW_CONF_WEBPAGE_WIDTH) {
855 htmlparser_newline();
856 }
857 }
858 }
859}
860/*-----------------------------------------------------------------------------------*/
861void
862htmlparser_link(char *text, unsigned char textlen, char *url)
863{
864 static unsigned char *linkurlptr;
865
866 linkurlptr = add_pagewidget(text, textlen, CTK_WIDGET_HYPERLINK, 0);
867 if(linkurlptr != NULL &&
868 strlen(url) < WWW_CONF_MAX_URLLEN) {
869 strcpy(linkurlptr, url);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000870 }
871}
872/*-----------------------------------------------------------------------------------*/
873#if WWW_CONF_RENDERSTATE
874void
875htmlparser_renderstate(unsigned char s)
876{
877 if((s & HTMLPARSER_RENDERSTATE_STATUSMASK) ==
878 HTMLPARSER_RENDERSTATE_BEGIN) {
879 renderstate |= s & ~HTMLPARSER_RENDERSTATE_STATUSMASK;
880 } else {
881 renderstate &= ~(s & ~HTMLPARSER_RENDERSTATE_STATUSMASK);
882 }
883}
884#endif /* WWW_CONF_RENDERSTATE */
885
886#if WWW_CONF_FORMS
887/*-----------------------------------------------------------------------------------*/
888void
889htmlparser_submitbutton(char *text, char *name,
890 char *formname, char *formaction)
891{
892 register struct formattribs *form;
adamdunkels269d7be2004-09-03 09:55:22 +0000893
894 form = add_pagewidget(text, strlen(text), CTK_WIDGET_BUTTON, 1);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000895 if(form != NULL) {
896 strncpy(form->formaction, formaction, WWW_CONF_MAX_FORMACTIONLEN);
897 strncpy(form->formname, formname, WWW_CONF_MAX_FORMNAMELEN);
898 strncpy(form->inputname, name, WWW_CONF_MAX_INPUTNAMELEN);
899 form->inputtype = FORMINPUTTYPE_SUBMITBUTTON;
900 }
adamdunkels30c449e2003-07-31 23:12:05 +0000901
902
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000903}
904/*-----------------------------------------------------------------------------------*/
905void
906htmlparser_inputfield(char *text, char *name,
907 char *formname, char *formaction)
908{
909 register struct formattribs *form;
adamdunkels269d7be2004-09-03 09:55:22 +0000910
911 form = add_pagewidget(text, strlen(text), CTK_WIDGET_TEXTENTRY, 1);
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000912 if(form != NULL) {
913 strncpy(form->formaction, formaction, WWW_CONF_MAX_FORMACTIONLEN);
914 strncpy(form->formname, formname, WWW_CONF_MAX_FORMNAMELEN);
915 strncpy(form->inputname, name, WWW_CONF_MAX_INPUTNAMELEN);
916 form->inputtype = FORMINPUTTYPE_INPUTFIELD;
917 }
918}
919/*-----------------------------------------------------------------------------------*/
920static void
921formsubmit(struct formattribs *attribs)
922{
923 unsigned char i, j;
adamdunkels1a23b5b2003-08-05 13:50:02 +0000924 register char *urlptr, *valueptr;
925 register struct formattribs *faptr;
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000926
927 urlptr = &tmpurl[0];
928
929 strncpy(urlptr, attribs->formaction, WWW_CONF_MAX_URLLEN);
930 tmpurl[WWW_CONF_MAX_URLLEN] = 0;
931 urlptr += strlen(urlptr);
932 *urlptr = ISO_questionmark;
933 ++urlptr;
934
935
936 /* Construct an URL by finding all input field forms with the same
937 formname as the current submit button, and add the submit button
938 URL stuff as well. */
939 for(i = 0; i < pagewidgetptr; ++i) {
940 if(urlptr - &tmpurl[0] >= WWW_CONF_MAX_URLLEN) {
941 break;
942 }
943
944 faptr = &pagewidgetattribs[i].form;
945
946 if(strcmp(attribs->formaction, faptr->formaction) == 0 &&
947 strcmp(attribs->formname, faptr->formname) == 0 &&
948 (faptr->inputtype == FORMINPUTTYPE_INPUTFIELD ||
949 faptr == attribs)) {
950
951 /* Copy the name of the input field into the URL and append a
952 questionmark. */
953 strncpy(urlptr, faptr->inputname, WWW_CONF_MAX_URLLEN - strlen(tmpurl));
954 tmpurl[WWW_CONF_MAX_URLLEN] = 0;
955 urlptr += strlen(urlptr);
956 *urlptr = ISO_eq;
957 ++urlptr;
958
959 /* Convert and copy the contents of the input field to the URL
960 and append an ampersand. */
961 valueptr = pagewidgets[i].widget.textentry.text;
962 petsciiconv_toascii(valueptr, WWW_CONF_MAX_INPUTVALUELEN);
963 for(j = 0; j < WWW_CONF_MAX_INPUTVALUELEN; ++j) {
964 if(urlptr - &tmpurl[0] >= WWW_CONF_MAX_URLLEN) {
965 break;
966 }
967 *urlptr = *valueptr;
968 if(*urlptr == ISO_space) {
969 *urlptr = ISO_plus;
970 }
971 if(*urlptr == 0) {
972 break;
973 }
974 ++urlptr;
975 ++valueptr;
976 }
977
978 *urlptr = ISO_ampersand;
979 ++urlptr;
980 }
981 }
982 --urlptr;
983 *urlptr = 0;
adamdunkels89c1f4e2003-08-09 13:33:25 +0000984 log_back();
adamdunkelsca9ddcb2003-03-19 14:13:31 +0000985 open_link(tmpurl);
986}
987/*-----------------------------------------------------------------------------------*/
988#endif /* WWW_CONF_FORMS */