1 /* GOPHER ACCESS HTGopher.c
5 ** 26 Sep 90 Adapted from other accesses (News, HTTP) TBL
6 ** 29 Nov 91 Downgraded to C, for portable implementation.
9 #define GOPHER_PORT 70 /* See protocol spec */
10 #define BIG 1024 /* Bug */
11 #define LINE_LENGTH 256 /* Bug */
13 /* Gopher entity types:
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 */
29 #include "HTUtils.h" /* Coding convention macros */
40 #include <appkit/defaults.h>
41 #define GOPHER_PROGRESS(foo)
43 #define GOPHER_PROGRESS(foo) printf("%s\n", (foo))
46 extern HTStyleSheet * styleSheet;
48 #define NEXT_CHAR HTGetChararcter()
52 /* Module-wide variables
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 */
59 PRIVATE HTStyle *addressStyle; /* For address etc */
60 PRIVATE HTStyle *heading1Style; /* For heading level 1 */
61 PRIVATE HTStyle *textStyle; /* Text style */
64 /* Matrix of allowed characters in filenames
65 ** -----------------------------------------
68 PRIVATE BOOL acceptable[256];
69 PRIVATE BOOL acceptable_inited = NO;
71 PRIVATE void init_acceptable NOARGS
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;
81 PRIVATE CONST char hex[17] = "0123456789abcdef";
83 /* Decdoe one hex character
86 PRIVATE char from_hex ARGS1(char, c)
88 return (c>='0')&&(c<='9') ? c-'0'
89 : (c>='A')&&(c<='F') ? c-'A'+10
90 : (c>='a')&&(c<='f') ? c-'a'+10
96 /* Get Styles from stylesheet
97 ** --------------------------
99 PRIVATE void get_styles NOARGS
101 if (!heading1Style) heading1Style = HTStyleNamed(styleSheet, "Heading1");
102 if (!addressStyle) addressStyle = HTStyleNamed(styleSheet, "Address");
103 if (!textStyle) textStyle = HTStyleNamed(styleSheet, "Example");
107 /* Paste in an Anchor
108 ** ------------------
110 ** The title of the destination is set, as there is no way
111 ** of knowing what the title is when we arrive.
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.
118 PRIVATE void write_anchor ARGS2(CONST char *,text, CONST char *,addr)
120 HTChildAnchor *anchor;
121 HTParentAnchor *dest;
123 HText_beginAnchor(HT,
124 anchor = HTAnchor_findChildAndLink(node_anchor, "", addr, 0));
125 dest = HTAnchor_parent(
126 HTAnchor_followMainLink((HTAnchor *)anchor));
128 if (!HTAnchor_title(dest)) HTAnchor_setTitle(dest, text);
130 HText_appendText(HT, text);
135 /* Parse a Gopher Menu document
136 ** ============================
140 PRIVATE void parse_menu ARGS2 (
142 HTParentAnchor *,anAnchor)
148 char *name, *selector; /* Gopher menu fields */
155 #define HEX_ESCAPE '%'
157 if (!HTAnchor_title(anAnchor))
158 HTAnchor_setTitle(anAnchor, arg);/* Tell user something's happening */
160 node_anchor = anAnchor;
161 HT = HText_new(anAnchor);
163 HText_beginAppend(HT);
164 HText_appendText(HT, "Select one of:\n");
166 while ((ch=NEXT_CHAR) != (char)EOF) {
169 *p = ch; /* Put character in line */
170 if (p< &line[BIG-1]) p++;
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);
179 /* Break on line with a dot by itself */
180 if ((gtype=='.') && ((*p=='\r') || (*p==0))) break;
184 selector = strchr(name, TAB);
186 *selector++ = 0; /* Terminate name */
187 host = strchr(selector, TAB);
189 *host++ = 0; /* Terminate selector */
190 port = strchr(host, TAB);
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 */
201 } /* gtype and name ok */
203 if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */
204 write_anchor(name, selector);
205 HText_appendParagraph(HT);
207 } else if (port) { /* Other types need port */
208 if (gtype == GOPHER_TELNET) {
209 if (*selector) sprintf(address, "telnet://%s@%s/",
211 else sprintf(address, "telnet://%s/", host);
213 } else { /* If parsed ok */
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;
221 *q++ = HEX_ESCAPE; /* Means hex coming */
222 *q++ = hex[(*p) >> 4];
223 *q++ = hex[(*p) & 15];
226 *q++ = 0; /* terminate address */
228 write_anchor(name, address);
229 HText_appendParagraph(HT);
230 } else { /* parse error */
231 if (TRACE) fprintf(stderr,
232 "HTGopher: Bad menu item.\n");
233 HText_appendText(HT, line);
234 HText_appendParagraph(HT);
237 p = line; /* Start again at beginning of line */
239 } /* if end of line */
241 } /* Loop over characters */
247 /* Display a Gopher Index document
248 ** -------------------------------
251 PRIVATE void display_index ARGS2 (
253 HTParentAnchor *,anAnchor)
255 node_anchor = anAnchor;
256 HT = HText_new(anAnchor);
257 HText_beginAppend(HT);
258 HText_setStyle(HT, heading1Style);
259 HText_appendText(HT, arg);
260 HText_setStyle(HT, textStyle);
261 HText_appendText(HT, "\nThis is a searchable index.\n");
263 if (!HTAnchor_title(anAnchor))
264 HTAnchor_setTitle(anAnchor, arg);/* Tell user something's happening */
271 /* Load by name HTLoadGopher
274 ** Bug: No decoding of strange data types as yet.
277 PUBLIC int HTLoadGopher ARGS3(
279 HTParentAnchor *,anAnchor,
282 char *command; /* The whole command */
283 int status; /* tcp return */
284 char gtype; /* Gopher Node type */
285 char * selector; /* Selector string */
287 struct sockaddr_in soc_address; /* Binary network address */
288 struct sockaddr_in* sin = &soc_address;
290 diagnostic = diag; /* set global flag */
292 if (!acceptable_inited) init_acceptable();
294 if (!arg) return -3; /* Bad if no name sepcified */
295 if (!*arg) return -2; /* Bad if name had zero length */
297 if (TRACE) printf("HTGopher: Looking for %s\n", arg);
303 sin->sin_family = AF_INET; /* Family, host order */
304 sin->sin_port = htons(GOPHER_PORT); /* Default: new port, */
306 if (TRACE) printf("HTTPAccess: Looking for %s\n", arg);
308 /* Get node name and optional port number:
311 char *p1 = HTParse(arg, "", PARSE_HOST);
312 HTParseInet(sin, p1);
316 /* Get entity type, and selector string.
319 char * p1 = HTParse(arg, "", PARSE_PATH|PARSE_PUNCTUATION);
320 gtype = '1'; /* Default = menu */
322 if ((*selector++=='/') && (*selector)) { /* Skip first slash */
323 gtype = *selector++; /* Pick up gtype */
325 if (gtype == GOPHER_INDEX) {
326 HTAnchor_setIndex(anAnchor); /* Search is allowed */
327 selector = strchr(selector, '?'); /* Look for search string */
328 if (!selector || !*selector) { /* No search required */
329 display_index(arg, anAnchor); /* Display "cover page" */
330 return 1; /* Local function only */
332 command = malloc(strlen(selector)+ 2 + 1);
333 if (command == NULL) outofmem(__FILE__, "HTLoadGopher");
334 strcpy(command, selector);
336 } else { /* Not index */
338 char * q = command = malloc(strlen(selector)+2+1);
339 if (command == NULL) outofmem(__FILE__, "HTLoadGopher");
340 while (*p) { /* Decode hex */
341 if (*p == HEX_ESCAPE) {
348 if (!c) break; /* Odd number of chars! */
349 *q++ = (b<<4) + from_hex(c);
351 *q++ = *p++; /* Record */
354 *q++ = 0; /* Terminate command */
359 strcat(command, "\r\n"); /* Include CR for telnet compat. */
362 /* Set up a socket to the server for the data:
364 s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
365 status = connect(s, (struct sockaddr*)&soc_address, sizeof(soc_address));
367 if (TRACE) printf("HTTPAccess: Unable to connect to remote host for `%s'.\n",
370 return HTInetStatus("connect");
373 HTInitInput(s); /* Set up input buffereing */
375 if (TRACE) printf("HTGopher: Connected, writing command `%s' to socket %d\n", command, s);
380 for(p = command; *p; p++) {
386 status = NETWRITE(s, command, (int)strlen(command));
389 if (TRACE) printf("HTGopher: Unable to send command.\n");
390 return HTInetStatus("send");
393 /* Now read the data from the socket:
395 if (diagnostic==2) gtype = GOPHER_TEXT; /* Read as plain text anyway */
400 HTParseFormat(WWW_HTML, anAnchor, s);
405 parse_menu(arg, anAnchor);
409 default: /* @@ parse as plain text */
410 HTParseFormat(WWW_PLAINTEXT, anAnchor, s);
412 } /* switch(gtype) */