Annotation of libwww/Library/src/HTGopher.c, revision 1.2
1.1 timbl 1: /* GOPHER ACCESS HTGopher.c
2: ** =============
3: **
4: ** History:
5: ** 26 Sep 90 Adapted from other accesses (News, HTTP) TBL
6: ** 29 Nov 91 Downgraded to C, for portable implementation.
7: */
8:
1.2 ! timbl 9: /* Implements:
! 10: */
! 11: #include "HTGopher.h"
! 12:
1.1 timbl 13: #define GOPHER_PORT 70 /* See protocol spec */
14: #define BIG 1024 /* Bug */
15: #define LINE_LENGTH 256 /* Bug */
16:
17: /* Gopher entity types:
18: */
19: #define GOPHER_TEXT '0'
20: #define GOPHER_MENU '1'
21: #define GOPHER_CSO '2'
22: #define GOPHER_ERROR '3'
23: #define GOPHER_MACBINHEX '4'
24: #define GOPHER_PCBINHEX '5'
25: #define GOPHER_UUENCODED '6'
26: #define GOPHER_INDEX '7'
27: #define GOPHER_TELNET '8'
28: #define GOPHER_HTML 'h' /* HTML */
29: #define GOPHER_DUPLICATE '+'
30: #define GOPHER_WWW 'w' /* W3 address */
31:
32: #include <ctype.h>
33: #include "HTUtils.h" /* Coding convention macros */
34: #include "tcp.h"
35:
36:
37: #include "HTParse.h"
38: #include "HTFormat.h"
39: #include "HTTCP.h"
40:
1.2 ! timbl 41: /* Hypertext object building machinery
! 42: */
! 43: #include "HTML.h"
! 44:
! 45: #define PUTC(c) (*targetClass.put_character)(target, c)
! 46: #define PUTS(s) (*targetClass.put_string)(target, s)
! 47: #define START(e) (*targetClass.start_element)(target, e, 0, 0)
! 48: #define END(e) (*targetClass.end_element)(target, e)
! 49: #define END_TARGET (*targetClass.end_document)(target)
! 50: #define FREE_TARGET (*targetClass.free)(target)
! 51: struct _HTStructured {
! 52: CONST HTStructuredClass * isa;
! 53: /* ... */
! 54: };
! 55:
! 56: PRIVATE HTStructured *target; /* the new hypertext */
! 57: PRIVATE HTStructuredClass targetClass; /* Its action routines */
! 58:
! 59:
1.1 timbl 60: #ifdef NeXTStep
61: #include <appkit/defaults.h>
62: #define GOPHER_PROGRESS(foo)
63: #else
64: #define GOPHER_PROGRESS(foo) fprintf(stderr, "%s\n", (foo))
65: #endif
66:
67: #define NEXT_CHAR HTGetChararcter()
68:
69:
70:
71: /* Module-wide variables
72: */
73: PRIVATE int s; /* Socket for GopherHost */
74:
75:
1.2 ! timbl 76:
1.1 timbl 77: /* Matrix of allowed characters in filenames
78: ** -----------------------------------------
79: */
80:
81: PRIVATE BOOL acceptable[256];
82: PRIVATE BOOL acceptable_inited = NO;
83:
84: PRIVATE void init_acceptable NOARGS
85: {
86: unsigned int i;
87: char * good =
88: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$";
89: for(i=0; i<256; i++) acceptable[i] = NO;
90: for(;*good; good++) acceptable[(unsigned int)*good] = YES;
91: acceptable_inited = YES;
92: }
93:
94: PRIVATE CONST char hex[17] = "0123456789abcdef";
95:
96: /* Decdoe one hex character
97: */
98:
99: PRIVATE char from_hex ARGS1(char, c)
100: {
101: return (c>='0')&&(c<='9') ? c-'0'
102: : (c>='A')&&(c<='F') ? c-'A'+10
103: : (c>='a')&&(c<='f') ? c-'a'+10
104: : 0;
105: }
106:
107:
108:
109: /* Paste in an Anchor
110: ** ------------------
111: **
112: ** The title of the destination is set, as there is no way
113: ** of knowing what the title is when we arrive.
114: **
115: ** On entry,
116: ** HT is in append mode.
117: ** text points to the text to be put into the file, 0 terminated.
118: ** addr points to the hypertext refernce address 0 terminated.
119: */
120: PRIVATE void write_anchor ARGS2(CONST char *,text, CONST char *,addr)
121: {
1.2 ! timbl 122:
! 123:
! 124:
! 125: BOOL present[HTML_A_ATTRIBUTES];
! 126: CONST char * value[HTML_A_ATTRIBUTES];
1.1 timbl 127:
1.2 ! timbl 128: int i;
! 129:
! 130: for (i=0; i<HTML_A_ATTRIBUTES; i++) present[i]=0;
! 131: present[HTML_A_HREF] = YES;
! 132: value[HTML_A_HREF] = addr;
! 133: present[HTML_A_TITLE] = YES;
! 134: value[HTML_A_TITLE] = text;
! 135:
! 136: (*targetClass.start_element)(target, HTML_A, present, value);
1.1 timbl 137:
1.2 ! timbl 138: PUTS(text);
! 139: END(HTML_A);
1.1 timbl 140: }
141:
142:
143: /* Parse a Gopher Menu document
144: ** ============================
145: **
146: */
147:
148: PRIVATE void parse_menu ARGS2 (
1.2 ! timbl 149: CONST char *, arg,
! 150: HTParentAnchor *, anAnchor)
1.1 timbl 151: {
152: char gtype;
153: char ch;
154: char line[BIG];
155: char address[BIG];
156: char *name, *selector; /* Gopher menu fields */
157: char *host;
158: char *port;
159: char *p = line;
1.2 ! timbl 160: CONST char *title;
1.1 timbl 161:
162: #define TAB '\t'
163: #define HEX_ESCAPE '%'
164:
165:
1.2 ! timbl 166: title = HTAnchor_title(anAnchor);
! 167: if (title) {
! 168: START(HTML_H1);
! 169: PUTS(title);
! 170: END(HTML_H1);
! 171: } else
! 172: PUTS("Select one of:\n\n");
1.1 timbl 173:
1.2 ! timbl 174: START(HTML_MENU);
1.1 timbl 175: while ((ch=NEXT_CHAR) != (char)EOF) {
1.2 ! timbl 176: START(HTML_LI);
1.1 timbl 177: if (ch != '\n') {
178: *p = ch; /* Put character in line */
179: if (p< &line[BIG-1]) p++;
180:
181: } else {
182: *p++ = 0; /* Terminate line */
183: p = line; /* Scan it to parse it */
184: port = 0; /* Flag "not parsed" */
185: if (TRACE) fprintf(stderr, "HTGopher: Menu item: %s\n", line);
186: gtype = *p++;
187:
188: /* Break on line with a dot by itself */
189: if ((gtype=='.') && ((*p=='\r') || (*p==0))) break;
190:
191: if (gtype && *p) {
192: name = p;
193: selector = strchr(name, TAB);
194: if (selector) {
195: *selector++ = 0; /* Terminate name */
196: host = strchr(selector, TAB);
197: if (host) {
198: *host++ = 0; /* Terminate selector */
199: port = strchr(host, TAB);
200: if (port) {
201: char *junk;
202: port[0] = ':'; /* delimit host a la W3 */
203: junk = strchr(port, TAB);
204: if (junk) *junk++ = 0; /* Chop port */
205: if ((port[1]=='0') && (!port[2]))
206: port[0] = 0; /* 0 means none */
207: } /* no port */
208: } /* host ok */
209: } /* selector ok */
210: } /* gtype and name ok */
211:
212: if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */
213: write_anchor(name, selector);
214:
215: } else if (port) { /* Other types need port */
216: if (gtype == GOPHER_TELNET) {
217: if (*selector) sprintf(address, "telnet://%s@%s/",
218: selector, host);
219: else sprintf(address, "telnet://%s/", host);
220:
221: } else { /* If parsed ok */
222: char *q;
223: char *p;
224: sprintf(address, "//%s/%c", host, gtype);
225: q = address+ strlen(address);
226: for(p=selector; *p; p++) { /* Encode selector string */
227: if (acceptable[*p]) *q++ = *p;
228: else {
229: *q++ = HEX_ESCAPE; /* Means hex coming */
230: *q++ = hex[(TOASCII(*p)) >> 4];
231: *q++ = hex[(TOASCII(*p)) & 15];
232: }
233: }
234: *q++ = 0; /* terminate address */
235: }
1.2 ! timbl 236: PUTS(" "); /* Prettier JW/TBL */
1.1 timbl 237: write_anchor(name, address);
1.2 ! timbl 238:
1.1 timbl 239: } else { /* parse error */
240: if (TRACE) fprintf(stderr,
241: "HTGopher: Bad menu item.\n");
1.2 ! timbl 242: PUTS(line);
! 243:
1.1 timbl 244: } /* parse error */
245:
246: p = line; /* Start again at beginning of line */
247:
248: } /* if end of line */
249:
250: } /* Loop over characters */
251:
1.2 ! timbl 252: END(HTML_MENU);
! 253: END_TARGET;
! 254: FREE_TARGET;
! 255:
1.1 timbl 256: return;
257: }
258:
259: /* Display a Gopher Index document
260: ** -------------------------------
261: */
262:
263: PRIVATE void display_index ARGS2 (
264: CONST char *, arg,
265: HTParentAnchor *,anAnchor)
266: {
1.2 ! timbl 267:
! 268: START(HTML_H1);
! 269: PUTS(arg);
! 270: END(HTML_H1);
! 271:
! 272: PUTS("\nPlease enter words to search for.\n");
1.1 timbl 273:
274: if (!HTAnchor_title(anAnchor))
1.2 ! timbl 275: HTAnchor_setTitle(anAnchor, arg);
1.1 timbl 276:
1.2 ! timbl 277: END_TARGET;
! 278: FREE_TARGET;
1.1 timbl 279: return;
280: }
281:
282:
283: /* De-escape a selector into a command
284: ** -----------------------------------
285: **
286: ** The % hex escapes are converted. Otheriwse, the string is copied.
287: */
288: PRIVATE void de_escape ARGS2(char *, command, CONST char *, selector)
289: {
290: CONST char * p = selector;
291: char * q = command;
292: if (command == NULL) outofmem(__FILE__, "HTLoadGopher");
293: while (*p) { /* Decode hex */
294: if (*p == HEX_ESCAPE) {
295: char c;
296: unsigned int b;
297: p++;
298: c = *p++;
299: b = from_hex(c);
300: c = *p++;
301: if (!c) break; /* Odd number of chars! */
302: *q++ = FROMASCII((b<<4) + from_hex(c));
303: } else {
304: *q++ = *p++; /* Record */
305: }
306: }
307: *q++ = 0; /* Terminate command */
308:
309: }
310:
311:
312: /* Load by name HTLoadGopher
313: ** ============
314: **
315: ** Bug: No decoding of strange data types as yet.
316: **
317: */
1.2 ! timbl 318: PUBLIC int HTLoadGopher ARGS4(
! 319: CONST char *, arg,
! 320: HTParentAnchor *, anAnchor,
! 321: HTFormat, format_out,
! 322: HTStream*, sink)
1.1 timbl 323: {
324: char *command; /* The whole command */
325: int status; /* tcp return */
326: char gtype; /* Gopher Node type */
327: char * selector; /* Selector string */
328:
329: struct sockaddr_in soc_address; /* Binary network address */
330: struct sockaddr_in* sin = &soc_address;
331:
332: if (!acceptable_inited) init_acceptable();
333:
334: if (!arg) return -3; /* Bad if no name sepcified */
335: if (!*arg) return -2; /* Bad if name had zero length */
336:
337: if (TRACE) fprintf(stderr, "HTGopher: Looking for %s\n", arg);
338:
339:
340: /* Set up defaults:
341: */
342: sin->sin_family = AF_INET; /* Family, host order */
343: sin->sin_port = htons(GOPHER_PORT); /* Default: new port, */
344:
345: if (TRACE) fprintf(stderr, "HTTPAccess: Looking for %s\n", arg);
346:
347: /* Get node name and optional port number:
348: */
349: {
350: char *p1 = HTParse(arg, "", PARSE_HOST);
351: int status = HTParseInet(sin, p1);
352: free(p1);
353: if (status) return status; /* Bad */
354: }
355:
356: /* Get entity type, and selector string.
357: */
358: {
359: char * p1 = HTParse(arg, "", PARSE_PATH|PARSE_PUNCTUATION);
360: gtype = '1'; /* Default = menu */
361: selector = p1;
362: if ((*selector++=='/') && (*selector)) { /* Skip first slash */
363: gtype = *selector++; /* Pick up gtype */
364: }
365: if (gtype == GOPHER_INDEX) {
366: char * query;
367: HTAnchor_setIndex(anAnchor); /* Search is allowed */
368: query = strchr(selector, '?'); /* Look for search string */
369: if (!query || !query[1]) { /* No search required */
1.2 ! timbl 370: target = HTML_new(anAnchor, sink);
! 371: targetClass = *target->isa;
1.1 timbl 372: display_index(arg, anAnchor); /* Display "cover page" */
373: return 1; /* Local function only */
374: }
375: *query++ = 0; /* Skip '?' */
376: command = malloc(strlen(selector)+ 1 + strlen(query)+ 2 + 1);
377: if (command == NULL) outofmem(__FILE__, "HTLoadGopher");
378:
379: de_escape(command, selector); /* Bug fix TBL 921208 */
380:
381: strcat(command, "\t");
382:
383: { /* Remove plus signs 921006 */
384: char *p;
385: for (p=query; *p; p++) {
386: if (*p == '+') *p = ' ';
387: }
388: }
389: strcat(command, query);
390:
391: } else { /* Not index */
392: command = command = malloc(strlen(selector)+2+1);
393: de_escape(command, selector);
394: }
395: free(p1);
396: }
397:
398: strcat(command, "\r\n"); /* Include CR for telnet compat. */
399:
400:
401: /* Set up a socket to the server for the data:
402: */
403: s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
404: status = connect(s, (struct sockaddr*)&soc_address, sizeof(soc_address));
405: if (status<0){
406: if (TRACE) fprintf(stderr, "HTTPAccess: Unable to connect to remote host for `%s'.\n",
407: arg);
408: free(command);
409: return HTInetStatus("connect");
410: }
411:
412: HTInitInput(s); /* Set up input buffering */
413:
414: if (TRACE) fprintf(stderr, "HTGopher: Connected, writing command `%s' to socket %d\n", command, s);
415:
416: #ifdef NOT_ASCII
417: {
418: char * p;
419: for(p = command; *p; p++) {
420: *p = TOASCII(*p);
421: }
422: }
423: #endif
424:
425: status = NETWRITE(s, command, (int)strlen(command));
426: free(command);
427: if (status<0){
428: if (TRACE) fprintf(stderr, "HTGopher: Unable to send command.\n");
429: return HTInetStatus("send");
430: }
431:
432: /* Now read the data from the socket:
433: */
434: switch (gtype) {
435:
436: case GOPHER_HTML :
1.2 ! timbl 437: HTParseSocket(WWW_HTML, format_out, anAnchor, s, sink);
! 438: break;
1.1 timbl 439:
440: case GOPHER_MENU :
441: case GOPHER_INDEX :
1.2 ! timbl 442: target = HTML_new(anAnchor, sink);
! 443: targetClass = *target->isa;
1.1 timbl 444: parse_menu(arg, anAnchor);
1.2 ! timbl 445: break;
! 446:
1.1 timbl 447: case GOPHER_TEXT :
448: default: /* @@ parse as plain text */
1.2 ! timbl 449: HTParseSocket(WWW_PLAINTEXT, format_out, anAnchor, s, sink);
! 450: break;
! 451:
1.1 timbl 452: } /* switch(gtype) */
1.2 ! timbl 453:
! 454: NETCLOSE(s);
! 455: return HT_LOADED;
1.1 timbl 456: }
1.2 ! timbl 457:
! 458: PUBLIC HTProtocol HTGopher = { "gopher", HTLoadGopher, NULL };
1.1 timbl 459:
Webmaster