1 /* Internal "file" protocol implementation */
11 #include <sys/types.h>
12 #include <sys/stat.h> /* OS/2 needs this after sys/types.h */
14 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
22 #include "cache/cache.h"
23 #include "config/options.h"
24 #include "encoding/encoding.h"
25 #include "intl/gettext/libintl.h"
26 #include "main/module.h"
27 #include "network/connection.h"
28 #include "network/socket.h"
29 #include "osdep/osdep.h"
30 #include "protocol/common.h"
31 #include "protocol/file/cgi.h"
32 #include "protocol/file/file.h"
33 #include "protocol/http/http.h"
34 #include "protocol/uri.h"
35 #include "util/conv.h"
36 #include "util/file.h"
37 #include "util/memory.h"
38 #include "util/string.h"
41 static struct option_info file_options
[] = {
42 INIT_OPT_TREE("protocol", N_("Local files"),
44 N_("Options specific to local browsing.")),
46 INIT_OPT_BOOL("protocol.file", N_("Allow reading special files"),
47 "allow_special_files", 0, 0,
48 N_("Whether to allow reading from non-regular files.\n"
49 "Note this can be dangerous; reading /dev/urandom or\n"
50 "/dev/zero can ruin your day!")),
52 INIT_OPT_BOOL("protocol.file", N_("Show hidden files in directory listing"),
53 "show_hidden_files", 0, 1,
54 N_("When set to false, files with name starting with a dot will be\n"
55 "hidden in local directories listing.")),
57 INIT_OPT_BOOL("protocol.file", N_("Try encoding extensions"),
58 "try_encoding_extensions", 0, 1,
59 N_("When set, if we can't open a file named 'filename', we'll try\n"
60 "to open 'filename' with some encoding extension appended\n"
61 "(ie. 'filename.gz'); it depends on the supported encodings.")),
66 struct module file_protocol_module
= struct_module(
67 /* name: */ N_("File"),
68 /* options: */ file_options
,
70 /* submodules: */ NULL
,
77 /* Directory listing */
79 /* Based on the @entry attributes and file-/dir-/linkname is added to the @data
80 * fragment. All the strings are in the system charset. */
82 add_dir_entry(struct directory_entry
*entry
, struct string
*page
,
83 int pathlen
, unsigned char *dircolor
)
85 unsigned char *lnk
= NULL
;
86 struct string html_encoded_name
;
87 struct string uri_encoded_name
;
89 if (!init_string(&html_encoded_name
)) return;
90 if (!init_string(&uri_encoded_name
)) {
91 done_string(&html_encoded_name
);
95 encode_uri_string(&uri_encoded_name
, entry
->name
+ pathlen
, -1, 1);
96 add_html_to_string(&html_encoded_name
, entry
->name
+ pathlen
,
97 strlen(entry
->name
) - pathlen
);
99 /* add_to_string(&fragment, &fragmentlen, " "); */
100 add_html_to_string(page
, entry
->attrib
, strlen(entry
->attrib
));
101 add_to_string(page
, "<a href=\"");
102 add_string_to_string(page
, &uri_encoded_name
);
104 if (entry
->attrib
[0] == 'd') {
105 add_char_to_string(page
, CHAR_DIR_SEP
);
107 #ifdef FS_UNIX_SOFTLINKS
108 } else if (entry
->attrib
[0] == 'l') {
110 unsigned char buf
[MAX_STR_LEN
];
111 int readlen
= readlink(entry
->name
, buf
, MAX_STR_LEN
);
113 if (readlen
> 0 && readlen
!= MAX_STR_LEN
) {
115 lnk
= straconcat(" -> ", buf
, (unsigned char *) NULL
);
118 if (!stat(entry
->name
, &st
) && S_ISDIR(st
.st_mode
))
119 add_char_to_string(page
, '/');
123 add_to_string(page
, "\">");
125 if (entry
->attrib
[0] == 'd' && *dircolor
) {
126 /* The <b> is for the case when use_document_colors is off. */
127 string_concat(page
, "<font color=\"", dircolor
, "\"><b>",
128 (unsigned char *) NULL
);
131 add_string_to_string(page
, &html_encoded_name
);
132 done_string(&uri_encoded_name
);
133 done_string(&html_encoded_name
);
135 if (entry
->attrib
[0] == 'd' && *dircolor
) {
136 add_to_string(page
, "</b></font>");
139 add_to_string(page
, "</a>");
141 add_html_to_string(page
, lnk
, strlen(lnk
));
145 add_char_to_string(page
, '\n');
148 /* First information such as permissions is gathered for each directory entry.
149 * Finally the sorted entries are added to the @data->fragment one by one. */
151 add_dir_entries(struct directory_entry
*entries
, unsigned char *dirpath
,
154 unsigned char dircolor
[8];
155 int dirpathlen
= strlen(dirpath
);
158 /* Setup @dircolor so it's easy to check if we should color dirs. */
159 if (get_opt_bool("document.browse.links.color_dirs")) {
160 color_to_string(get_opt_color("document.colors.dirs"),
161 (unsigned char *) &dircolor
);
166 for (i
= 0; entries
[i
].name
; i
++) {
167 add_dir_entry(&entries
[i
], page
, dirpathlen
, dircolor
);
168 mem_free(entries
[i
].attrib
);
169 mem_free(entries
[i
].name
);
172 /* We may have allocated space for entries but added none. */
173 mem_free_if(entries
);
176 /* Generates a HTML page listing the content of @directory with the path
178 /* Returns a connection state. S_OK if all is well. */
179 static inline enum connection_state
180 list_directory(struct connection
*conn
, unsigned char *dirpath
,
183 int show_hidden_files
= get_opt_bool("protocol.file.show_hidden_files");
184 struct directory_entry
*entries
;
185 enum connection_state state
;
188 entries
= get_directory_entries(dirpath
, show_hidden_files
);
190 if (errno
) return -errno
;
194 state
= init_directory_listing(page
, conn
->uri
);
198 add_dir_entries(entries
, dirpath
, page
);
200 if (!add_to_string(page
, "</pre>\n<hr/>\n</body>\n</html>\n")) {
209 read_special(struct connection
*conn
, int fd
)
211 struct read_buffer
*rb
;
213 if (!init_http_connection_info(conn
, 1, 0, 1)) {
214 abort_connection(conn
, S_OUT_OF_MEM
);
217 conn
->socket
->fd
= fd
;
218 if (fd
!= STDIN_FILENO
) conn
->popen
= 1;
219 rb
= alloc_read_buffer(conn
->socket
);
221 abort_connection(conn
, S_OUT_OF_MEM
);
224 memcpy(rb
->data
, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n", 45);
228 conn
->unrestartable
= 1;
229 conn
->socket
->state
= SOCKET_END_ONCLOSE
;
230 read_from_socket(conn
->socket
, rb
, S_SENT
, http_got_header
);
233 /* To reduce redundant error handling code [calls to abort_connection()]
234 * most of the function is build around conditions that will assign the error
235 * code to @state if anything goes wrong. The rest of the function will then just
236 * do the necessary cleanups. If all works out we end up with @state being S_OK
237 * resulting in a cache entry being created with the fragment data generated by
238 * either reading the file content or listing a directory. */
240 file_protocol_handler(struct connection
*connection
)
242 unsigned char *redirect_location
= NULL
;
243 struct string page
, name
;
244 enum connection_state state
;
245 int set_dir_content_type
= 0;
247 if (get_cmd_opt_bool("anonymous")) {
248 if (strcmp(connection
->uri
->string
, "file:///dev/stdin")
249 || isatty(STDIN_FILENO
)) {
250 abort_connection(connection
, S_FILE_ANONYMOUS
);
256 if (!execute_cgi(connection
)) return;
257 #endif /* CONFIG_CGI */
259 /* This function works on already simplified file-scheme URI pre-chewed
260 * by transform_file_url(). By now, the function contains no hostname
261 * part anymore, possibly relative path is converted to an absolute one
262 * and uri->data is just the final path to file/dir we should try to
265 if (!init_string(&name
)
266 || !add_uri_to_string(&name
, connection
->uri
, URI_PATH
)) {
268 abort_connection(connection
, S_OUT_OF_MEM
);
272 decode_uri_string(&name
);
274 if (file_is_dir(name
.source
)) {
275 /* In order for global history and directory listing to
276 * function properly the directory url must end with a
277 * directory separator. */
278 if (name
.source
[0] && !dir_sep(name
.source
[name
.length
- 1])) {
279 redirect_location
= STRING_DIR_SEP
;
282 state
= list_directory(connection
, name
.source
, &page
);
283 set_dir_content_type
= 1;
287 if (!strcmp(name
.source
, "/dev/stdin")) {
289 read_special(connection
, STDIN_FILENO
);
292 if (!strncmp(name
.source
, "/dev/fd/", 8)) {
293 int fd
= atoi(name
.source
+ 8);
297 read_special(connection
, fd
);
301 state
= read_encoded_file(&name
, &page
);
302 /* FIXME: If state is now S_ENCODE_ERROR we should try loading
303 * the file undecoded. --jonas */
309 struct cache_entry
*cached
;
311 /* Try to add fragment data to the connection cache if either
312 * file reading or directory listing worked out ok. */
313 cached
= connection
->cached
= get_cache_entry(connection
->uri
);
314 if (!connection
->cached
) {
315 if (!redirect_location
) done_string(&page
);
316 state
= S_OUT_OF_MEM
;
318 } else if (redirect_location
) {
319 if (!redirect_cache(cached
, redirect_location
, 1, 0))
320 state
= S_OUT_OF_MEM
;
323 add_fragment(cached
, 0, page
.source
, page
.length
);
324 connection
->from
+= page
.length
;
326 if (!cached
->head
&& set_dir_content_type
) {
329 /* If the system charset somehow
330 * changes after the directory listing
331 * has been generated, it should be
332 * parsed with the original charset. */
333 head
= straconcat("\r\nContent-Type: text/html; charset=",
334 get_cp_mime_name(get_cp_index("System")),
335 "\r\n", (unsigned char *) NULL
);
337 /* Not so gracefully handle failed memory
340 state
= S_OUT_OF_MEM
;
342 /* Setup directory listing for viewing. */
343 mem_free_set(&cached
->head
, head
);
350 abort_connection(connection
, state
);