jcs
/detritus
/amendments
/18
*: Switch to page model, cache them and add back/forward navigation
jcs made amendment 18 about 1 year ago
--- browser.c Mon Oct 28 13:54:36 2024
+++ browser.c Sun Nov 3 11:23:08 2024
@@ -19,6 +19,7 @@
#include <string.h>
#include "detritus.h"
+#include "browser.h"
#include "focusable.h"
#include "util.h"
@@ -33,44 +34,56 @@
static Rect zerorect = { 0, 0, 0, 0 };
static Pattern fill_pattern;
-extern struct request_handler gemini_handler;
-extern struct request_handler gopher_handler;
+extern struct uri_handler gemini_handler;
+extern struct uri_handler gopher_handler;
-struct request_handler * request_handlers[] = {
+struct uri_handler * uri_handlers[] = {
&gemini_handler,
&gopher_handler,
};
-void browser_setup_shadow(struct browser *browser);
bool browser_close(struct focusable *focusable);
void browser_idle(struct focusable *focusable, EventRecord *event);
void browser_update_menu(struct browser *browser);
+void browser_update_buttons(struct browser *browser);
void browser_update(struct focusable *focusable, EventRecord *event);
-void browser_use_shadow(struct browser *browser);
-void browser_reveal_shadow(struct browser *browser);
void browser_key_down(struct focusable *focusable, EventRecord *event);
void browser_mouse_down(struct focusable *focusable, EventRecord *event);
bool browser_handle_menu(struct focusable *focusable, short menu,
short item);
void browser_atexit(struct focusable *focusable);
-void browser_go(struct browser *browser);
-bool browser_load_url(struct browser *browser, char *uristr);
+void browser_go(struct browser *browser, short dir);
+page_handle browser_create_page(struct browser *browser, char *uristr);
void browser_draw_status(struct browser *browser);
-void browser_stop_request(struct browser *browser);
-void browser_cleanup(struct browser *browser);
+void browser_follow_redir(struct browser *browser);
+void browser_stop_loading_page(struct browser *browser);
+void browser_free_links(struct browser *browser);
void
browser_idle(struct focusable *focusable, EventRecord *event)
{
struct browser *browser = (struct browser *)focusable->cookie;
+ struct uri_handler *handler;
+ struct URI *redir;
TEIdle(browser->uri_te);
- if (browser->request &&
- !browser->handler->process_request(browser->request))
- /* processing failed/finished */
- browser_stop_request(browser);
+ if (browser->loading_page) {
+ HLock(browser->loading_page);
+ handler = (*(browser->loading_page))->handler;
+
+ if (!handler->process(browser->loading_page)) {
+ if ((*(browser->loading_page))->redir_to)
+ browser_follow_redir(browser);
+ else {
+ browser_stop_loading_page(browser);
+ browser->redirs = 0;
+ }
+ }
+
+ HUnlock(browser->loading_page);
+ }
}
struct browser *
@@ -79,7 +92,8 @@ browser_init(void)
char title[64];
struct browser *browser;
struct focusable *focusable;
- Rect bounds, te_bounds, padding;
+ Handle rgnsave;
+ Rect bounds, te_bounds, padding, r;
long width, height;
browser = xmalloczero(sizeof(struct browser));
@@ -104,11 +118,44 @@ browser_init(void)
panic("Can't create window");
SetPort(browser->win);
- /* uri TE */
+ browser->header = NewRgn();
+ OpenRgn();
+ r.top = 0;
+ r.left = 0;
+ r.right = bounds.right - bounds.left;
+ r.bottom = 26;
+ FrameRect(&r);
+
+ rgnsave = browser->win->rgnSave;
+ browser->win->rgnSave = NULL;
+
+ /* back and forward */
bounds.top = PADDING;
bounds.left = PADDING;
- bounds.right = browser->win->portRect.right - PADDING - 50;
+ bounds.right = bounds.left + 20;
bounds.bottom = bounds.top + 16;
+ browser->back = NewControl(browser->win, &bounds, "\p<", true,
+ 1, 1, 1, pushButProc, 0L);
+ HiliteControl(browser->back, 255);
+
+ browser->win->rgnSave = rgnsave;
+ FrameRoundRect(&bounds, 10, 10);
+ browser->win->rgnSave = NULL;
+
+ bounds.top = PADDING;
+ bounds.left = bounds.right - 1;
+ bounds.right = bounds.left + 20;
+ browser->fwd = NewControl(browser->win, &bounds, "\p>", true,
+ 1, 1, 1, pushButProc, 0L);
+ HiliteControl(browser->fwd, 255);
+
+ browser->win->rgnSave = rgnsave;
+ FrameRoundRect(&bounds, 10, 10);
+ browser->win->rgnSave = NULL;
+
+ /* uri TE */
+ bounds.left = bounds.right + PADDING;
+ bounds.right = browser->win->portRect.right - PADDING - 60;
te_bounds = bounds;
InsetRect(&te_bounds, 2, 2);
TextFont(geneva);
@@ -119,11 +166,20 @@ browser_init(void)
TEAutoView(false, browser->uri_te);
TEActivate(browser->uri_te);
+ browser->win->rgnSave = rgnsave;
+ FrameRect(&bounds);
+ browser->win->rgnSave = NULL;
+
bounds.left = bounds.right + PADDING;
bounds.right = browser->win->portRect.right - PADDING;
- browser->go_button = NewControl(browser->win, &bounds, "\pGo", true,
+ browser->go_stop = NewControl(browser->win, &bounds, "\pGo", true,
1, 1, 1, pushButProc, 0L);
+ HiliteControl(browser->go_stop, 255);
+ browser->win->rgnSave = rgnsave;
+ FrameRoundRect(&bounds, 10, 10);
+ CloseRgn(browser->header);
+
/* output TV */
bounds.left = 0;
bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH + 1;
@@ -166,135 +222,137 @@ browser_init(void)
focusable->atexit = browser_atexit;
focusable_add(focusable);
- DrawControls(browser->win);
- browser_setup_shadow(browser);
-
- browser_statusf(browser, "Hello, cyberpals!");
+ snprintf(browser->status_text, sizeof(browser->status_text),
+ "Hello, cyberpals!");
+ browser_update(focusable, NULL);
return browser;
}
-void
-browser_setup_shadow(struct browser *browser)
+bool
+browser_close(struct focusable *focusable)
{
- long width, height;
+ struct browser *browser = (struct browser *)focusable->cookie;
+ page_handle pageh, fpage, bpage, tpage;
+ struct page *page;
- width = browser->win->portRect.right - browser->win->portRect.left;
- height = browser->win->portRect.bottom - browser->win->portRect.top;
+ browser_stop_loading_page(browser);
- if (browser->shadow) {
- ClosePort(browser->shadow);
- if (browser->shadow->portBits.baseAddr)
- xfree(&browser->shadow->portBits.baseAddr);
- xfree(&browser->shadow);
+ if (browser->current_page) {
+ pageh = browser->current_page;
+ HLock(pageh);
+ fpage = (*pageh)->fwd_page;
+ HUnlock(pageh);
+ while (fpage) {
+ HLock(fpage);
+ tpage = (*fpage)->fwd_page;
+ (*fpage)->handler->cleanup(fpage);
+ HLock(fpage);
+ xfree(&(*fpage)->uri);
+ DisposeHandle(fpage);
+ fpage = tpage;
+ }
+
+ HLock(pageh);
+ bpage = (*pageh)->back_page;
+ HUnlock(pageh);
+ while (bpage) {
+ HLock(bpage);
+ tpage = (*bpage)->back_page;
+ (*bpage)->handler->cleanup(bpage);
+ HLock(bpage);
+ xfree(&(*bpage)->uri);
+ DisposeHandle(bpage);
+ bpage = tpage;
+ }
+
+ HLock(pageh);
+ (*pageh)->handler->cleanup(pageh);
+ HLock(pageh);
+ xfree(&(*pageh)->uri);
+ DisposeHandle(pageh);
}
- GetPort(&browser->shadow_old_port);
+ browser_free_links(browser);
+
+ TEDispose(browser->uri_te);
+ TVDispose(browser->output_tv);
+ DisposeWindow(browser->win);
+ xfree(&browser);
+ focusable->cookie = NULL;
- browser->shadow = (GrafPtr)xmalloczero(sizeof(GrafPort));
- if (browser->shadow == NULL)
- panic("Out of memory");
- OpenPort(browser->shadow);
- browser->shadow->portBits.rowBytes = (((width - 1) / 16) + 1) * 2;
- browser->shadow->portBits.baseAddr =
- xmalloc((long)browser->shadow->portBits.rowBytes * height);
- if (browser->shadow->portBits.baseAddr == NULL)
- panic("Out of memory");
- browser->shadow->portBits.bounds.right = width;
- browser->shadow->portBits.bounds.bottom = height;
- browser->shadow->portBits.rowBytes = (((width - 1) / 16) + 1) * 2;
- browser->shadow->portRect = browser->shadow->portBits.bounds;
-
- RectRgn(browser->shadow->clipRgn, &browser->shadow->portRect);
- RectRgn(browser->shadow->visRgn, &browser->shadow->portRect);
+ scsi_cleanup();
- SetPort(browser->shadow_old_port);
+ return true;
}
void
-browser_use_shadow(struct browser *browser)
+browser_stop_loading_page(struct browser *browser)
{
- if (++browser->shadow_refcnt != 1)
+ page_handle pageh;
+ struct page *page;
+
+ if (!browser->loading_page)
return;
+
+ HLock(browser->loading_page);
+ page = *(browser->loading_page);
+ page->handler->cleanup(browser->loading_page);
+ HLock(browser->loading_page);
- if (browser->shadow_refcnt == 0)
- Debugger();
-
- /* TODO: fix controls not drawing on the canvas */
- return;
+ /* only dispose if uncommitted */
+ if (browser->current_page != browser->loading_page) {
+ HLock(browser->loading_page);
+ xfree(&(*(browser->loading_page))->uri);
+ DisposeHandle(browser->loading_page);
+ }
- GetPort(&browser->shadow_old_port);
- SetPort(browser->shadow);
+ browser->loading_page = NULL;
+ browser_update_buttons(browser);
}
void
-browser_reveal_shadow(struct browser *browser)
+browser_follow_redir(struct browser *browser)
{
- if (browser->shadow_refcnt <= 0)
- Debugger();
-
- if (--browser->shadow_refcnt != 0)
+ page_handle pageh;
+ struct page *page;
+ struct URI *redir;
+
+ if (!browser->loading_page)
return;
-/* TODO: fix controls not drawing on the canvas */
-return;
+ HLock(browser->loading_page);
+ redir = (*(browser->loading_page))->redir_to;
+ HUnlock(browser->loading_page);
- SetPort(browser->shadow_old_port);
-
- CopyBits(&browser->shadow->portBits,
- &browser->win->portBits, &browser->shadow->portRect,
- &browser->win->portRect, srcCopy, NULL);
- browser->shadow_old_port = NULL;
- ValidRect(&browser->win->portRect);
-}
+ browser_stop_loading_page(browser);
-bool
-browser_close(struct focusable *focusable)
-{
- struct browser *browser = (struct browser *)focusable->cookie;
+ if (!redir)
+ return;
- browser_stop_request(browser);
-
- TEDispose(browser->uri_te);
- TVDispose(browser->output_tv);
- DisposeWindow(browser->win);
-
- browser_cleanup(browser);
- xfree(&browser);
- focusable->cookie = NULL;
-
- scsi_cleanup();
+ if (++browser->redirs == 5)
+ warn("Too many redirections, not following to %s",
+ redir->str);
- return true;
+ browser_statusf(browser, "Following redirection to %s", redir->str);
+ browser_go_uri(browser, redir->str);
+
+ xfree(&redir);
}
void
-browser_stop_request(struct browser *browser)
+browser_free_links(struct browser *browser)
{
- if (!browser->request)
- return;
-
- browser->handler->free_request(browser->request);
- browser->handler = NULL;
- browser->request = NULL;
-}
-
-void
-browser_cleanup(struct browser *browser)
-{
- size_t n;
- struct browser_link *link, *nlink;
+ struct browser_link *link;
- if (browser->first_link) {
+ while (browser->first_link) {
link = browser->first_link;
- while (link) {
- nlink = link->next_link;
- xfree(&link);
- link = nlink;
- }
- browser->first_link = NULL;
- browser->last_link = NULL;
+ browser->first_link = link->next_link;
+ xfree(&link);
}
+
+ browser->first_link = NULL;
+ browser->last_link = NULL;
}
void
@@ -303,8 +361,8 @@ browser_atexit(struct focusable *focusable)
struct browser *browser = (struct browser *)focusable->cookie;
if (browser) {
- browser_stop_request(browser);
- browser_cleanup(browser);
+ browser_stop_loading_page(browser);
+ browser_free_links(browser);
}
scsi_cleanup();
@@ -320,24 +378,70 @@ browser_update_menu(struct browser *browser)
TextFont(systemFont);
TextSize(12);
-#if 0
- te = *(browser->te);
+ HLock(browser->uri_te);
+ te = *(browser->uri_te);
- DisableItem(edit_menu, EDIT_MENU_CUT_ID);
-
- if (te->selStart == te->selEnd)
+ if (te->selStart == te->selEnd) {
+ DisableItem(edit_menu, EDIT_MENU_CUT_ID);
DisableItem(edit_menu, EDIT_MENU_COPY_ID);
- else
+ } else {
+ EnableItem(edit_menu, EDIT_MENU_CUT_ID);
EnableItem(edit_menu, EDIT_MENU_COPY_ID);
+ }
+
+ EnableItem(edit_menu, EDIT_MENU_PASTE_ID);
+ EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+
+ HUnlock(browser->uri_te);
+}
- DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
-
- if (te->nLines == 0) {
- DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+void
+browser_update_buttons(struct browser *browser)
+{
+ TERec *te;
+ bool is_go;
+
+ if (browser->current_page) {
+ HLock(browser->current_page);
+
+ if ((*(browser->current_page))->back_page)
+ HiliteControl(browser->back, 0);
+ else
+ HiliteControl(browser->back, 255);
+
+ if ((*(browser->current_page))->fwd_page)
+ HiliteControl(browser->fwd, 0);
+ else
+ HiliteControl(browser->fwd, 255);
+
+ HUnlock(browser->current_page);
} else {
- EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+ HiliteControl(browser->back, 255);
+ HiliteControl(browser->fwd, 255);
}
-#endif
+
+ if (browser->loading_page) {
+ HiliteControl(browser->go_stop, 0);
+ HLock(browser->go_stop);
+ is_go = ((*(browser->go_stop))->contrlTitle[1] == 'G');
+ HUnlock(browser->go_stop);
+ if (is_go)
+ SetCTitle(browser->go_stop, "\pStop");
+ } else {
+ HLock(browser->uri_te);
+ te = *(browser->uri_te);
+ if (te->teLength)
+ HiliteControl(browser->go_stop, 0);
+ else
+ HiliteControl(browser->go_stop, 255);
+ HUnlock(browser->uri_te);
+
+ HLock(browser->go_stop);
+ is_go = ((*(browser->go_stop))->contrlTitle[1] == 'G');
+ HUnlock(browser->go_stop);
+ if (!is_go)
+ SetCTitle(browser->go_stop, "\pGo");
+ }
}
void
@@ -353,30 +457,28 @@ browser_update(struct focusable *focusable, EventRecor
switch (what) {
case -1:
case updateEvt:
- browser_use_shadow(browser);
-
- FillRect(&browser->win->portRect, fill_pattern);
+ FillRgn(browser->header, fill_pattern);
+ DrawControls(browser->win);
+ DrawGrowIconOnly(browser->win);
+ browser_draw_status(browser);
+
HLock(browser->uri_te);
r = (*(browser->uri_te))->viewRect;
- HUnlock(browser->uri_te);
- FillRect(&r, white);
- TEUpdate(&r, browser->uri_te);
InsetRect(&r, -1, -1);
FrameRect(&r);
+ TEUpdate(&r, browser->uri_te);
+ HUnlock(browser->uri_te);
HLock(browser->output_tv);
r = (*(browser->output_tv))->view;
- HUnlock(browser->output_tv);
- TVUpdate(browser->output_tv);
InsetRect(&r, -1, -1);
FrameRect(&r);
+ HUnlock(browser->output_tv);
+ TVUpdate(browser->output_tv);
- DrawGrowIconOnly(browser->win);
- DrawControls(browser->win);
- browser_draw_status(browser);
-
- browser_reveal_shadow(browser);
+ ValidRect(&browser->win->portRect);
+
browser_update_menu(browser);
break;
}
@@ -416,8 +518,7 @@ browser_mouse_down(struct focusable *focusable, EventR
if (browser->first_link &&
(off = TVGetOffset(browser->output_tv, p)) >= 0) {
- for (link = browser->first_link; link && link->next_link;
- link = link->next_link) {
+ for (link = browser->first_link; link; link = link->next_link) {
if ((link->pos <= off) && (off < link->pos + link->len)) {
#if 0
if (event->modifiers & cmdKey) {
@@ -425,7 +526,7 @@ browser_mouse_down(struct focusable *focusable, EventR
break;
}
#endif
- browser_load_url(browser, link->uri);
+ browser_create_page(browser, link->uri);
break;
}
}
@@ -437,8 +538,16 @@ browser_mouse_down(struct focusable *focusable, EventR
switch (part = FindControl(p, browser->win, &control)) {
case inButton:
- if (TrackControl(control, p, 0L) && control == browser->go_button)
- browser_go(browser);
+ if (TrackControl(control, p, 0L) && control == browser->go_stop) {
+ if (browser->loading_page) {
+ browser_stop_loading_page(browser);
+ browser_statusf(browser, "Stopped loading");
+ } else
+ browser_go(browser, 0);
+ } else if (TrackControl(control, p, 0L) && control == browser->back)
+ browser_go(browser, -1);
+ else if (TrackControl(control, p, 0L) && control == browser->fwd)
+ browser_go(browser, 1);
break;
case inUpButton:
case inDownButton:
@@ -468,17 +577,20 @@ void
browser_key_down(struct focusable *focusable, EventRecord *event)
{
struct browser *browser = (struct browser *)(focusable->cookie);
+ TERec *te;
char k;
k = (event->message & charCodeMask);
if (k == '\r') {
- browser_go(browser);
+ browser_go(browser, 0);
return;
}
TEKey(k, browser->uri_te);
TESelView(browser->uri_te);
+
+ browser_update_buttons(browser);
}
bool
@@ -489,9 +601,15 @@ browser_handle_menu(struct focusable *focusable, short
switch (menu) {
case EDIT_MENU_ID:
switch (item) {
+ case EDIT_MENU_CUT_ID:
+ TECut(browser->uri_te);
+ return true;
case EDIT_MENU_COPY_ID:
TECopy(browser->uri_te);
return true;
+ case EDIT_MENU_PASTE_ID:
+ TEPaste(browser->uri_te);
+ return true;
case EDIT_MENU_SELECT_ALL_ID:
TESetSelect(0, 1024 * 32, browser->uri_te);
return true;
@@ -503,57 +621,124 @@ browser_handle_menu(struct focusable *focusable, short
}
void
-browser_go(struct browser *browser)
+browser_go_uri(struct browser *browser, char *uristr)
{
TERec *te;
- short len;
- char *uristr;
+ Rect r;
+ TESetText(uristr, strlen(uristr), browser->uri_te);
+ TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te);
+
HLock(browser->uri_te);
- te = *(browser->uri_te);
- HLock(te->hText);
- len = te->teLength;
- uristr = xmalloc(len + 1);
- if (uristr == NULL) {
- warn("Out of memory");
- return;
- }
- memcpy(uristr, *(te->hText), len);
- uristr[len] = '\0';
- HUnlock(te->hText);
+ r = (*(browser->uri_te))->viewRect;
HUnlock(browser->uri_te);
+ TEUpdate(&r, browser->uri_te);
+
+ browser_go(browser, 0);
+}
- browser_load_url(browser, uristr);
- xfree(&uristr);
+void
+browser_go(struct browser *browser, short dir)
+{
+ TERec *te;
+ page_handle pageh, opage;
+ struct page *page;
+ short len;
+ char *uristr;
+
+ if (dir == 0) {
+ HLock(browser->uri_te);
+ te = *(browser->uri_te);
+ len = te->teLength;
+ uristr = xmalloc(len + 1);
+ if (uristr == NULL) {
+ warn("Out of memory");
+ return;
+ }
+ HLock(te->hText);
+ memcpy(uristr, *(te->hText), len);
+ HUnlock(te->hText);
+ uristr[len] = '\0';
+ HUnlock(browser->uri_te);
+
+ browser_create_page(browser, uristr);
+ xfree(&uristr);
+ } else {
+ browser_stop_loading_page(browser);
+
+ pageh = browser->current_page;
+ if (!pageh)
+ return;
+
+ HLock(pageh);
+ page = *pageh;
+ if (dir == -1)
+ opage = page->back_page;
+ else
+ opage = page->fwd_page;
+ HUnlock(pageh);
+ if (!opage)
+ return;
+
+ HLock(opage);
+ if ((*opage)->handler->init(opage) && (*opage)->content_len == 0)
+ /* page didn't load before, try again */
+ browser->loading_page = opage;
+ HLock(opage);
+ (*opage)->handler->process(opage);
+ HUnlock(opage);
+ }
}
-bool
-browser_load_url(struct browser *browser, char *uristr)
+page_handle
+browser_create_page(struct browser *browser, char *uristr)
{
- struct request_handler *handler;
+ page_handle pageh;
+ struct page *page;
+ struct uri_handler *handler;
struct URI *uri;
short n;
- browser_stop_request(browser);
+ browser_stop_loading_page(browser);
- for (n = 0; n < nitems(request_handlers); n++) {
- handler = request_handlers[n];
+ for (n = 0; n < nitems(uri_handlers); n++) {
+ handler = uri_handlers[n];
- if ((uri = handler->parse_uri(uristr)) != NULL) {
- browser->handler = handler;
- break;
+ if ((uri = handler->parse_uri(uristr)) == NULL)
+ continue;
+
+ pageh = (page_handle)NewHandleClear(sizeof(struct page) +
+ PAGE_CONTENT_CHUNK_SIZE);
+ if (pageh == NULL) {
+ warn("Out of memory for new page");
+ return;
}
+ HLock(pageh);
+ page = *pageh;
+ page->browser = browser;
+ page->uri = uri;
+ page->handler = handler;
+ page->content_size = PAGE_CONTENT_CHUNK_SIZE;
+ HUnlock(pageh);
+
+ browser->loading_page = pageh;
+ browser_update_buttons(browser);
+
+ if (handler->init(pageh))
+ return pageh;
+
+ /* handler failed, no point in keeping page */
+ browser->loading_page = NULL;
+ browser_update_buttons(browser);
+ HLock(pageh);
+ page = *pageh;
+ xfree(&page->uri);
+ DisposeHandle(pageh);
+ return NULL;
}
- if (uri == NULL) {
- warn("Could not parse URI");
- return false;
- }
-
- if ((browser->request = browser->handler->init_request(browser, uri)))
- return true;
-
- return false;
+ warn("Could not parse URI \"%s\"", uristr);
+ return NULL;
}
size_t
@@ -568,9 +753,7 @@ browser_statusf(struct browser *browser, const char *f
browser->status_length = sizeof(browser->status_text) - 1;
va_end(argptr);
- browser_use_shadow(browser);
browser_draw_status(browser);
- browser_reveal_shadow(browser);
}
void
@@ -631,8 +814,6 @@ browser_print(struct browser *browser, const char *str
} else
TVAppend(browser->output_tv, &style, (char *)str, len);
- //TVUpdateScrollbar(browser->output_tv, browser->output_tv_scroller);
-
return len;
}
@@ -670,10 +851,10 @@ browser_print_link(struct browser *browser, const char
if (browser->last_link)
browser->last_link->next_link = link;
- else {
+ else
browser->first_link = link;
- browser->last_link = link;
- }
+
+ browser->last_link = link;
browser->style |= STYLE_LINK;
browser_print(browser, link->title, link->len);
@@ -683,25 +864,82 @@ browser_print_link(struct browser *browser, const char
}
void
-browser_commit_to_uri(struct browser *browser, struct URI *uri)
+browser_commit_to_page(struct browser *browser, page_handle pageh)
{
- WindowPtr win;
+ struct page *page;
+ page_handle tpage, fpage, bpage;
Rect r;
- GetPort(&win);
- SetPort(browser->win);
+ browser_free_links(browser);
+
+ HLock(pageh);
+ page = *pageh;
- browser_cleanup(browser);
+ if (browser->current_page == pageh)
+ Debugger();
+
+ TESetText(page->uri->str, strlen(page->uri->str), browser->uri_te);
+ TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te);
HLock(browser->uri_te);
r = (*(browser->uri_te))->viewRect;
HUnlock(browser->uri_te);
- TESetText(uri->str, strlen(uri->str), browser->uri_te);
- TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te);
TEUpdate(&r, browser->uri_te);
TVClear(browser->output_tv);
TVUpdateScrollbar(browser->output_tv, browser->output_tv_scroller);
- SetPort(win);
+ if (browser->current_page) {
+ HLock(browser->current_page);
+ fpage = (*(browser->current_page))->fwd_page;
+ bpage = (*(browser->current_page))->back_page;
+ HUnlock(browser->current_page);
+
+ if (pageh == bpage) {
+ /* we're going backwards in history */
+ } else if (pageh == fpage) {
+ /* we're going fowards in history */
+ } else {
+ /* purge any forward pages because we're forking history now */
+ while (fpage) {
+ HLock(fpage);
+ tpage = (*fpage)->fwd_page;
+ DisposeHandle(fpage);
+ fpage = tpage;
+ }
+
+ HLock(browser->current_page);
+ (*(browser->current_page))->fwd_page = pageh;
+ HUnlock(browser->current_page);
+
+ page->back_page = browser->current_page;
+ }
+ }
+
+ browser->current_page = pageh;
+ HUnlock(pageh);
+
+ browser_update_buttons(browser);
+}
+
+size_t
+browser_grow_page_content(page_handle pageh, size_t len)
+{
+ unsigned long size;
+
+ HLock(pageh);
+ size = (*pageh)->content_size + MAX(len, PAGE_CONTENT_CHUNK_SIZE);
+ HUnlock(pageh);
+
+ SetHandleSize(pageh, sizeof(struct page) + size);
+ if (MemError() != 0) {
+ warn("Out of memory growing page handle");
+ return 0;
+ }
+
+ HLock(pageh);
+ (*pageh)->content_size = size;
+ HUnlock(pageh);
+
+ return size;
}
--- browser.h Mon Oct 28 13:45:20 2024
+++ browser.h Sun Nov 3 11:22:13 2024
@@ -18,6 +18,7 @@
#define __BROWSER_H__
#include <stdlib.h>
+#include "detritus.h"
#include "textview.h"
#include "tcp.h"
#include "util.h"
@@ -43,19 +44,21 @@ struct browser_link {
struct browser {
WindowPtr win;
- GrafPtr shadow;
- GrafPtr shadow_old_port;
+ RgnHandle header;
short shadow_refcnt;
TEHandle uri_te;
- ControlHandle go_button;
+ ControlHandle back;
+ ControlHandle fwd;
+ ControlHandle go_stop;
TVHandle output_tv;
ControlHandle output_tv_scroller;
Rect status_rect;
unsigned short status_length;
- char status_text[512];
+ short redirs;
+ char status_text[256];
- struct request_handler *handler;
- void *request;
+ page_handle current_page;
+ page_handle loading_page;
unsigned long style;
struct browser_link *first_link;
@@ -69,6 +72,8 @@ size_t browser_print_link(struct browser *browser, con
size_t browser_print(struct browser *browser, const char *str, size_t len);
//#define BROWSER_DEBUGF(x) browser_statusf
#define BROWSER_DEBUGF(x)
-void browser_commit_to_uri(struct browser *browser, struct URI *uri);
+void browser_commit_to_page(struct browser *browser, page_handle pageh);
+size_t browser_grow_page_content(page_handle pageh, size_t len);
+void browser_go_uri(struct browser *browser, char *uristr);
#endif
\ No newline at end of file
--- detritus.h Fri Oct 25 21:47:44 2024
+++ detritus.h Sun Nov 3 11:22:22 2024
@@ -14,10 +14,9 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef __GEMINO_H__
-#define __GEMINO_H__
+#ifndef __DETRITUS_H__
+#define __DETRITUS_H__
-#include "browser.h"
#include "util.h"
#define PROGRAM_NAME "Detritus"
@@ -36,6 +35,10 @@
#define EDIT_MENU_PASTE_ID 3
#define EDIT_MENU_SELECT_ALL_ID 4
+#define HISTORY_MENU_ID 131
+
+#define BOOKMARKS_MENU_ID 132
+
extern uint8_t tls_req_last_id;
struct tls_init_request {
@@ -46,27 +49,51 @@ struct tls_init_request {
};
struct URI {
-#define URI_PROTOCOL_LEN 20
- char protocol[URI_PROTOCOL_LEN + 1];
-#define URI_HOSTNAME_LEN 255
- char hostname[URI_HOSTNAME_LEN + 1];
-#define URI_PATH_LEN 512
- char path[URI_PATH_LEN + 1];
+#define URI_MAX_PROTOCOL_LEN 20
+ char *protocol;
+#define URI_MAX_HOSTNAME_LEN 255
+ char *hostname;
+#define URI_MAX_PATH_LEN 512
+ char *path;
+#define URI_MAX_STR_LEN (URI_MAX_PROTOCOL_LEN + 3 + URI_MAX_HOSTNAME_LEN + \
+ URI_MAX_PATH_LEN)
+ char *str;
unsigned short port;
- char str[800];
};
-struct request_handler {
+typedef struct page **page_handle;
+struct page {
+ struct URI *uri;
+ struct browser *browser;
+ page_handle back_page;
+ page_handle fwd_page;
+ size_t content_size;
+#define PAGE_CONTENT_CHUNK_SIZE 1024
+ size_t content_len;
+ size_t content_pos;
+ struct uri_handler *handler;
+ void *handler_cookie;
+ char type[32];
+ short parse_state;
+ struct URI *redir_to;
+ char content[];
+};
+
+struct uri_handler {
struct URI * (*parse_uri)(char *uristr);
- void * (*init_request)(struct browser *browser, struct URI *uri);
- bool (*process_request)(void *cookie);
- void (*free_request)(void *cookie);
+ /* do any post-init on a new page, like doing a request if no content */
+ bool (*init)(page_handle pageh);
+ /* process any request or parse existing content */
+ bool (*process)(page_handle pageh);
+ /* cleanup when done loading, such as freeing request */
+ void (*cleanup)(page_handle pageh);
};
-extern MenuHandle file_menu, edit_menu;
+extern MenuHandle file_menu, edit_menu, bookmarks_menu;
void menu_defaults(void);
-struct URI * generic_parse_uri(char *protocol, char *uristr);
+struct URI * parse_uri(char *uristr, char *restrict_protocol);
+struct URI * build_relative_uri(struct URI *uri, char *relative, size_t len);
short scsi_find_tls(void);
uint8_t scsi_tls_init(struct tls_init_request *req);
--- gemini.c Sat Oct 26 14:54:01 2024
+++ gemini.c Sun Nov 3 20:05:38 2024
@@ -19,6 +19,7 @@
#include <string.h>
#include "detritus.h"
+#include "browser.h"
#define GEMINI_PORT 1965
@@ -29,21 +30,16 @@ enum {
};
enum {
- GEM_STATE_HEADER,
- GEM_STATE_GEMTEXT,
- GEM_STATE_REDIRECT,
- GEM_STATE_DOWNLOAD
+ PARSE_STATE_HEADER,
+ PARSE_STATE_GEMTEXT,
+ PARSE_STATE_DOWNLOAD
};
struct gemini_request {
- struct browser *browser;
- struct URI *uri;
-
uint8_t tls_id;
unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */
unsigned char tcp_input[2048];
- size_t tcp_input_len;
- short state;
+ unsigned long tcp_input_len;
TCPiopb tcp_iopb;
StreamPtr tcp_stream;
@@ -51,48 +47,41 @@ struct gemini_request {
TCPStatusPB tcp_status_pb;
bool tcp_done_reading;
- char message[member_size(struct URI, str) + 3];
+ char message[URI_MAX_STR_LEN + 1];
size_t message_len;
- short gem_state;
- char response[1024];
- size_t response_len;
- size_t total_response_len;
-
- char mime_type[64];
- unsigned long style;
+ short state;
};
struct URI * gemini_parse_uri(char *uristr);
-void * gemini_init_request(struct browser *browser, struct URI *uri);
-bool gemini_process_request(void *cookie);
-void gemini_free(void *cookie);
-static bool shuffle_read_tls_ciphertext(struct gemini_request *req);
-static bool shuffle_read_tcp_ciphertext(struct gemini_request *req,
- short space);
-static bool shuffle_tls_send_plaintext(struct gemini_request *req,
- short space);
-static bool shuffle_tls_read_plaintext(struct gemini_request *req);
-static bool parse_response(struct gemini_request *req);
-static bool parse_header(struct gemini_request *req, char *str);
-static void consume_response(struct gemini_request *req, size_t len);
+bool gemini_init_request(page_handle pageh);
+bool gemini_process(page_handle pageh);
+void gemini_cleanup(page_handle pageh);
-struct request_handler gemini_handler = {
+static bool shuffle_read_tls_ciphertext(struct page *page);
+static bool shuffle_read_tcp_ciphertext(struct page *page, short space);
+static bool shuffle_tls_send_plaintext(struct page *page, short space);
+static bool shuffle_tls_read_plaintext(struct page *page);
+static bool parse_header(struct page *page, char *str, size_t len);
+static bool parse_content(struct page *page);
+
+struct uri_handler gemini_handler = {
gemini_parse_uri,
gemini_init_request,
- gemini_process_request,
- gemini_free,
+ gemini_process,
+ gemini_cleanup,
};
struct URI *
gemini_parse_uri(char *uristr)
{
- return generic_parse_uri("gemini", uristr);
+ return parse_uri(uristr, "gemini");
}
-void *
-gemini_init_request(struct browser *browser, struct URI *uri)
+bool
+gemini_init_request(page_handle pageh)
{
+ struct page *page;
struct gemini_request *req = NULL;
struct tls_init_request tls_req;
char ip_s[12 + 3 + 1];
@@ -100,6 +89,15 @@ gemini_init_request(struct browser *browser, struct UR
short err;
ip_addr ip, local_ip;
tcp_port port, local_port;
+
+ HLock(pageh);
+ page = *pageh;
+ if (page->content_len) {
+ /* already fetched, nothing to do here but reset */
+ page->content_pos = 0;
+ page->parse_state = PARSE_STATE_HEADER;
+ goto done;
+ }
req = xmalloczero(sizeof(struct gemini_request));
if (req == NULL) {
@@ -107,189 +105,235 @@ gemini_init_request(struct browser *browser, struct UR
goto fail;
}
- req->browser = browser;
- req->uri = uri;
- if (req->uri->port == 0)
- req->uri->port = GEMINI_PORT;
+ if (page->uri->port == 0)
+ page->uri->port = GEMINI_PORT;
- browser_statusf(browser, "Resolving \"%s\"...", req->uri->hostname);
+ browser_statusf(page->browser, "Resolving \"%s\"...",
+ page->uri->hostname);
- err = DNSResolveName(req->uri->hostname, &ip, NULL);
+ err = DNSResolveName(page->uri->hostname, &ip, NULL);
if (err) {
- browser_statusf(browser, "Error: Failed resolving: %d", err);
+ browser_statusf(page->browser, "Error: Failed resolving: %d", err);
goto fail;
}
long2ip(ip, (char *)&ip_s);
- browser_statusf(browser, "Connecting to %s port %d...", ip_s,
- req->uri->port);
+ browser_statusf(page->browser, "Connecting to %s port %d...", ip_s,
+ page->uri->port);
err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf,
sizeof(req->tcp_buf), NULL, NULL, NULL, false);
if (err) {
- browser_statusf(browser, "Error: TCPCreate failed: %d", err);
+ browser_statusf(page->browser, "Error: TCPCreate failed: %d", err);
goto fail;
}
err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip,
- req->uri->port, &local_ip, &local_port, NULL, NULL, false);
+ page->uri->port, &local_ip, &local_port, NULL, NULL, false);
if (err) {
- browser_statusf(browser,
+ browser_statusf(page->browser,
"Error: Failed connecting to %s (%s) port %d: %d",
- req->uri->hostname, ip_s, req->uri->port, err);
+ page->uri->hostname, ip_s, page->uri->port, err);
goto fail;
}
err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
NULL, NULL, false);
if (err) {
- browser_statusf(browser,
+ browser_statusf(page->browser,
"Error: Failed TCPStatus on connection to %s (%s) port %d: %d",
- req->uri->hostname, ip_s, req->uri->port, err);
+ page->uri->hostname, ip_s, page->uri->port, err);
goto fail;
}
req->message_len = snprintf(req->message, sizeof(req->message),
- "%s\r\n", req->uri->str);
+ "%s\r\n", page->uri->str);
- browser_statusf(browser, "Connected to %s", req->uri->hostname);
+ browser_statusf(page->browser,
+ "Connected to %s, performing TLS negotiation...",
+ page->uri->hostname);
memset(&tls_req, 0, sizeof(tls_req));
- strlcpy(tls_req.hostname, req->uri->hostname, sizeof(tls_req.hostname));
+ strlcpy(tls_req.hostname, page->uri->hostname,
+ sizeof(tls_req.hostname));
time = MAC_TO_UNIX_TIME(Time);
tls_req.unix_time[0] = (time >> 24) & 0xff;
tls_req.unix_time[1] = (time >> 16) & 0xff;
tls_req.unix_time[2] = (time >> 8) & 0xff;
tls_req.unix_time[3] = (time) & 0xff;
-
/* sad */
tls_req.flags[1] = BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY;
-
- browser_statusf(browser, "Performing TLS handshake...");
+
req->tls_id = scsi_tls_init(&tls_req);
req->state = REQ_STATE_NEGOTIATING;
if (req->tls_id == 0) {
- browser_statusf(browser, "TLS handshake failed!");
+ browser_statusf(page->browser, "TLS handshake failed!");
goto fail;
}
+
+ page->handler_cookie = req;
+
+done:
+ HUnlock(pageh);
+ return true;
- return req;
-
fail:
- if (req->tcp_stream) {
- _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
- _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
- }
+ if (req) {
+ if (req->tcp_stream) {
+ _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ }
- if (req)
xfree(&req);
+ page->handler_cookie = NULL;
+ }
- return NULL;
+ HUnlock(pageh);
+ return false;
}
void
-gemini_free(void *cookie)
+gemini_cleanup(page_handle pageh)
{
- struct gemini_request *req = (struct gemini_request *)cookie;
+ struct gemini_request *req;
+ struct page *page;
- if (req->tcp_stream) {
- _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
- _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
- }
+ HLock(pageh);
+ page = *pageh;
- if (req->tls_id)
- scsi_tls_close(req->tls_id);
+ if (page->handler_cookie) {
+ req = (struct gemini_request *)(page->handler_cookie);
+
+ if (req->tcp_stream) {
+ _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ }
+
+ if (req->tls_id)
+ scsi_tls_close(req->tls_id);
- xfree(&req->uri);
- xfree(&req);
+ xfree(&page->handler_cookie);
+ }
+
+ HUnlock(pageh);
}
bool
-gemini_process_request(void *cookie)
+gemini_process(page_handle pageh)
{
- struct gemini_request *req = (struct gemini_request *)cookie;
+ struct gemini_request *req;
+ struct page *page;
size_t len;
unsigned short slen;
short err, status;
short cipherspace, plainspace, error;
+ bool ret;
+
+ HLock(pageh);
+ page = *pageh;
+ if (page->handler_cookie == NULL) {
+ ret = parse_content(page);
+ HUnlock(pageh);
+ return ret;
+ }
+
+ req = (struct gemini_request *)(page->handler_cookie);
+
if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) {
- BROWSER_DEBUGF((req->browser, "TCP I/O Result %d, disconnecting",
+ BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting",
req->tcp_iopb.ioResult));
- return false;
+ goto finish_request;
}
status = scsi_tls_status(req->tls_id, &cipherspace, &plainspace,
&error);
while (status != 0) {
- if ((status & 0x1) && !req->response_len) {
+ if ((status & 0x1) && page->content_pos == page->content_len) {
/* closed */
if (error == 0)
- browser_statusf(req->browser,
- "Finished reading %ld bytes", req->total_response_len);
+ browser_statusf(page->browser,
+ "Finished reading %ld bytes", page->content_len);
else
- browser_statusf(req->browser,
- "Error: TLS handshake failed: %d (TLS 0x%x)",
+ browser_statusf(page->browser,
+ "Error: TLS handshake failed: %d (TLS status 0x%x)",
error, status);
- return false;
+ goto finish_request;
}
if (status & 0x2) {
/* tls has ciphertext for tcp */
- if (!shuffle_read_tls_ciphertext(req))
- return false;
+ if (!shuffle_read_tls_ciphertext(page))
+ goto finish_request;
+ HLock(pageh);
+ page = *pageh;
status &= ~0x2;
}
- if ((status & 0x10) || req->response_len) {
+ if ((status & 0x10) || page->content_pos < page->content_len) {
/* tls has plaintext data for us */
- if (!shuffle_tls_read_plaintext(req))
- return false;
+ if (!shuffle_tls_read_plaintext(page))
+ goto finish_request;
+ HLock(pageh);
+ page = *pageh;
status &= ~0x10;
}
if (status & 0x8) {
/* tls can read plaintext from us */
- if (!shuffle_tls_send_plaintext(req, plainspace))
- return false;
+ if (!shuffle_tls_send_plaintext(page, plainspace))
+ goto finish_request;
+ HLock(pageh);
+ page = *pageh;
status &= ~0x8;
}
if (status & 0x4) {
/* tls can read ciphertext from tcp */
- if (!shuffle_read_tcp_ciphertext(req, cipherspace))
- return false;
+ if (!shuffle_read_tcp_ciphertext(page, cipherspace))
+ goto finish_request;
+ HLock(pageh);
+ page = *pageh;
status &= ~0x4;
}
if (status) {
- browser_statusf(req->browser, "Status is 0x%x?", status);
- return false;
+ browser_statusf(page->browser, "TLS status is 0x%x?", status);
+ goto finish_request;
}
}
+ HUnlock(pageh);
return true;
+
+finish_request:
+ HUnlock(pageh);
+ gemini_cleanup(pageh);
+ return false;
}
static bool
-shuffle_read_tls_ciphertext(struct gemini_request *req)
+shuffle_read_tls_ciphertext(struct page *page)
{
+ struct gemini_request *req;
size_t len;
short err;
unsigned char *buf;
/* read ciphertext from TLS and send it out TCP */
+
+ req = (struct gemini_request *)(page->handler_cookie);
len = scsi_tls_read(req->tls_id, &buf, 0, true);
if (len == 0 || buf == NULL) {
- browser_statusf(req->browser,
+ browser_statusf(page->browser,
"Error: No ciphertext read from TLS when expected to");
return false;
}
- BROWSER_DEBUGF((req->browser,
+ BROWSER_DEBUGF((page->browser,
"Read %lu bytes of TLS ciphertext, forwarding to TCP", len));
if (req->tcp_done_reading)
@@ -302,7 +346,7 @@ shuffle_read_tls_ciphertext(struct gemini_request *req
err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL,
NULL, false);
if (err) {
- browser_statusf(req->browser, "Error: TCPSend failed: %d", err);
+ browser_statusf(page->browser, "Error: TCPSend failed: %d", err);
return false;
}
@@ -310,27 +354,30 @@ shuffle_read_tls_ciphertext(struct gemini_request *req
}
static bool
-shuffle_read_tcp_ciphertext(struct gemini_request *req, short space)
+shuffle_read_tcp_ciphertext(struct page *page, short space)
{
+ struct gemini_request *req;
size_t len, n;
short err;
unsigned short slen;
/* read ciphertext from TCP and send it to TLS */
+
+ req = (struct gemini_request *)(page->handler_cookie);
if (req->tcp_input_len == sizeof(req->tcp_input) ||
req->tcp_done_reading)
goto forward_ciphertext;
- err = _TCPStatus(&req->tcp_iopb, req->tcp_stream,
- &req->tcp_status_pb, NULL, NULL, false);
+ err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
+ NULL, NULL, false);
if (err) {
req->tcp_done_reading = true;
- browser_statusf(req->browser, "Error: Bad TCPStatus: %d", err);
+ browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err);
goto forward_ciphertext;
}
if (req->tcp_status_pb.connectionState != ConnectionStateEstablished) {
- BROWSER_DEBUGF((req->browser, "TCP connection closed (state %d)",
+ BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)",
req->tcp_status_pb.connectionState));
req->tcp_done_reading = true;
goto forward_ciphertext;
@@ -342,7 +389,7 @@ shuffle_read_tcp_ciphertext(struct gemini_request *req
slen = MIN(req->tcp_status_pb.amtUnreadData,
sizeof(req->tcp_input) - req->tcp_input_len);
if (!slen) {
- browser_statusf(req->browser,
+ browser_statusf(page->browser,
"Error: No buffer space available in tcp_input?");
goto forward_ciphertext;
}
@@ -351,11 +398,11 @@ shuffle_read_tcp_ciphertext(struct gemini_request *req
(Ptr)(req->tcp_input + req->tcp_input_len), &slen, NULL, NULL,
false);
if (err) {
- browser_statusf(req->browser, "Error: Failed TCPRcv: %d", err);
+ browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err);
goto forward_ciphertext;
}
req->tcp_input_len += slen;
- BROWSER_DEBUGF((req->browser,
+ BROWSER_DEBUGF((page->browser,
"Read %d bytes of TCP ciphertext, forwarding to TLS", slen));
forward_ciphertext:
@@ -365,11 +412,11 @@ forward_ciphertext:
slen = req->tcp_input_len;
if (slen > space)
slen = space;
- BROWSER_DEBUGF((req->browser,
+ BROWSER_DEBUGF((page->browser,
"Forwarding %d bytes of TCP ciphertext to TLS", slen));
len = scsi_tls_write(req->tls_id, req->tcp_input, slen, true);
if (len == 0) {
- browser_statusf(req->browser,
+ browser_statusf(page->browser,
"Error: Failed forwarding %d bytes of ciphertext to TLS", slen);
return false;
}
@@ -381,27 +428,31 @@ forward_ciphertext:
req->tcp_input_len - len);
req->tcp_input_len -= len;
}
- BROWSER_DEBUGF((req->browser,
+ BROWSER_DEBUGF((page->browser,
"Wrote %ld bytes of TCP ciphertext to TLS, %ld left", len,
req->tcp_input_len));
return true;
}
static bool
-shuffle_tls_send_plaintext(struct gemini_request *req, short space)
+shuffle_tls_send_plaintext(struct page *page, short space)
{
+ page_handle pageh;
+ struct gemini_request *req;
size_t slen, len;
/* send any plaintext from us to TLS */
+ req = (struct gemini_request *)(page->handler_cookie);
+
if (req->message_len == 0)
return true;
if (req->state == REQ_STATE_NEGOTIATING) {
- browser_statusf(req->browser, "Sending request...");
+ browser_statusf(page->browser, "Negotiated, sending request...");
req->state = REQ_STATE_SENDING_REQUEST;
} else if (req->state != REQ_STATE_SENDING_REQUEST) {
- browser_statusf(req->browser, "In bogus state (%d) instead of "
+ browser_statusf(page->browser, "In bogus state (%d) instead of "
"SENDING_REQUEST, disconnecting", req->state);
return false;
}
@@ -412,7 +463,7 @@ shuffle_tls_send_plaintext(struct gemini_request *req,
len = scsi_tls_write(req->tls_id, (unsigned char *)req->message, slen,
false);
if (!len) {
- browser_statusf(req->browser,
+ browser_statusf(page->browser,
"Error: Failed sending %ld bytes of plaintext to TLS", slen);
return false;
}
@@ -424,358 +475,352 @@ shuffle_tls_send_plaintext(struct gemini_request *req,
req->message_len - len);
req->message_len -= len;
}
- BROWSER_DEBUGF((req->browser, "Wrote %ld bytes of plaintext to TLS, "
+ BROWSER_DEBUGF((page->browser, "Wrote %ld bytes of plaintext to TLS, "
"%ld left", len, req->message_len));
return true;
}
static bool
-shuffle_tls_read_plaintext(struct gemini_request *req)
+shuffle_tls_read_plaintext(struct page *page)
{
+ page_handle pageh;
+ struct gemini_request *req;
size_t len;
unsigned char *buf;
/* read as much plaintext from TLS as we can buffer */
- if (req->state == REQ_STATE_NEGOTIATING) {
- browser_clear(req->browser);
- req->state = REQ_STATE_PARSING_RESPONSE;
- req->gem_state = GEM_STATE_HEADER;
- }
+ req = (struct gemini_request *)(page->handler_cookie);
- len = sizeof(req->response) - req->response_len;
- if (len > 0) {
- len = scsi_tls_read(req->tls_id, &buf, len, false);
- if (len > 0) {
- if (len > sizeof(req->response) + req->response_len)
- panic("response overflow!");
- memcpy(req->response + req->response_len, buf, len);
- req->response_len += len;
- req->total_response_len += len;
-
- browser_statusf(req->browser,
- "Read %ld bytes", req->total_response_len);
- }
+ len = scsi_tls_read(req->tls_id, &buf, PAGE_CONTENT_CHUNK_SIZE, false);
+ if (len == 0)
+ return true;
+
+ if (page->content_len + len >= page->content_size) {
+ pageh = (page_handle)RecoverHandle(page);
+ if (!browser_grow_page_content(pageh, len))
+ return false;
+ HLock(pageh);
+ page = *pageh;
+ req = (struct gemini_request *)(page->handler_cookie);
}
+
+ memcpy(page->content + page->content_len, buf, len);
+ page->content_len += len;
- if (req->response_len)
- return parse_response(req);
-
- return true;
+ browser_statusf(page->browser, "Read %ld bytes", page->content_len);
+
+ return parse_content(page);
}
static bool
-parse_response(struct gemini_request *req)
+parse_content(struct page *page)
{
size_t n, trail, skip, len;
char c;
+ bool newline;
handle_state:
- switch (req->gem_state) {
- case GEM_STATE_HEADER: {
+ switch (page->parse_state) {
+ case PARSE_STATE_HEADER: {
short status;
- for (n = 0; n < req->response_len; n++) {
- c = req->response[n];
+ for (n = page->content_pos; n < page->content_len; n++) {
+ c = page->content[n];
- if (!(c == '\n' && n && req->response[n - 1] == '\r'))
+ if (!(c == '\n' && n && page->content[n - 1] == '\r'))
continue;
- req->response[n] = '\0';
- req->response[n - 1] = '\0';
-
- if (!parse_header(req, req->response)) {
- BROWSER_DEBUGF((req->browser,
+ if (!parse_header(page, page->content + page->content_pos,
+ n - page->content_pos - 2)) {
+ BROWSER_DEBUGF((page->browser,
"Header parsing failed, disconnecting"));
return false;
}
- if (strncmp(req->mime_type, "text/gemini", 11) == 0)
- req->gem_state = GEM_STATE_GEMTEXT;
- else
- req->gem_state = GEM_STATE_DOWNLOAD;
+ if (strcmp(page->type, "text/gemini") == 0) {
+ page->parse_state = PARSE_STATE_GEMTEXT;
+ browser_commit_to_page(page->browser,
+ (page_handle)RecoverHandle(page));
+ } else
+ page->parse_state = PARSE_STATE_DOWNLOAD;
- consume_response(req, n + 1);
+ page->content_pos = n + 1;
/* avoid a round trip through idle handler */
goto handle_state;
}
+
+ if (page->handler_cookie == NULL)
+ /* no more data will be coming */
+ return false;
+
break;
}
- case GEM_STATE_REDIRECT:
+ case PARSE_STATE_DOWNLOAD:
/* TODO */
break;
- case GEM_STATE_DOWNLOAD:
- /* TODO */
- break;
- case GEM_STATE_GEMTEXT:
-restart_parse:
- for (n = 0; n <= req->response_len; n++) {
- if (n == req->response_len && n < 3) {
- /*
- * End of buffer with no newline, but not enough chars
- * to determine what this line is, skip until we get more.
- */
- break;
- }
-
- if (!(n == req->response_len || req->response[n] == '\n'))
+ case PARSE_STATE_GEMTEXT:
+ TVAutoCalc(page->browser->output_tv, false);
+
+ for (n = page->content_pos; n <= page->content_len; n++) {
+ if (!(n == page->content_len || page->content[n] == '\n'))
continue;
- len = n + 1;
+ len = n - page->content_pos + 1;
trail = 0;
skip = 0;
+ newline = false;
- if (n < req->response_len && req->response[n] == '\n') {
- req->response[n] = '\r';
- if (n > 0 && req->response[n - 1] == '\r') {
+ if (n < page->content_len && page->content[n] == '\n') {
+ len--;
+ trail = 1;
+ newline = true;
+
+ if (n > 0 && page->content[n - 1] == '\r') {
len--;
- trail = 1;
+ trail++;
}
- } else if (req->response_len < sizeof(req->response) &&
- !req->tcp_done_reading) {
+ } else if (page->handler_cookie != NULL) {
/*
* No newline found at the end of the buffer, but the
* buffer isn't full so wait for more data.
*/
break;
}
+
+ /* check for pre first because the other styles can be in it */
+ if (page->content[page->content_pos] == '`' &&
+ page->content[page->content_pos + 1] == '`' &&
+ page->content[page->content_pos + 2] == '`') {
+ /* ``` toggle */
+ if (page->browser->style & STYLE_PRE)
+ page->browser->style = STYLE_NONE;
+ else
+ page->browser->style = STYLE_PRE;
+
+ /* the rest of the line can be a description, ignore */
+ trail += len;
+ len = 0;
+ newline = false;
+ goto print_line;
+ } else if (page->browser->style & STYLE_PRE)
+ goto print_line;
- /*
- * If we didn't find a newline but we're at the end of the
- * buffer, shift it down first to see if we can fit the whole
- * line in the buffer at once */
-
- if (req->response[0] == '=' && req->response[1] == '>') {
+ if (page->content[page->content_pos] == '=' &&
+ page->content[page->content_pos + 1] == '>') {
/* link */
+ ssize_t link_len = 0;
+ ssize_t title_len = 0;
+ ssize_t j, tlen;
+ char *link = NULL;
+ char *title = NULL;
+ char *tcontent;
+ struct URI *newuri;
+
skip = 2;
len -= 2;
- if (req->response[skip] == ' ') {
- skip++;
+ tlen = len;
+
+ tcontent = page->content + page->content_pos + skip;
+ link = tcontent;
+
+ /* skip leading whitespace */
+ while (len && (c = *tcontent) && (c == ' ' || c == '\t')) {
+ tcontent++;
+ link++;
len--;
}
- browser_print_link(req->browser, req->response + skip,
- len, NULL, 0);
- req->browser->style = STYLE_NONE;
- consume_response(req, skip + len + trail);
- goto restart_parse;
+
+ /* consume link */
+ while (len && (c = *tcontent) && c != ' ' && c != '\t') {
+ tcontent++;
+ len--;
+ link_len++;
+ }
+
+ /* consume whitespace after link */
+ while (len && (c = *tcontent) && (c == ' ' || c == '\t')) {
+ tcontent++;
+ len--;
+ }
+
+ if (len) {
+ title = tcontent;
+ title_len = len;
+
+ /* trim trailing whitespace from title */
+ while (title_len && (c = title[title_len - 1]) &&
+ (c == ' ' || c == '\t')) {
+ title_len--;
+ }
+
+ if (title_len == 0)
+ title = NULL;
+ }
+
+ newuri = build_relative_uri(page->uri, link, link_len);
+ if (newuri) {
+ browser_print_link(page->browser,
+ newuri->str, strlen(newuri->str), title, title_len);
+ xfree(&newuri);
+ } else
+ browser_print_link(page->browser,
+ link, link_len, title, title_len);
+ page->browser->style = STYLE_NONE;
+ browser_print(page->browser, "\r", 1);
+ page->content_pos += skip + tlen + trail;
+ continue;
}
- if (req->response[0] == '`' && req->response[1] == '`' &&
- req->response[2] == '`') {
- /* ``` toggle */
- req->browser->style = STYLE_PRE;
- consume_response(req, len);
- goto restart_parse;
- }
-
- if (req->response[0] == '#' && req->response[1] == '#' &&
- req->response[2] == '#') {
+ if (page->content[page->content_pos] == '#' &&
+ page->content[page->content_pos + 1] == '#' &&
+ page->content[page->content_pos + 2] == '#') {
/* ### h3 */
- req->browser->style = STYLE_H3;
+ page->browser->style = STYLE_H3;
skip = 3;
len -= 3;
- if (req->response[skip] == ' ') {
+ while ((c = page->content[page->content_pos + skip]) &&
+ (c == ' ' || c == '\t')) {
skip++;
len--;
}
- browser_print(req->browser, req->response + skip, len);
- consume_response(req, skip + len + trail);
- req->browser->style = STYLE_NONE;
- goto restart_parse;
+ goto print_line;
}
- if (req->response[0] == '#' && req->response[1] == '#') {
+ if (page->content[page->content_pos] == '#' &&
+ page->content[page->content_pos + 1] == '#') {
/* ## h2 */
- req->browser->style = STYLE_H2;
+ page->browser->style = STYLE_H2;
skip = 2;
len -= 2;
- if (req->response[skip] == ' ') {
+ while ((c = page->content[page->content_pos + skip]) &&
+ (c == ' ' || c == '\t')) {
skip++;
len--;
}
- browser_print(req->browser, req->response + skip, len);
- consume_response(req, skip + len + trail);
- req->browser->style = STYLE_NONE;
- goto restart_parse;
+ goto print_line;
}
- if (req->response[0] == '#') {
+ if (page->content[page->content_pos] == '#') {
/* # h1 */
- req->browser->style = STYLE_H1;
+ page->browser->style = STYLE_H1;
skip = 1;
len--;
- if (req->response[skip] == ' ') {
+ while ((c = page->content[page->content_pos + skip]) &&
+ (c == ' ' || c == '\t')) {
skip++;
len--;
}
- browser_print(req->browser, req->response + skip, len);
- consume_response(req, skip + len + trail);
- req->browser->style = STYLE_NONE;
- goto restart_parse;
+ goto print_line;
}
- if (req->response[0] == '*') {
+ if (page->content[page->content_pos] == '*') {
/* * list item */
- req->browser->style = STYLE_LIST;
+ page->browser->style = STYLE_LIST;
skip = 1;
len--;
- if (req->response[skip] == ' ') {
+ while ((c = page->content[page->content_pos + skip]) &&
+ (c == ' ' || c == '\t')) {
skip++;
len--;
}
- browser_print(req->browser, req->response + skip, len);
- consume_response(req, skip + len + trail);
- req->browser->style = STYLE_NONE;
- goto restart_parse;
+ browser_print(page->browser, " • ", 3);
+ goto print_line;
}
- if (req->response[0] == '>') {
+ if (page->content[page->content_pos] == '>') {
/* > quote text */
- req->browser->style = STYLE_QUOTE;
+ page->browser->style = STYLE_QUOTE;
skip = 1;
len--;
- if (req->response[skip] == ' ') {
+ while ((c = page->content[page->content_pos + skip]) &&
+ (c == ' ' || c == '\t')) {
skip++;
len--;
}
- browser_print(req->browser, req->response + skip, len);
- consume_response(req, skip + len + trail);
- req->browser->style = STYLE_NONE;
- goto restart_parse;
+ goto print_line;
}
-
- if (n == req->response_len) {
- /* end of buffer with no start, probably a continuation */
- browser_print(req->browser, req->response, n);
- consume_response(req, n);
- break;
- }
-
- /* just plain text */
- browser_print(req->browser, req->response, len - trail);
- consume_response(req, len + trail);
- goto restart_parse;
+
+print_line:
+ if (len)
+ browser_print(page->browser,
+ page->content + page->content_pos + skip, len);
+
+ page->content_pos += skip + len + trail;
+ if (!(page->browser->style & STYLE_PRE))
+ page->browser->style = STYLE_NONE;
+ if (newline)
+ browser_print(page->browser, "\r", 1);
}
+
+ TVAutoCalc(page->browser->output_tv, true);
+ TVCalcLines(page->browser->output_tv);
+ TVUpdate(page->browser->output_tv);
+ TVUpdateScrollbar(page->browser->output_tv,
+ page->browser->output_tv_scroller);
break;
}
return true;
}
-static void
-consume_response(struct gemini_request *req, size_t len)
-{
- if (len == req->response_len) {
- req->response_len = 0;
- memset(req->response, 0, sizeof(req->response));
- } else {
- memmove(req->response, req->response + len,
- sizeof(req->response) - len);
- req->response_len -= len;
- memset(req->response + req->response_len, 0,
- sizeof(req->response) - req->response_len);
- }
-}
-
static bool
-parse_header(struct gemini_request *req, char *str)
+parse_header(struct page *page, char *str, size_t len)
{
short status;
+ char fail[32];
- BROWSER_DEBUGF((req->browser, "Received header: %s", str));
+ BROWSER_DEBUGF((page->browser, "Received header: %s", str));
if (!(str[0] >= '0' && str[0] <= '9' &&
str[1] >= '0' && str[1] <= '9' && str[2] == ' ')) {
- browser_statusf(req->browser, "Error: Malformed response %s", str);
+ browser_statusf(page->browser, "Error: Malformed response %s", str);
return false;
}
status = ((str[0] - '0') * 10) + (str[1] - '0');
+ memcpy(fail, str, MIN(len, sizeof(fail)));
+ fail[MIN(len, sizeof(fail) - 1)] = '\0';
+
if (status >= 10 && status <= 19) {
/* input, not supported */
- browser_statusf(req->browser,
- "Error: Input not supported (%d)", status);
+ browser_statusf(page->browser, "Error: Input not supported (%d)",
+ status);
return false;
}
if (status >= 20 && status <= 29) {
/* success */
- strlcpy(req->mime_type, str + 3, sizeof(req->mime_type));
+ if (len + 3 + 1 > sizeof(page->type))
+ len = sizeof(page->type) - 1;
+ memcpy(page->type, str + 3, len - 2);
+ page->type[len - 2] = '\0';
return true;
}
if (status >= 30 && status <= 39) {
/* redirect */
- /* TODO */
+ page->redir_to = build_relative_uri(page->uri, str + 3, len - 2);
+ /* TODO: infinite loop detection */
return false;
}
if (status >= 40 && status <= 49) {
/* temp fail */
- browser_statusf(req->browser, "Error: Server reported temporary "
- "failure (%d)", status);
+ browser_statusf(page->browser, "Error: Server reported temporary "
+ "failure: %s", fail);
return false;
}
if (status >= 50 && status <= 59) {
/* perm fail */
- browser_statusf(req->browser, "Error: Server reported failure (%d)",
- status);
+ browser_statusf(page->browser, "Error: Server reported "
+ "permanent failure: %s", fail);
return false;
}
if (status >= 60 && status <= 69) {
/* auth, not supported */
- browser_statusf(req->browser, "Error: Auth not supported (%d)",
+ browser_statusf(page->browser, "Error: Auth not supported (%d)",
status);
return false;
}
- browser_statusf(req->browser, "Error: Unsupported status %d", status);
+ browser_statusf(page->browser, "Error: Unsupported status: %s", fail);
return false;
}
-
-#if 0
-void
-browser_click_link(struct browser *browser, struct browser_link *link)
-{
- static char new_uri[member_size(struct tcp_request, uri)];
- ssize_t n;
- size_t len;
- Rect r;
-
- if (strncmp(link->link, "gemini://", 9) == 0)
- len = strlcpy(new_uri, link->link, sizeof(new_uri));
- else {
- /* relative, remove any file from path */
- len = 0;
- for (n = browser->req->uri_len - 1; n >= 0; n--) {
- if (browser->req->uri[n] != '/')
- continue;
-
- /* gemini://host/path -> gemini://host/ */
- len = n + 1;
- memcpy(new_uri, browser->req->uri, len);
- break;
- }
- if (len == 0) {
- warn("failed making new uri from \"%s\" and \"%s\"",
- browser->req->uri, link->link);
- return;
- }
-
- n = strlen(link->link);
- memcpy(new_uri + len, link->link, n);
- new_uri[len + n] = '\0';
- len = len + n;
- }
-
- TESetText(new_uri, len, browser->uri_te);
- TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te);
-
- HLock(browser->uri_te);
- r = (*(browser->uri_te))->viewRect;
- HUnlock(browser->uri_te);
- TEUpdate(&r, browser->uri_te);
-
-bool
-browser_load_url(struct browser *browser, char *uristr)
- browser_connect(browser);
-}
-
-#endif
\ No newline at end of file
--- gopher.c Tue Oct 29 11:56:09 2024
+++ gopher.c Fri Nov 1 16:21:32 2024
@@ -19,104 +19,106 @@
#include <string.h>
#include "detritus.h"
+#include "browser.h"
#define GOPHER_PORT 70
struct gopher_request {
- struct browser *browser;
- struct URI *uri;
- char type;
-
unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */
- char response[2048];
- size_t response_len;
- size_t total_response_len;
TCPiopb tcp_iopb;
StreamPtr tcp_stream;
wdsEntry tcp_wds[2];
TCPStatusPB tcp_status_pb;
- bool tcp_done_reading;
- char message[member_size(struct URI, path) + 3];
- size_t message_len;
+ char selector[URI_MAX_PATH_LEN + 3];
+ size_t selector_len;
};
struct URI * gopher_parse_uri(char *uristr);
-void * gopher_init_request(struct browser *browser, struct URI *uri);
-bool gopher_process_request(void *cookie);
-void gopher_free(void *cookie);
-static bool parse_response(struct gopher_request *req);
-static void consume_response(struct gopher_request *req, size_t len);
-static void gopher_print_menu(struct gopher_request *req, size_t len);
+bool gopher_init_request(page_handle pageh);
+bool gopher_process(page_handle pageh);
+void gopher_cleanup(page_handle pageh);
-struct request_handler gopher_handler = {
+static bool parse_content(struct page *page);
+static void gopher_print_menu(struct page *page, char *line, size_t len);
+
+struct uri_handler gopher_handler = {
gopher_parse_uri,
gopher_init_request,
- gopher_process_request,
- gopher_free,
+ gopher_process,
+ gopher_cleanup,
};
struct URI *
gopher_parse_uri(char *uristr)
{
- return generic_parse_uri("gopher", uristr);
+ return parse_uri(uristr, "gopher");
}
-void *
-gopher_init_request(struct browser *browser, struct URI *uri)
+bool
+gopher_init_request(page_handle pageh)
{
+ struct page *page;
struct gopher_request *req = NULL;
+ size_t len;
char ip_s[12 + 3 + 1];
short err;
ip_addr ip, local_ip;
tcp_port port, local_port;
+ HLock(pageh);
+ page = *pageh;
+ if (page->content_len) {
+ /* already fetched, nothing to do here but reset */
+ page->content_pos = 0;
+ goto done;
+ }
+
req = xmalloczero(sizeof(struct gopher_request));
if (req == NULL) {
warn("Out of memory");
goto fail;
}
- req->browser = browser;
- req->uri = uri;
- if (req->uri->port == 0)
- req->uri->port = GOPHER_PORT;
+ if (page->uri->port == 0)
+ page->uri->port = GOPHER_PORT;
- browser_statusf(browser, "Resolving \"%s\"...", req->uri->hostname);
+ browser_statusf(page->browser, "Resolving \"%s\"...",
+ page->uri->hostname);
- err = DNSResolveName(req->uri->hostname, &ip, NULL);
+ err = DNSResolveName(page->uri->hostname, &ip, NULL);
if (err) {
- browser_statusf(browser, "Error: Failed resolving: %d", err);
+ browser_statusf(page->browser, "Error: Failed resolving: %d", err);
goto fail;
}
long2ip(ip, (char *)&ip_s);
- browser_statusf(browser, "Connecting to %s port %d...", ip_s,
- req->uri->port);
+ browser_statusf(page->browser, "Connecting to %s port %d...", ip_s,
+ page->uri->port);
err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf,
sizeof(req->tcp_buf), NULL, NULL, NULL, false);
if (err) {
- browser_statusf(browser, "Error: TCPCreate failed: %d", err);
+ browser_statusf(page->browser, "Error: TCPCreate failed: %d", err);
goto fail;
}
err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip,
- req->uri->port, &local_ip, &local_port, NULL, NULL, false);
+ page->uri->port, &local_ip, &local_port, NULL, NULL, false);
if (err) {
- browser_statusf(browser,
+ browser_statusf(page->browser,
"Error: Failed connecting to %s (%s) port %d: %d",
- req->uri->hostname, ip_s, req->uri->port, err);
+ page->uri->hostname, ip_s, page->uri->port, err);
goto fail;
}
err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
NULL, NULL, false);
if (err) {
- browser_statusf(browser,
+ browser_statusf(page->browser,
"Error: Failed TCPStatus on connection to %s (%s) port %d: %d",
- req->uri->hostname, ip_s, req->uri->port, err);
+ page->uri->hostname, ip_s, page->uri->port, err);
goto fail;
}
@@ -124,250 +126,287 @@ gopher_init_request(struct browser *browser, struct UR
* If we have a path other than /, assume the first character is the
* type and should not be sent, per RFC 4266.
*/
- if (req->uri->path[0] == '\0' ||
- (req->uri->path[0] == '/' && req->uri->path[1] == '\0')) {
- req->type = '1';
- req->message_len = snprintf(req->message, sizeof(req->message),
+ if (page->uri->path[0] == '\0' ||
+ (page->uri->path[0] == '/' && page->uri->path[1] == '\0')) {
+ page->type[0] = '1';
+ req->selector_len = snprintf(req->selector, sizeof(req->selector),
"\r\n");
} else {
/* /0/blah -> blah */
- req->type = req->uri->path[1];
- req->message_len = snprintf(req->message, sizeof(req->message),
- "%s\r\n", req->uri->path + 2);
+ page->type[0] = page->uri->path[1];
+ req->selector_len = snprintf(req->selector, sizeof(req->selector),
+ "%s\r\n", page->uri->path + 2);
}
- browser_commit_to_uri(browser, req->uri);
- browser_statusf(browser, "Connected to %s, sending request...",
- req->uri->hostname);
+ browser_statusf(page->browser, "Connected to %s, sending request...",
+ page->uri->hostname);
memset(&req->tcp_wds, 0, sizeof(req->tcp_wds));
- req->tcp_wds[0].ptr = (Ptr)req->message;
- req->tcp_wds[0].length = req->message_len;
+ req->tcp_wds[0].ptr = (Ptr)req->selector;
+ req->tcp_wds[0].length = req->selector_len;
err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL,
NULL, false);
if (err) {
- browser_statusf(req->browser, "Error: TCPSend failed: %d", err);
+ browser_statusf(page->browser, "Error: TCPSend failed: %d", err);
goto fail;
}
- return req;
+ page->handler_cookie = req;
+
+done:
+ HUnlock(pageh);
+ browser_commit_to_page(page->browser, pageh);
+ return true;
+
fail:
- if (req->tcp_stream) {
- _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
- _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ if (req) {
+ if (req->tcp_stream) {
+ _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ }
+
+ xfree(&req);
+ page->handler_cookie = NULL;
}
- if (req)
- xfree(&req);
-
- return NULL;
+ HUnlock(pageh);
+ return false;
}
void
-gopher_free(void *cookie)
+gopher_cleanup(page_handle pageh)
{
- struct gopher_request *req = (struct gopher_request *)cookie;
+ struct gopher_request *req;
+ struct page *page;
- if (req->tcp_stream) {
- _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
- _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ HLock(pageh);
+ page = *pageh;
+ req = (struct gopher_request *)(page->handler_cookie);
+
+ if (req) {
+ if (req->tcp_stream) {
+ _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false);
+ }
+
+ xfree(&page->handler_cookie);
}
- xfree(&req->uri);
- xfree(&req);
+ HUnlock(pageh);
}
bool
-gopher_process_request(void *cookie)
+gopher_process(page_handle pageh)
{
- struct gopher_request *req = (struct gopher_request *)cookie;
- size_t len, n;
+ struct gopher_request *req;
+ struct page *page;
+ size_t size, len, n;
short err;
unsigned short slen;
bool ret;
+ HLock(pageh);
+ page = *pageh;
+
+ if (page->handler_cookie == NULL)
+ goto parse;
+
+ req = (struct gopher_request *)(page->handler_cookie);
+
if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) {
- BROWSER_DEBUGF((req->browser, "TCP I/O Result %d, disconnecting",
+ BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting",
req->tcp_iopb.ioResult));
- return false;
+ goto finish_request;
}
- if (req->response_len < sizeof(req->response) &&
- !req->tcp_done_reading) {
- err = _TCPStatus(&req->tcp_iopb, req->tcp_stream,
- &req->tcp_status_pb, NULL, NULL, false);
- if (err) {
- browser_statusf(req->browser, "Error: Bad TCPStatus: %d", err);
+ err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
+ NULL, NULL, false);
+ if (err) {
+ browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err);
+ goto finish_request;
+ }
+
+ if (req->tcp_status_pb.connectionState !=
+ ConnectionStateEstablished) {
+ BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)",
+ req->tcp_status_pb.connectionState));
+ goto finish_request;
+ }
+ if (req->tcp_status_pb.amtUnreadData == 0)
+ goto parse;
+
+ if (page->content_len + req->tcp_status_pb.amtUnreadData >=
+ page->content_size) {
+ if (!browser_grow_page_content(pageh,
+ req->tcp_status_pb.amtUnreadData))
return false;
- }
- if (req->tcp_status_pb.connectionState !=
- ConnectionStateEstablished) {
- BROWSER_DEBUGF((req->browser, "TCP connection closed "
- "(state %d)", req->tcp_status_pb.connectionState));
- req->tcp_done_reading = true;
- goto parse;
- }
+ HLock(pageh);
+ page = *pageh;
+ req = (struct gopher_request *)(page->handler_cookie);
+ }
+
+ slen = req->tcp_status_pb.amtUnreadData;
+ err = _TCPRcv(&req->tcp_iopb, req->tcp_stream,
+ (Ptr)(page->content + page->content_len), &slen, NULL, NULL, false);
+ if (err) {
+ browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err);
+ goto finish_request;
+ }
+ page->content_len += slen;
+ browser_statusf(page->browser, "Read %ld bytes", page->content_len);
- if (req->tcp_status_pb.amtUnreadData == 0)
- goto parse;
-
- slen = MIN(req->tcp_status_pb.amtUnreadData,
- sizeof(req->response) - req->response_len);
- if (!slen) {
- browser_statusf(req->browser,
- "Error: No buffer space available in response?");
- return false;
- }
-
- err = _TCPRcv(&req->tcp_iopb, req->tcp_stream,
- (Ptr)(req->response + req->response_len), &slen, NULL, NULL,
- false);
- if (err) {
- browser_statusf(req->browser, "Error: Failed TCPRcv: %d", err);
- goto parse;
- }
- req->response_len += slen;
- req->total_response_len += slen;
- BROWSER_DEBUGF((req->browser, "Read %d bytes of TCP data", slen));
- browser_statusf(req->browser,
- "Read %ld bytes", req->total_response_len);
+ if (page->content_len >= 3 &&
+ (page->content_len == 3 ||
+ page->content[page->content_len - 4] == '\n') &&
+ page->content[page->content_len - 3] == '.' &&
+ page->content[page->content_len - 2] == '\r' &&
+ page->content[page->content_len - 1] == '\n') {
+ /* ".\r\n" is end of transfer */
+ page->content_len -= 3;
+ goto finish_request;
}
+ goto parse;
+
+finish_request:
+ gopher_cleanup(pageh);
+
parse:
- if (req->response_len) {
- TVAutoCalc(req->browser->output_tv, false);
- ret = parse_response(req);
- TVAutoCalc(req->browser->output_tv, true);
- TVCalcLines(req->browser->output_tv);
- TVUpdate(req->browser->output_tv);
- TVUpdateScrollbar(req->browser->output_tv,
- req->browser->output_tv_scroller);
- if (ret == false)
- browser_statusf(req->browser,
- "Finished parsing %ld bytes", req->total_response_len);
- return ret;
+ HLock(pageh);
+ page = *pageh;
+
+ if (page->content_pos == page->content_len) {
+ if (!page->handler_cookie) {
+ /* nothing left to do */
+ return false;
+ }
+ HUnlock(pageh);
+ return true;
}
- return true;
+ TVAutoCalc(page->browser->output_tv, false);
+ ret = parse_content(page);
+ TVAutoCalc(page->browser->output_tv, true);
+ TVCalcLines(page->browser->output_tv);
+ TVUpdate(page->browser->output_tv);
+ TVUpdateScrollbar(page->browser->output_tv,
+ page->browser->output_tv_scroller);
+
+ if (ret) {
+ HUnlock(pageh);
+ return true;
+ }
+
+ browser_statusf(page->browser, "Finished loading %ld bytes",
+ page->content_len);
+
+ /* release some memory if we can */
+ if (page->content_size > page->content_len) {
+ size = page->content_len;
+ page->content_size = size;
+ HUnlock(pageh);
+ SetHandleSize(pageh, sizeof(struct page) + size);
+ }
+
+request_fail:
+ HUnlock(pageh);
+ return false;
}
static bool
-parse_response(struct gopher_request *req)
+parse_content(struct page *page)
{
long n;
- if (req->type == '1') {
+ if (page->type[0] == '1') {
/* gopher menu */
-parse_menu:
- for (n = 0; n < req->response_len; n++) {
- if (n > 0 && req->response[n - 1] == '\r' &&
- req->response[n] == '\n') {
+ for (n = page->content_pos; n < page->content_len; n++) {
+ if (n > 0 && page->content[n - 1] == '\r' &&
+ page->content[n] == '\n') {
/* print line */
- req->response[n - 1] = '\0';
- if (req->response[0] == '.' && n == 2 &&
- req->response_len - n == 1)
- /* ".\r\n" is end of transfer */
- req->tcp_done_reading = true;
- else
- gopher_print_menu(req, n + 1);
- consume_response(req, n + 1);
- goto parse_menu;
+ gopher_print_menu(page, page->content +
+ page->content_pos, n - page->content_pos);
+ page->content_pos = n + 1;
}
-
- /* otherwise leave data in the buffer */
}
- if (req->response_len == sizeof(req->response)) {
- /* just dump it so we can move on */
- gopher_print_menu(req, req->response_len);
- consume_response(req, req->response_len);
+
+ if (!page->handler_cookie &&
+ page->content_pos < page->content_len) {
+ gopher_print_menu(page, page->content + page->content_pos,
+ page->content_len - page->content_pos);
+ page->content_pos = page->content_len;
+ return false;
}
- } else if (req->type == '0') {
+ } else if (page->type[0] == '0') {
/* text file, convert newlines and display */
-find_lines:
- for (n = 0; n < req->response_len; n++) {
- if (n > 0 && req->response[n - 1] == '\r' &&
- req->response[n] == '\n') {
- /* \r\n -> \r */
- if (req->response[0] == '.' && n == 2 &&
- req->response_len - n == 1)
- /* ".\r\n" is end of transfer */
- req->tcp_done_reading = true;
- else
- browser_print(req->browser, req->response, n);
- consume_response(req, n + 1);
- goto find_lines;
- } else if (n > 0 && req->response[n - 1] == '\r') {
- /* lone \r */
- browser_print(req->browser, req->response, n);
- consume_response(req, n);
- goto find_lines;
- } else if (n > 0 && req->response[n] == '\n') {
+ for (n = page->content_pos; n < page->content_len; n++) {
+ if (page->content[n] == '\r') {
+ browser_print(page->browser,
+ page->content + page->content_pos,
+ n - page->content_pos + 1);
+ page->content_pos = n + 1;
+ if (page->content[n + 1] == '\n') {
+ page->content_pos++;
+ n++;
+ }
+ } else if (page->content[n] == '\n') {
/* lone \n */
- req->response[n] = '\r';
- browser_print(req->browser, req->response, n);
- consume_response(req, n);
- goto find_lines;
+ browser_print(page->browser,
+ page->content + page->content_pos,
+ n - page->content_pos);
+ page->content_pos = n + 1;
}
- /* otherwise leave data in the buffer */
}
- if (req->response_len == sizeof(req->response)) {
- /* gosh this is one long line */
- browser_print(req->browser, req->response, req->response_len);
- consume_response(req, req->response_len);
+
+ if (!page->handler_cookie &&
+ page->content_pos < page->content_len) {
+ browser_print(page->browser, page->content + page->content_pos,
+ page->content_len - page->content_pos);
+ page->content_pos = page->content_len;
+ return false;
}
} else {
/* anything else, just download it */
- browser_print(req->browser, req->response, req->response_len);
- consume_response(req, req->response_len);
+ browser_print(page->browser, page->content + page->content_pos,
+ page->content_len - page->content_pos);
+ page->content_pos = page->content_len;
}
-
- return true;
+
+ if (page->handler_cookie)
+ return true;
+
+ return false;
}
static void
-consume_response(struct gopher_request *req, size_t len)
+gopher_print_menu(struct page *page, char *line, size_t len)
{
- if (len == req->response_len) {
- req->response_len = 0;
- memset(req->response, 0, sizeof(req->response));
- } else {
- memmove(req->response, req->response + len,
- sizeof(req->response) - len);
- req->response_len -= len;
- memset(req->response + req->response_len, 0,
- sizeof(req->response) - req->response_len);
- }
-}
-
-static void
-gopher_print_menu(struct gopher_request *req, size_t len)
-{
- static char uri[member_size(struct URI, str)], prefix[5];
+ static char uri[URI_MAX_STR_LEN + 1], prefix[5];
char type, *label, *selector = NULL, *hostname = NULL;
unsigned short port = 0;
long n;
- type = req->response[0];
- label = req->response + 1;
-
+ type = line[0];
+ label = line + 1;
+
for (n = 1; n < len; n++) {
- if (req->response[n] != '\t')
+ if (line[n] != '\t')
continue;
- req->response[n] = '\0';
+ line[n] = '\0';
if (selector == NULL)
- selector = req->response + n + 1;
+ selector = line + n + 1;
else if (hostname == NULL)
- hostname = req->response + n + 1;
+ hostname = line + n + 1;
else {
- port = atoi(req->response + n + 1);
+ port = atoi(line + n + 1);
break;
}
}
- req->browser->style = STYLE_PRE;
+ page->browser->style = STYLE_PRE;
switch (type) {
case '0':
@@ -382,27 +421,32 @@ gopher_print_menu(struct gopher_request *req, size_t l
/* document */
snprintf(prefix, sizeof(prefix), "[%c] ", type);
- browser_print(req->browser, prefix, 4);
+ browser_print(page->browser, prefix, 4);
len = snprintf(uri, sizeof(uri), "gopher://%s/%c%s", hostname,
type, selector);
- browser_print_link(req->browser, uri, len, label, strlen(label));
- browser_print(req->browser, "\r", 1);
+ browser_print_link(page->browser, uri, len, label, strlen(label));
+ browser_print(page->browser, "\r", 1);
break;
case 'i':
/* informational */
- browser_print(req->browser, label, strlen(label));
- browser_print(req->browser, "\r", 1);
+ browser_print(page->browser, label, strlen(label));
+ browser_print(page->browser, "\r", 1);
break;
default:
- browser_print(req->browser, "type: ", 0);
- browser_print(req->browser, (char *)&type, 1);
- browser_print(req->browser, " label:", 0);
- browser_print(req->browser, label, 0);
- browser_print(req->browser, " selector:", 0);
- browser_print(req->browser, selector, 0);
- browser_print(req->browser, " host:", 0);
- browser_print(req->browser, hostname, 0);
- browser_print(req->browser, "\r", 1);
+ browser_print(page->browser, "type: ", 0);
+ browser_print(page->browser, (char *)&type, 1);
+ //browser_print(page->browser, " label:", 0);
+ //browser_print(page->browser, label, 0);
+ //browser_print(page->browser, " selector:", 0);
+ //browser_print(page->browser, selector, 0);
+ //browser_print(page->browser, " host:", 0);
+ //browser_print(page->browser, hostname, 0);
+ browser_print(page->browser, "\r", 1);
+ }
+
+ for (n = 1; n < len; n++) {
+ if (line[n] == '\0')
+ line[n] = '\t';
}
}
\ No newline at end of file
--- main.c Mon Oct 28 09:36:29 2024
+++ main.c Sat Nov 2 16:44:31 2024
@@ -18,11 +18,12 @@
#include <string.h>
#include "detritus.h"
+#include "browser.h"
#include "focusable.h"
#include "tcp.h"
#include "util.h"
-MenuHandle apple_menu, file_menu, edit_menu;
+MenuHandle apple_menu, file_menu, edit_menu, bookmarks_menu;
bool quitting = false;
bool handle_menu(long menu_id);
@@ -58,7 +59,14 @@ main(void)
AddResMenu(apple_menu, 'DRVR');
file_menu = GetMHandle(FILE_MENU_ID);
edit_menu = GetMHandle(EDIT_MENU_ID);
+ bookmarks_menu = GetMHandle(BOOKMARKS_MENU_ID);
menu_defaults();
+
+AppendMenu(bookmarks_menu, "\p.");
+SetItem(bookmarks_menu, 1, "\pgemini://geminiprotocol.net/");
+AppendMenu(bookmarks_menu, "\p.");
+SetItem(bookmarks_menu, 2, "\pgopher://gopher.floodgap.com/");
+
DrawMenuBar();
progress("Finding BlueSCSI TLS Accelerator...");
@@ -210,7 +218,16 @@ handle_menu(long menu_id)
break;
}
break;
+ case BOOKMARKS_MENU_ID: {
+ Str255 uri;
+ struct browser *browser = (struct browser *)focused->cookie;
+
+ GetItem(bookmarks_menu, item, uri);
+ PtoCstr(uri);
+ browser_go_uri(browser,(char *)uri);
+ break;
}
+ }
handled:
HiliteMenu(0);
@@ -233,51 +250,168 @@ handle_exit(void)
}
struct URI *
-generic_parse_uri(char *protocol, char *uristr)
+parse_uri(char *uristr, char *restrict_protocol)
{
+ static char protocol[URI_MAX_PROTOCOL_LEN + 1];
+ static char hostname[URI_MAX_HOSTNAME_LEN + 1];
+ static char path[URI_MAX_PATH_LEN + 1];
+ static char str[URI_MAX_STR_LEN + 1];
struct URI *uri;
+ char *data;
+ size_t protocol_len, hostname_len, path_len, str_len, size;
short count;
+ unsigned short port;
- uri = xmalloczero(sizeof(struct URI));
- if (uri == NULL)
- return NULL;
-
/* protocol://host/path */
if (count = 0, sscanf(uristr,
- "%" STR(URI_PROTOCOL_LEN) "[^:]://%" STR(URI_HOSTNAME_LEN) "[^/]%"
- STR(URI_PATH_LEN) "s%n",
- &uri->protocol, &uri->hostname, &uri->path, &count) == 3 &&
- count > 10)
+ "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%"
+ STR(URI_MAX_HOSTNAME_LEN) "[^/]%"
+ STR(URI_MAX_PATH_LEN) "s%n",
+ &protocol, &hostname, &path, &count) == 3 && count > 10)
goto parse_ok;
/* protocol://host/ */
if (count = 0, sscanf(uristr,
- "%" STR(URI_PROTOCOL_LEN) "[^:]://%" STR(URI_HOSTNAME_LEN) "[^/]/%n",
- &uri->protocol, &uri->hostname, &count) == 2 && count > 10) {
- uri->path[0] = '/';
- uri->path[1] = '\0';
+ "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%"
+ STR(URI_MAX_HOSTNAME_LEN) "[^/]/%n",
+ &protocol, &hostname, &count) == 2 && count > 10) {
+ path[0] = '/';
+ path[1] = '\0';
goto parse_ok;
}
/* gemini://host */
if (count = 0, sscanf(uristr,
- "%" STR(URI_PROTOCOL_LEN) "[^:]://%" STR(URI_HOSTNAME_LEN) "[^/]%n",
- &uri->protocol, &uri->hostname, &count) == 2 && count > 10) {
- uri->path[0] = '/';
- uri->path[1] = '\0';
+ "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%"
+ STR(URI_MAX_HOSTNAME_LEN) "[^/]%n",
+ &protocol, &hostname, &count) == 2 && count > 10) {
+ path[0] = '/';
+ path[1] = '\0';
goto parse_ok;
}
/* failed */
- xfree(&uri);
return NULL;
parse_ok:
- if (protocol != NULL && strcasecmp(protocol, uri->protocol) != 0) {
- xfree(&uri);
+ if (restrict_protocol != NULL &&
+ strcasecmp(restrict_protocol, protocol) != 0)
return NULL;
- }
- snprintf(uri->str, sizeof(uri->str), "%s://%s%s",
+
+ protocol_len = strlen(protocol);
+ hostname_len = strlen(hostname);
+ path_len = strlen(path);
+ str_len = protocol_len + 3 + hostname_len + path_len;
+
+ size = sizeof(struct URI) + protocol_len + 1 + hostname_len + 1 +
+ path_len + 1 + str_len + 1;
+ uri = xmalloc(size);
+ if (uri == NULL)
+ return NULL;
+
+ uri->port = 0;
+
+ data = (char *)uri + sizeof(struct URI);
+
+ uri->protocol = data;
+ memcpy(uri->protocol, protocol, protocol_len);
+ uri->protocol[protocol_len] = '\0';
+ data += protocol_len + 1;
+
+ uri->hostname = data;
+ memcpy(uri->hostname, hostname, hostname_len);
+ uri->hostname[hostname_len] = '\0';
+ data += hostname_len + 1;
+
+ uri->path = data;
+ memcpy(uri->path, path, path_len);
+ uri->path[path_len] = '\0';
+ data += path_len + 1;
+
+ uri->str = data;
+ data += snprintf(uri->str, str_len + 1, "%s://%s%s",
uri->protocol, uri->hostname, uri->path);
+ data++;
+
+ if (data > (char *)uri + size)
+ panic("URI overflow");
+
return uri;
+}
+
+struct URI *
+build_relative_uri(struct URI *uri, char *relative, size_t len)
+{
+ static char path[URI_MAX_PATH_LEN + 1];
+ static char str[URI_MAX_STR_LEN + 1];
+ size_t slen, plen, n;
+
+ /* http://a.b/c + //d/e -> http://d/e */
+ if (relative[0] == '/' && relative[1] == '/') {
+ /* retain protocol, new host and path */
+ slen = snprintf(str, sizeof(str), "%s://", uri->protocol);
+ while (slen < sizeof(str) - 1 && len) {
+ str[slen - 1] = *relative++;
+ slen++;
+ }
+ str[slen] = '\0';
+
+ return parse_uri(str, NULL);
+ }
+
+ /* http://a.b/c + /d/e -> http://a.b/d/e */
+ if (relative[0] == '/') {
+ /* retain protocol and host, new path */
+ slen = snprintf(str, sizeof(str), "%s://%s", uri->protocol,
+ uri->hostname);
+ while (slen < sizeof(str) - 1 && len) {
+ str[slen] = *relative++;
+ slen++;
+ }
+ str[slen] = '\0';
+
+ return parse_uri(str, NULL);
+ }
+
+ for (n = 0; n <= len; n++) {
+ /* http://a.b/c + gemini://d/e -> gemini://d/e */
+ if (n < len - 2 && relative[n] == ':' && relative[n + 1] == '/' &&
+ relative[n + 2] == '/') {
+ /* protocol found, this isn't relative */
+ if (len >= sizeof(str))
+ len = sizeof(str) - 1;
+ memcpy(str, relative, len);
+ str[len] = '\0';
+ return parse_uri(str, NULL);
+ }
+
+ /* http://a.b/c/d.html + e/f.html -> http://a.b/c/e/f.html */
+ if (relative[n] == '/' || n == len) {
+ /* remove last component in uri path up to slash */
+ plen = strlcpy(path, uri->path, sizeof(path));
+ while (plen && path[plen - 1] != '/') {
+ path[plen - 1] = '\0';
+ plen--;
+ }
+
+ if (plen == 0) {
+ path[0] = '/';
+ path[1] = '\0';
+ plen = 1;
+ }
+
+ if (plen >= sizeof(path) - 1)
+ plen = sizeof(path) - 2;
+
+ memcpy(path + plen, relative, len);
+ path[plen + len] = '\0';
+ snprintf(str, sizeof(str), "%s://%s%s", uri->protocol,
+ uri->hostname, path);
+ return parse_uri(str, NULL);
+ }
+ }
+
+ /* what the heck is this? */
+ Debugger();
+ return parse_uri(relative, NULL);
}
--- util.c Tue Oct 1 20:15:14 2024
+++ util.c Sat Oct 26 21:10:37 2024
@@ -1622,7 +1622,8 @@ TEGetWidth(short off, TEHandle te)
#define ceildiv(a,b) ((a / b) + (!!(a % b)))
void
-UpdateScrollbarForTE(GrafPtr win, ControlHandle control, TEHandle te, bool reset)
+UpdateScrollbarForTE(GrafPtr win, ControlHandle control, TEHandle te,
+ bool reset)
{
size_t vlines, telines;
TERec *ter;
@@ -1985,14 +1986,21 @@ RestoreHiddenMenuBar(void)
bool
CommandPeriodPressed(void)
{
- EventRecord event;
+ KeyMap km;
+ bool cmd_down;
+ GetKeys(&km);
+ cmd_down = (km[1] & (1 << 15)) != 0;
+ if (cmd_down)
+ ;//Debugger();
+#if 0
if (!GetNextEvent(keyDownMask | autoKeyMask, &event))
return false;
if ((event.modifiers & cmdKey) &&
((event.message & charCodeMask) == '.'))
return true;
+#endif
return false;
}