diff --git a/src/callbacks.c b/src/callbacks.c index 00bd1ca..45f37dd 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -15,6 +15,8 @@ static char const _license[] = "You should have received a copy of the GNU General Public License along\n" "with Surfer; if not, write to the Free Software Foundation, Inc., 59 Temple\n" "Place, Suite 330, Boston, MA 02111-1307 USA\n"; +/* TODO: + * - implement on_file_open() :) */ diff --git a/src/ghtml-gtkhtml.c b/src/ghtml-gtkhtml.c index 6ea3bdb..bc3dfc0 100644 --- a/src/ghtml-gtkhtml.c +++ b/src/ghtml-gtkhtml.c @@ -13,46 +13,88 @@ * Surfer; if not, write to the Free Software Foundation, Inc., 59 Temple Place, * Suite 330, Boston, MA 02111-1307 USA */ /* TODO: + * - implement http protocol again * - fix URL generation for relative path + * - fix links from directory listing * - progressive file load * - update the URL and title of the main window - * - implement selection */ + * - implement selection + * - need to take care of CSRF? eg remotely load local files */ +#include +#include +#include +#include #include #include #include #include #define GNET_EXPERIMENTAL #include +#include #include "ghtml.h" -/* ghtml */ +/* GHtml */ /* private */ +typedef struct _GHtmlConn GHtmlConn; + typedef struct _GHtml { + Surfer * surfer; + + gchar * html_base; + gchar * html_url; /* FIXME implement history */ + /* connections */ + struct _GHtmlConn ** conns; + size_t conns_cnt; + /* html widget */ - gchar * html_base; HtmlDocument * html_document; gchar * html_title; GtkWidget * html_view; } GHtml; +struct _GHtmlConn +{ + GHtml * ghtml; + + char * url; + guint64 content_length; + guint64 data_received; + HtmlStream * stream; + + /* file */ + GIOChannel * file; + guint64 file_size; + guint64 file_read; + + /* http */ + GConnHttp * http; +}; + /* prototypes */ -static gboolean _ghtml_document_load(GHtml * ghtml, gchar const * base, +static GHtmlConn * _ghtmlconn_new(GHtml * ghtml, HtmlStream * stream, gchar const * url); +static void _ghtmlconn_delete(GHtmlConn * ghtmlconn); + +static int _ghtml_document_load(GHtml * ghtml, gchar const * url); static gchar * _ghtml_make_url(gchar const * base, gchar const * url); +static int _ghtml_stream_load(GHtml * ghtml, HtmlStream * stream, + gchar const * url); /* callbacks */ static void _on_link_clicked(HtmlDocument * document, const gchar * url); static void _on_request_url(HtmlDocument * document, const gchar * url, HtmlStream * stream); static void _on_set_base(HtmlDocument * document, const gchar * url); +static void _on_submit(HtmlDocument * document, const gchar * url, + const gchar * method, const gchar * encoding); static void _on_title_changed(HtmlDocument * document, const gchar * title); @@ -65,7 +107,11 @@ GtkWidget * ghtml_new(Surfer * surfer) if((ghtml = malloc(sizeof(*ghtml))) == NULL) return NULL; + ghtml->surfer = surfer; ghtml->html_base = NULL; + ghtml->html_url = NULL; + ghtml->conns = NULL; + ghtml->conns_cnt = 0; ghtml->html_view = html_view_new(); ghtml->html_document = html_document_new(); ghtml->html_title = NULL; @@ -76,6 +122,8 @@ GtkWidget * ghtml_new(Surfer * surfer) G_CALLBACK(_on_request_url), NULL); g_signal_connect(G_OBJECT(ghtml->html_document), "set-base", G_CALLBACK( _on_set_base), NULL); + g_signal_connect(G_OBJECT(ghtml->html_document), "submit", G_CALLBACK( + _on_submit), NULL); g_signal_connect(G_OBJECT(ghtml->html_document), "title-changed", G_CALLBACK(_on_title_changed), NULL); html_view_set_document(HTML_VIEW(ghtml->html_view), @@ -112,10 +160,12 @@ char const * ghtml_get_link_message(GtkWidget * ghtml) } -char const * ghtml_get_location(GtkWidget * ghtml) +char const * ghtml_get_location(GtkWidget * widget) { - /* FIXME implement */ - return NULL; + GHtml * ghtml; + + ghtml = g_object_get_data(G_OBJECT(widget), "ghtml"); + return ghtml->html_url; } @@ -152,11 +202,12 @@ void ghtml_load_url(GtkWidget * widget, char const * url) fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, url); #endif ghtml = g_object_get_data(G_OBJECT(widget), "ghtml"); - if(_ghtml_document_load(ghtml, NULL, url) != TRUE) + if(_ghtml_document_load(ghtml, url) != 0) return; - /* FIXME with current code another base may have been set in between */ g_free(ghtml->html_base); ghtml->html_base = g_strdup(url); + g_free(ghtml->html_url); + ghtml->html_url = g_strdup(url); } @@ -166,10 +217,9 @@ void ghtml_refresh(GtkWidget * widget) GHtml * ghtml; ghtml = g_object_get_data(G_OBJECT(widget), "ghtml"); - if(ghtml->html_base == NULL) + if(ghtml->html_url == NULL) return; - /* FIXME should differentiate URL and base */ - _ghtml_document_load(ghtml, NULL, ghtml->html_base); + _ghtml_document_load(ghtml, ghtml->html_url); } @@ -236,60 +286,96 @@ void ghtml_zoom_reset(GtkWidget * widget) /* private */ /* functions */ -/* ghtml_document_load */ -static gboolean _load_write_stream(HtmlStream * stream, gchar const * base, - gchar const * url); - -static gboolean _ghtml_document_load(GHtml * ghtml, gchar const * base, +static GHtmlConn * _ghtmlconn_new(GHtml * ghtml, HtmlStream * stream, gchar const * url) { -#ifdef DEBUG - fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\")\n", __func__, base, url); -#endif - html_document_open_stream(ghtml->html_document, "text/html"); - if(_load_write_stream(ghtml->html_document->current_stream, base, url) - != TRUE) + GHtmlConn ** p; + GHtmlConn * c; + + if((p = realloc(ghtml->conns, sizeof(*p) * (ghtml->conns_cnt + 1))) + == NULL) + return NULL; + ghtml->conns = p; + if((c = malloc(sizeof(*c))) == NULL) + return NULL; + ghtml->conns[ghtml->conns_cnt] = c; + c->ghtml = ghtml; + c->url = strdup(url); + c->content_length = 0; + c->data_received = 0; + c->stream = stream; + c->file = NULL; + c->file_size = 0; + c->file_read = 0; + c->http = NULL; + if(c->url == NULL) { - html_document_write_stream(ghtml->html_document, "500", 3); - html_document_close_stream(ghtml->html_document); - return FALSE; /* FIXME report error */ + _ghtmlconn_delete(c); + return NULL; } - html_document_close_stream(ghtml->html_document); - return TRUE; + ghtml->conns_cnt++; +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s(%p, %p, \"%s\") => %p\n", __func__, ghtml, + stream, url, c); +#endif + return c; } -static gboolean _load_write_stream(HtmlStream * stream, gchar const * base, - gchar const * url) -{ - gchar * u; - gchar * buf = NULL; - gsize len = 0; - guint response; - gboolean error; - if((u = _ghtml_make_url(base, url)) == NULL) - return FALSE; +/* ghtmlconn_delete */ +static void _ghtmlconn_delete_file(GHtmlConn * ghtmlconn); +static void _ghtmlconn_delete_http(GHtmlConn * ghtmlconn); + +static void _ghtmlconn_delete(GHtmlConn * ghtmlconn) +{ + GHtml * ghtml = ghtmlconn->ghtml; + size_t i; + #ifdef DEBUG - fprintf(stderr, "DEBUG: %s() url=\"%s\"\n", __func__, u); + fprintf(stderr, "DEBUG: %s(%p)\n", __func__, ghtmlconn); #endif - if(u[0] == '/') - error = g_file_get_contents(u, &buf, &len, NULL) == FALSE; - else if(strncmp("file:/", u, 6) == 0) - error = g_file_get_contents(&u[5], &buf, &len, NULL) == FALSE; - /* XXX assuming the rest is http */ - else - error = gnet_http_get(u, &buf, &len, &response) != TRUE; - g_free(u); - if(error) - return FALSE; /* FIXME report error */ - html_stream_write(stream, buf, len); - return TRUE; + for(i = 0; i < ghtml->conns_cnt; i++) + if(ghtml->conns[i] == ghtmlconn) + { + ghtml->conns[i] = NULL; /* don't double free later */ + break; + } + if(ghtmlconn->file != NULL) + _ghtmlconn_delete_file(ghtmlconn); + else if(ghtmlconn->http != NULL) + _ghtmlconn_delete_http(ghtmlconn); + free(ghtmlconn->url); + if(ghtmlconn->stream != NULL) + html_stream_close(ghtmlconn->stream); + free(ghtmlconn); +} + +static void _ghtmlconn_delete_file(GHtmlConn * ghtmlconn) +{ + /* FIXME implement */ +} + +static void _ghtmlconn_delete_http(GHtmlConn * ghtmlconn) +{ + /* FIXME implement */ +} + + +/* ghtml_document_load */ +static int _ghtml_document_load(GHtml * ghtml, gchar const * url) +{ + html_document_open_stream(ghtml->html_document, "text/html"); + return _ghtml_stream_load(ghtml, ghtml->html_document->current_stream, + url); } /* ghtml_make_url */ static gchar * _ghtml_make_url(gchar const * base, gchar const * url) { + char * b; + char * p; + if(url == NULL) return NULL; /* XXX use a more generic protocol finder (strchr(':')) */ @@ -300,30 +386,195 @@ static gchar * _ghtml_make_url(gchar const * base, gchar const * url) if(base != NULL) { if(url[0] == '/') - /* FIXME construct from / */ + /* FIXME construct from / of base */ return g_strdup_printf("%s%s", base, url); - /* FIXME construct from last / */ - return g_strdup_printf("%s/%s", base, url); + /* construct from basename */ + if((b = strdup(base)) == NULL) + return NULL; + if((p = strrchr(b, '/')) != NULL) + *p = '\0'; + p = g_strdup_printf("%s/%s", b, url); + free(b); + return p; } /* base is NULL, url is not NULL */ - if(strncmp("ftp", url, 3) == 0) - return g_strdup_printf("%s%s", "ftp://", url); if(url[0] == '/') return g_strdup(url); + /* guess protocol */ + if(strncmp("ftp", url, 3) == 0) + return g_strdup_printf("%s%s", "ftp://", url); return g_strdup_printf("%s%s", "http://", url); } +/* ghtml_stream_load */ +static gboolean _stream_load_idle(gpointer data); +static gboolean _stream_load_idle_directory(GHtmlConn * conn); +static gboolean _stream_load_idle_file(GHtmlConn * conn); +static gboolean _stream_load_watch_file(GIOChannel * source, + GIOCondition condition, gpointer data); +static gboolean _stream_load_idle_http(GHtmlConn * conn); + +static int _ghtml_stream_load(GHtml * ghtml, HtmlStream * stream, + gchar const * url) +{ + GHtmlConn * conn; + + if((conn = _ghtmlconn_new(ghtml, stream, url)) == NULL) + return -1; + g_idle_add(_stream_load_idle, conn); + return 0; +} + +static gboolean _stream_load_idle(gpointer data) +{ + GHtmlConn * conn = data; + +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s(%p) \"%s\"\n", __func__, data, conn->url); +#endif + if(conn->url[0] == '/') + return _stream_load_idle_file(conn); + if(strncmp(conn->url, "file:", 5) == 0) + { + strcpy(conn->url, &conn->url[5]); /* XXX no corruption? */ + return _stream_load_idle_file(conn); + } + if(strncmp(conn->url, "http:", 5) == 0) + return _stream_load_idle_http(conn); + surfer_error(conn->ghtml->surfer, "Unknown protocol", 0); + _ghtmlconn_delete(conn); + return FALSE; +} + +static gboolean _stream_load_idle_directory(GHtmlConn * conn) +{ + const char tail[] = "\n
\n\n"; + char buf[1024]; + DIR * dir; + struct dirent * de; + + if((dir = opendir(conn->url)) == NULL) + surfer_error(conn->ghtml->surfer, strerror(errno), 0); + else + { + snprintf(buf, sizeof(buf), "%s%s%s%s%s", "" + "Index of ", conn->url, "\n" + "

