From 6842f8712508262d0ea27692f13caa686419601e Mon Sep 17 00:00:00 2001
From: cinap_lenrek
Date: Sun, 4 Sep 2011 19:16:30 +0200
Subject: add mothra
---
sys/src/cmd/mothra/auth.c | 68 ++
sys/src/cmd/mothra/cistr.c | 29 +
sys/src/cmd/mothra/crackurl.c | 188 ++++++
sys/src/cmd/mothra/file.c | 48 ++
sys/src/cmd/mothra/filetype.c | 106 +++
sys/src/cmd/mothra/forms.c | 661 +++++++++++++++++++
sys/src/cmd/mothra/ftp.c | 428 ++++++++++++
sys/src/cmd/mothra/getpix.c | 126 ++++
sys/src/cmd/mothra/gopher.c | 38 ++
sys/src/cmd/mothra/gopher2html.c | 230 +++++++
sys/src/cmd/mothra/help.html | 78 +++
sys/src/cmd/mothra/html.h | 201 ++++++
sys/src/cmd/mothra/html.syntax.c | 340 ++++++++++
sys/src/cmd/mothra/http.c | 486 ++++++++++++++
sys/src/cmd/mothra/libpanel/button.c | 189 ++++++
sys/src/cmd/mothra/libpanel/canvas.c | 51 ++
sys/src/cmd/mothra/libpanel/draw.c | 286 ++++++++
sys/src/cmd/mothra/libpanel/edit.c | 239 +++++++
sys/src/cmd/mothra/libpanel/entry.c | 130 ++++
sys/src/cmd/mothra/libpanel/event.c | 50 ++
sys/src/cmd/mothra/libpanel/frame.c | 39 ++
sys/src/cmd/mothra/libpanel/group.c | 38 ++
sys/src/cmd/mothra/libpanel/init.c | 13 +
sys/src/cmd/mothra/libpanel/label.c | 50 ++
sys/src/cmd/mothra/libpanel/list.c | 190 ++++++
sys/src/cmd/mothra/libpanel/mem.c | 108 +++
sys/src/cmd/mothra/libpanel/message.c | 104 +++
sys/src/cmd/mothra/libpanel/mkfile | 33 +
sys/src/cmd/mothra/libpanel/pack.c | 167 +++++
sys/src/cmd/mothra/libpanel/panel.h | 174 +++++
sys/src/cmd/mothra/libpanel/panel.pdf | Bin 0 -> 87888 bytes
sys/src/cmd/mothra/libpanel/pldefs.h | 103 +++
sys/src/cmd/mothra/libpanel/popup.c | 113 ++++
sys/src/cmd/mothra/libpanel/print.c | 56 ++
sys/src/cmd/mothra/libpanel/pulldown.c | 160 +++++
sys/src/cmd/mothra/libpanel/rtext.c | 230 +++++++
sys/src/cmd/mothra/libpanel/rtext.h | 11 +
sys/src/cmd/mothra/libpanel/scrltest.c | 65 ++
sys/src/cmd/mothra/libpanel/scroll.c | 21 +
sys/src/cmd/mothra/libpanel/scrollbar.c | 148 +++++
sys/src/cmd/mothra/libpanel/slider.c | 97 +++
sys/src/cmd/mothra/libpanel/textview.c | 246 +++++++
sys/src/cmd/mothra/libpanel/textwin.c | 488 ++++++++++++++
sys/src/cmd/mothra/libpanel/utf.c | 30 +
sys/src/cmd/mothra/mkfile | 35 +
sys/src/cmd/mothra/mothra.c | 1072 ++++++++++++++++++++++++++++++
sys/src/cmd/mothra/mothra.gif | Bin 0 -> 5278 bytes
sys/src/cmd/mothra/mothra.h | 143 ++++
sys/src/cmd/mothra/mothracompat.gif | Bin 0 -> 7334 bytes
sys/src/cmd/mothra/mothraenhanced.gif | Bin 0 -> 6997 bytes
sys/src/cmd/mothra/rdhtml.c | 1091 +++++++++++++++++++++++++++++++
sys/src/cmd/mothra/tcs.h | 172 +++++
sys/src/cmd/mothra/urlcanon.c | 70 ++
sys/src/cmd/mothra/version.c | 1 +
54 files changed, 9240 insertions(+)
create mode 100644 sys/src/cmd/mothra/auth.c
create mode 100644 sys/src/cmd/mothra/cistr.c
create mode 100644 sys/src/cmd/mothra/crackurl.c
create mode 100644 sys/src/cmd/mothra/file.c
create mode 100644 sys/src/cmd/mothra/filetype.c
create mode 100644 sys/src/cmd/mothra/forms.c
create mode 100644 sys/src/cmd/mothra/ftp.c
create mode 100644 sys/src/cmd/mothra/getpix.c
create mode 100644 sys/src/cmd/mothra/gopher.c
create mode 100644 sys/src/cmd/mothra/gopher2html.c
create mode 100644 sys/src/cmd/mothra/help.html
create mode 100644 sys/src/cmd/mothra/html.h
create mode 100644 sys/src/cmd/mothra/html.syntax.c
create mode 100644 sys/src/cmd/mothra/http.c
create mode 100644 sys/src/cmd/mothra/libpanel/button.c
create mode 100644 sys/src/cmd/mothra/libpanel/canvas.c
create mode 100644 sys/src/cmd/mothra/libpanel/draw.c
create mode 100644 sys/src/cmd/mothra/libpanel/edit.c
create mode 100644 sys/src/cmd/mothra/libpanel/entry.c
create mode 100644 sys/src/cmd/mothra/libpanel/event.c
create mode 100644 sys/src/cmd/mothra/libpanel/frame.c
create mode 100644 sys/src/cmd/mothra/libpanel/group.c
create mode 100644 sys/src/cmd/mothra/libpanel/init.c
create mode 100644 sys/src/cmd/mothra/libpanel/label.c
create mode 100644 sys/src/cmd/mothra/libpanel/list.c
create mode 100644 sys/src/cmd/mothra/libpanel/mem.c
create mode 100644 sys/src/cmd/mothra/libpanel/message.c
create mode 100644 sys/src/cmd/mothra/libpanel/mkfile
create mode 100644 sys/src/cmd/mothra/libpanel/pack.c
create mode 100644 sys/src/cmd/mothra/libpanel/panel.h
create mode 100644 sys/src/cmd/mothra/libpanel/panel.pdf
create mode 100644 sys/src/cmd/mothra/libpanel/pldefs.h
create mode 100644 sys/src/cmd/mothra/libpanel/popup.c
create mode 100644 sys/src/cmd/mothra/libpanel/print.c
create mode 100644 sys/src/cmd/mothra/libpanel/pulldown.c
create mode 100644 sys/src/cmd/mothra/libpanel/rtext.c
create mode 100644 sys/src/cmd/mothra/libpanel/rtext.h
create mode 100644 sys/src/cmd/mothra/libpanel/scrltest.c
create mode 100644 sys/src/cmd/mothra/libpanel/scroll.c
create mode 100644 sys/src/cmd/mothra/libpanel/scrollbar.c
create mode 100644 sys/src/cmd/mothra/libpanel/slider.c
create mode 100644 sys/src/cmd/mothra/libpanel/textview.c
create mode 100644 sys/src/cmd/mothra/libpanel/textwin.c
create mode 100644 sys/src/cmd/mothra/libpanel/utf.c
create mode 100644 sys/src/cmd/mothra/mkfile
create mode 100644 sys/src/cmd/mothra/mothra.c
create mode 100644 sys/src/cmd/mothra/mothra.gif
create mode 100644 sys/src/cmd/mothra/mothra.h
create mode 100644 sys/src/cmd/mothra/mothracompat.gif
create mode 100644 sys/src/cmd/mothra/mothraenhanced.gif
create mode 100644 sys/src/cmd/mothra/rdhtml.c
create mode 100644 sys/src/cmd/mothra/tcs.h
create mode 100644 sys/src/cmd/mothra/urlcanon.c
create mode 100644 sys/src/cmd/mothra/version.c
diff --git a/sys/src/cmd/mothra/auth.c b/sys/src/cmd/mothra/auth.c
new file mode 100644
index 000000000..3e99e6324
--- /dev/null
+++ b/sys/src/cmd/mothra/auth.c
@@ -0,0 +1,68 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+
+static int
+basicauth(char *arg, char *str, int n)
+{
+ int i;
+ char *p;
+ char buf[1024];
+ Biobuf *b;
+
+ if(strncmp(arg, "realm=", 6) == 0)
+ arg += 6;
+ if(*arg == '"'){
+ arg++;
+ for(p = arg; *p && *p != '"'; p++);
+ *p = 0;
+ } else {
+ for(p = arg; *p && *p != ' ' && *p != '\t'; p++);
+ *p = 0;
+ }
+
+ p = getenv("home");
+ if(p == 0){
+ werrstr("$home not set");
+ return -1;
+ }
+ snprint(buf, sizeof(buf), "%s/lib/mothra/insecurity", p);
+ b = Bopen(buf, OREAD);
+ if(b == 0){
+ werrstr("www password file %s: %r", buf);
+ return -1;
+ }
+
+ i = strlen(arg);
+ while(p = Brdline(b, '\n'))
+ if(strncmp(arg, p, i) == 0 && p[i] == '\t')
+ break;
+ if(p == 0){
+ Bterm(b);
+ werrstr("no basic password for domain `%s'", arg);
+ return -1;
+ }
+
+ p[Blinelen(b)-1] = 0;
+ for(p += i; *p == '\t'; p++);
+ if (enc64(buf, sizeof buf, (uchar*)p, strlen(p)) < 0) {
+ Bterm(b);
+ werrstr("password too long: %s", p);
+ return -1;
+ }
+ snprint(str, n, "Authorization: Basic %s\r\n", buf);
+ return 0;
+}
+
+int
+auth(Url *url, char *str, int n)
+{
+ if(cistrcmp(url->authtype, "basic") == 0)
+ return basicauth(url->autharg, str, n);
+ werrstr("unknown auth method %s", url->authtype);
+ return -1;
+}
diff --git a/sys/src/cmd/mothra/cistr.c b/sys/src/cmd/mothra/cistr.c
new file mode 100644
index 000000000..f6aec8681
--- /dev/null
+++ b/sys/src/cmd/mothra/cistr.c
@@ -0,0 +1,29 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+int cistrcmp(char *s1, char *s2){
+ int c1, c2;
+
+ for(; *s1; s1++, s2++){
+ c1 = isupper(*s1) ? tolower(*s1) : *s1;
+ c2 = isupper(*s2) ? tolower(*s2) : *s2;
+ if (c1 < c2) return -1;
+ if (c1 > c2) return 1;
+ }
+ return 0;
+}
+int cistrncmp(char *s1, char *s2, int n){
+ int c1, c2;
+
+ for(; *s1 && n!=0; s1++, s2++, --n){
+ c1 = isupper(*s1) ? tolower(*s1) : *s1;
+ c2 = isupper(*s2) ? tolower(*s2) : *s2;
+ if (c1 < c2) return -1;
+ if (c1 > c2) return 1;
+ }
+ return 0;
+}
diff --git a/sys/src/cmd/mothra/crackurl.c b/sys/src/cmd/mothra/crackurl.c
new file mode 100644
index 000000000..f04f42575
--- /dev/null
+++ b/sys/src/cmd/mothra/crackurl.c
@@ -0,0 +1,188 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+enum{
+ IP=1, /* url can contain //ipaddress[:port] */
+ REL=2, /* fill in ip address & root of name from current, if necessary */
+ SSL=4, /* use SSL/TLS encryption */
+};
+Scheme scheme[]={
+ "http:", HTTP, IP|REL, 80,
+ "https:", HTTP, IP|REL|SSL, 443,
+ "ftp:", FTP, IP|REL, 21,
+ "file:", FILE, REL, 0,
+ "telnet:", TELNET, IP, 0,
+ "mailto:", MAILTO, 0, 0,
+ "gopher:", GOPHER, IP, 70,
+ 0, HTTP, IP|REL, 80,
+};
+int endaddr(int c){
+ return c=='/' || c==':' || c=='?' || c=='#' || c=='\0';
+}
+/*
+ * Remove ., mu/.. and empty components from path names.
+ * Empty last components of urls are significant, and
+ * therefore preserved.
+ */
+void urlcanon(char *name){
+ char *s, *t;
+ char **comp, **p, **q;
+ int rooted;
+ rooted=name[0]=='/';
+ /*
+ * Break the name into a list of components
+ */
+ comp=emalloc((strlen(name)+2)*sizeof(char *));
+ p=comp;
+ *p++=name;
+ for(s=name;;s++){
+ if(*s=='/'){
+ *p++=s+1;
+ *s='\0';
+ }
+ else if(*s=='\0' || *s=='?')
+ break;
+ }
+ *p=0;
+ /*
+ * go through the component list, deleting components that are empty (except
+ * the last component) or ., and any .. and its non-.. predecessor.
+ */
+ p=q=comp;
+ while(*p){
+ if(strcmp(*p, "")==0 && p[1]!=0
+ || strcmp(*p, ".")==0)
+ p++;
+ else if(strcmp(*p, "..")==0 && q!=comp && strcmp(q[-1], "..")!=0){
+ --q;
+ p++;
+ }
+ else
+ *q++=*p++;
+ }
+ *q=0;
+ /*
+ * rebuild the path name
+ */
+ s=name;
+ if(rooted) *s++='/';
+ for(p=comp;*p;p++){
+ t=*p;
+ while(*t) *s++=*t++;
+ if(p[1]!=0) *s++='/';
+ }
+ *s='\0';
+ free(comp);
+}
+/*
+ * True url parsing is a nightmare.
+ * This assumes that there are two basic syntaxes
+ * for url's -- with and without an ip address.
+ * If the type identifier or the ip address and port number
+ * or the relative address is missing from urlname or is empty,
+ * it is copied from cur.
+ */
+void crackurl(Url *url, char *urlname, Url *cur){
+ char *relp, *tagp, *httpname;
+ int len;
+ Scheme *up;
+ char buf[30];
+ /*
+ * The following lines `fix' the most egregious urlname syntax errors
+ */
+ while(*urlname==' ' || *urlname=='\t' || *urlname=='\n') urlname++;
+ relp=strchr(urlname, '\n');
+ if(relp) *relp='\0';
+ /*
+ * In emulation of Netscape, attach a free "http://"
+ * to names beginning with "www.".
+ */
+ if(strncmp(urlname, "www.", 4)==0){
+ httpname=emalloc(strlen(urlname)+8);
+ strcpy(httpname, "http://");
+ strcat(httpname, urlname);
+ crackurl(url, httpname, cur);
+ free(httpname);
+ return;
+ }
+ url->port=cur->port;
+ strncpy(url->ipaddr, cur->ipaddr, sizeof(url->ipaddr));
+ strncpy(url->reltext, cur->reltext, sizeof(url->reltext));
+ if(strchr(urlname, ':')==0){
+ up=cur->scheme;
+ if(up==0){
+ up=&scheme[0];
+ cur->scheme=up;
+ }
+ }
+ else{
+ for(up=scheme;up->name;up++){
+ len=strlen(up->name);
+ if(strncmp(urlname, up->name, len)==0){
+ urlname+=len;
+ break;
+ }
+ }
+ if(up->name==0) up=&scheme[0]; /* default to http: */
+ }
+ url->access=up->type;
+ url->scheme=up;
+ if(up!=cur->scheme)
+ url->reltext[0]='\0';
+ if(up->flags&IP && strncmp(urlname, "//", 2)==0){
+ urlname+=2;
+ for(relp=urlname;!endaddr(*relp);relp++);
+ len=relp-urlname;
+ strncpy(url->ipaddr, urlname, len);
+ url->ipaddr[len]='\0';
+ urlname=relp;
+ if(*urlname==':'){
+ urlname++;
+ url->port=atoi(urlname);
+ while(!endaddr(*urlname)) urlname++;
+ }
+ else
+ url->port=up->port;
+ if(*urlname=='\0') urlname="/";
+ }
+ url->ssl = up->flags&SSL;
+
+ tagp=strchr(urlname, '#');
+ if(tagp){
+ *tagp='\0';
+ strncpy(url->tag, tagp+1, sizeof(url->tag));
+ }
+ else
+ url->tag[0]='\0';
+ if(!(up->flags&REL) || *urlname=='/')
+ strncpy(url->reltext, urlname, sizeof(url->reltext));
+ else if(urlname[0]){
+ relp=strrchr(url->reltext, '/');
+ if(relp==0)
+ strncpy(url->reltext, urlname, sizeof(url->reltext));
+ else
+ strcpy(relp+1, urlname);
+ }
+ urlcanon(url->reltext);
+ if(tagp) *tagp='#';
+ /*
+ * The following mess of strcpys and strcats
+ * can't be changed to a few sprints because
+ * urls are not necessarily composed of legal utf
+ */
+ strcpy(url->fullname, up->name);
+ if(up->flags&IP){
+ strncat(url->fullname, "//", sizeof(url->fullname));
+ strncat(url->fullname, url->ipaddr, sizeof(url->fullname));
+ if(url->port!=up->port){
+ snprint(buf, sizeof(buf), ":%d", url->port);
+ strncat(url->fullname, buf, sizeof(url->fullname));
+ }
+ }
+ strcat(url->fullname, url->reltext);
+ url->map=0;
+}
diff --git a/sys/src/cmd/mothra/file.c b/sys/src/cmd/mothra/file.c
new file mode 100644
index 000000000..50ae60f25
--- /dev/null
+++ b/sys/src/cmd/mothra/file.c
@@ -0,0 +1,48 @@
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+/*
+ * fd is the result of a successful open(name, OREAD),
+ * where name is the name of a directory. We convert
+ * this into an html page containing links to the files
+ * in the directory.
+ */
+int dir2html(char *name, int fd){
+ int p[2], first;
+ Dir *dir;
+ int i, n;
+ if(pipe(p)==-1){
+ close(fd);
+ return -1;
+ }
+ switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
+ case -1:
+ close(fd);
+ return -1;
+ case 0:
+ close(p[1]);
+ fprint(p[0], "\n");
+ fprint(p[0], "Directory %s\n", name);
+ fprint(p[0], "\n");
+ fprint(p[0], "\n");
+ fprint(p[0], "%s
\n", name);
+ fprint(p[0], "\n");
+ first=1;
+ while((n = dirread(fd, &dir)) > 0) {
+ for (i = 0; i < n; i++)
+ fprint(p[0], "- %s%s\n", name, dir[i].name, dir[i].name,
+ dir[i].mode&DMDIR?"/":"");
+ free(dir);
+ }
+ fprint(p[0], "
\n");
+ fprint(p[0], "\n");
+ _exits(0);
+ default:
+ close(fd);
+ close(p[0]);
+ return p[1];
+ }
+}
diff --git a/sys/src/cmd/mothra/filetype.c b/sys/src/cmd/mothra/filetype.c
new file mode 100644
index 000000000..71ae92bed
--- /dev/null
+++ b/sys/src/cmd/mothra/filetype.c
@@ -0,0 +1,106 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+typedef struct Kind Kind;
+struct Kind{
+ char *name;
+ int kind;
+};
+int klook(char *s, Kind *k){
+ while(k->name && cistrcmp(k->name, s)!=0)
+ k++;
+ return k->kind;
+}
+Kind suffix[]={
+ ".html", HTML,
+ ".htm", HTML,
+ "/", HTML,
+ ".gif", GIF,
+ ".jpe", JPEG,
+ ".jpg", JPEG,
+ ".jpeg", JPEG,
+ ".png", PNG,
+ ".pic", PIC,
+ ".au", AUDIO,
+ ".tif", TIFF,
+ ".tiff", TIFF,
+ ".xbm", XBM,
+ ".txt", PLAIN,
+ ".text", PLAIN,
+ ".ai", POSTSCRIPT,
+ ".eps", POSTSCRIPT,
+ ".ps", POSTSCRIPT,
+ ".pdf", PDF,
+ ".zip", ZIP,
+ 0, HTML
+};
+int suflook(char *s, int len, Kind *k){
+ int l;
+ while(k->name){
+ l=strlen(k->name);
+ if(l<=len && cistrcmp(k->name, s+len-l)==0) return k->kind;
+ k++;
+ }
+ return k->kind;
+}
+int suffix2type(char *name){
+ int len, kind, restore;
+ char *s;
+ len=strlen(name);
+ if(len>=2 && cistrcmp(name+len-2, ".Z")==0){
+ kind=COMPRESS;
+ len-=2;
+ }
+ else if(len>=3 && cistrcmp(name+len-3, ".gz")==0){
+ kind=GUNZIP;
+ len-=3;
+ }
+ else
+ kind=0;
+ restore=name[len];
+ name[len]='\0';
+ for(s=name+len;s!=name && *s!='.';--s);
+ kind|=suflook(name, len, suffix);
+ name[len]=restore;
+ return kind;
+}
+Kind content[]={
+ "text/html", HTML,
+ "text/x-html", HTML,
+ "application/html", HTML,
+ "application/x-html", HTML,
+ "text/plain", PLAIN,
+ "image/gif", GIF,
+ "image/jpeg", JPEG,
+ "image/pjpeg", JPEG,
+ "image/png", PNG,
+ "image/tiff", TIFF,
+ "image/x-xbitmap", XBM,
+ "image/x-bitmap", XBM,
+ "image/xbitmap", XBM,
+ "application/postscript", POSTSCRIPT,
+ "application/pdf", PDF,
+ "application/octet-stream", SUFFIX,
+ "application/zip", ZIP,
+ 0, HTML
+};
+int content2type(char *s, char *name){
+ int type;
+ type=klook(s, content);
+ if(type==SUFFIX) type=suffix2type(name);
+ return type;
+}
+Kind encoding[]={
+ "x-compress", COMPRESS,
+ "compress", COMPRESS,
+ "x-gzip", GUNZIP,
+ "gzip", GUNZIP,
+ 0, 0
+};
+int encoding2type(char *s){
+ return klook(s, encoding);
+}
diff --git a/sys/src/cmd/mothra/forms.c b/sys/src/cmd/mothra/forms.c
new file mode 100644
index 000000000..5e08a3242
--- /dev/null
+++ b/sys/src/cmd/mothra/forms.c
@@ -0,0 +1,661 @@
+/*
+ * type=image is treated like submit
+ */
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+#include "html.h"
+typedef struct Field Field;
+typedef struct Option Option;
+struct Form{
+ int method;
+ Url *action;
+ Field *fields, *efields;
+ Form *next;
+};
+struct Field{
+ Field *next;
+ Form *form;
+ char *name;
+ char *value;
+ int checked;
+ int size; /* should be a point, but that feature is deprecated */
+ int maxlength;
+ int type;
+ int rows, cols;
+ Option *options;
+ int multiple;
+ int state; /* is the button marked? */
+ Panel *p;
+ Panel *pulldown;
+ Panel *textwin;
+};
+/*
+ * Field types
+ */
+enum{
+ TYPEIN=1,
+ CHECK,
+ PASSWD,
+ RADIO,
+ SUBMIT,
+ RESET,
+ SELECT,
+ TEXTWIN,
+ HIDDEN,
+ INDEX,
+};
+struct Option{
+ int selected;
+ int def;
+ char label[NLABEL+1];
+ char *value;
+ Option *next;
+};
+void h_checkinput(Panel *, int, int);
+void h_radioinput(Panel *, int, int);
+void h_submitinput(Panel *, int);
+void h_submittype(Panel *, char *);
+void h_submitindex(Panel *, char *);
+void h_resetinput(Panel *, int);
+void h_select(Panel *, int, int);
+void h_cut(Panel *, int);
+void h_paste(Panel *, int);
+void h_snarf(Panel *, int);
+void h_edit(Panel *);
+char *selgen(Panel *, int);
+char *nullgen(Panel *, int);
+Field *newfield(Form *form){
+ Field *f;
+ f=emallocz(sizeof(Field), 1);
+ if(form->efields==0)
+ form->fields=f;
+ else
+ form->efields->next=f;
+ form->efields=f;
+ f->next=0;
+ f->form=form;
+ return f;
+}
+/*
+ * Called by rdhtml on seeing a forms-related tag
+ */
+void rdform(Hglob *g){
+ char *s;
+ Field *f;
+ Option *o, **op;
+ Form *form;
+ switch(g->tag){
+ default:
+ fprint(2, "Bad tag <%s> in rdform (Can't happen!)\n", g->token);
+ return;
+ case Tag_form:
+ if(g->form){
+ htmlerror(g->name, g->lineno, "nested forms illegal\n");
+ break;
+ }
+ g->form=emallocz(sizeof(Form), 1);
+ g->form->action=emalloc(sizeof(Url));
+ s=pl_getattr(g->attr, "action");
+ if(s==0)
+ *g->form->action=*g->dst->url;
+ else
+ crackurl(g->form->action, s, g->dst->base);
+ s=pl_getattr(g->attr, "method");
+ if(s==0)
+ g->form->method=GET;
+ else if(cistrcmp(s, "post")==0)
+ g->form->method=POST;
+ else{
+ if(cistrcmp(s, "get")!=0)
+ htmlerror(g->name, g->lineno,
+ "unknown form method %s\n", s);
+ g->form->method=GET;
+ }
+ g->form->fields=0;
+
+ g->form->next = g->dst->form;
+ g->dst->form = g->form;
+
+ break;
+ case Tag_input:
+ if(g->form==0){
+ BadTag:
+ htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n",
+ tag[g->tag].name);
+ break;
+ }
+ f=newfield(g->form);
+ s=pl_getattr(g->attr, "name");
+ if(s==0)
+ f->name=0;
+ else
+ f->name=strdup(s);
+ s=pl_getattr(g->attr, "value");
+ if(s==0)
+ f->value=strdup("");
+ else
+ f->value=strdup(s);
+ f->checked=pl_hasattr(g->attr, "checked");
+ s=pl_getattr(g->attr, "size");
+ if(s==0)
+ f->size=20;
+ else
+ f->size=atoi(s);
+ s=pl_getattr(g->attr, "maxlength");
+ if(s==0)
+ f->maxlength=0x3fffffff;
+ else
+ f->maxlength=atoi(s);
+ s=pl_getattr(g->attr, "type");
+ /* bug -- password treated as text */
+ if(s==0 || cistrcmp(s, "text")==0 || cistrcmp(s, "password")==0 || cistrcmp(s, "int")==0){
+ s=pl_getattr(g->attr, "name");
+ if(s!=0 && strcmp(s, "isindex")==0)
+ f->type=INDEX;
+ else
+ f->type=TYPEIN;
+ /*
+ * If there's exactly one attribute, use its value as the name,
+ * regardless of the attribute name. This makes
+ * http://linus.att.com/ias/puborder.html work.
+ */
+ if(s==0){
+ if(g->attr[0].name && g->attr[1].name==0)
+ f->name=strdup(g->attr[0].value);
+ else
+ f->name=strdup("no-name");
+ }
+ }
+ else if(cistrcmp(s, "checkbox")==0)
+ f->type=CHECK;
+ else if(cistrcmp(s, "radio")==0)
+ f->type=RADIO;
+ else if(cistrcmp(s, "submit")==0)
+ f->type=SUBMIT;
+ else if(cistrcmp(s, "image")==0){
+ /* presotto's egregious hack to make image submits do something */
+ if(f->name){
+ free(f->name);
+ f->name=0;
+ }
+ f->type=SUBMIT;
+ } else if(cistrcmp(s, "reset")==0)
+ f->type=RESET;
+ else if(cistrcmp(s, "hidden")==0)
+ f->type=HIDDEN;
+ else{
+ htmlerror(g->name, g->lineno, "bad field type %s, ignored", s);
+ break;
+ }
+ if((f->type==CHECK || f->type==RADIO) && !pl_hasattr(g->attr, "value")){
+ free(f->value);
+ f->value=strdup("on");
+ }
+ if(f->type!=HIDDEN)
+ pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
+ break;
+ case Tag_select:
+ if(g->form==0) goto BadTag;
+ f=newfield(g->form);
+ s=pl_getattr(g->attr, "name");
+ if(s==0){
+ f->name=strdup("select");
+ htmlerror(g->name, g->lineno, "select has no name=\n");
+ }
+ else
+ f->name=strdup(s);
+ s=pl_getattr(g->attr, "size");
+ if(s==0) f->size=4;
+ else{
+ f->size=atoi(s);
+ if(f->size<=0) f->size=1;
+ }
+ f->multiple=pl_hasattr(g->attr, "multiple");
+ f->type=SELECT;
+ f->options=0;
+ g->text=g->token;
+ g->tp=g->text;
+ g->etext=g->text;
+ break;
+ case Tag_option:
+ if(g->form==0) goto BadTag;
+ f=g->form->efields;
+ o=emallocz(sizeof(Option), 1);
+ for(op=&f->options;*op;op=&(*op)->next);
+ *op=o;
+ o->next=0;
+ g->text=o->label;
+ g->tp=o->label;
+ g->etext=o->label+NLABEL;
+ memset(o->label, 0, NLABEL+1);
+ *g->tp++=' ';
+ o->def=pl_hasattr(g->attr, "selected");
+ o->selected=o->def;
+ s=pl_getattr(g->attr, "value");
+ if(s==0)
+ o->value=o->label+1;
+ else
+ o->value=strdup(s);
+ break;
+ case Tag_textarea:
+ if(g->form==0) goto BadTag;
+ f=newfield(g->form);
+ s=pl_getattr(g->attr, "name");
+ if(s==0){
+ f->name=strdup("enter text");
+ htmlerror(g->name, g->lineno, "select has no name=\n");
+ }
+ else
+ f->name=strdup(s);
+ s=pl_getattr(g->attr, "rows");
+ f->rows=s?atoi(s):8;
+ s=pl_getattr(g->attr, "cols");
+ f->cols=s?atoi(s):30;
+ f->type=TEXTWIN;
+ /* suck up initial text */
+ pl_htmloutput(g, g->nsp, f->name, f);
+ break;
+ case Tag_isindex:
+ /*
+ * Make up a form with one tag, of type INDEX
+ * I have seen a page with ,
+ * which is nonstandard and not handled here.
+ */
+ form=emalloc(sizeof(Form));
+ form->fields=0;
+ form->efields=0;
+ form->action=emalloc(sizeof(Url));
+ s=pl_getattr(g->attr, "action");
+ if(s==0)
+ *form->action=*g->dst->url;
+ else
+ crackurl(form->action, s, g->dst->base);
+ form->method=GET;
+ form->fields=0;
+ f=newfield(form);
+ f->name=0;
+ f->value=strdup("");
+ f->size=20;
+ f->maxlength=0x3fffffff;
+ f->type=INDEX;
+ pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
+ break;
+ }
+}
+/*
+ * Called by rdhtml on seeing a forms-related end tag
+ */
+void endform(Hglob *g){
+ switch(g->tag){
+ case Tag_form:
+ g->form=0;
+ break;
+ case Tag_select:
+ if(g->form==0)
+ htmlerror(g->name, g->lineno, " not in form, ignored\n");
+ else
+ pl_htmloutput(g, g->nsp, g->form->efields->name,g->form->efields);
+ break;
+ case Tag_textarea:
+ break;
+ }
+}
+char *nullgen(Panel *, int ){
+ return 0;
+}
+char *selgen(Panel *p, int index){
+ Option *a;
+ Field *f;
+ f=p->userp;
+ if(f==0) return 0;
+ for(a=f->options;index!=0 && a!=0;--index,a=a->next);
+ if(a==0) return 0;
+ a->label[0]=a->selected?'*':' ';
+ return a->label;
+}
+char *seloption(Field *f){
+ Option *a;
+ for(a=f->options;a!=0;a=a->next)
+ if(a->selected)
+ return a->label+1;
+ return f->name;
+}
+void mkfieldpanel(Rtext *t){
+ Action *a;
+ Panel *win, *scrl, *menu, *pop, *button;
+ Field *f;
+
+ if((a = t->user) == nil)
+ return;
+ if((f = a->field) == nil)
+ return;
+
+ f->p=0;
+ switch(f->type){
+ case TYPEIN:
+ f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submittype);
+ break;
+ case CHECK:
+ f->p=plcheckbutton(0, 0, "", h_checkinput);
+ f->state=f->checked;
+ plsetbutton(f->p, f->checked);
+ break;
+ case RADIO:
+ f->p=plradiobutton(0, 0, "", h_radioinput);
+ f->state=f->checked;
+ plsetbutton(f->p, f->checked);
+ break;
+ case SUBMIT:
+ f->p=plbutton(0, 0, f->value[0]?f->value:"submit", h_submitinput);
+ break;
+ case RESET:
+ f->p=plbutton(0, 0, f->value[0]?f->value:"reset", h_resetinput);
+ break;
+ case SELECT:
+ f->pulldown=plgroup(0,0);
+ scrl=plscrollbar(f->pulldown, PACKW|FILLY);
+ win=pllist(f->pulldown, PACKN, nullgen, f->size, h_select);
+ win->userp=f;
+ plinitlist(win, PACKN, selgen, f->size, h_select);
+ plscroll(win, 0, scrl);
+ plpack(f->pulldown, Rect(0,0,1024,1024));
+ f->p=plpulldown(0, FIXEDX, seloption(f), f->pulldown, PACKS);
+ f->p->fixedsize.x=f->pulldown->r.max.x-f->pulldown->r.min.x;
+ break;
+ case TEXTWIN:
+ menu=plgroup(0,0);
+ f->p=plframe(0,0);
+ pllabel(f->p, PACKN|FILLX, f->name);
+ scrl=plscrollbar(f->p, PACKW|FILLY);
+ pop=plpopup(f->p, PACKN|FILLX, 0, menu, 0);
+ f->textwin=pledit(pop, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height),
+ 0, 0, h_edit);
+ f->textwin->userp=f;
+ button=plbutton(menu, PACKN|FILLX, "cut", h_cut);
+ button->userp=f->textwin;
+ button=plbutton(menu, PACKN|FILLX, "paste", h_paste);
+ button->userp=f->textwin;
+ button=plbutton(menu, PACKN|FILLX, "snarf", h_snarf);
+ button->userp=f->textwin;
+ plscroll(f->textwin, 0, scrl);
+ break;
+ case INDEX:
+ f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submitindex);
+ break;
+ }
+ if(f->p){
+ f->p->userp=f;
+ free(t->text);
+ t->text=0;
+ t->p=f->p;
+ t->hot=1;
+ }
+}
+void h_checkinput(Panel *p, int, int v){
+ ((Field *)p->userp)->state=v;
+}
+void h_radioinput(Panel *p, int, int v){
+ Field *f, *me;
+ me=p->userp;
+ me->state=v;
+ if(v){
+ for(f=me->form->fields;f;f=f->next)
+ if(f->type==RADIO && f!=me && strcmp(f->name, me->name)==0){
+ plsetbutton(f->p, 0);
+ f->state=0;
+ pldraw(f->p, screen);
+ }
+ }
+}
+void h_select(Panel *p, int, int index){
+ Option *a;
+ Field *f;
+ f=p->userp;
+ if(f==0) return;
+ if(!f->multiple) for(a=f->options;a;a=a->next) a->selected=0;
+ for(a=f->options;index!=0 && a!=0;--index,a=a->next);
+ if(a==0) return;
+ a->selected=!a->selected;
+ plinitpulldown(f->p, FIXEDX, seloption(f), f->pulldown, PACKS);
+ pldraw(f->p, screen);
+}
+void h_resetinput(Panel *p, int){
+ Field *f;
+ Option *o;
+ for(f=((Field *)p->userp)->form->fields;f;f=f->next) switch(f->type){
+ case TYPEIN:
+ case PASSWD:
+ plinitentry(f->p, 0, f->size*chrwidth, f->value, 0);
+ break;
+ case CHECK:
+ case RADIO:
+ f->state=f->checked;
+ plsetbutton(f->p, f->checked);
+ break;
+ case SELECT:
+ for(o=f->options;o;o=o->next)
+ o->selected=o->def;
+ break;
+ }
+ pldraw(text, screen);
+}
+void h_edit(Panel *p){
+ plgrabkb(p);
+}
+Rune *snarfbuf=0;
+int nsnarfbuf=0;
+void h_snarf(Panel *p, int){
+ int s0, s1;
+ Rune *text;
+ p=p->userp;
+ plegetsel(p, &s0, &s1);
+ if(s0==s1) return;
+ text=pleget(p);
+ if(snarfbuf) free(snarfbuf);
+ nsnarfbuf=s1-s0;
+ snarfbuf=malloc(nsnarfbuf*sizeof(Rune));
+ if(snarfbuf==0){
+ fprint(2, "No mem\n");
+ exits("no mem");
+ }
+ memmove(snarfbuf, text+s0, nsnarfbuf*sizeof(Rune));
+}
+void h_cut(Panel *p, int b){
+ h_snarf(p, b);
+ plepaste(p->userp, 0, 0);
+}
+void h_paste(Panel *p, int){
+ plepaste(p->userp, snarfbuf, nsnarfbuf);
+}
+int ulen(char *s){
+ int len;
+ len=0;
+ for(;*s;s++){
+ if(strchr("/$-_@.!*'(), ", *s)
+ || 'a'<=*s && *s<='z'
+ || 'A'<=*s && *s<='Z'
+ || '0'<=*s && *s<='9')
+ len++;
+ else
+ len+=3;
+ }
+ return len;
+}
+int hexdigit(int v){
+ return 0<=v && v<=9?'0'+v:'A'+v-10;
+}
+char *ucpy(char *buf, char *s){
+ for(;*s;s++){
+ if(strchr("/$-_@.!*'(),", *s)
+ || 'a'<=*s && *s<='z'
+ || 'A'<=*s && *s<='Z'
+ || '0'<=*s && *s<='9')
+ *buf++=*s;
+ else if(*s==' ')
+ *buf++='+';
+ else{
+ *buf++='%';
+ *buf++=hexdigit((*s>>4)&15);
+ *buf++=hexdigit(*s&15);
+ }
+ }
+ *buf='\0';
+ return buf;
+}
+char *runetou(char *buf, Rune r){
+ char rbuf[2];
+ if(r<=255){
+ rbuf[0]=r;
+ rbuf[1]='\0';
+ buf=ucpy(buf, rbuf);
+ }
+ return buf;
+}
+/*
+ * If there's exactly one button with type=text, then
+ * a CR in the button is supposed to submit the form.
+ */
+void h_submittype(Panel *p, char *){
+ int ntype;
+ Field *f;
+ ntype=0;
+ for(f=((Field *)p->userp)->form->fields;f;f=f->next) if(f->type==TYPEIN) ntype++;
+ if(ntype==1) h_submitinput(p, 0);
+}
+void h_submitindex(Panel *p, char *){
+ h_submitinput(p, 0);
+}
+void h_submitinput(Panel *p, int){
+ Form *form;
+ int size, nrune;
+ char *buf, *bufp, sep;
+ Rune *rp;
+ Field *f;
+ Option *o;
+ form=((Field *)p->userp)->form;
+ if(form->method==GET) size=ulen(form->action->fullname)+1;
+ else size=1;
+ for(f=form->fields;f;f=f->next) switch(f->type){
+ case TYPEIN:
+ case PASSWD:
+ size+=ulen(f->name)+1+ulen(plentryval(f->p))+1;
+ break;
+ case INDEX:
+ size+=ulen(plentryval(f->p))+1;
+ break;
+ case CHECK:
+ case RADIO:
+ if(!f->state) break;
+ case HIDDEN:
+ size+=ulen(f->name)+1+ulen(f->value)+1;
+ break;
+ case SELECT:
+ for(o=f->options;o;o=o->next)
+ if(o->selected)
+ size+=ulen(f->name)+1+ulen(o->value)+1;
+ break;
+ case TEXTWIN:
+ size+=ulen(f->name)+1+plelen(f->textwin)*3+1;
+ break;
+ }
+ buf=emalloc(size);
+ if(form->method==GET){
+ strcpy(buf, form->action->fullname);
+ sep='?';
+ }
+ else{
+ buf[0]='\0';
+ sep=0;
+ }
+ bufp=buf+strlen(buf);
+ if(form->method==GET && bufp!=buf && bufp[-1]=='?') *--bufp='\0'; /* spurious ? */
+ for(f=form->fields;f;f=f->next) switch(f->type){
+ case TYPEIN:
+ case PASSWD:
+ if(sep) *bufp++=sep;
+ sep='&';
+ bufp=ucpy(bufp, f->name);
+ *bufp++='=';
+ bufp=ucpy(bufp, plentryval(f->p));
+ break;
+ case INDEX:
+ if(sep) *bufp++=sep;
+ sep='&';
+ bufp=ucpy(bufp, plentryval(f->p));
+ break;
+ case CHECK:
+ case RADIO:
+ if(!f->state) break;
+ case HIDDEN:
+ if(sep) *bufp++=sep;
+ sep='&';
+ bufp=ucpy(bufp, f->name);
+ *bufp++='=';
+ bufp=ucpy(bufp, f->value);
+ break;
+ case SELECT:
+ for(o=f->options;o;o=o->next)
+ if(o->selected){
+ if(sep) *bufp++=sep;
+ sep='&';
+ bufp=ucpy(bufp, f->name);
+ *bufp++='=';
+ bufp=ucpy(bufp, o->value);
+ }
+ break;
+ case TEXTWIN:
+ if(sep) *bufp++=sep;
+ sep='&';
+ bufp=ucpy(bufp, f->name);
+ *bufp++='=';
+ rp=pleget(f->textwin);
+ for(nrune=plelen(f->textwin);nrune!=0;--nrune)
+ bufp=runetou(bufp, *rp++);
+ *bufp='\0';
+ break;
+ }
+ if(form->method==GET){
+fprint(2, "GET %s\n", buf);
+ geturl(buf, GET, 0, 0, 0);
+ }
+ else{
+fprint(2, "POST %s: %s\n", form->action->fullname, buf);
+ geturl(form->action->fullname, POST, buf, 0, 0);
+ }
+ free(buf);
+}
+
+void freeform(void *p)
+{
+ Form *form;
+ Field *f;
+ Option *o;
+
+ while(form = p){
+ p = form->next;
+ free(form->action);
+ while(f = form->fields){
+ form->fields = f->next;
+
+ if(f->p!=0)
+ plfree(f->p);
+
+ free(f->name);
+ free(f->value);
+
+ while(o = f->options){
+ f->options = o->next;
+ if(o->value != o->label+1)
+ free(o->value);
+ free(o);
+ }
+
+ free(f);
+ }
+ free(form);
+ }
+}
diff --git a/sys/src/cmd/mothra/ftp.c b/sys/src/cmd/mothra/ftp.c
new file mode 100644
index 000000000..78c3ab5da
--- /dev/null
+++ b/sys/src/cmd/mothra/ftp.c
@@ -0,0 +1,428 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+
+enum
+{
+ /* return codes */
+ Extra= 1,
+ Success= 2,
+ Incomplete= 3,
+ TempFail= 4,
+ PermFail= 5,
+
+ NAMELEN= 28,
+ Nnetdir= 3*NAMELEN, /* max length of network directory paths */
+ Ndialstr= 64, /* max length of dial strings */
+};
+
+typedef struct Ftp Ftp;
+struct Ftp
+{
+ char net[Nnetdir];
+ Biobuf *ftpctl;
+ Url *u;
+};
+
+static int ftpdebug;
+
+
+/*
+ * read from biobuf turning cr/nl into nl
+ */
+char*
+getcrnl(Biobuf *b)
+{
+ char *p, *ep;
+
+ p = Brdline(b, '\n');
+ if(p == nil)
+ return nil;
+ ep = p + Blinelen(b) - 1;
+ if(*(ep-1) == '\r')
+ ep--;
+ *ep = 0;
+ return p;
+}
+
+char*
+readfile(char *file, char *buf, int len)
+{
+ int n, fd;
+
+ fd = open(file, OREAD);
+ if(fd < 0)
+ return nil;
+ n = read(fd, buf, len-1);
+ close(fd);
+ if(n <= 0)
+ return nil;
+ buf[n] = 0;
+ return buf;
+}
+
+char*
+sysname(void)
+{
+ static char sys[Ndbvlen];
+ char *p;
+
+ p = readfile("/dev/sysname", sys, sizeof(sys));
+ if(p == nil)
+ return "unknown";
+ return p;
+}
+
+char*
+domainname(void)
+{
+ static char domain[Ndbvlen];
+ Ndbtuple *t;
+
+ if(*domain)
+ return domain;
+
+ t = csgetval(0, "sys", sysname(), "dom", domain);
+ if(t){
+ ndbfree(t);
+ return domain;
+ } else
+ return sysname();
+}
+
+static int
+sendrequest(Biobuf *b, char *fmt, ...)
+{
+ char buf[2*1024], *s;
+ va_list args;
+
+ va_start(args, fmt);
+ s = buf + vsnprint(buf, (sizeof(buf)-4) / sizeof(*buf), fmt, args);
+ va_end(args);
+ *s++ = '\r';
+ *s++ = '\n';
+ if(write(Bfildes(b), buf, s - buf) != s - buf)
+ return -1;
+ if(ftpdebug)
+ write(2, buf, s - buf);
+ return 0;
+}
+
+static int
+getreply(Biobuf *b, char *msg, int len)
+{
+ char *line;
+ int rv;
+ int i, n;
+
+ while(line = getcrnl(b)){
+ /* add line to message buffer, strip off \r */
+ n = Blinelen(b);
+ if(ftpdebug)
+ write(2, line, n);
+ if(n > len - 1)
+ i = len - 1;
+ else
+ i = n;
+ if(i > 0){
+ memmove(msg, line, i);
+ msg += i;
+ len -= i;
+ *msg = 0;
+ }
+
+ /* stop if not a continuation */
+ rv = atoi(line);
+ if(rv >= 100 && rv < 600 && (n == 4 || (n > 4 && line[3] == ' ')))
+ return rv/100;
+ }
+
+ return -1;
+}
+
+int
+terminateftp(Ftp *d)
+{
+ if(d->ftpctl){
+ close(Bfildes(d->ftpctl));
+ Bterm(d->ftpctl);
+ free(d->ftpctl);
+ d->ftpctl = nil;
+ }
+ free(d);
+ return -1;
+}
+
+Biobuf*
+hello(Ftp *d)
+{
+ int fd;
+ char *p;
+ Biobuf *b;
+ char msg[1024];
+ char ndir[Nnetdir];
+
+ snprint(msg, sizeof msg, "tcp!%s!%d", d->u->ipaddr, d->u->port);
+ fd = dial(msg, 0, ndir, 0);
+ if(fd < 0){
+ d->ftpctl = nil;
+ return nil;
+ }
+ b = emalloc(sizeof(Biobuf));
+ Binit(b, fd, OREAD);
+ d->ftpctl = b;
+
+ /* remember network for the data connections */
+ p = strrchr(ndir, '/');
+ if(p == 0){
+ fprint(2, "dial is out of date\n");
+ return nil;
+ }
+ *p = 0;
+ strcpy(d->net, ndir);
+
+ /* wait for hello from other side */
+ if(getreply(b, msg, sizeof(msg)) != Success){
+ fprint(2, "instead of hello: %s\n", msg);
+ return nil;
+ }
+ return b;
+}
+
+int
+logon(Ftp *d)
+{
+ char msg[1024];
+
+ /* login anonymous */
+ sendrequest(d->ftpctl, "USER anonymous");
+ switch(getreply(d->ftpctl, msg, sizeof(msg))){
+ case Success:
+ return 0;
+ case Incomplete:
+ break; /* need password */
+ default:
+ fprint(2, "login failed: %s\n", msg);
+ werrstr(msg);
+ return -1;
+ }
+
+ /* send user id as password */
+ sprint(msg, "%s@", getuser());
+ sendrequest(d->ftpctl, "PASS %s", msg);
+ if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
+ fprint(2, "login failed: %s\n", msg);
+ werrstr(msg);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+xfertype(Ftp *d, char *t)
+{
+ char msg[1024];
+
+ sendrequest(d->ftpctl, "TYPE %s", t);
+ if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
+ fprint(2, "can't set type %s: %s\n", t, msg);
+ werrstr(msg);
+ return -1;
+ }
+ return 0;
+}
+
+int
+passive(Ftp *d)
+{
+ char msg[1024];
+ char dialstr[Ndialstr];
+ char *f[6];
+ char *p;
+ int fd;
+
+ sendrequest(d->ftpctl, "PASV");
+ if(getreply(d->ftpctl, msg, sizeof(msg)) != Success)
+ return -1;
+
+ /* get address and port number from reply, this is AI */
+ p = strchr(msg, '(');
+ if(p == nil){
+ for(p = msg+3; *p; p++)
+ if(isdigit(*p))
+ break;
+ } else
+ p++;
+ if(getfields(p, f, 6, 0, ",") < 6){
+ fprint(2, "passive mode protocol botch: %s\n", msg);
+ werrstr("ftp protocol botch");
+ return -1;
+ }
+ snprint(dialstr, sizeof(dialstr), "%s!%s.%s.%s.%s!%d", d->net,
+ f[0], f[1], f[2], f[3],
+ ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff));
+
+
+ /* open data connection */
+ fd = dial(dialstr, 0, 0, 0);
+ if(fd < 0){
+ fprint(2, "passive mode connect to %s failed: %r\n", dialstr);
+ return -1;
+ }
+
+ /* tell remote to send a file */
+ sendrequest(d->ftpctl, "RETR %s", d->u->reltext);
+ if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){
+ fprint(2, "passive mode retrieve failed: %s\n", msg);
+ werrstr(msg);
+ return -1;
+ }
+ return fd;
+}
+
+int
+active(Ftp *d)
+{
+ char msg[1024];
+ char buf[Ndialstr];
+ char netdir[Nnetdir];
+ char newdir[Nnetdir];
+ uchar ipaddr[4];
+ int dfd, cfd, listenfd;
+ char *p;
+ int port;
+
+ /* get a channel to listen on, let kernel pick the port number */
+ sprint(buf, "%s!*!0", d->net);
+ listenfd = announce(buf, netdir);
+ if(listenfd < 0){
+ fprint(2, "can't listen for ftp callback: %r\n", buf);
+ return -1;
+ }
+
+ /* get the local address and port number */
+ sprint(newdir, "%s/local", netdir);
+ readfile(newdir, buf, sizeof buf);
+ p = strchr(buf, '!')+1;
+ parseip(ipaddr, buf);
+ port = atoi(p);
+
+ /* tell remote side address and port*/
+ sendrequest(d->ftpctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2],
+ ipaddr[3], port>>8, port&0xff);
+ if(getreply(d->ftpctl, msg, sizeof(msg)) != Success){
+ close(listenfd);
+ werrstr("ftp protocol botch");
+ fprint(2, "active mode connect failed %s\n", msg);
+ return -1;
+ }
+
+ /* tell remote to send a file */
+ sendrequest(d->ftpctl, "RETR %s", d->u->reltext);
+ if(getreply(d->ftpctl, msg, sizeof(msg)) != Extra){
+ close(listenfd);
+ fprint(2, "active mode connect failed: %s\n", msg);
+ werrstr(msg);
+ return -1;
+ }
+
+ /* wait for a new call */
+ cfd = listen(netdir, newdir);
+ close(listenfd);
+ if(cfd < 0){
+ fprint(2, "active mode connect failed: %r\n");
+ return -1;
+ }
+
+ /* open the data connection and close the control connection */
+ dfd = accept(cfd, newdir);
+ close(cfd);
+ if(dfd < 0){
+ fprint(2, "active mode connect failed: %r\n");
+ werrstr("ftp protocol botch");
+ return -1;
+ }
+
+ return dfd;
+}
+
+/*
+ * Given a url, return a file descriptor on which caller can
+ * read an ftp document.
+ * The caller is responsible for processing redirection loops.
+ */
+int
+ftp(Url *url)
+{
+ int n;
+ int data;
+ Ftp *d;
+ int pfd[2];
+ char buf[2048];
+
+ if(url->type == 0)
+ url->type = PLAIN;
+
+ d = (Ftp*)emalloc(sizeof(Ftp));
+ d->u = url;
+ d->ftpctl = nil;
+
+ if(hello(d) == nil)
+ return terminateftp(d);
+ if(logon(d) < 0)
+ return terminateftp(d);
+
+ switch(url->type){
+ case PLAIN:
+ case HTML:
+ if(xfertype(d, "A") < 0)
+ return terminateftp(d);
+ break;
+ default:
+ if(xfertype(d, "I") < 0)
+ return terminateftp(d);
+ break;
+ }
+
+ /* first try passive mode, then active */
+ data = passive(d);
+ if(data < 0){
+ if(d->ftpctl == nil)
+ return -1;
+ data = active(d);
+ if(data < 0)
+ return -1;
+ }
+
+ if(pipe(pfd) < 0)
+ return -1;
+
+ switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
+ case -1:
+ werrstr("Can't fork");
+ close(pfd[0]);
+ close(pfd[1]);
+ return terminateftp(d);
+ case 0:
+ close(pfd[0]);
+ while((n=read(data, buf, sizeof(buf)))>0)
+ write(pfd[1], buf, n);
+ if(n<0)
+ fprint(2, "ftp: %s: %r\n", url->fullname);
+ _exits(0);
+ default:
+ close(pfd[1]);
+ close(data);
+ terminateftp(d);
+ return pfd[0];
+ }
+ return -1;
+}
diff --git a/sys/src/cmd/mothra/getpix.c b/sys/src/cmd/mothra/getpix.c
new file mode 100644
index 000000000..19e2b8b23
--- /dev/null
+++ b/sys/src/cmd/mothra/getpix.c
@@ -0,0 +1,126 @@
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+
+typedef struct Pix Pix;
+struct Pix{
+ Pix *next;
+ Image *b;
+ int width;
+ int height;
+ char name[NNAME];
+};
+
+char *pixcmd[]={
+[GIF] "gif -9t",
+[JPEG] "jpg -9t",
+[PNG] "png -9t",
+[PIC] "fb/3to1 /lib/fb/cmap/rgbv",
+[TIFF] "/sys/lib/mothra/tiffcvt",
+[XBM] "fb/xbm2pic",
+};
+
+void storebitmap(Rtext *t, Image *b){
+ t->b=b;
+ free(t->text);
+ t->text=0;
+}
+
+void getimage(Rtext *t, Www *w){
+ int pfd[2];
+ Action *ap;
+ Url url;
+ Image *b;
+ int fd;
+ char err[512];
+ Pix *p;
+
+ ap=t->user;
+ crackurl(&url, ap->image, w->base);
+ for(p=w->pix;p!=nil; p=p->next)
+ if(strcmp(ap->image, p->name)==0 && ap->width==p->width && ap->height==p->height){
+ storebitmap(t, p->b);
+ free(ap->image);
+ ap->image=0;
+ w->changed=1;
+ return;
+ }
+ fd=urlopen(&url, GET, 0);
+ if(fd==-1){
+ Err:
+ snprint(err, sizeof(err), "[%s: %r]", url.fullname);
+ free(t->text);
+ t->text=strdup(err);
+ free(ap->image);
+ ap->image=0;
+ w->changed=1;
+ close(fd);
+ return;
+ }
+ if(url.type!=GIF
+ && url.type!=JPEG
+ && url.type!=PNG
+ && url.type!=PIC
+ && url.type!=TIFF
+ && url.type!=XBM){
+ werrstr("unknown image type");
+ goto Err;
+ }
+
+ if((fd = pipeline(pixcmd[url.type], fd)) < 0)
+ goto Err;
+ if(ap->width>0 || ap->height>0){
+ char buf[80];
+ char *p;
+
+ p = buf;
+ p += sprint(p, "resize");
+ if(ap->width>0)
+ p += sprint(p, " -x %d", ap->width);
+ if(ap->height>0)
+ p += sprint(p, " -y %d", ap->height);
+ if((fd = pipeline(buf, fd)) < 0)
+ goto Err;
+ }
+ b=readimage(display, fd, 1);
+ if(b==0){
+ werrstr("can't read image");
+ goto Err;
+ }
+ close(fd);
+ p = emallocz(sizeof(Pix), 1);
+ strncpy(p->name, ap->image, sizeof(p->name));
+ p->b=b;
+ p->width=ap->width;
+ p->height=ap->height;
+ p->next=w->pix;
+ w->pix=p;
+ storebitmap(t, b);
+ free(ap->image);
+ ap->image=0;
+ w->changed=1;
+}
+
+void getpix(Rtext *t, Www *w){
+ Action *ap;
+
+ for(;t!=0;t=t->next){
+ ap=t->user;
+ if(ap && ap->image)
+ getimage(t, w);
+ }
+}
+
+void freepix(void *p)
+{
+ Pix *x, *xx;
+ xx = p;
+ while(x = xx){
+ xx = x->next;
+ freeimage(x->b);
+ free(x);
+ }
+}
diff --git a/sys/src/cmd/mothra/gopher.c b/sys/src/cmd/mothra/gopher.c
new file mode 100644
index 000000000..2e231eae6
--- /dev/null
+++ b/sys/src/cmd/mothra/gopher.c
@@ -0,0 +1,38 @@
+#include
+#include
+#include
+#include
+#include
+#include "mothra.h"
+void httpheader(Url *, char *);
+/*
+ * Given a url, return a file descriptor on which caller can
+ * read a gopher document.
+ */
+int gopher(Url *url){
+ int pfd[2];
+ char port[30];
+ if(pipe(pfd)==-1) return -1;
+ switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
+ case -1:
+ close(pfd[0]);
+ close(pfd[1]);
+ return -1;
+ case 0:
+ dup(pfd[1], 1);
+ close(pfd[0]);
+ close(pfd[1]);
+ sprint(port, "%d", url->port);
+ execl("/bin/aux/gopher2html",
+ "gopher2html", url->ipaddr, port, url->reltext+1, 0);
+ fprint(2, "Can't exec aux/gopher2html!\n");
+ print("Mothra error\n");
+ print("Mothra error
\n");
+ print("Can't exec aux/gopher2html!\n");
+ exits("no exec");
+ default:
+ close(pfd[1]);
+ url->type=HTML;
+ return pfd[0];
+ }
+}
diff --git a/sys/src/cmd/mothra/gopher2html.c b/sys/src/cmd/mothra/gopher2html.c
new file mode 100644
index 000000000..e0c57d736
--- /dev/null
+++ b/sys/src/cmd/mothra/gopher2html.c
@@ -0,0 +1,230 @@
+/*
+ * Reads gopher output from a TCP port, outputs
+ * html on standard output.
+ * Usage: gopher2html gopher-string
+ * where gopher-string is the string sent to
+ * the gopher server to get the document.
+ *
+ * Gopher protocol is described in rfc1436
+ */
+#include
+#include
+char *cmd;
+int ifd;
+void errexit(char *s, ...){
+ static char buf[1024];
+ char *out;
+ va_list args;
+ va_start(args, s);
+ out = doprint(buf, buf+sizeof(buf), s, args);
+ va_end(args);
+ *out='\0';
+ print("%s error\n", cmd);
+ print("%s error
\n", cmd);
+ print("%s\n", buf);
+ exits("gopher error");
+}
+void wtext(char *buf, char *ebuf){
+ char *bp;
+ for(bp=buf;bp!=ebuf;bp++){
+ if(*bp=='<' || *bp=='>' || *bp=='&' || *bp=='"'){
+ if(bp!=buf) write(1, buf, bp-buf);
+ buf=bp+1;
+ switch(*bp){
+ case '<': print("<"); break;
+ case '>': print(">"); break;
+ case '&': print("&"); break;
+ case '"': print("""); break;
+ }
+ }
+ }
+ if(bp!=buf) write(1, buf, bp-buf);
+}
+void savefile(char *name, char *type){
+ int fd, n;
+ char save[30], buf[1024];
+ for(n=1;;n++){
+ if(n==100) errexit("can't save binary file %s: %r", name);
+ sprint(save, "gopher.save.%d", n);
+ fd=create(save, OWRITE, 0444);
+ if(fd!=-1) break;
+ }
+ print("%s%s
\n", name);
+ print("Saving %s file %s in %s...\n", type, name, save);
+ while((n=read(ifd, buf, sizeof buf))>0) write(fd, buf, n);
+ close(fd);
+ print("done
%s
\n", title);
+ while((n=read(ifd, buf, sizeof buf))>0) wtext(buf, buf+n);
+ print("\n");
+}
+/*
+ * A directory entry contains
+ * type name selector host port
+ * all tab separated, except type and name (type is one character)
+ */
+char ibuf[1024], *ibp, *eibuf;
+#define EOF (-1)
+int get(void){
+ int n;
+Again:
+ if(ibp==eibuf){
+ n=read(ifd, ibuf, sizeof(ibuf));
+ if(n<=0) return EOF;
+ eibuf=ibuf+n;
+ ibp=ibuf;
+ }
+ if(*ibp=='\r'){
+ ibp++;
+ goto Again;
+ }
+ return *ibp++&255;
+}
+char *escape(char *in){
+ static char out[516];
+ char *op, *eop;
+ eop=out+512;
+ op=out;
+ for(;*in;in++){
+ if(op%s\n", title);
+ print("%s
\n", title);
+ for(;;){
+ type=get();
+ if(type==EOF || type=='.') break;
+ bp=name;
+ while((c=get())!=EOF && c!='\t') if(bp!=&name[512]) *bp++=c;
+ ename=bp;
+ bp=selector;
+ while((c=get())!=EOF && c!='\t') if(bp!=&selector[512]) *bp++=c;
+ *bp='\0';
+ bp=host;
+ while((c=get())!=EOF && c!='\t') if(bp!=&host[512]) *bp++=c;
+ *bp='\0';
+ bp=port;
+ while((c=get())!=EOF && c!='\t' && c!='\n') if(bp!=&port[512]) *bp++=c;
+ while(c!=EOF && c!='\n') c=get();
+ *bp='\0';
+ switch(type){
+ case '3':
+ print("- ");
+ wtext(name, ename);
+ break;
+ case '7':
+ print("
- ",
+ host, port, type, escape(selector));
+ wtext(name, ename);
+ break;
+ default:
+ print("
- ",
+ host, port, type, escape(selector));
+ wtext(name, ename);
+ print("\n");
+ break;
+ }
+ }
+ print("
\n");
+}
+int hexdigit(int c){
+ if('0'<=c && c<='9') return c-'0';
+ if('a'<=c && c<='f') return c-'a'+10;
+ if('A'<=c && c<='F') return c-'A'+10;
+ return -1;
+}
+void unescape(char *s){
+ char *t;
+ int hi, lo;
+ t=s;
+ while(*s){
+ if(*s=='%'
+ && (hi=hexdigit(s[1]))>=0
+ && (lo=hexdigit(s[2]))>=0){
+ *t++=hi*16+lo;
+ s+=3;
+ }
+ else *t++=*s++;
+ }
+ *t='\0';
+}
+void main(int argc, char *argv[]){
+ char dialstr[1024];
+ char *name;
+ cmd=argv[0];
+ if(argc!=4) errexit("Usage: %s host port selector", argv[0]);
+ sprint(dialstr, "tcp!%s!%s", argv[1], argv[2]);
+ ifd=dial(dialstr, 0, 0, 0);
+ if(ifd==-1) errexit("can't call %s:%s", argv[1], argv[2]);
+ unescape(argv[3]);
+ switch(argv[3][0]){
+ case '/':
+ fprint(ifd, "\r\n");
+ copydir(argv[3]);
+ break;
+ case '\0':
+ fprint(ifd, "\r\n");
+ copydir(argv[1]);
+ break;
+ case '7': /* index query */
+ name=strchr(argv[3], '?');
+ if(name!=0){
+ if(name==argv[3]+1){
+ argv[3][1]=argv[3][0];
+ argv[3]++;
+ }
+ else
+ *name='\t';
+ name++;
+ }
+ else
+ name=argv[3];
+ fprint(ifd, "%s\r\n", argv[3]+1);
+ copydir(name);
+ break;
+ default:
+ fprint(ifd, "%s\r\n", argv[3]+1);
+ name=strrchr(argv[3], '/');
+ if(name==0) name=argv[3];
+ else name++;
+ switch(argv[3][0]){
+ default: errexit("sorry, can't handle %s (type %c)",
+ argv[3]+1, argv[3][0]);
+ case '0': copyfile(name); break;
+ case '1': copydir(name); break;
+ case '4': savefile(name, "Macintosh BINHEX"); break;
+ case '5': savefile(name, "DOS binary"); break;
+ case '6': savefile(name, "uuencoded"); break;
+ case '9': savefile(name, "binary"); break;
+ case 'g': savefile(name, "GIF"); break;
+ case 'I': savefile(name, "some sort of image"); break;
+ }
+ break;
+ }
+ exits(0);
+}
diff --git a/sys/src/cmd/mothra/help.html b/sys/src/cmd/mothra/help.html
new file mode 100644
index 000000000..771f4b138
--- /dev/null
+++ b/sys/src/cmd/mothra/help.html
@@ -0,0 +1,78 @@
+Mothra help
+
+
+Mothra Help
+
+Mothra is a World-wide Web browser. Its display looks like this:
+
+
The display's regions, from top to bottom, are:
+
+- Error messages and other information.
+
- A text input window in which commands can be typed.
+
- A scrollable list of titles of previously visited documents, with the most recent first.
+Pointing at one of these lines with mouse button 1 revisits the document.
+
- The title of the currently-displayed document.
+
- The URL of the currently-displayed document.
+
- The scrollable document display. Underlined text and
+images surrounded by boxes may be pointed at with button 1 to
+visit the files that they refer to. Files that are not
+HTML documents (for example images or mailto: urls) cause
+9v or mail to pop up in a new 8½ window.
+
+Mouse Action
+Pointing with button
+2 instead of button 1 selects a url without following it;
+the url will be displayed in the selection: area and commands
+will refer to the url, but it will not be drawn in the document display.
+Button 3 pops up a command menu that contains
+
+- alt display
switches to (or from) the alternate display, which shows only
+the scrollable document display area. This might be useful when running mothra
+in a small window.
+ - snarf url
copies the selected url into the snarf buffer.
+ - paste
appends the snarf buffer to the command window.
+ - inline pix
turn off/on loading of inline images. Image maps cannot be disabled.
+ - fix cmap
reload the default plan 9 colormap
+ - save hit
appends the selected url to file:$home/lib/hit.html
+ - hit list
displays file:$home/lib/hit.html
+ - exit
+
+Commands
+The commands you can type are:
+
+- g [url]
get the page with the given url (default, the selection.)
+ - r [url]
refresh the display if the URL changes.
+Otherwise, you will probably see a cached version.
+ - s file
save the current page in the given file.
+ - w file
write a bitmap image of the document display area in the given file.
+ - q
exit.
+ - ?
get help.
+ - h
get help.
+
+
+
Configuration
+Mothra gets configuration information from the environment.
+
+- $url
The default url displayed when mothra starts.
+A url given on the command line overrides this.
+The default is /sys/lib/mothra/start.html
+ - $httpproxy
The network address of an http proxy server,
+in the format expected by dial(2). If $httpproxy is not set
+or is null, no proxy server is used.
+
+Command line
+If the mothra command has an argument, it is the name of a url to visit
+instead of the startup page. Giving mothra the -i flag disables loading
+of inline images. The inline pix menu item will reset this option.
+Files
+Mothra creates several files in $home/lib/mothra.
+
+- mothra.log
a list of all the url's visited
+ - mothra.err
a log of error messages, mostly uninteresting
+ - hit.html
the hit list used by the save hit
+and hit list commands. Since save hit only
+adds new urls to the end of this file, it is safe to edit it
+to add annotation or sort the saved urls.
+
+
+