1 /* The MIT License
2 
3    Copyright (c) 2008 by Genome Research Ltd (GRL).
4                  2010 by Attractive Chaos <attractor@live.co.uk>
5 
6    Permission is hereby granted, free of charge, to any person obtaining
7    a copy of this software and associated documentation files (the
8    "Software"), to deal in the Software without restriction, including
9    without limitation the rights to use, copy, modify, merge, publish,
10    distribute, sublicense, and/or sell copies of the Software, and to
11    permit persons to whom the Software is furnished to do so, subject to
12    the following conditions:
13 
14    The above copyright notice and this permission notice shall be
15    included in all copies or substantial portions of the Software.
16 
17    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21    BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22    ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24    SOFTWARE.
25 */
26 
27 /* Probably I will not do socket programming in the next few years and
28    therefore I decide to heavily annotate this file, for Linux and
29    Windows as well.  -ac */
30 
31 #include <config.h>
32 
33 #include <time.h>
34 #include <stdio.h>
35 #include <ctype.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <unistd.h>
40 #include <sys/types.h>
41 
42 #ifndef _WIN32
43 #include <netdb.h>
44 #include <arpa/inet.h>
45 #include <sys/socket.h>
46 #include <sys/select.h>
47 #endif
48 
49 #include "htslib/knetfile.h"
50 
51 /* In winsock.h, the type of a socket is SOCKET, which is: "typedef
52  * u_int SOCKET". An invalid SOCKET is: "(SOCKET)(~0)", or signed
53  * integer -1. In knetfile.c, I use "int" for socket type
54  * throughout. This should be improved to avoid confusion.
55  *
56  * In Linux/Mac, recv() and read() do almost the same thing. You can see
57  * in the header file that netread() is simply an alias of read(). In
58  * Windows, however, they are different and using recv() is mandatory.
59  */
60 
61 /* This function tests if the file handler is ready for reading (or
62  * writing if is_read==0). */
socket_wait(int fd,int is_read)63 static int socket_wait(int fd, int is_read)
64 {
65 	fd_set fds, *fdr = 0, *fdw = 0;
66 	struct timeval tv;
67 	int ret;
68 	tv.tv_sec = 5; tv.tv_usec = 0; // 5 seconds time out
69 	FD_ZERO(&fds);
70 	FD_SET(fd, &fds);
71 	if (is_read) fdr = &fds;
72 	else fdw = &fds;
73 	ret = select(fd+1, fdr, fdw, 0, &tv);
74 #ifndef _WIN32
75 	if (ret == -1) perror("select");
76 #else
77 	if (ret == 0)
78 		fprintf(stderr, "select time-out\n");
79 	else if (ret == SOCKET_ERROR)
80 		fprintf(stderr, "select: %d\n", WSAGetLastError());
81 #endif
82 	return ret;
83 }
84 
85 #ifndef _WIN32
86 /* This function does not work with Windows due to the lack of
87  * getaddrinfo() in winsock. It is addapted from an example in "Beej's
88  * Guide to Network Programming" (http://beej.us/guide/bgnet/). */
socket_connect(const char * host,const char * port)89 static int socket_connect(const char *host, const char *port)
90 {
91 #define __err_connect(func) do { perror(func); freeaddrinfo(res); return -1; } while (0)
92 
93 	int ai_err, on = 1, fd;
94 	struct linger lng = { 0, 0 };
95 	struct addrinfo hints, *res = 0;
96 	memset(&hints, 0, sizeof(struct addrinfo));
97 	hints.ai_family = AF_UNSPEC;
98 	hints.ai_socktype = SOCK_STREAM;
99 	/* In Unix/Mac, getaddrinfo() is the most convenient way to get
100 	 * server information. */
101 	if ((ai_err = getaddrinfo(host, port, &hints, &res)) != 0) { fprintf(stderr, "can't resolve %s:%s: %s\n", host, port, gai_strerror(ai_err)); return -1; }
102 	if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) __err_connect("socket");
103 	/* The following two setsockopt() are used by ftplib
104 	 * (http://nbpfaus.net/~pfau/ftplib/). I am not sure if they
105 	 * necessary. */
106 	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) __err_connect("setsockopt");
107 	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) __err_connect("setsockopt");
108 	if (connect(fd, res->ai_addr, res->ai_addrlen) != 0) __err_connect("connect");
109 	freeaddrinfo(res);
110 	return fd;
111 }
112 #else
113 /* MinGW's printf has problem with "%lld" */
int64tostr(char * buf,int64_t x)114 char *int64tostr(char *buf, int64_t x)
115 {
116 	int cnt;
117 	int i = 0;
118 	do {
119 		buf[i++] = '0' + x % 10;
120 		x /= 10;
121 	} while (x);
122 	buf[i] = 0;
123 	for (cnt = i, i = 0; i < cnt/2; ++i) {
124 		int c = buf[i]; buf[i] = buf[cnt-i-1]; buf[cnt-i-1] = c;
125 	}
126 	return buf;
127 }
128 
strtoint64(const char * buf)129 int64_t strtoint64(const char *buf)
130 {
131 	int64_t x;
132 	for (x = 0; *buf != '\0'; ++buf)
133 		x = x * 10 + ((int64_t) *buf - 48);
134 	return x;
135 }
136 /* In windows, the first thing is to establish the TCP connection. */
knet_win32_init()137 int knet_win32_init()
138 {
139 	WSADATA wsaData;
140 	return WSAStartup(MAKEWORD(2, 2), &wsaData);
141 }
knet_win32_destroy()142 void knet_win32_destroy()
143 {
144 	WSACleanup();
145 }
146 /* A slightly modfied version of the following function also works on
147  * Mac (and presummably Linux). However, this function is not stable on
148  * my Mac. It sometimes works fine but sometimes does not. Therefore for
149  * non-Windows OS, I do not use this one. */
socket_connect(const char * host,const char * port)150 static SOCKET socket_connect(const char *host, const char *port)
151 {
152 #define __err_connect(func)										\
153 	do {														\
154 		fprintf(stderr, "%s: %d\n", func, WSAGetLastError());	\
155 		return -1;												\
156 	} while (0)
157 
158 	int on = 1;
159 	SOCKET fd;
160 	struct linger lng = { 0, 0 };
161 	struct sockaddr_in server;
162 	struct hostent *hp = 0;
163 	// open socket
164 	if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) __err_connect("socket");
165 	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on)) == -1) __err_connect("setsockopt");
166 	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char*)&lng, sizeof(lng)) == -1) __err_connect("setsockopt");
167 	// get host info
168 	if (isalpha(host[0])) hp = gethostbyname(host);
169 	else {
170 		struct in_addr addr;
171 		addr.s_addr = inet_addr(host);
172 		hp = gethostbyaddr((char*)&addr, 4, AF_INET);
173 	}
174 	if (hp == 0) __err_connect("gethost");
175 	// connect
176 	server.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
177 	server.sin_family= AF_INET;
178 	server.sin_port = htons(atoi(port));
179 	if (connect(fd, (struct sockaddr*)&server, sizeof(server)) != 0) __err_connect("connect");
180 	// freehostent(hp); // strangely in MSDN, hp is NOT freed (memory leak?!)
181 	return fd;
182 }
183 #endif
184 
my_netread(int fd,void * buf,off_t len)185 static off_t my_netread(int fd, void *buf, off_t len)
186 {
187 	off_t rest = len, curr, l = 0;
188 	/* recv() and read() may not read the required length of data with
189 	 * one call. They have to be called repeatedly. */
190 	while (rest) {
191 		if (socket_wait(fd, 1) <= 0) break; // socket is not ready for reading
192 		curr = netread(fd, (void*)((char*)buf + l), rest);
193 		/* According to the glibc manual, section 13.2, a zero returned
194 		 * value indicates end-of-file (EOF), which should mean that
195 		 * read() will not return zero if EOF has not been met but data
196 		 * are not immediately available. */
197 		if (curr == 0) break;
198 		l += curr; rest -= curr;
199 	}
200 	return l;
201 }
202 
203 /*************************
204  * FTP specific routines *
205  *************************/
206 
kftp_get_response(knetFile * ftp)207 static int kftp_get_response(knetFile *ftp)
208 {
209 #ifndef _WIN32
210 	unsigned char c;
211 #else
212 	char c;
213 #endif
214 	int n = 0;
215 	char *p;
216 	if (socket_wait(ftp->ctrl_fd, 1) <= 0) return 0;
217 	while (netread(ftp->ctrl_fd, &c, 1)) { // FIXME: this is *VERY BAD* for unbuffered I/O
218 		//fputc(c, stderr);
219 		if (n >= ftp->max_response) {
220 			ftp->max_response = ftp->max_response? ftp->max_response<<1 : 256;
221 			ftp->response = (char*)realloc(ftp->response, ftp->max_response);
222 		}
223 		ftp->response[n++] = c;
224 		if (c == '\n') {
225 			if (n >= 4 && isdigit(ftp->response[0]) && isdigit(ftp->response[1]) && isdigit(ftp->response[2])
226 				&& ftp->response[3] != '-') break;
227 			n = 0;
228 			continue;
229 		}
230 	}
231 	if (n < 2) return -1;
232 	ftp->response[n-2] = 0;
233 	return strtol(ftp->response, &p, 0);
234 }
235 
kftp_send_cmd(knetFile * ftp,const char * cmd,int is_get)236 static int kftp_send_cmd(knetFile *ftp, const char *cmd, int is_get)
237 {
238 	if (socket_wait(ftp->ctrl_fd, 0) <= 0) return -1; // socket is not ready for writing
239     int len = strlen(cmd);
240 	if ( netwrite(ftp->ctrl_fd, cmd, len) != len ) return -1;
241 	return is_get? kftp_get_response(ftp) : 0;
242 }
243 
kftp_pasv_prep(knetFile * ftp)244 static int kftp_pasv_prep(knetFile *ftp)
245 {
246 	char *p;
247 	int v[6];
248 	kftp_send_cmd(ftp, "PASV\r\n", 1);
249 	for (p = ftp->response; *p && *p != '('; ++p);
250 	if (*p != '(') return -1;
251 	++p;
252 	sscanf(p, "%d,%d,%d,%d,%d,%d", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]);
253 	memcpy(ftp->pasv_ip, v, 4 * sizeof(int));
254 	ftp->pasv_port = (v[4]<<8&0xff00) + v[5];
255 	return 0;
256 }
257 
258 
kftp_pasv_connect(knetFile * ftp)259 static int kftp_pasv_connect(knetFile *ftp)
260 {
261 	char host[80], port[10];
262 	if (ftp->pasv_port == 0) {
263 		fprintf(stderr, "[kftp_pasv_connect] kftp_pasv_prep() is not called before hand.\n");
264 		return -1;
265 	}
266 	sprintf(host, "%d.%d.%d.%d", ftp->pasv_ip[0], ftp->pasv_ip[1], ftp->pasv_ip[2], ftp->pasv_ip[3]);
267 	sprintf(port, "%d", ftp->pasv_port);
268 	ftp->fd = socket_connect(host, port);
269 	if (ftp->fd == -1) return -1;
270 	return 0;
271 }
272 
kftp_connect(knetFile * ftp)273 int kftp_connect(knetFile *ftp)
274 {
275 	ftp->ctrl_fd = socket_connect(ftp->host, ftp->port);
276 	if (ftp->ctrl_fd == -1) return -1;
277 	kftp_get_response(ftp);
278 	kftp_send_cmd(ftp, "USER anonymous\r\n", 1);
279 	kftp_send_cmd(ftp, "PASS kftp@\r\n", 1);
280 	kftp_send_cmd(ftp, "TYPE I\r\n", 1);
281 	return 0;
282 }
283 
kftp_reconnect(knetFile * ftp)284 int kftp_reconnect(knetFile *ftp)
285 {
286 	if (ftp->ctrl_fd != -1) {
287 		netclose(ftp->ctrl_fd);
288 		ftp->ctrl_fd = -1;
289 	}
290 	netclose(ftp->fd);
291 	ftp->fd = -1;
292 	return kftp_connect(ftp);
293 }
294 
295 // initialize ->type, ->host, ->retr and ->size
kftp_parse_url(const char * fn,const char * mode)296 knetFile *kftp_parse_url(const char *fn, const char *mode)
297 {
298 	knetFile *fp;
299 	char *p;
300 	int l;
301 	if (strstr(fn, "ftp://") != fn) return 0;
302 	for (p = (char*)fn + 6; *p && *p != '/'; ++p);
303 	if (*p != '/') return 0;
304 	l = p - fn - 6;
305 	fp = (knetFile*)calloc(1, sizeof(knetFile));
306 	fp->type = KNF_TYPE_FTP;
307 	fp->fd = -1;
308 	/* the Linux/Mac version of socket_connect() also recognizes a port
309 	 * like "ftp", but the Windows version does not. */
310 	fp->port = strdup("21");
311 	fp->host = (char*)calloc(l + 1, 1);
312 	if (strchr(mode, 'c')) fp->no_reconnect = 1;
313 	strncpy(fp->host, fn + 6, l);
314 	fp->retr = (char*)calloc(strlen(p) + 8, 1);
315 	sprintf(fp->retr, "RETR %s\r\n", p);
316     fp->size_cmd = (char*)calloc(strlen(p) + 8, 1);
317     sprintf(fp->size_cmd, "SIZE %s\r\n", p);
318 	fp->seek_offset = 0;
319 	return fp;
320 }
321 // place ->fd at offset off
kftp_connect_file(knetFile * fp)322 int kftp_connect_file(knetFile *fp)
323 {
324 	int ret;
325 	long long file_size;
326 	if (fp->fd != -1) {
327 		netclose(fp->fd);
328 		if (fp->no_reconnect) kftp_get_response(fp);
329 	}
330 	kftp_pasv_prep(fp);
331     kftp_send_cmd(fp, fp->size_cmd, 1);
332 #ifndef _WIN32
333     // If the file does not exist, the response will be "550 Could not get file
334     // size". Be silent on failure, hts_idx_load can be trying the existence of .csi or .tbi.
335     if ( sscanf(fp->response,"%*d %lld", &file_size) != 1 ) return -1;
336 #else
337 	const char *p = fp->response;
338 	while (*p != ' ') ++p;
339 	while (*p < '0' || *p > '9') ++p;
340 	file_size = strtoint64(p);
341 #endif
342 	fp->file_size = file_size;
343 	if (fp->offset>=0) {
344 		char tmp[32];
345 #ifndef _WIN32
346 		sprintf(tmp, "REST %lld\r\n", (long long)fp->offset);
347 #else
348 		strcpy(tmp, "REST ");
349 		int64tostr(tmp + 5, fp->offset);
350 		strcat(tmp, "\r\n");
351 #endif
352 		kftp_send_cmd(fp, tmp, 1);
353 	}
354 	kftp_send_cmd(fp, fp->retr, 0);
355 	kftp_pasv_connect(fp);
356 	ret = kftp_get_response(fp);
357 	if (ret != 150) {
358 		fprintf(stderr, "[kftp_connect_file] %s\n", fp->response);
359 		netclose(fp->fd);
360 		fp->fd = -1;
361 		return -1;
362 	}
363 	fp->is_ready = 1;
364 	return 0;
365 }
366 
367 
368 /**************************
369  * HTTP specific routines *
370  **************************/
371 
khttp_parse_url(const char * fn,const char * mode)372 knetFile *khttp_parse_url(const char *fn, const char *mode)
373 {
374 	knetFile *fp;
375 	char *p, *proxy, *q;
376 	int l;
377 	if (strstr(fn, "http://") != fn) return 0;
378 	// set ->http_host
379 	for (p = (char*)fn + 7; *p && *p != '/'; ++p);
380 	l = p - fn - 7;
381 	fp = (knetFile*)calloc(1, sizeof(knetFile));
382 	fp->http_host = (char*)calloc(l + 1, 1);
383 	strncpy(fp->http_host, fn + 7, l);
384 	fp->http_host[l] = 0;
385 	for (q = fp->http_host; *q && *q != ':'; ++q);
386 	if (*q == ':') *q++ = 0;
387 	// get http_proxy
388 	proxy = getenv("http_proxy");
389 	// set ->host, ->port and ->path
390 	if (proxy == 0) {
391 		fp->host = strdup(fp->http_host); // when there is no proxy, server name is identical to http_host name.
392 		fp->port = strdup(*q? q : "80");
393 		fp->path = strdup(*p? p : "/");
394 	} else {
395 		fp->host = (strstr(proxy, "http://") == proxy)? strdup(proxy + 7) : strdup(proxy);
396 		for (q = fp->host; *q && *q != ':'; ++q);
397 		if (*q == ':') *q++ = 0;
398 		fp->port = strdup(*q? q : "80");
399 		fp->path = strdup(fn);
400 	}
401 	fp->type = KNF_TYPE_HTTP;
402 	fp->ctrl_fd = fp->fd = -1;
403 	fp->seek_offset = 0;
404 	return fp;
405 }
406 
khttp_connect_file(knetFile * fp)407 int khttp_connect_file(knetFile *fp)
408 {
409 	int ret, l = 0;
410 	char *buf, *p;
411 	if (fp->fd != -1) netclose(fp->fd);
412 	fp->fd = socket_connect(fp->host, fp->port);
413 	buf = (char*)calloc(0x10000, 1); // FIXME: I am lazy... But in principle, 64KB should be large enough.
414 	l += sprintf(buf + l, "GET %s HTTP/1.0\r\nHost: %s\r\n", fp->path, fp->http_host);
415 	if (fp->offset != 0) l += sprintf(buf + l, "Range: bytes=%lld-\r\n", (long long)fp->offset);
416 	l += sprintf(buf + l, "\r\n");
417 	if ( netwrite(fp->fd, buf, l) != l ) { free(buf); return -1; }
418 	l = 0;
419 	while (netread(fp->fd, buf + l, 1)) { // read HTTP header; FIXME: bad efficiency
420 		if (buf[l] == '\n' && l >= 3)
421 			if (strncmp(buf + l - 3, "\r\n\r\n", 4) == 0) break;
422 		++l;
423 	}
424 	buf[l] = 0;
425 	if (l < 14) { // prematured header
426 		free(buf);
427 		netclose(fp->fd);
428 		fp->fd = -1;
429 		return -1;
430 	}
431 	ret = strtol(buf + 8, &p, 0); // HTTP return code
432 	if (ret == 200 && fp->offset>0) { // 200 (complete result); then skip beginning of the file
433 		off_t rest = fp->offset;
434 		while (rest) {
435 			off_t l = rest < 0x10000? rest : 0x10000;
436 			rest -= my_netread(fp->fd, buf, l);
437 		}
438 	} else if (ret != 206 && ret != 200) {
439 		// failed to open file
440 		free(buf);
441 		netclose(fp->fd);
442 		switch (ret) {
443 		case 401: errno = EPERM; break;
444 		case 403: errno = EACCES; break;
445 		case 404: errno = ENOENT; break;
446 		case 407: errno = EPERM; break;
447 		case 408: errno = ETIMEDOUT; break;
448 		case 410: errno = ENOENT; break;
449 		case 503: errno = EAGAIN; break;
450 		case 504: errno = ETIMEDOUT; break;
451 		default:  errno = (ret >= 400 && ret < 500)? EINVAL : EIO; break;
452 		}
453 		fp->fd = -1;
454 		return -1;
455 	}
456 	free(buf);
457 	fp->is_ready = 1;
458 	return 0;
459 }
460 
461 /********************
462  * Generic routines *
463  ********************/
464 
knet_open(const char * fn,const char * mode)465 knetFile *knet_open(const char *fn, const char *mode)
466 {
467 	knetFile *fp = 0;
468 	if (mode[0] != 'r') {
469 		fprintf(stderr, "[knet_open] only mode \"r\" is supported.\n");
470 		errno = ENOTSUP;
471 		return 0;
472 	}
473 	if (strstr(fn, "ftp://") == fn) {
474 		fp = kftp_parse_url(fn, mode);
475 		if (fp == 0) return 0;
476 		if (kftp_connect(fp) == -1) {
477 			knet_close(fp);
478 			return 0;
479 		}
480 		kftp_connect_file(fp);
481 	} else if (strstr(fn, "http://") == fn) {
482 		fp = khttp_parse_url(fn, mode);
483 		if (fp == 0) return 0;
484 		khttp_connect_file(fp);
485 	} else { // local file
486 #ifdef _WIN32
487 		/* In windows, O_BINARY is necessary. In Linux/Mac, O_BINARY may
488 		 * be undefined on some systems, although it is defined on my
489 		 * Mac and the Linux I have tested on. */
490 		int fd = open(fn, O_RDONLY | O_BINARY);
491 #else
492 		int fd = open(fn, O_RDONLY);
493 #endif
494 		if (fd == -1) {
495 			perror("open");
496 			return 0;
497 		}
498 		fp = (knetFile*)calloc(1, sizeof(knetFile));
499 		fp->type = KNF_TYPE_LOCAL;
500 		fp->fd = fd;
501 		fp->ctrl_fd = -1;
502 	}
503 	if (fp && fp->fd == -1) {
504 		knet_close(fp);
505 		return 0;
506 	}
507 	return fp;
508 }
509 
knet_dopen(int fd,const char * mode)510 knetFile *knet_dopen(int fd, const char *mode)
511 {
512 	knetFile *fp = (knetFile*)calloc(1, sizeof(knetFile));
513 	fp->type = KNF_TYPE_LOCAL;
514 	fp->fd = fd;
515 	return fp;
516 }
517 
knet_read(knetFile * fp,void * buf,size_t len)518 ssize_t knet_read(knetFile *fp, void *buf, size_t len)
519 {
520 	off_t l = 0;
521 	if (fp->fd == -1) return 0;
522 	if (fp->type == KNF_TYPE_FTP) {
523 		if (fp->is_ready == 0) {
524 			if (!fp->no_reconnect) kftp_reconnect(fp);
525 			kftp_connect_file(fp);
526 		}
527 	} else if (fp->type == KNF_TYPE_HTTP) {
528 		if (fp->is_ready == 0)
529 			khttp_connect_file(fp);
530 	}
531 	if (fp->type == KNF_TYPE_LOCAL) { // on Windows, the following block is necessary; not on UNIX
532 		size_t rest = len;
533 		ssize_t curr;
534 		while (rest) {
535 			do {
536 				curr = read(fp->fd, (void*)((char*)buf + l), rest);
537 			} while (curr < 0 && EINTR == errno);
538 			if (curr < 0) return -1;
539 			if (curr == 0) break;
540 			l += curr; rest -= curr;
541 		}
542 	} else l = my_netread(fp->fd, buf, len);
543 	fp->offset += l;
544 	return l;
545 }
546 
knet_seek(knetFile * fp,off_t off,int whence)547 off_t knet_seek(knetFile *fp, off_t off, int whence)
548 {
549 	if (whence == SEEK_SET && off == fp->offset) return 0;
550 	if (fp->type == KNF_TYPE_LOCAL) {
551 		/* Be aware that lseek() returns the offset after seeking, while fseek() returns zero on success. */
552 		off_t offset = lseek(fp->fd, off, whence);
553 		if (offset == -1) return -1;
554 		fp->offset = offset;
555 		return fp->offset;
556 	} else if (fp->type == KNF_TYPE_FTP) {
557 		if (whence == SEEK_CUR) fp->offset += off;
558 		else if (whence == SEEK_SET) fp->offset = off;
559 		else if (whence == SEEK_END) fp->offset = fp->file_size + off;
560 		else return -1;
561 		fp->is_ready = 0;
562 		return fp->offset;
563 	} else if (fp->type == KNF_TYPE_HTTP) {
564 		if (whence == SEEK_END) { // FIXME: can we allow SEEK_END in future?
565 			fprintf(stderr, "[knet_seek] SEEK_END is not supported for HTTP. Offset is unchanged.\n");
566 			errno = ESPIPE;
567 			return -1;
568 		}
569 		if (whence == SEEK_CUR) fp->offset += off;
570 		else if (whence == SEEK_SET) fp->offset = off;
571 		else return -1;
572 		fp->is_ready = 0;
573 		return fp->offset;
574 	}
575 	errno = EINVAL;
576 	fprintf(stderr,"[knet_seek] %s\n", strerror(errno));
577 	return -1;
578 }
579 
knet_close(knetFile * fp)580 int knet_close(knetFile *fp)
581 {
582 	if (fp == 0) return 0;
583 	if (fp->ctrl_fd != -1) netclose(fp->ctrl_fd); // FTP specific
584 	if (fp->fd != -1) {
585 		/* On Linux/Mac, netclose() is an alias of close(), but on
586 		 * Windows, it is an alias of closesocket(). */
587 		if (fp->type == KNF_TYPE_LOCAL) close(fp->fd);
588 		else netclose(fp->fd);
589 	}
590 	free(fp->host); free(fp->port);
591 	free(fp->response); free(fp->retr); // FTP specific
592 	free(fp->path); free(fp->http_host); // HTTP specific
593 	free(fp);
594 	return 0;
595 }
596 
597 #ifdef KNETFILE_MAIN
main(void)598 int main(void)
599 {
600 	char *buf;
601 	knetFile *fp;
602 	int type = 4, l;
603 #ifdef _WIN32
604 	knet_win32_init();
605 #endif
606 	buf = calloc(0x100000, 1);
607 	if (type == 0) {
608 		fp = knet_open("knetfile.c", "r");
609 		knet_seek(fp, 1000, SEEK_SET);
610 	} else if (type == 1) { // NCBI FTP, large file
611 		fp = knet_open("ftp://ftp.ncbi.nih.gov/1000genomes/ftp/data/NA12878/alignment/NA12878.chrom6.SLX.SRP000032.2009_06.bam", "r");
612 		knet_seek(fp, 2500000000ll, SEEK_SET);
613 		l = knet_read(fp, buf, 255);
614 	} else if (type == 2) {
615 		fp = knet_open("ftp://ftp.sanger.ac.uk/pub4/treefam/tmp/index.shtml", "r");
616 		knet_seek(fp, 1000, SEEK_SET);
617 	} else if (type == 3) {
618 		fp = knet_open("http://www.sanger.ac.uk/Users/lh3/index.shtml", "r");
619 		knet_seek(fp, 1000, SEEK_SET);
620 	} else if (type == 4) {
621 		fp = knet_open("http://www.sanger.ac.uk/Users/lh3/ex1.bam", "r");
622 		knet_read(fp, buf, 10000);
623 		knet_seek(fp, 20000, SEEK_SET);
624 		knet_seek(fp, 10000, SEEK_SET);
625 		l = knet_read(fp, buf+10000, 10000000) + 10000;
626 	}
627 	if (type != 4 && type != 1) {
628 		knet_read(fp, buf, 255);
629 		buf[255] = 0;
630 		printf("%s\n", buf);
631 	} else write(fileno(stdout), buf, l);
632 	knet_close(fp);
633 	free(buf);
634 	return 0;
635 }
636 #endif
637