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