1 /*
2     gutenfetch - a small utility to list and fetch books available through
3 	project gutenberg
4 
5     Copyright (C) 2001, 2002, 2003, 2004 Russell Francis
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the
19 
20 	Free Software Foundation, Inc.
21 	59 Temple Place, Suite 330
22 	Boston, MA  02111-1307  USA
23 
24 Last updated on $Date: 2004/07/07 02:41:22 $ by $Author: johntabularasa $.
25 */
26 #include "stddefs.h"
27 #include "libgutenfetch_servers.h"
28 #include "list.h"
29 #ifdef HAVE_STDIO_H
30 #	include <stdio.h>
31 #endif
32 #ifdef HAVE_FCNTL_H
33 #	include <fcntl.h>
34 #endif
35 #ifdef HAVE_STDLIB_H
36 #	include <stdlib.h>
37 #endif
38 #ifdef HAVE_STRING_H
39 #	include <string.h>
40 #endif
41 #ifdef HAVE_STRINGS_H
42 #	include <strings.h>
43 #endif
44 #ifdef HAVE_PTHREAD
45 	#ifdef HAVE_PTHREAD_H
46 	#	include <pthread.h>
47 	#endif
48 #endif
49 
50 #ifdef HAVE_PTHREAD
51 static pthread_mutex_t active_server_mutex;
52 static pthread_mutex_t aussie_server_mutex;
53 #endif /* HAVE_PTHREAD */
54 static gutenfetch_server_t *active_server = NULL;
55 static gutenfetch_server_t *aussie_server = NULL;
56 static gutenfetch_server_t **potential_servers = NULL;
57 
58 /* Private functions */
59 
60 gutenfetch_continent_t
61 	gutenfetch_get_continent_from_string(char *);
62 
63 
64 /**
65  * gutenfetch_get_continent_from_string
66  *
67  * Given a string, return the numeric constant
68  * associated with that continent.
69  *
70  * @param str The string representing an unknown continent.
71  * @return A valid gutenfetch_continent_t, which relates to
72  * 		the string.
73  */
74 gutenfetch_continent_t
gutenfetch_get_continent_from_string(char * str)75 gutenfetch_get_continent_from_string(char *str)
76 {
77 	if (str != NULL) {
78 		if (strcasecmp(str, "africa") == 0) {
79 			return AFRICA;
80 		} else if (strcasecmp(str, "asia") == 0) {
81 			return ASIA;
82 		} else if ((strcasecmp(str, "australasia_oceania") == 0) ||
83 					(strcasecmp(str, "australia") == 0) ||
84 					(strcasecmp(str, "oceania") ==0)) {
85 			return AUSTRALASIA_OCEANIA;
86 		} else if (strcasecmp(str, "europe") == 0) {
87 			return EUROPE;
88 		} else if (strcasecmp(str, "north_america") == 0) {
89 			return NORTH_AMERICA;
90 		} else if (strcasecmp(str, "south_america") == 0) {
91 			return SOUTH_AMERICA;
92 		}
93 	}
94 
95 	return UNKNOWN_CONTINENT;
96 }
97 
98 
99 /* Global functions */
100 
101 /**
102  * gutenfetch_servers_init
103  *
104  * Initialize our default servers and set any
105  * other persistent variables for this module.
106  *
107  * @return GUTENFETCH_OK on success or an error code.
108  */
109 gutenfetch_error_t
gutenfetch_servers_init(void)110 gutenfetch_servers_init(void)
111 {
112 	gutenfetch_server_t *tserv;
113 
114 #ifdef HAVE_PTHREAD
115 	if (pthread_mutex_init(&active_server_mutex, NULL) != 0)
116 		return GUTENFETCH_SEE_ERRNO;
117 	if (pthread_mutex_init(&aussie_server_mutex, NULL) != 0)
118 		return GUTENFETCH_SEE_ERRNO;
119 #endif
120 
121 	/* Setup our default server */
122 	tserv = gutenfetch_new_server(
123 		"ftp://ibiblio.org/pub/docs/books/gutenberg/",
124 		"University of North Carolina - FTP",
125 		"Chapel Hill, North Carolina",
126 		NORTH_AMERICA);
127 
128 	if (tserv == NULL)
129 		return GUTENFETCH_NOMEM;
130 
131 #ifdef HAVE_PTHREAD
132 	pthread_mutex_lock(&active_server_mutex);
133 #endif
134 	active_server = tserv;
135 #ifdef HAVE_PTHREAD
136 	pthread_mutex_unlock(&active_server_mutex);
137 #endif
138 
139 	tserv = gutenfetch_new_server(
140 		"ftp://gutenberg.net.au/",
141 		"Project Gutenberg of Australia",
142 		"??, Australia",
143 		AUSTRALASIA_OCEANIA);
144 
145 	if (tserv == NULL)
146 		return GUTENFETCH_NOMEM;
147 #ifdef HAVE_PTHREAD
148 	pthread_mutex_lock(&aussie_server_mutex);
149 #endif
150 	aussie_server = tserv;
151 #ifdef HAVE_PTHREAD
152 	pthread_mutex_unlock(&aussie_server_mutex);
153 #endif
154 
155 	gutenfetch_load_potential_servers();
156 	return GUTENFETCH_OK;
157 }
158 
159 /**
160  * gutenfetch_servers_shutdown
161  *
162  * Free any resources held by this module.
163  */
164 void
gutenfetch_servers_shutdown(void)165 gutenfetch_servers_shutdown(void)
166 {
167 #ifdef HAVE_PTHREAD
168 	pthread_mutex_lock(&active_server_mutex);
169 #endif
170 	gutenfetch_free_server(active_server);
171 #ifdef HAVE_PTHREAD
172 	pthread_mutex_unlock(&active_server_mutex);
173 	pthread_mutex_destroy(&active_server_mutex);
174 #endif
175 #ifdef HAVE_PTHREAD
176 	pthread_mutex_lock(&aussie_server_mutex);
177 #endif
178 	gutenfetch_free_server(aussie_server);
179 #ifdef HAVE_PTHREAD
180 	pthread_mutex_unlock(&aussie_server_mutex);
181 	pthread_mutex_destroy(&aussie_server_mutex);
182 #endif
183 	gutenfetch_free_servers(potential_servers);
184 }
185 
186 /**
187  * gutenfetch_load_potential_servers
188  *
189  * Read in a list of potential servers and store.
190  * Currently reads from a file on disk but perhaps
191  * in the future it could look on the network for
192  * a newer copy. ;)
193  *
194  * @return GUTENFETCH_OK or another error code.
195  */
196 gutenfetch_error_t
gutenfetch_load_potential_servers(void)197 gutenfetch_load_potential_servers(void)
198 {
199 #define POTSERVERSFILE "servers.txt"
200 #define BUFFER_SIZE 4096
201 	int fd;
202 	int state;
203 //	int last_state;
204 	char *filename;
205 	char buffer[BUFFER_SIZE];
206 	gutenfetch_error_t errcode;
207 	size_t bytes_avail;
208 	size_t bytes_read;
209 	size_t filename_length;
210 	size_t host_index = 0;
211 	char host[BUFFER_SIZE];
212 	size_t area_index = 0;
213 	char area[BUFFER_SIZE];
214 	size_t name_index = 0;
215 	char name[BUFFER_SIZE];
216 	size_t continent_index = 0;
217 	char continent[BUFFER_SIZE];
218 	size_t server_count;
219 //	gutenfetch_server_t **pot_servers = NULL;
220 	gutenfetch_server_t **temp = NULL;
221 
222 	/* This is a list of the states we can be
223 	 * in while parsing the servers.txt file. */
224 	enum {
225 		LOOKING_FOR_ENTRY,
226 		LOOKING_FOR_NAME,
227 		READING_NAME,
228 		LOOKING_FOR_AREA,
229 		READING_AREA,
230 		LOOKING_FOR_HOST,
231 		READING_HOST,
232 		LOOKING_FOR_CONTINENT,
233 		READING_CONTINENT,
234 		LOOKING_FOR_CLOSING,
235 		IGNORE_UNTIL_NEWLINE
236 	};
237 
238 
239 	filename_length = strlen(DATADIR) + strlen(DIR_SEPARATOR) +
240 		strlen(POTSERVERSFILE) + 1;
241 	filename = malloc(sizeof(char) * filename_length);
242 	snprintf(filename, filename_length, "%s%s%s",
243 		DATADIR, DIR_SEPARATOR, POTSERVERSFILE);
244 
245 	fd = open(filename, O_RDONLY);
246 	FREE_NULL(filename);
247 	if (fd == -1)
248 		return GUTENFETCH_SEE_ERRNO;
249 
250 	server_count = 0;
251 	bytes_avail = 0;
252 	bytes_read = 0;
253 	state = LOOKING_FOR_ENTRY;
254 	while (TRUE) {
255 		if (bytes_read == bytes_avail) { /* read more. */
256 			bytes_avail = read(fd, buffer, BUFFER_SIZE);
257 			if (bytes_avail < 0) { /* Error condition. */
258 				errcode = GUTENFETCH_SEE_ERRNO;
259 				break;
260 			} else if (bytes_avail == 0) { /* EOF */
261 				errcode = GUTENFETCH_OK;
262 				break;
263 			}
264 			bytes_read = 0;
265 		}
266 		switch (state) {
267 		case LOOKING_FOR_ENTRY:
268 			if (buffer[bytes_read] == '{')
269 				state = LOOKING_FOR_NAME;
270 			break;
271 		case LOOKING_FOR_NAME:
272 			if (buffer[bytes_read] == '"') {
273 				name_index = 0;
274 				state = READING_NAME;
275 			}
276 			break;
277 		case READING_NAME:
278 			if (buffer[bytes_read] == '"') {
279 				name[name_index] = '\0';
280 				state = LOOKING_FOR_AREA;
281 			} else {
282 				name[name_index++] = buffer[bytes_read];
283 				if (name_index == BUFFER_SIZE - 1) {
284 					name_index = 0;
285 					state = LOOKING_FOR_ENTRY;
286 				}
287 			}
288 			break;
289 		case LOOKING_FOR_AREA:
290 			if (buffer[bytes_read] == '"') {
291 				area_index = 0;
292 				state = READING_AREA;
293 			}
294 			break;
295 		case READING_AREA:
296 			if (buffer[bytes_read] == '"') {
297 				area[area_index] = '\0';
298 				state = LOOKING_FOR_HOST;
299 			} else {
300 				area[area_index++] = buffer[bytes_read];
301 				if (area_index == BUFFER_SIZE - 1) {
302 					area_index = 0;
303 					state = LOOKING_FOR_ENTRY;
304 				}
305 			}
306 			break;
307 		case LOOKING_FOR_HOST:
308 			if (buffer[bytes_read] == '"') {
309 				host_index = 0;
310 				state = READING_HOST;
311 			}
312 			break;
313 		case READING_HOST:
314 			if (buffer[bytes_read] == '"') {
315 				host[host_index] = '\0';
316 				state = LOOKING_FOR_CONTINENT;
317 			} else {
318 				host[host_index++] = buffer[bytes_read];
319 				if (host_index == BUFFER_SIZE - 1) {
320 					host_index = 0;
321 					state = LOOKING_FOR_ENTRY;
322 				}
323 			}
324 			break;
325 		case LOOKING_FOR_CONTINENT:
326 			if (buffer[bytes_read] == '"') {
327 				continent_index = 0;
328 				state = READING_CONTINENT;
329 			}
330 			break;
331 		case READING_CONTINENT:
332 			if (buffer[bytes_read] == '"') {
333 				continent[continent_index] = '\0';
334 				state = LOOKING_FOR_CLOSING;
335 			} else {
336 				continent[continent_index++] = buffer[bytes_read];
337 				if (continent_index == BUFFER_SIZE - 1) {
338 					continent_index = 0;
339 					state = LOOKING_FOR_ENTRY;
340 				}
341 			}
342 			break;
343 		case LOOKING_FOR_CLOSING:
344 			if (buffer[bytes_read] == '}') {
345 				/* add entry to list of potential servers. */
346 				server_count++;
347 				temp = realloc(potential_servers,
348 					sizeof(gutenfetch_server_t *) * (server_count + 1));
349 				if (temp == NULL) {
350 					close(fd);
351 					gutenfetch_free_servers(potential_servers);
352 					return GUTENFETCH_NOMEM;
353 				}
354 				potential_servers = temp;
355 				potential_servers[server_count - 1] =
356 					gutenfetch_new_server(host, name, area,
357 						gutenfetch_get_continent_from_string(continent));
358 
359 				/* don't add the server if there was a problem. */
360 				if (potential_servers[server_count - 1] == NULL)
361 					server_count--;
362 				else
363 					potential_servers[server_count] = NULL;
364 
365 				state = LOOKING_FOR_ENTRY;
366 			}
367 			break;
368 //		case IGNORE_UNTIL_NEWLINE:
369 //			if (buffer[bytes_read] == '\n')
370 //				state = last_state;
371 //			break;
372 		}
373 		++bytes_read;
374 	}
375 	close(fd);
376 
377 	return errcode;
378 }
379 
380 /**
381  * gutenfetch_list_servers
382  *
383  * Return a NULL-terminated list of potential servers.
384  *
385  * @param continent Restrict results to servers on
386  * 		a specific continent if you would like.
387  * @return A NULL-terminated list of potential gutenfetch
388  *		servers on the continent specified.
389  */
390 gutenfetch_server_t **
gutenfetch_list_servers(gutenfetch_continent_t continent)391 gutenfetch_list_servers(gutenfetch_continent_t continent)
392 {
393 	int i;
394 	int num_of_servers = 0;
395 	gutenfetch_server_t **ss;
396 	size_t ssi;
397 
398 
399 	/* count the potential servers we have in our list. */
400 	i = 0;
401 	while (potential_servers[i] != NULL) {
402 		if ((continent == ALL_CONTINENTS) || (potential_servers[i]->continent == continent))
403 			++num_of_servers;
404 		++i;
405 	}
406 	ss = malloc(sizeof(gutenfetch_server_t*) * (num_of_servers + 1));
407 
408 	if (ss == NULL)
409 		return NULL;
410 
411 	ssi = 0;
412 	i = 0;
413 	while (potential_servers[i] != NULL) {
414 		if ((continent == ALL_CONTINENTS) || (potential_servers[i]->continent == continent)) {
415 			ss[ssi++] = gutenfetch_duplicate_server( potential_servers[i] );
416 		}
417 		++i;
418 	}
419 	ss[ssi] = NULL;
420 	return ss;
421 }
422 
423 /**
424  * gutenfetch_get_active_server
425  *
426  * Retreive a copy of the active server.
427  *
428  * @return NULL or a copy of the current active server which must
429  *		be freed using gutenfetch_free_server().
430  */
431 gutenfetch_server_t *
gutenfetch_get_active_server(void)432 gutenfetch_get_active_server(void)
433 {
434 	gutenfetch_server_t *server;
435 
436 #ifdef HAVE_PTHREAD
437 	pthread_mutex_lock(&active_server_mutex);
438 #endif
439 	server = gutenfetch_duplicate_server(active_server);
440 #ifdef HAVE_PTHREAD
441 	pthread_mutex_unlock(&active_server_mutex);
442 #endif
443 
444 	return server;
445 }
446 
447 /**
448  * gutenfetch_get_aussie_server
449  *
450  * Retreive a copy of the active australian server.
451  *
452  * @return NULL or a copy of the current active server which must
453  *		be freed using gutenfetch_free_server().
454  */
455 gutenfetch_server_t *
gutenfetch_get_aussie_server(void)456 gutenfetch_get_aussie_server(void)
457 {
458 	gutenfetch_server_t *server;
459 
460 #ifdef HAVE_PTHREAD
461 	pthread_mutex_lock(&aussie_server_mutex);
462 #endif
463 	server = gutenfetch_duplicate_server(aussie_server);
464 #ifdef HAVE_PTHREAD
465 	pthread_mutex_unlock(&aussie_server_mutex);
466 #endif
467 
468 	return server;
469 }
470 
471 
472 /**
473  * gutenfetch_set_active_server
474  *
475  * A short version of gutenfetch_set_active_server_full.
476  * It only takes the url of the gutenfetch server.
477  *
478  * @param url The full URL of the gutenfetch server.
479  * @return GUTENFETCH_OK, or an error code.
480  */
481 gutenfetch_error_t
gutenfetch_set_active_server(char * url)482 gutenfetch_set_active_server(char *url)
483 {
484 	gutenfetch_error_t errcode;
485 	gutenfetch_server_t *server;
486 
487 	if (url == NULL)
488 		return GUTENFETCH_BAD_PARAM;
489 
490 	server = gutenfetch_new_server(url, NULL, NULL, UNKNOWN_CONTINENT);
491 	errcode = gutenfetch_set_active_server_full(server);
492 	gutenfetch_free_server(server);
493 	return errcode;
494 }
495 
496 /**
497  * gutenfetch_ser_active_server_full
498  *
499  * Set the current active server from a valid
500  * gutenfetch_server_t structre.
501  *
502  * @param server The server we wish to make the current
503  *		gutenfetch server.
504  * @return GUTENFETCH_OK, or another error code.
505  */
506 gutenfetch_error_t
gutenfetch_set_active_server_full(gutenfetch_server_t * server)507 gutenfetch_set_active_server_full(gutenfetch_server_t *server)
508 {
509 	gutenfetch_server_t *temp0;
510 
511 	if (server == NULL)
512 		return GUTENFETCH_BAD_PARAM;
513 	if (server->host == NULL)
514 		return GUTENFETCH_BAD_PARAM;
515 
516 	temp0 = gutenfetch_duplicate_server(server);
517 	if (temp0 == NULL)
518 		return GUTENFETCH_NOMEM;
519 
520 #ifdef HAVE_PTHREAD
521 	pthread_mutex_lock(&active_server_mutex);
522 #endif
523 	gutenfetch_free_server(active_server);
524 	active_server = temp0;
525 #ifdef HAVE_PTHREAD
526 	pthread_mutex_unlock(&active_server_mutex);
527 #endif
528 
529 	return GUTENFETCH_OK;
530 }
531 
532 /**
533  * gutenfetch_new_server
534  *
535  * Given the raw information for a new server,
536  * create and return a server structure.
537  *
538  * @param host The full URL of the host (required)
539  * @param name The human readable name for the server or NULL.
540  * @param area The human readable geographic area for the server or NULL.
541  * @param continent The continent that the server is located on.
542  * @return NULL on error, or a valid gutenfetch_server_t structure.
543  */
544 gutenfetch_server_t *
gutenfetch_new_server(char * host,char * name,char * area,gutenfetch_continent_t continent)545 gutenfetch_new_server(
546 	char *host,
547 	char *name,
548 	char *area,
549 	gutenfetch_continent_t continent)
550 {
551 	gutenfetch_server_t *server;
552 	if (host == NULL)
553 		return NULL;
554 
555 	server = malloc(sizeof(gutenfetch_server_t));
556 	if (server == NULL)
557 		return NULL;
558 
559 	server->host = strdup(host);
560 	server->name = (name != NULL) ? strdup(name) : NULL;
561 	server->area = (area != NULL) ? strdup(area) : NULL;
562 	server->continent = continent;
563 
564 	return server;
565 }
566 
567 /**
568  * gutenfetch_duplicate_server
569  *
570  * Given a pointer to a gutenfetch server, duplicate it
571  * and return the result.
572  *
573  * @param server The gutenfetch server we wish to duplicate.
574  * @return The newly allocated gutenfetch server struct.
575  *		This result must be freed with a call to gutenfetch_free_server().
576  */
577 gutenfetch_server_t *
gutenfetch_duplicate_server(gutenfetch_server_t * server)578 gutenfetch_duplicate_server(gutenfetch_server_t *server)
579 {
580 	gutenfetch_server_t *ns;
581 
582 	if (server == NULL)
583 		return NULL;
584 
585 	ns = malloc(sizeof(gutenfetch_server_t));
586 	if (ns == NULL)
587 		return NULL;
588 
589 	ns->host = NULL;
590 	ns->name = NULL;
591 	ns->area = NULL;
592 	ns->continent = server->continent;
593 
594 	if (server->host != NULL) {
595 		ns->host = strdup(server->host);
596 		if (ns->host == NULL) {
597 			gutenfetch_free_server(ns);
598 			return NULL;
599 		}
600 	}
601 
602 	if (server->name != NULL) {
603 		ns->name = strdup(server->name);
604 		if (ns->name == NULL) {
605 			gutenfetch_free_server(ns);
606 			return NULL;
607 		}
608 	}
609 
610 	if (server->area != NULL) {
611 		ns->area = strdup(server->area);
612 		if (ns->area == NULL) {
613 			gutenfetch_free_server(ns);
614 			return NULL;
615 		}
616 	}
617 
618 	return ns;
619 }
620 
621 /**
622  * gutenfetch_free_server
623  *
624  * Release the memory used by a gutenfetch_server_t struct.
625  *
626  * @param server The server we wish to free.
627  */
628 void
gutenfetch_free_server(gutenfetch_server_t * server)629 gutenfetch_free_server(gutenfetch_server_t *server)
630 {
631 	if (server != NULL) {
632 		FREE_NULL(server->host);
633 		FREE_NULL(server->name);
634 		FREE_NULL(server->area);
635 	}
636 	FREE_NULL(server);
637 }
638 
639 /**
640  * gutenfetch_free_servers
641  *
642  * Given a NULL-terminated list of gutenfetch_server_t **, release
643  * the resources held by all.
644  *
645  * @param servers The list we wish to free.
646  */
647 void
gutenfetch_free_servers(gutenfetch_server_t ** servers)648 gutenfetch_free_servers(gutenfetch_server_t **servers)
649 {
650 	int i;
651 
652 	if (servers != NULL) {
653 		for (i = 0; servers[i] != NULL; ++i) {
654 			gutenfetch_free_server( servers[i] );
655 		}
656 		FREE_NULL( servers );
657 	}
658 }
659