1 /********************************************************************
2 * $Author: jgoerzen $
3 * $Revision: 1.1 $
4 * $Date: 2000/08/19 00:28:56 $
5 * $Source: /home/jgoerzen/tmp/gopher-umn/gopher/head/object/GDgopherdir.c,v $
6 * $State: Exp $
7 *
8 * Paul Lindner, University of Minnesota CIS.
9 *
10 * Copyright 1991, 1992 by the Regents of the University of Minnesota
11 * see the file "Copyright" in the distribution for conditions of use.
12 *********************************************************************
13 * MODULE: GDgopherdir.c
14 * Implement gopher directory routines
15 *********************************************************************
16 * Revision History:
17 * $Log: GDgopherdir.c,v $
18 * Revision 1.1 2000/08/19 00:28:56 jgoerzen
19 * Initial revision
20 *
21 * Revision 3.38 1996/01/04 18:26:51 lindner
22 * Updates for autoconf
23 *
24 * Revision 3.37 1995/10/31 16:47:47 lindner
25 * Switch to void+ pointer
26 *
27 * Revision 3.36 1995/09/26 05:16:32 lindner
28 * more fixes...
29 *
30 * Revision 3.35 1995/09/25 22:07:16 lindner
31 * Ansification
32 *
33 * Revision 3.34 1995/04/15 07:07:38 lindner
34 * Add ControlC handling feature...
35 *
36 * Revision 3.33 1995/02/17 18:34:09 lindner
37 * Fix stupid bug
38 *
39 * Revision 3.32 1995/02/16 22:32:43 lindner
40 * HTML icon support
41 *
42 * Revision 3.31 1995/02/13 19:09:25 lindner
43 * Fix for link updating
44 *
45 * Revision 3.30 1995/02/06 22:11:26 lindner
46 * Fix for GDsearch comparision
47 *
48 * Revision 3.29 1995/02/01 22:06:22 lindner
49 * Put back GDaddGSmerge, ugh.
50 *
51 * Revision 3.27 1994/10/18 21:36:34 lindner
52 * Remove unused variable
53 *
54 * Revision 3.26 1994/07/21 22:29:00 lindner
55 * misc hacks
56 *
57 * Revision 3.25 1994/07/06 03:01:30 lindner
58 * VMS doesn't have strcoll
59 *
60 * Revision 3.24 1994/06/29 06:47:51 lindner
61 * Use strcoll to sort if GINTERNATIONAL is defined
62 *
63 * Revision 3.23 1994/06/29 05:45:54 lindner
64 * Mods to pump tickets to the net
65 *
66 * Revision 3.22 1994/04/27 19:21:37 lindner
67 * Fix for semicolons after Debugmsg
68 *
69 * Revision 3.21 1994/04/25 03:36:56 lindner
70 * Modifications for Debug() and mismatched NULL arguments, added Debugmsg
71 *
72 * Revision 3.20 1994/04/21 21:24:57 lindner
73 * Fix fcn header
74 *
75 * Revision 3.19 1994/04/19 14:32:27 lindner
76 * Change GD and GSfromLink routines to use FIO
77 *
78 * Revision 3.18 1994/04/13 04:28:58 lindner
79 * Fix for Type=X items
80 *
81 * Revision 3.17 1994/02/20 16:28:19 lindner
82 * Remove dead code for GDtoNetHTML
83 *
84 * Revision 3.16 1994/01/10 03:27:28 lindner
85 * Better GDdeleteGS method, allow ignoring of items by doing a Type=X in a .names file
86 *
87 * Revision 3.15 1993/11/29 01:07:57 lindner
88 * In GDtoLink(), add FALSE argument to GStoLink() call in order to prevent
89 * the Admin and ModDate information from being saved in the user bookmark
90 * file. (Macrides)
91 *
92 * Revision 3.14 1993/11/02 06:15:15 lindner
93 * HTML additions
94 *
95 * Revision 3.13 1993/08/23 20:56:34 lindner
96 * Fix for empty directory in g+ client
97 *
98 * Revision 3.12 1993/08/19 20:51:33 lindner
99 * Mitra comments
100 *
101 * Revision 3.11 1993/08/19 20:24:04 lindner
102 * Mitra's Debug patch
103 *
104 * Revision 3.10 1993/07/29 20:02:16 lindner
105 * Removed dead variables
106 *
107 * Revision 3.9 1993/07/27 05:30:22 lindner
108 * Mondo Debug overhaul from Mitra
109 *
110 * Revision 3.8 1993/07/27 00:30:08 lindner
111 * plus patch from Mitra
112 *
113 * Revision 3.7 1993/07/14 20:37:08 lindner
114 * Negative numbering patches
115 *
116 * Revision 3.6 1993/06/22 06:07:17 lindner
117 * Added Domain= hacks..
118 *
119 * Revision 3.5 1993/04/15 21:35:12 lindner
120 * Debug code, better .Link processing, better GDfromNet()
121 *
122 * Revision 3.4 1993/03/26 19:50:44 lindner
123 * Mitra fixes for better/clearer fromNet code
124 *
125 * Revision 3.3 1993/03/24 17:04:49 lindner
126 * bad strcmp() can check unmalloced() mem, fixed
127 *
128 * Revision 3.2 1993/03/18 22:13:29 lindner
129 * filtering, compression fixes
130 *
131 * Revision 3.1 1993/02/11 18:03:01 lindner
132 * Initial revision
133 *
134 * Revision 2.1 1993/02/09 22:46:50 lindner
135 * Many additions for gopher+
136 *
137 * Revision 1.4 1993/01/31 00:22:51 lindner
138 * Changed GDaddGS to merge entries with the same path.
139 * Added GDplusfromNet() to siphon data from network.
140 * GDfromLink now knows about ~/ inside of Path=
141 * Changed GDSearch to ignore leading character.
142 *
143 * Revision 1.3 1992/12/19 04:44:09 lindner
144 * Added GDplustoNet()
145 *
146 * Revision 1.2 1992/12/16 20:37:04 lindner
147 * Added function GDsearch(), does a linear search of a gopher directory
148 *
149 * Revision 1.1 1992/12/10 23:27:52 lindner
150 * gopher 1.1 release
151 *
152 *
153 *********************************************************************/
154
155 #include <stdio.h>
156
157 #include "GDgopherdir.h"
158 #include "Malloc.h"
159 #include "util.h"
160
161 #include "String.h"
162 #include "fileio.h"
163 #include "Debug.h"
164 #include "fileio.h"
165
166 /***********************************************************************
167 ** Stuff for GopherDirObjs
168 **
169 ***********************************************************************/
170
171
172 GopherDirObj*
GDnew(int size)173 GDnew(int size)
174 {
175 GopherDirObj *temp;
176
177 temp = (GopherDirObj*) malloc(sizeof(GopherDirObj));
178
179 temp->Gophers = DAnew(size, GSnew, GSinit, GSdestroy, GScpy);
180
181 temp->Title = STRnew();
182 temp->Location = NULL;
183 temp->currentitem = 1;
184
185 GDinit(temp);
186 return(temp);
187 }
188
189
190 void
GDdestroy(GopherDirObj * gd)191 GDdestroy(GopherDirObj *gd)
192 {
193 DAdestroy(gd->Gophers);
194 if (gd->Location != NULL)
195 GSdestroy(gd->Location);
196
197 STRdestroy(gd->Title);
198 free(gd);
199 }
200
201
202 void
GDinit(GopherDirObj * gd)203 GDinit(GopherDirObj *gd)
204 {
205 DAinit(gd->Gophers);
206 STRinit(gd->Title);
207 gd->Location = NULL;
208 }
209
210
211 void
GDsetLocation(GopherDirObj * gd,GopherObj * gs)212 GDsetLocation(GopherDirObj *gd, GopherObj *gs)
213 {
214 if (gd->Location == NULL)
215 gd->Location = GSnew();
216 else
217 GSinit(gd->Location);
218
219 GScpy(gd->Location, gs);
220 }
221
222
223
224 /** This proc adds a GopherObj to a gopherdir.
225 It will attempt to merge two items if need be..
226 **/
227 void
GDaddGSmerge(GopherDirObj * gd,GopherObj * gs)228 GDaddGSmerge(GopherDirObj *gd, GopherObj *gs)
229 {
230 int num;
231
232 num = GDSearch(gd, GSgetPath(gs));
233
234 if (num == -1)
235 GDaddGS(gd, gs);
236 else {
237 if (GSgetType(gs) == 'X') {
238 gd = GDdeleteGS(gd, num);
239 } else
240 GSmerge(GDgetEntry(gd, num),gs);
241 }
242 }
243
244 /*
245 * This one never tries to merge
246 */
247
248 void
GDaddGS(GopherDirObj * gd,GopherObj * gs)249 GDaddGS(GopherDirObj *gd, GopherObj *gs)
250 {
251
252 if (GSgetType(gs) != 'X')
253 DApush(gd->Gophers, (char*)gs);
254 }
255
256
257 /*
258 * Really weird!!! We need this for qsort, don't know why we can't use
259 * GScmp...
260 */
261
262 #define sgn(a) ((a) == 0 ? 0 : (a) < 0 ? -1 : 1)
263
264 static int
GSqsortcmp(GopherObj ** gs1,GopherObj ** gs2)265 GSqsortcmp(GopherObj **gs1, GopherObj **gs2)
266 {
267 if (GSgetTitle(*gs1) == NULL)
268 return(1);
269 if (GSgetTitle(*gs2) == NULL)
270 return(-1);
271
272 /** No numbering set on either entry, or both numbered
273 entries have the same number **/
274
275 if (GSgetNum(*gs1) == GSgetNum(*gs2))
276 return(strcoll(GSgetTitle(*gs1), GSgetTitle(*gs2)));
277
278 /** If the signs are equal, compare the numbers conventionally **/
279
280 /** N.B. If the signs ARE equal, they cannot be 0 (otherwise we would **/
281 /** have had the above case, because only the sign of 0 is 0) */
282 if (sgn(GSgetNum(*gs1)) == sgn(GSgetNum(*gs2)))
283 return(GSgetNum(*gs1) < GSgetNum(*gs2) ? -1 : 1);
284
285 /** The signs must be different, so we can use a conventional test, **/
286 /** remembering only to say positive numbers go before negative ones **/
287 return(GSgetNum(*gs1) > GSgetNum(*gs2) ? -1 : 1);
288 }
289
290 /*
291 * Sorts a gopher directory
292 */
293
294 void
GDsort(GopherDirObj * gd)295 GDsort(GopherDirObj *gd)
296 {
297
298 DAsort(gd->Gophers, GSqsortcmp);
299 }
300
301
302 void
GDtoNet(GopherDirObj * gd,int sockfd,GSformat fmt,char * ticket,void (* prefcn)())303 GDtoNet(GopherDirObj *gd, int sockfd, GSformat fmt, char *ticket,
304 void (*prefcn)())
305 {
306 int i;
307 GopherObj *gs;
308 Debugmsg("GDplustoNet\n");
309
310 if (fmt == GSFORM_HTML) {
311 writestring(sockfd, "<DL COMPACT>\r\n");
312 }
313
314 for (i=0; i< GDgetNumitems(gd); i++) {
315 gs = GDgetEntry(gd, i);
316 if (fmt == GSFORM_HTML)
317 writestring(sockfd, "<DT>");
318
319 if (prefcn)
320 prefcn(gs, sockfd);
321 GStoNet(GDgetEntry(gd, i), sockfd, fmt, ticket);
322 }
323
324 if (fmt == GSFORM_HTML) {
325 writestring(sockfd, "</DL>\r\n");
326 }
327 }
328
329
330 void
GDplustoNet(GopherDirObj * gd,int sockfd,char ** filter,char * ticket)331 GDplustoNet(GopherDirObj *gd, int sockfd, char **filter, char *ticket)
332 {
333 int i;
334
335 for (i=0; i< GDgetNumitems(gd); i++) {
336 GSplustoNet(GDgetEntry(gd, i), sockfd,filter, ticket);
337 }
338 }
339
340 /*
341 * Gopher+ counterpart to GDfromNet()
342 * returns number of items found
343 */
344
345
346 int
GDplusfromNet(GopherDirObj * gd,int fd,int (* eachitem)())347 GDplusfromNet(GopherDirObj *gd, int fd, int (*eachitem)())
348 {
349 static GopherObj *TempGopher = NULL;
350 int j, result;
351 char inputline[256];
352
353 Debugmsg("GDplusfromNet:: start\r\n");
354 if (TempGopher == NULL)
355 TempGopher = GSnew();
356
357 /** State: _begin_ **/
358
359 result = readrecvbuf(fd, inputline, 1);
360 if (result <=0)
361 return(0);
362 else if (*inputline == '.') {
363 /*** Read off the rest of the junk... ***/
364 readline(fd,inputline,sizeof(inputline));
365 return(0);
366 }
367 else if (*inputline != '+')
368 return(0);
369
370 Debugmsg("after readrecvbuf");
371 /** State _FirstPlus_ **/
372
373 result = readtoken(fd, inputline, sizeof(inputline), ':');
374 if (result <=0)
375 return(result);
376
377 Debugmsg("after readtoken");
378 if (strcmp(inputline, "INFO")!=0) {
379 return(0);
380 }
381 Debugmsg("after INFO");
382 /** Read the space **/
383 if (readrecvbuf(fd, inputline, 1) <=0)
384 return(HARDERROR);
385
386 /*** State _FirstINFO_ ***/
387
388 for (j=0; !ControlCpressed ; j++) {
389
390 Debugmsg("for start");
391
392 GSinit(TempGopher);
393 result = GSplusfromNet(TempGopher, fd);
394
395 switch (result) {
396 case MORECOMING:
397 GDaddGS(gd, TempGopher);
398 if (eachitem != NULL)
399 eachitem();
400 break;
401
402 case FOUNDEOF:
403 GDaddGS(gd, TempGopher);
404 return(j+1);
405
406 case HARDERROR: /** Give up reading - bad read or protocol error **/
407 return(j);
408
409 case SOFTERROR: /** This one was bad, but we can try for next **/
410 j= j-1;
411 if (j<0) j=0;
412 break;
413 }
414
415 } /* for */
416
417 /** only get here if Control C pressed **/
418 ControlCpressed = FALSE;
419 return(j);
420 }
421
422 /*
423 * Fill up a GopherDirObj with GopherObjs, given a gopher directory coming
424 * from sockfd.
425 *
426 * For each GopherObj retrieved, eachitem() is executed.
427 *
428 */
429
430 void
GDfromNet(GopherDirObj * gd,int sockfd,int (* eachitem)())431 GDfromNet(GopherDirObj *gd, int sockfd, int (*eachitem)())
432 {
433 static GopherObj *TempGopher;
434 int i;
435 char *cp1, *cp2;
436
437 Debugmsg("GDfromNet...");
438 if (TempGopher == NULL)
439 TempGopher = GSnew();
440
441 for (; !ControlCpressed ;) {
442
443 GSinit(TempGopher);
444 i = GSfromNet(TempGopher, sockfd);
445
446 /* In gopher+1.2b2 this routine clears up if GSfromNet returns
447 a failure, better to clear up in GSfromNet so that the
448 system returns in a known state - note that other callers of
449 GSfromNet didn't clean up and crashed! */
450
451 switch (i) {
452
453 case 0:
454 if (GSgetType(TempGopher) == '3' &&
455 ((cp1 = GSgetTitle(TempGopher)) != NULL) &&
456 ((cp2 = strchr(cp1, '\n')) != NULL)) {
457 GopherObj *TempG2;
458 do {
459 TempG2 = GSnew();
460 GScpy(TempG2, TempGopher);
461 GSsetType(TempG2, A_INFO);
462 GSsetTitle(TempG2, cp2+1);
463 *cp2 = '\0';
464 GSsetTitle(TempGopher, cp1);
465 GDaddGS(gd, TempGopher);
466 TempGopher = TempG2;
467 if ((cp1 = GSgetTitle(TempGopher)) == NULL)
468 break;
469 } while ((cp2 = strchr(cp1, '\n')) != NULL);
470 }
471 GDaddGS(gd, TempGopher);
472 if (eachitem != NULL) eachitem();
473 break;
474
475 case 1: /* . on a line by itself, nothing more */
476 return;
477
478 case SOFTERROR: /** Unknown object type **/
479 break;
480
481 case HARDERROR:
482 return;
483 }
484 }/* for */
485 if (ControlCpressed) {
486 ControlCpressed = FALSE;
487 }
488 }
489
490
491 /*
492 * Given an open file descriptor and an inited GopherDirobj,
493 * read in gopher links, and add them to a gopherdir
494 */
495
496 void
GDfromLink(GopherDirObj * gd,FileIO * fio,char * host,int port,char * directory,char * peer)497 GDfromLink(GopherDirObj *gd, FileIO *fio, char *host, int port,
498 char *directory, char *peer)
499 {
500 GopherObj *gs;
501 int result;
502 char *cp;
503
504 gs = GSnew();
505
506 while (1) {
507 GSinit(gs);
508
509 result = GSfromLink(gs, fio, host, port,directory, peer);
510
511 if (result == HARDERROR)
512 break;
513
514 if (result == SOFTERROR)
515 continue;
516
517 cp = GSgetPath(gs);
518
519 if (*cp == '.')
520 GDaddGSmerge(gd, gs);
521 else
522 GDaddGS(gd, gs);
523
524 if (result == FOUNDEOF)
525 break;
526
527 }
528
529 GSdestroy(gs);
530 }
531
532
533 void
GDtoLink(GopherDirObj * gd,int fd)534 GDtoLink(GopherDirObj *gd, int fd)
535 {
536 int i;
537
538 for (i=0; i< GDgetNumitems(gd); i++) {
539 GStoLink(GDgetEntry(gd, i), fd, FALSE);
540 }
541
542 }
543
544 /*** Search for a specific gopher item ***/
545 /* Note first char is G0 type and is ignored*/
546 int
GDSearch(GopherDirObj * gd,char * text)547 GDSearch(GopherDirObj *gd, char *text)
548 {
549 int i;
550 GopherObj *gs;
551 int cplen;
552 char *cp;
553
554 Debug("GDSearch: %s;\n",text);
555
556 if (gd == NULL)
557 return(-1);
558
559 if (text == NULL)
560 return(-1);
561
562 if ((int) strlen(text) <= 1)
563 return(-1);
564
565 for (i=0; i< GDgetNumitems(gd); i++) {
566
567 gs = GDgetEntry(gd, i);
568 cp = GSgetPath(gs);
569 if (cp != NULL) {
570 cplen = strlen(cp);
571
572 if (cplen >1 && strcmp(text+1, cp+1) == 0) {
573 Debugmsg("Matched\n");
574 return(i);
575 }
576 }
577 }
578 Debugmsg("GDsearch: No Match\n");
579 return(-1);
580 }
581
582 /*
583 * Delete an item in a gopher GD.. Do it in place...
584 */
585
586 GopherDirObj *
GDdeleteGS(GopherDirObj * gd,int j)587 GDdeleteGS(GopherDirObj *gd, int j)
588 {
589 int i;
590
591 if (GDgetNumitems(gd) == j+1) {
592 /* Last item in the directory */
593 GSinit(GDgetEntry(gd,j));
594 GDsetNumitems(gd, j);
595 return(gd);
596 }
597
598 /** Okay, now let's copy the items down, one by one.. **/
599
600 for (i= j+1; i<GDgetNumitems(gd); i++) {
601 GScpy(GDgetEntry(gd,i-1), GDgetEntry(gd, i));
602 }
603
604 GDsetNumitems(gd, GDgetNumitems(gd)-1);
605
606 if (GDgetCurrentItem(gd) > GDgetNumitems(gd)-1)
607 GDsetCurrentItem(gd, GDgetCurrentItem(gd)-1);
608
609 return(gd);
610 }
611
612