fsp: Drop the protocol.fsp.sort, always sort entries.
[elinks.git] / src / protocol / fsp / fsp.c
blobfbc1d6deb73424a836573fe03b71d979f1f17a35
1 /* Internal FSP protocol implementation */
3 #ifndef _GNU_SOURCE
4 #define _GNU_SOURCE /* Needed for asprintf() */
5 #endif
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
11 #include <errno.h>
12 #include <fsplib.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #ifdef HAVE_FCNTL_H
17 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
18 #endif
19 #ifdef HAVE_UNISTD_H
20 #include <unistd.h>
21 #endif
23 #include "elinks.h"
25 #include "cache/cache.h"
26 #include "config/options.h"
27 #include "intl/gettext/libintl.h"
28 #include "main/module.h"
29 #include "main/select.h"
30 #include "network/connection.h"
31 #include "network/socket.h"
32 #include "osdep/osdep.h"
33 #include "protocol/auth/auth.h"
34 #include "protocol/common.h"
35 #include "protocol/protocol.h"
36 #include "protocol/fsp/fsp.h"
37 #include "protocol/uri.h"
38 #include "util/conv.h"
39 #include "util/memory.h"
40 #include "util/snprintf.h"
41 #include "util/string.h"
43 struct module fsp_protocol_module = struct_module(
44 /* name: */ N_("FSP"),
45 /* options: */ NULL,
46 /* hooks: */ NULL,
47 /* submodules: */ NULL,
48 /* data: */ NULL,
49 /* init: */ NULL,
50 /* done: */ NULL
54 /* Because functions of fsplib block waiting for a response from the
55 * server, and ELinks wants non-blocking operations so that other
56 * connections and the user interface keep working, this FSP protocol
57 * module forks a child process for each FSP connection. The child
58 * process writes the results to two pipes, which the main ELinks
59 * process then reads in a non-blocking fashion. The child process
60 * gets these pipes as its stdout and stderr.
62 * - If an error occurs, the child process writes "text/x-error"
63 * without newline to stderr, and an error code and a newline to
64 * stdout. The error code is either from errno or a negated value
65 * from enum connection_state, e.g. -S_OUT_OF_MEM. In particular,
66 * EPERM causes the parent process to prompt for username and
67 * password. (In this, fsplib differs from libsmbclient, which uses
68 * EACCES if authentication fails.)
70 * - If the resource is a regular file, the child process writes the
71 * estimated length of the file (in bytes) and a newline to stderr,
72 * and the contents of the file to stdout.
74 * - If the resource is a directory, the child process writes
75 * "text/html" without newline to stderr, and an HTML rendering
76 * of the directory listing to stdout.
78 * The exit code of the child process also indicates whether an error
79 * occurred, but the parent process ignores it. */
81 /* FSP synchronous connection management (child process):
83 * The child process generally does not bother to free the memory it
84 * allocates. When the process exits, the operating system will free
85 * the memory anyway. There is no point in changing this, because the
86 * child process also inherits memory allocations from the parent
87 * process, and it would be very cumbersome to free those. */
89 /* FIXME: Although it is probably not so much an issue, check if writes to
90 * stdout fails for directory listing like we do for file fetching. */
92 static void
93 fsp_error(int error)
95 printf("%d\n", error);
96 fprintf(stderr, "text/x-error");
97 /* In principle, this should perhaps call fsp_close_session to
98 * make the server accept any key from the next client process
99 * at this IP address. That doesn't seem necessary though:
100 * fsplib uses various IPC schemes to synchronize the use of
101 * server-provided keys between client processes, so the next
102 * client process will probably be able to use the key saved
103 * by this one. */
104 exit(1);
107 static int
108 compare(const void *av, const void *bv)
110 const FSP_RDENTRY *a = av, *b = bv;
111 int res = ((b->type == FSP_RDTYPE_DIR) - (a->type == FSP_RDTYPE_DIR));
113 if (res)
114 return res;
115 return strcmp(a->name, b->name);
118 static void
119 display_entry(const FSP_RDENTRY *fentry, const unsigned char dircolor[])
121 struct string string;
123 /* fentry->name is a fixed-size array and is followed by other
124 * members; thus, if the name reported by the server does not
125 * fit in the array, fsplib must either truncate or reject it.
126 * If fsplib truncates the name, it does not document whether
127 * fentry->namlen is the original length or the truncated
128 * length. ELinks therefore ignores fentry->namlen and
129 * instead measures the length on its own. */
130 const size_t namelen = strlen(fentry->name);
132 if (!init_string(&string)) return;
133 add_format_to_string(&string, "%10d", fentry->size);
134 add_to_string(&string, "\t<a href=\"");
135 /* The result of encode_uri_string does not include '&' or '<'
136 * which could mess up the HTML. */
137 encode_uri_string(&string, fentry->name, namelen, 0);
138 if (fentry->type == FSP_RDTYPE_DIR) {
139 add_to_string(&string, "/\">");
140 if (*dircolor) {
141 add_to_string(&string, "<font color=\"");
142 add_to_string(&string, dircolor);
143 add_to_string(&string, "\"><b>");
145 add_html_to_string(&string, fentry->name, namelen);
146 if (*dircolor) {
147 add_to_string(&string, "</b></font>");
149 } else {
150 add_to_string(&string, "\">");
151 add_html_to_string(&string, fentry->name, namelen);
153 add_to_string(&string, "</a>");
154 puts(string.source);
155 done_string(&string);
158 static void
159 sort_and_display_entries(FSP_DIR *dir, const unsigned char dircolor[])
161 /* fsp_readdir_native in fsplib 0.9 and earlier requires
162 * the third parameter to point to a non-null pointer
163 * even though it does not dereference that pointer
164 * and overwrites it with another one anyway.
165 * http://sourceforge.net/tracker/index.php?func=detail&aid=1875210&group_id=93841&atid=605738
166 * Work around the bug by using non-null &tmp.
167 * Nothing will actually read or write tmp. */
168 FSP_RDENTRY fentry, tmp, *table = NULL;
169 FSP_RDENTRY *fresult = &tmp;
170 int size = 0;
171 int i;
173 while (!fsp_readdir_native(dir, &fentry, &fresult)) {
174 FSP_RDENTRY *new_table;
176 if (!fresult) break;
177 if (!strcmp(fentry.name, "."))
178 continue;
179 new_table = mem_realloc(table, (size + 1) * sizeof(*table));
180 if (!new_table)
181 continue;
182 table = new_table;
183 copy_struct(&table[size], &fentry);
184 size++;
186 /* If size==0, then table==NULL. According to ISO/IEC 9899:1999
187 * 7.20.5p1, the NULL must not be given to qsort. */
188 if (size > 0)
189 qsort(table, size, sizeof(*table), compare);
191 for (i = 0; i < size; i++) {
192 display_entry(&table[i], dircolor);
196 static void
197 fsp_directory(FSP_SESSION *ses, struct uri *uri)
199 struct string buf;
200 FSP_DIR *dir;
201 unsigned char *data = get_uri_string(uri, URI_DATA);
202 unsigned char dircolor[8] = "";
204 if (!data)
205 fsp_error(-S_OUT_OF_MEM);
206 decode_uri(data);
207 if (init_directory_listing(&buf, uri) != S_OK)
208 fsp_error(-S_OUT_OF_MEM);
210 dir = fsp_opendir(ses, data);
211 if (!dir) fsp_error(errno);
213 fprintf(stderr, "text/html");
214 fclose(stderr);
216 puts(buf.source);
218 if (get_opt_bool("document.browse.links.color_dirs", NULL)) {
219 color_to_string(get_opt_color("document.colors.dirs", NULL),
220 dircolor);
223 sort_and_display_entries(dir, dircolor);
224 fsp_closedir(dir);
225 puts("</pre><hr/></body></html>");
226 fsp_close_session(ses);
227 exit(0);
230 #define READ_SIZE 4096
232 static void
233 do_fsp(struct connection *conn)
235 FSP_SESSION *ses;
236 struct stat sb;
237 struct uri *uri = conn->uri;
238 struct auth_entry *auth;
239 unsigned char *host = get_uri_string(uri, URI_HOST);
240 unsigned char *data = get_uri_string(uri, URI_DATA);
241 unsigned short port = (unsigned short)get_uri_port(uri);
242 unsigned char *password = NULL;
244 decode_uri(data);
245 if (uri->passwordlen) {
246 password = get_uri_string(uri, URI_PASSWORD);
247 } else {
248 auth = find_auth(uri);
249 if (auth) password = auth->password;
252 ses = fsp_open_session(host, port, password);
253 if (!ses) fsp_error(errno);
255 /* fsplib 0.8 ABI depends on _FILE_OFFSET_BITS
256 * https://sourceforge.net/tracker/index.php?func=detail&aid=1674729&group_id=93841&atid=605738
257 * If ELinks and fsplib are using different values of
258 * _FILE_OFFSET_BITS, then they get different definitions of
259 * struct stat, and the st_size stored by fsp_stat is
260 * typically not the same as the st_size read by ELinks.
261 * Fortunately, st_mode seems to have the same offset and size
262 * in both versions of struct stat.
264 * If all the bytes used by the 32-bit st_size are also used
265 * by the 64-bit st_size, then ELinks may be able to guess
266 * which ones they are, because the current version 2 of FSP
267 * supports only 32-bit file sizes in protocol packets. Begin
268 * by filling struct stat with 0xAA so that it's easier to
269 * detect which bytes fsp_stat has left unchanged. (Only
270 * sb.st_size really needs to be filled, but filling the rest
271 * too helps viewing the data with a debugger.) */
272 memset(&sb, 0xAA, sizeof(sb));
273 if (fsp_stat(ses, data, &sb)) fsp_error(errno);
275 if (S_ISDIR(sb.st_mode)) {
276 fsp_directory(ses, uri);
277 } else { /* regular file */
278 char buf[READ_SIZE];
279 FSP_FILE *file = fsp_fopen(ses, data, "r");
280 int r;
282 if (!file) {
283 fsp_error(errno);
286 #if SIZEOF_OFF_T >= 8
287 if (sb.st_size < 0 || sb.st_size > 0xFFFFFFFF) {
288 /* Probably a _FILE_OFFSET_BITS mismatch as
289 * described above. Try to detect which half
290 * of st_size is the real size. This may
291 * depend on the endianness of the processor
292 * and on the padding in struct stat. */
293 if ((sb.st_size & 0xFFFFFFFF00000000ULL) == 0xAAAAAAAA00000000ULL)
294 sb.st_size = sb.st_size & 0xFFFFFFFF;
295 else if ((sb.st_size & 0xFFFFFFFF) == 0xAAAAAAAA)
296 sb.st_size = (sb.st_size >> 32) & 0xFFFFFFFF;
297 else /* Can't figure it out. */
298 sb.st_size = 1;
300 #endif
302 /* Send filesize */
303 fprintf(stderr, "%" OFF_T_FORMAT "\n", (off_t)(sb.st_size));
304 fclose(stderr);
306 while ((r = fsp_fread(buf, 1, READ_SIZE, file)) > 0) {
307 int off = 0;
309 while (r) {
310 int w = safe_write(STDOUT_FILENO, buf + off, r);
312 if (w == -1) goto out;
313 off += w;
314 r -= w;
317 out:
318 fsp_fclose(file);
319 fsp_close_session(ses);
320 exit(0);
324 #undef READ_SIZE
328 /* FSP asynchronous connection management (parent process): */
330 /* Kill the current connection and ask for a username/password for the next
331 * try. */
332 static void
333 prompt_username_pw(struct connection *conn)
335 add_auth_entry(conn->uri, "FSP", NULL, NULL, 0);
336 abort_connection(conn, S_OK);
339 static void
340 fsp_got_error(struct socket *socket, struct read_buffer *rb)
342 int len = rb->length;
343 struct connection *conn = socket->conn;
344 int error;
346 if (len < 0) {
347 abort_connection(conn, -errno);
348 return;
351 /* There should be free space in the buffer, because
352 * @alloc_read_buffer allocated several kibibytes, and the
353 * child process wrote only an integer and a newline to the
354 * pipe. */
355 assert(rb->freespace >= 1);
356 if_assert_failed {
357 abort_connection(conn, S_INTERNAL);
358 return;
360 rb->data[len] = '\0';
361 error = atoi(rb->data);
362 kill_buffer_data(rb, len);
363 switch (error) {
364 case EPERM:
365 prompt_username_pw(conn);
366 break;
367 default:
368 abort_connection(conn, -error);
369 break;
373 static void
374 fsp_got_data(struct socket *socket, struct read_buffer *rb)
376 int len = rb->length;
377 struct connection *conn = socket->conn;
379 if (len < 0) {
380 abort_connection(conn, -errno);
381 return;
384 if (!len) {
385 abort_connection(conn, S_OK);
386 return;
389 socket->state = SOCKET_END_ONCLOSE;
390 conn->received += len;
391 if (add_fragment(conn->cached, conn->from, rb->data, len) == 1)
392 conn->tries = 0;
393 conn->from += len;
394 kill_buffer_data(rb, len);
396 read_from_socket(socket, rb, S_TRANS, fsp_got_data);
399 static void
400 fsp_got_header(struct socket *socket, struct read_buffer *rb)
402 struct connection *conn = socket->conn;
403 struct read_buffer *buf;
404 int error = 0;
406 conn->cached = get_cache_entry(conn->uri);
407 if (!conn->cached) {
408 /* Even though these are pipes rather than real
409 * sockets, call close_socket instead of close, to
410 * ensure that abort_connection won't try to close the
411 * file descriptors again. (Could we skip the calls
412 * and assume abort_connection will do them?) */
413 close_socket(socket);
414 close_socket(conn->data_socket);
415 abort_connection(conn, S_OUT_OF_MEM);
416 return;
418 socket->state = SOCKET_END_ONCLOSE;
420 if (rb->length > 0) {
421 unsigned char *ctype = memacpy(rb->data, rb->length);
423 if (ctype && *ctype) {
424 if (!strcmp(ctype, "text/x-error")) {
425 error = 1;
426 mem_free(ctype);
427 } else {
428 if (ctype[0] >= '0' && ctype[0] <= '9') {
429 #ifdef HAVE_ATOLL
430 conn->est_length = (off_t)atoll(ctype);
431 #else
432 conn->est_length = (off_t)atol(ctype);
433 #endif
434 mem_free(ctype);
436 /* avoid read from socket error */
437 if (!conn->est_length) {
438 abort_connection(conn, S_OK);
439 return;
442 else mem_free_set(&conn->cached->content_type, ctype);
444 } else {
445 mem_free_if(ctype);
449 buf = alloc_read_buffer(conn->data_socket);
450 if (!buf) {
451 close_socket(socket);
452 close_socket(conn->data_socket);
453 abort_connection(conn, S_OUT_OF_MEM);
454 return;
457 if (error) {
458 mem_free_set(&conn->cached->content_type, stracpy("text/html"));
459 read_from_socket(conn->data_socket, buf, S_CONN, fsp_got_error);
460 } else {
461 read_from_socket(conn->data_socket, buf, S_CONN, fsp_got_data);
466 void
467 fsp_protocol_handler(struct connection *conn)
469 int fsp_pipe[2] = { -1, -1 };
470 int header_pipe[2] = { -1, -1 };
471 pid_t cpid;
473 if (c_pipe(fsp_pipe) || c_pipe(header_pipe)) {
474 int s_errno = errno;
476 if (fsp_pipe[0] >= 0) close(fsp_pipe[0]);
477 if (fsp_pipe[1] >= 0) close(fsp_pipe[1]);
478 if (header_pipe[0] >= 0) close(header_pipe[0]);
479 if (header_pipe[1] >= 0) close(header_pipe[1]);
480 abort_connection(conn, -s_errno);
481 return;
483 conn->from = 0;
484 conn->unrestartable = 1;
485 find_auth(conn->uri); /* remember username and password */
487 cpid = fork();
488 if (cpid == -1) {
489 int s_errno = errno;
491 close(fsp_pipe[0]);
492 close(fsp_pipe[1]);
493 close(header_pipe[0]);
494 close(header_pipe[1]);
495 retry_connection(conn, -s_errno);
496 return;
499 if (!cpid) {
500 dup2(fsp_pipe[1], 1);
501 dup2(open("/dev/null", O_RDONLY), 0);
502 dup2(header_pipe[1], 2);
503 close(fsp_pipe[0]);
504 close(header_pipe[0]);
506 /* There may be outgoing data in stdio buffers
507 * inherited from the parent process. The parent
508 * process is going to write this data, so the child
509 * process must not do that. Closing the file
510 * descriptors ensures this.
512 * FIXME: If something opens more files and gets the
513 * same file descriptors and does not close them
514 * before exit(), then stdio may attempt to write the
515 * buffers to the wrong files. This might happen for
516 * example if fsplib calls syslog(). */
517 close_all_non_term_fd();
518 do_fsp(conn);
520 } else {
521 struct read_buffer *buf2;
523 conn->data_socket->fd = fsp_pipe[0];
524 conn->socket->fd = header_pipe[0];
525 set_nonblocking_fd(conn->data_socket->fd);
526 set_nonblocking_fd(conn->socket->fd);
527 close(fsp_pipe[1]);
528 close(header_pipe[1]);
529 buf2 = alloc_read_buffer(conn->socket);
530 if (!buf2) {
531 close_socket(conn->data_socket);
532 close_socket(conn->socket);
533 abort_connection(conn, S_OUT_OF_MEM);
534 return;
536 read_from_socket(conn->socket, buf2, S_CONN, fsp_got_header);