Index of ", conn->url, + "

\n
\n
    \n"); +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s", buf); +#endif + html_stream_write(conn->stream, buf, strlen(buf)); + while((de = readdir(dir)) != NULL) + { + snprintf(buf, sizeof(buf), "%s%s%s%s%s", + "
  • d_name, "\">", + de->d_name, "
  • \n"); +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s", buf); +#endif + html_stream_write(conn->stream, buf, strlen(buf)); + } +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s", tail); +#endif + html_stream_write(conn->stream, tail, sizeof(tail) - 1); + closedir(dir); + surfer_set_progress(conn->ghtml->surfer, 1.0); + } + _ghtmlconn_delete(conn); + return FALSE; +} + +static gboolean _stream_load_idle_file(GHtmlConn * conn) +{ + int fd; + struct stat st; + GIOChannel * channel; + + surfer_set_progress(conn->ghtml->surfer, 0.0); + if((fd = open(conn->url, O_RDONLY)) < 0) + { + surfer_error(conn->ghtml->surfer, "Not implemented yet", 0); + _ghtmlconn_delete(conn); + } + else + { + if(fstat(fd, &st) == 0) + { + if(S_ISDIR(st.st_mode)) + { + close(fd); + return _stream_load_idle_directory(conn); + } + conn->file_size = st.st_size; + } + channel = g_io_channel_unix_new(fd); + g_io_add_watch(channel, G_IO_IN, _stream_load_watch_file, conn); + } + return FALSE; +} + +static gboolean _stream_load_watch_file(GIOChannel * source, + GIOCondition condition, gpointer data) +{ + GHtmlConn * conn = data; + gsize len; + char buf[BUFSIZ]; + gdouble fraction; + + if(condition != G_IO_IN) + { + _ghtmlconn_delete(conn); + return FALSE; + } + if(g_io_channel_read(source, buf, sizeof(buf), &len) + != G_IO_ERROR_NONE) + { + /* FIXME report error */ + _ghtmlconn_delete(conn); + return FALSE; + } + if(len == 0) /* no more data */ + { + _ghtmlconn_delete(conn); + return FALSE; + } + html_stream_write(conn->stream, buf, len); + conn->file_read+=len; + if(conn->file_size > 0) + { + fraction = conn->file_read; + surfer_set_progress(conn->ghtml->surfer, + fraction / conn->file_size); + } + return TRUE; +} + +static gboolean _stream_load_idle_http(GHtmlConn * conn) +{ + surfer_error(conn->ghtml->surfer, "Not implemented yet", 0); + _ghtmlconn_delete(conn); + return FALSE; +} + + /* callbacks */ static void _on_link_clicked(HtmlDocument * document, const gchar * url) { GHtml * ghtml; + gchar * link; -#ifdef DEBUG - fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, url); -#endif ghtml = g_object_get_data(G_OBJECT(document), "ghtml"); - _ghtml_document_load(ghtml, ghtml->html_base, url); + if((link = _ghtml_make_url(ghtml->html_base, url)) == NULL) + return; /* FIXME report error */ +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s(\"%s\") base=\"%s\" => \"%s\"\n", __func__, + url, ghtml->html_base, link); +#endif + _ghtml_document_load(ghtml, link); + g_free(link); } @@ -331,13 +582,17 @@ static void _on_request_url(HtmlDocument * document, const gchar * url, HtmlStream * stream) { GHtml * ghtml; + gchar * link; -#ifdef DEBUG - fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, url); -#endif ghtml = g_object_get_data(G_OBJECT(document), "ghtml"); - _load_write_stream(stream, ghtml->html_base, url); - html_stream_close(stream); + if((link = _ghtml_make_url(ghtml->html_base, url)) == NULL) + return; /* FIXME report error */ +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s(\"%s\") base=\"%s\" => \"%s\"\n", __func__, + url, ghtml->html_base, link); +#endif + _ghtml_stream_load(ghtml, stream, link); + g_free(link); } @@ -354,6 +609,29 @@ static void _on_set_base(HtmlDocument * document, const gchar * url) } +static void _on_submit(HtmlDocument * document, const gchar * url, + const gchar * method, const gchar * encoding) +{ + GHtml * ghtml; + +#ifdef DEBUG + fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", \"%s\")\n", __func__, + url, method, encoding); +#endif + ghtml = g_object_get_data(G_OBJECT(document), "ghtml"); + if(strcmp(method, "GET") == 0) + _ghtml_document_load(ghtml, url); + else if(strcmp(method, "POST") == 0) + _ghtml_document_load(ghtml, url); + else + { + /* FIXME implement */ + surfer_error(ghtml->surfer, "Unsupported method", 0); + return; + } +} + + static void _on_title_changed(HtmlDocument * document, const gchar * title) { GHtml * ghtml; @@ -364,5 +642,5 @@ static void _on_title_changed(HtmlDocument * document, const gchar * title) ghtml = g_object_get_data(G_OBJECT(document), "ghtml"); g_free(ghtml->html_title); ghtml->html_title = g_strdup(title); - /* FIXME emit signal */ + surfer_set_title(ghtml->surfer, title); } diff --git a/src/project.conf b/src/project.conf index 1dbb1ef..c865574 100644 --- a/src/project.conf +++ b/src/project.conf @@ -18,7 +18,7 @@ cflags=`pkg-config --cflags gtk+-2.0 firefox-gtkmozembed` ldflags=`pkg-config --libs gtk+-2.0 firefox-gtkmozembed` #cppflags=-D WITH_GTKHTML #cflags=`pkg-config --cflags gtk+-2.0 libgtkhtml-2.0 gnet-2.0` -#ldflags=`pkg-config --libs gtk+-2.0 libgtkhtml-2.0 gnet-2.0` +#ldflags=-L $(PREFIX)/lib -Wl,-rpath,$(PREFIX)/lib -l System `pkg-config --libs gtk+-2.0 libgtkhtml-2.0 gnet-2.0` sources=surfer.c,ghtml.c,callbacks.c,main.c install=$(BINDIR) diff --git a/src/surfer.c b/src/surfer.c index e5ea1e8..6b3dec9 100644 --- a/src/surfer.c +++ b/src/surfer.c @@ -123,13 +123,14 @@ Surfer * surfer_new(char const * url) GtkWidget * toolbar; GtkToolItem * toolitem; GtkWidget * widget; + GtkWidget * hbox; if((surfer = malloc(sizeof(*surfer))) == NULL) return NULL; /* window */ surfer->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(surfer->window), 800, 600); - gtk_window_set_title(GTK_WINDOW(surfer->window), "Web surfer"); + surfer_set_title(surfer, NULL); g_signal_connect(G_OBJECT(surfer->window), "delete_event", G_CALLBACK( on_closex), surfer); vbox = gtk_vbox_new(FALSE, 0); @@ -209,12 +210,16 @@ Surfer * surfer_new(char const * url) ghtml_load_url(surfer->view, url); gtk_box_pack_start(GTK_BOX(vbox), surfer->view, TRUE, TRUE, 0); /* statusbar */ + hbox = gtk_hbox_new(FALSE, 0); + surfer->progress = gtk_progress_bar_new(); + gtk_box_pack_start(GTK_BOX(hbox), surfer->progress, FALSE, FALSE, 0); surfer->statusbar = gtk_statusbar_new(); surfer->statusbar_id = gtk_statusbar_push(GTK_STATUSBAR( surfer->statusbar), gtk_statusbar_get_context_id(GTK_STATUSBAR( surfer->statusbar), ""), "Ready"); - gtk_box_pack_start(GTK_BOX(vbox), surfer->statusbar, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), surfer->statusbar, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(surfer->window), vbox); gtk_widget_grab_focus(GTK_WIDGET(surfer->tb_path)); gtk_widget_show_all(surfer->window); @@ -279,6 +284,7 @@ static GtkWidget * _new_menubar(Surfer * surfer) gtk_menu_item_set_submenu(GTK_MENU_ITEM(menubar), menu); gtk_menu_bar_append(GTK_MENU_BAR(tb_menubar), menubar); } + gtk_window_add_accel_group(GTK_WINDOW(surfer->window), group); return tb_menubar; } #endif /* !FOR_EMBEDDED */ @@ -293,6 +299,30 @@ void surfer_delete(Surfer * surfer) } +/* accessors */ +/* surfer_set_progress */ +void surfer_set_progress(Surfer * surfer, gdouble fraction) +{ + char buf[10]; + + snprintf(buf, sizeof(buf), "%.1f%%", fraction * 100); + gtk_progress_bar_set_text(GTK_PROGRESS_BAR(surfer->progress), buf); + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(surfer->progress), + fraction); +} + + +/* surfer_set_title */ +void surfer_set_title(Surfer * surfer, char const * title) +{ + char buf[256]; + + snprintf(buf, sizeof(buf), "%s%s%s", "Web surfer", title != NULL + ? " - " : "", title != NULL ? title : ""); + gtk_window_set_title(GTK_WINDOW(surfer->window), buf); +} + + /* useful */ /* surfer_error */ int surfer_error(Surfer * surfer, char const * message, int ret) @@ -302,6 +332,7 @@ int surfer_error(Surfer * surfer, char const * message, int ret) dialog = gtk_message_dialog_new(GTK_WINDOW(surfer->window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message); + gtk_window_set_title(GTK_WINDOW(dialog), "Error"); g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK( gtk_widget_destroy), NULL); gtk_widget_show(dialog); diff --git a/src/surfer.h b/src/surfer.h index affa4d8..b98389a 100644 --- a/src/surfer.h +++ b/src/surfer.h @@ -51,6 +51,7 @@ typedef struct _Surfer GtkToolItem * tb_refresh; GtkWidget * tb_path; GtkWidget * view; + GtkWidget * progress; GtkWidget * statusbar; guint statusbar_id; } Surfer; @@ -65,6 +66,11 @@ Surfer * surfer_new(char const * url); void surfer_delete(Surfer * surfer); +/* accessors */ +void surfer_set_progress(Surfer * surfer, gdouble fraction); +void surfer_set_title(Surfer * surfer, char const * title); + + /* useful */ int surfer_error(Surfer * surfer, char const * message, int ret);