From f305f78412f3796b4849beef34b043370f36f4ec Mon Sep 17 00:00:00 2001 From: Kelvin Sherlock Date: Thu, 8 Mar 2012 21:15:46 -0500 Subject: [PATCH] initial version --- connect.c | 171 ++++++++++++++++++ gopher.c | 206 ++++++++++++++++++++++ url.c | 519 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ url.h | 63 +++++++ 4 files changed, 959 insertions(+) create mode 100644 connect.c create mode 100644 gopher.c create mode 100644 url.c create mode 100644 url.h diff --git a/connect.c b/connect.c new file mode 100644 index 0000000..dbb0520 --- /dev/null +++ b/connect.c @@ -0,0 +1,171 @@ +#include "Connect.h" + +//static char pstring[256]; + + +static Word LoginAndOpen(ConnectBuffer *buffer) +{ + Word ipid; + Word terr; + + + ipid = TCPIPLogin( + buffer->memID, + buffer->dnr.DNRIPaddress, + buffer->port, + 0x0000, 0x0040); + + if (_toolErr) + { + buffer->state = ConnectStateError; + return -1; + } + + terr = TCPIPOpenTCP(ipid); + if (_toolErr || terr) + { + TCPIPLogout(ipid); + buffer->state = ConnectStateError; + buffer->terr = terr; + buffer->ipid = 0; + return -1; + } + + buffer->ipid = ipid; + buffer->state = ConnectStateConnecting; + + return 0; +} + + +Word ConnectionPoll(ConnectBuffer *buffer) +{ + Word state; + if (!buffer) return -1; + state = buffer->state; + + if (state == 0) return -1; + if (state == ConnectStateConnected) return 1; + if (state == ConnectStateDisconnected) return 1; + if (state == ConnectStateError) return -1; + + TCPIPPoll(); + + if (state == ConnectStateDNR) + { + if (buffer->dnr.DNRstatus == DNR_OK) + { + return LoginAndOpen(buffer); + } + else if (buffer->dnr.DNRstatus != DNR_Pending) + { + buffer->state = ConnectStateError; + return -1; + } + } + + if (state == ConnectStateConnecting || state == ConnectStateDisconnecting) + { + Word terr; + static srBuff sr; + + terr = TCPIPStatusTCP(buffer->ipid, &sr); + + if (state == ConnectStateDisconnecting) + { + // these are not errors. + if (terr == tcperrConClosing || terr == tcperrClosing) + terr = tcperrOK; + } + + if (terr || _toolErr) + { + //CloseAndLogout(buffer); + + TCPIPCloseTCP(buffer->ipid); + TCPIPLogout(buffer->ipid); + buffer->ipid = 0; + buffer->state = ConnectStateError; + buffer->terr = terr; + return -1; + } + + if (sr.srState == TCPSESTABLISHED) // && state == ConnectStateConnecting) + { + buffer->state = ConnectStateConnected; + return 1; + } + + if (sr.srState == TCPSCLOSED || sr.srState == TCPSTIMEWAIT) + { + TCPIPLogout(buffer->ipid); + buffer->ipid = 0; + buffer->state = ConnectStateDisconnected; + return 1; + } + } + + return 0; +} + +Word ConnectionOpen(ConnectBuffer *buffer, const char *host, Word port) +{ + buffer->state = 0; + buffer->ipid = 0; + buffer->terr = 0; + buffer->port = port; + + // 1. check if we need to do DNR. + if (TCPIPValidateIPString(host)) + { + cvtRec cvt; + TCPIPConvertIPToHex(&cvt, host); + buffer->dnr.DNRIPaddress = cvt.cvtIPAddress; + buffer->dnr.DNRstatus = DNR_OK; // fake it. + + return LoginAndOpen(buffer); + } + // do dnr. + TCPIPDNRNameToIP(host, &buffer->dnr); + if (_toolErr) + { + buffer->state = ConnectStateError; + return -1; + } + buffer->state = ConnectStateDNR; + return 0; +} + +void ConnectionInit(ConnectBuffer *buffer, Word memID) +{ + buffer->memID = memID; + buffer->ipid = 0; + buffer->terr = 0; + buffer->state = 0; + buffer->port = 0; + buffer->dnr.DNRstatus = 0; + buffer->dnr.DNRIPaddress = 0; +} + +Word ConnectionClose(ConnectBuffer *buffer) +{ + Word state = buffer->state; + + // todo -- how do you close if not yet connected? + if (state == ConnectStateConnected) + { + buffer->state = ConnectStateDisconnecting; + buffer->terr = TCPIPCloseTCP(buffer->ipid); + return 0; + } + + if (state == ConnectStateDNR) + { + TCPIPCancelDNR(&buffer->dnr); + buffer->state = 0; + return 1; + } + + return -1; +} + diff --git a/gopher.c b/gopher.c new file mode 100644 index 0000000..820fce6 --- /dev/null +++ b/gopher.c @@ -0,0 +1,206 @@ +#include +#include + +#include + + +//#include "url.h" +#include "Connect.h" + +#include + +/* + * connect gopher.floodgap.com:70 + * send path + * read output. + * text -- ends with . + * bin -- ends when connection is closed. + */ + +// startup/shutdown flags. +enum { + kLoaded = 1, + kStarted = 2, + kConnected = 4 +}; + +Word StartUp(void) +{ + word status; + word flags = 0; + + status = TCPIPStatus(); + if (_toolErr) + { + LoadOneTool(54,0x0200); + if (_toolErr) return -1; + + status = 0; + flags |= kLoaded; + } + + if (!status) + { + TCPIPStartUp(); + if (_toolErr) return -1; + flags |= kStarted; + } + + status = TCPIPGetConnectStatus(); + if (!status) + { + TCPIPConnect(NULL); + flags |= kConnected; + } + return flags; +} + +void ShutDown(word flags) +{ + if (flags & kConnected) + { + TCPIPDisconnect(false, NULL); + if (_toolErr) return; + } + if (flags & kStarted) + { + TCPIPShutDown(); + if (_toolErr) return; + } + if (flags & kLoaded) + { + UnloadOneTool(54); + } +} + +#if 0 +void do_url(const char *url) +{ + URLComponents components; + ConnectBuffer buffer; + char *host; + + if (ParseURL(url, strlen(url), &components)) + { + fprintf(stderr, "Invalid URL: %s\n", url); + return; + } + if (!url.host.length) + { + fprintf(stderr, "No host\n"); + return; + } + if (!components.portNumber) components.portNumber = 70; + host = malloc(url.host.length + 1); + URLComponentGetCString(url, &components, host, URLComponentHost); + + ConnectInit(&buffer, myID); + + + ConnectCString(&buffer, host, components.portNumber); + + if (components.path.length) + { + rv = TCPIPWriteTCP(buffer.ipid, + url + components.path.location, + components.path.length, + 0, 0); + } + rv = TCPIPWriteTCP(buffer.ipid, "\r\n", 2, 1, 0); + + + while (!ConnectionPoll(&buffer)) ; + if (buffer.state == ConnectionStateError) + { + fprintf(stderr, "Unable to open host: %s:%u\n", host, components.portNumber); + free(host); + return; + } + + + ConnectionClose(&buffer); + + while (!ConnectionPoll(&buffer)) ; // wait for it to close. + + free (host); +} + +#endif +int main(int argc, char **argv) +{ + Handle h; + ConnectBuffer buffer; + + int i; + Word rv; + Word flags; + + // getopt for -b binary + +#if 0 + for (i = 1; i < argc; ++i) + { + do_url(argv[i]); + } +#endif + + + flags = StartUp(); + fprintf(stdout, "flags: %u\n", flags); + + ConnectionInit(&buffer, MMStartUp()); + + rv = ConnectionOpen(&buffer, "\pgopher.floodgap.com", 70); + while (!rv) + { + rv = ConnectionPoll(&buffer); + } + if (buffer.state == ConnectStateConnected) + { + fprintf(stdout, "Connected!\n"); + } + + fprintf(stdout, "%x %x %x\n", rv, buffer.state, buffer.terr); + + rv = TCPIPWriteTCP(buffer.ipid, "\r\n", 2, 1, 0); + if (rv) + { + fprintf(stdout, "TCPIPWriteTCP: %x\n", rv); + } + + for(;;) + { + rrBuff rb; + //rlrBuff rb; + Handle h; + Word count; + + TCPIPPoll(); + rv = TCPIPReadTCP(buffer.ipid, 2, NULL, 4096, &rb); + + //rv = TCPIPReadLineTCP(buffer.ipid, "\p\r\n", 2, NULL, 4096, &rb); + + if (rv) break; + h = rb.rrBuffHandle; + count = rb.rrBuffCount; + + if (!h) continue; + + HLock(h); + fwrite(*h, count, 1, stdout); + fputs("", stdout); + DisposeHandle(h); + } + + + rv = ConnectionClose(&buffer); + while (!rv) + { + rv = ConnectionPoll(&buffer); + } + fprintf(stdout, "%x %x %x\n", rv, buffer.state, buffer.terr); + + ShutDown(flags); + +} + diff --git a/url.c b/url.c new file mode 100644 index 0000000..5ce9eca --- /dev/null +++ b/url.c @@ -0,0 +1,519 @@ +#include +#include +#include +#include + + + +#include "url.h" + +enum { + kScheme, + kUser, + kPassword, + kHost, + kPort, + kPath, + kParams, + kQuery, + kFragment +}; + + +/* + * scheme://username:password@domain:port/path;parms?query_string#fragment_id + * + * + */ + +int URLGetComponentCString(const char *url, struct URLComponents *components, int type, char *dest) +{ + URLRange *rangePtr; + URLRange r; + + if (!url || !components) return -1; + + + if (type < URLComponentScheme || type > URLComponentPathAndQuery) return -1; + + rangePtr = &components->scheme; + + r = rangePtr[type]; + + if (!dest) return r.length; + else + { + if (r.length) + { + memcpy(dest, url + r.location, r.length); + } + dest[r.length] = 0; + + return r.length; + } +} + + +#if 0 +int schemeType(const char *string) +{ + if (!string || !*string) return SCHEME_NONE; + + static struct { + int length; + const char *data + } table[] = { + { 4, "file:" }, + { 3, "ftp:" }, + { 6, "gopher:" }, + { 4, "http:" }, + { 5, "https:" }, + { 6, "mailto:" }, + { 4, "news:" }, + { 4, "nntp:" }, + { 6, "telnet:" } + }; + + switch(*string) + { + case 'f': + // ftp, file + if (!strcmp(string, "file")) return SCHEME_FILE; + if (!strcmp(string, "ftp")) return SCHEME_FTP; + break; + + case 'g': + // gopher + if (!strcmp(string, "gopher")) return SCHEME_GOPHER; + break; + + case 'h': + // http, https + if (!strcmp(string, "https")) return SCHEME_HTTPS; + if (!strcmp(string, "http")) return SCHEME_HTTP; + break; + + case 'm': + // mailto + if (!strcmp(string, "mailto")) return SCHEME_MAILTO; + break; + + case 'n': + // news, nntp + if (!strcmp(string, "news")) return SCHEME_NEWS; + if (!strcmp(string, "nntp")) return SCHEME_NNTP; + break; + + case 't': + // telnet + if (!strcmp(string, "telnet")) return SCHEME_TELNET; + break; + + } + + return SCHEME_UNKNOWN; + +} + +#endif + +int ParseURL(const char *url, int length, struct URLComponents *components) +{ + char c; + int state; + int hasScheme; + int hasAt; + + int i, l; + + URLRange range = { 0, 0 }; + URLRange *rangePtr; + + + if (!components || !url) return 0; + + rangePtr = &components->scheme; + memset(components, 0, sizeof(URLComponents)); + + state = kScheme; + + // 1 check for a scheme. + // [A-Za-z0-9.+-]+ ':' + + + hasScheme = 0; + hasAt = 0; + range.location = 0; + + for(i = 0; i < length; ++i) + { + c = url[i]; + + if (c == ':') + { + hasScheme = 1; + break; + } + + if (c >= 'a' && c <= 'z') continue; + if (c >= 'A' && c <= 'Z') continue; + if (c >= '0' && c <= '9') continue; + + //if (isalnum(c)) continue; + if (c == '.') continue; + if (c == '+') continue; + if (c == '-') continue; + + break; + } + + if (hasScheme) + { + if (i == 0) return 0; + range.length = i; + + components->scheme = range; + ++i; // skip the ':' + } + else + { + state = kPath; + i = 0; + } + + // 2. check for // + // //user:password@domain:port/ + // otherwise, it's a path + + if (strncmp(url + i, "//", 2) == 0) + { + state = kUser; + i += 2; + } + else + { + state = kPath; + } + + //range = (URLRange){ i, 0 }; + range.location = i; range.length = 0; + + for( ; i < length; ++i) + { + c = url[i]; + + if ((c < 32) || (c & 0x80)) + { + // goto invalid_char; + return 0; + } + + switch(c) + { + // illegal chars. + // % # not allowed but allowed. + + case ' ': + case '"': + case '<': + case '>': + case '[': + case '\\': + case ']': + case '^': + case '`': + case '{': + case '|': + case '}': + case 0x7f: + // goto invalid_char; + return 0; + break; + + + case ':': + // username -> password + // domain -> port + // password -> error + // port -> error + // all others, no transition. + switch(state) + { + case kUser: + case kHost: + l = i - range.location; + if (l > 0) + { + range.length = l; + rangePtr[state] = range; + } + //range = (URLRange){ i + 1 , 0 }; + range.location = i + 1; range.length = 0; + + state++; + break; + + + case kPassword: + case kPort: + return 0; + + } + break; + + + case '@': + // username -> domain + // password -> domain + // all others no transition. + // path -> error? + + switch(state) + { + case kUser: + case kPassword: + hasAt = 1; + l = i - range.location; + if (l > 0) + { + range.length = l; + rangePtr[state] = range; + } + //range = (URLRange){ i + 1 , 0 }; + range.location = i + 1; range.length = 0; + + state = kHost; + break; + } + break; + + case '/': + // domain -> path + // port -> path + + // (must be switched to domain:port) + // username -> path + // password -> path + // all others, no transition. + + // '/' is part of the filename. + switch (state) + { + case kUser: + hasAt = 1; // close enough + state = kHost; // username was actually the domain. + // pass through. + case kPassword: // need to switch to domain:port later. + case kHost: + case kPort: + l = i - range.location; + if (l > 0) + { + range.length = l; + rangePtr[state] = range; + } + + //range = (URLRange){ i, 0 }; // '/' is part of path. + range.location = i; range.length = 0; + + state = kPath; + break; + } + break; + + case ';': + // path -> param + // all others, no transition. + + if (state == kPath) + { + l = i - range.location; + if (l > 0) + { + range.length = l; + rangePtr[state] = range; + } + //range = (URLRange){ i + 1 , 0 }; + range.location = i + 1; range.length = 0; + state = kParams; + } + break; + + case '?': + // path -> query + // param -> query + // all others, no transition. + + if (state == kPath || state == kParams) + { + l = i - range.location; + if (l > 0) + { + range.length = l; + rangePtr[state] = range; + } + //range = (URLRange){ i + 1 , 0 }; + range.location = i + 1; range.length = 0; + + state = kQuery; + } + break; + + + case '#': + // fragment -> no transition + // everything else transitions to a fragment. + // can immediately end parsing since we know the length... + + l = i - range.location; + if (l > 0) + { + range.length = l; + rangePtr[state] = range; + } + + state = kFragment; + range.location = i + 1; range.length = 0; + + i = length; // break from loop. + break; + } + + } + // finish up the last portion. + if (state) + { + l = i - range.location; + if (l > 0) + { + range.length = l; + rangePtr[state] = range; + } + } + + + if (!hasAt) + { + // user:name was actually domain:port + components->host = components->user; + components->port = components->password; + + components->user.location = 0; + components->user.length = 0; + components->password.location = 0; + components->password.length = 0; + } + + // port number conversion. + if (components->port.location) + { + const char *tmp = url + components->port.location; + int p = 0; + + l = components->port.length; + + + for (i = 0; i < l; ++i) + { + c = tmp[i]; + + if ((c < '0') || (c > '9')) + { + p = 0; + break; + } + // convert to number. + c &= 0x0f; + + // p *= 10; + // 10x = 2x + 8x = (x << 1) + (x << 3) + if (p) + { + int p2; + + p2 = p << 1; + p = p2 << 2; + p = p + p2; + } + p += c; + } + + components->portNumber = p; + } + + #if 0 + // path and query. + // path;params?query + range = components->path; + if (range.length) + { + if (components->params.length) + range.length += components->params.length + 1; + if (components->query.length) + range.length += components->query.length + 1; + + components->pathAndQuery = range; + } + #endif + + return 1; + +} + +void test(const char *url) +{ + URLComponents data; + int ok; + + char *buffer; + buffer = strdup(url); // enough space. + + if (!url || !*url) return; + + ok = ParseURL(url, strlen(url), &data); + + printf("%s (%s)\n", url, ok ? "ok" : "error"); + + + URLGetComponentCString(url, &data, URLComponentScheme, buffer); + printf(" scheme: %s\n", buffer); + + URLGetComponentCString(url, &data, URLComponentUser, buffer); + printf(" username: %s\n", buffer); + + URLGetComponentCString(url, &data, URLComponentPassword, buffer); + printf(" password: %s\n", buffer); + + URLGetComponentCString(url, &data, URLComponentHost, buffer); + printf(" host: %s\n", buffer); + + URLGetComponentCString(url, &data, URLComponentPort, buffer); + printf(" port: %s [%d]\n", buffer, data.portNumber); + + URLGetComponentCString(url, &data, URLComponentPath, buffer); + printf(" path: %s\n", buffer); + + URLGetComponentCString(url, &data, URLComponentParams, buffer); + printf(" params: %s\n", buffer); + + URLGetComponentCString(url, &data, URLComponentQuery, buffer); + printf(" query: %s\n", buffer); + + URLGetComponentCString(url, &data, URLComponentFragment, buffer); + printf(" fragment: %s\n", buffer); + + free(buffer); + +} + +int main(int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) + { + test(argv[i]); + } + + return 0; +} diff --git a/url.h b/url.h new file mode 100644 index 0000000..81e2548 --- /dev/null +++ b/url.h @@ -0,0 +1,63 @@ +#ifndef __url_h__ +#define __url_h__ + +enum { + SCHEME_UNKNOWN = -1, + SCHEME_NONE = 0, + SCHEME_FILE, + SCHEME_FTP, + SCHEME_GOPHER, + SCHEME_HTTP, + SCHEME_HTTPS, + SCHEME_MAILTO, + SCHEME_NEWS, + SCHEME_NNTP, + SCHEME_TELNET +}; + + +typedef struct URLRange { + int location; + int length; +} URLRange; + +enum { + URLComponentScheme, + URLComponentUser, + URLComponentPassword, + URLComponentHost, + URLComponentPort, + URLComponentPath, + URLComponentParams, + URLComponentQuery, + URLComponentFragment, + URLComponentPathAndQuery +}; + +typedef struct URLComponents { + + int schemeType; + int portNumber; + + URLRange scheme; + URLRange user; + URLRange password; + URLRange host; + URLRange port; + URLRange path; + URLRange params; + URLRange query; + URLRange fragment; + + URLRange pathAndQuery; + +} URLComponents; + +int ParseURL(const char *url, int length, struct URLComponents *components); + +int URLGetComponentCString(const char *url, struct URLComponents *, int, char *); +int URLGetComponentPString(const char *url, struct URLComponents *, int, char *); +//int URLGetComponentGSString(const char *url, struct URLComponents *, int, GSString255Ptr); + + +#endif