Merge branch 'master' of https://git.maemo.org/projects/erwise
[erwise] / Cl / WWWLibrary / HTGopher.c
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) printf("%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");
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[(*p) >> 4];
223                             *q++ = hex[(*p) & 15];
224                         }
225                     }
226                     *q++ = 0;                   /* terminate address */
227                 }
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);
235             } /* parse error */
236             
237             p = line;   /* Start again at beginning of line */
238             
239         } /* if end of line */
240         
241     } /* Loop over characters */
242         
243     HText_endAppend(HT);
244     return;
245 }
246
247 /*      Display a Gopher Index document
248 **      -------------------------------
249 */
250
251 PRIVATE void display_index ARGS2 (
252         CONST char *,   arg,
253         HTParentAnchor *,anAnchor)
254 {
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");
262         
263     if (!HTAnchor_title(anAnchor))
264         HTAnchor_setTitle(anAnchor, arg);/* Tell user something's happening */
265     
266     HText_endAppend(HT);
267     return;
268 }
269
270
271 /*              Load by name                                    HTLoadGopher
272 **              ============
273 **
274 **       Bug:   No decoding of strange data types as yet.
275 **
276 */
277 PUBLIC int HTLoadGopher ARGS3(
278         CONST char *,arg,
279         HTParentAnchor *,anAnchor,
280         int,diag)
281 {
282     char *command;                      /* The whole command */
283     int status;                         /* tcp return */
284     char gtype;                         /* Gopher Node type */
285     char * selector;                    /* Selector string */
286  
287     struct sockaddr_in soc_address;     /* Binary network address */
288     struct sockaddr_in* sin = &soc_address;
289
290     diagnostic = diag;                  /* set global flag */
291     
292     if (!acceptable_inited) init_acceptable();
293     
294     if (!arg) return -3;                /* Bad if no name sepcified     */
295     if (!*arg) return -2;               /* Bad if name had zero length  */
296     
297     if (TRACE) printf("HTGopher: Looking for %s\n", arg);
298     get_styles();
299     
300     
301 /*  Set up defaults:
302 */
303     sin->sin_family = AF_INET;                  /* Family, host order  */
304     sin->sin_port = htons(GOPHER_PORT);         /* Default: new port,  */
305
306     if (TRACE) printf("HTTPAccess: Looking for %s\n", arg);
307
308 /* Get node name and optional port number:
309 */
310     {
311         char *p1 = HTParse(arg, "", PARSE_HOST);
312         HTParseInet(sin, p1);
313         free(p1);
314     }
315     
316 /* Get entity type, and selector string.
317 */        
318     {
319         char * p1 = HTParse(arg, "", PARSE_PATH|PARSE_PUNCTUATION);
320         gtype = '1';            /* Default = menu */
321         selector = p1;
322         if ((*selector++=='/') && (*selector)) {        /* Skip first slash */
323             gtype = *selector++;                        /* Pick up gtype */
324         }
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 */
331             }
332             command = malloc(strlen(selector)+ 2 + 1);
333               if (command == NULL) outofmem(__FILE__, "HTLoadGopher");
334             strcpy(command, selector);
335             
336         } else {                                /* Not index */
337             char * p = selector;
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) {
342                     char c;
343                     unsigned int b;
344                     p++;
345                     c = *p++;
346                     b =   from_hex(c);
347                     c = *p++;
348                     if (!c) break;      /* Odd number of chars! */
349                     *q++ = (b<<4) + from_hex(c);
350                 } else {
351                     *q++ = *p++;        /* Record */
352                 }
353             }
354             *q++ = 0;   /* Terminate command */
355         }
356         free(p1);
357     }
358     
359     strcat(command, "\r\n");            /* Include CR for telnet compat. */
360     
361
362 /*      Set up a socket to the server for the data:
363 */      
364     s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
365     status = connect(s, (struct sockaddr*)&soc_address, sizeof(soc_address));
366     if (status<0){
367         if (TRACE) printf("HTTPAccess: Unable to connect to remote host for `%s'.\n",
368             arg);
369         free(command);
370         return HTInetStatus("connect");
371     }
372     
373     HTInitInput(s);             /* Set up input buffereing */
374     
375     if (TRACE) printf("HTGopher: Connected, writing command `%s' to socket %d\n", command, s);
376     
377 #ifdef NOT_ASCII
378     {
379         char * p;
380         for(p = command; *p; p++) {
381             *p = TOASCII(*p);
382         }
383     }
384 #endif
385
386     status = NETWRITE(s, command, (int)strlen(command));
387     free(command);
388     if (status<0){
389         if (TRACE) printf("HTGopher: Unable to send command.\n");
390             return HTInetStatus("send");
391     }
392
393 /*      Now read the data from the socket:
394 */    
395     if (diagnostic==2) gtype = GOPHER_TEXT;     /* Read as plain text anyway */
396     
397     switch (gtype) {
398     
399     case GOPHER_HTML :
400         HTParseFormat(WWW_HTML, anAnchor, s);
401         return 1;
402
403     case GOPHER_MENU :
404     case GOPHER_INDEX :
405         parse_menu(arg, anAnchor);
406         return 1;
407         
408     case GOPHER_TEXT :
409     default:                    /* @@ parse as plain text */
410         HTParseFormat(WWW_PLAINTEXT, anAnchor, s);
411         return 1;
412     } /* switch(gtype) */
413     /*NOTREACHED*/
414 }
415