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