| #include <gio/gio.h> | |
| #include <string.h> | |
| static int port = 8080; | |
| static char *root = NULL; | |
| static GOptionEntry cmd_entries[] = { | |
| {"port", 'p', 0, G_OPTION_ARG_INT, &port, | |
| "Local port to bind to", NULL}, | |
| G_OPTION_ENTRY_NULL | |
| }; | |
| static void | |
| send_error (GOutputStream *out, | |
| int error_code, | |
| const char *reason) | |
| { | |
| char *res; | |
| res = g_strdup_printf ("HTTP/1.0 %d %s\r\n\r\n" | |
| "<html><head><title>%d %s</title></head>" | |
| "<body>%s</body></html>", | |
| error_code, reason, | |
| error_code, reason, | |
| reason); | |
| g_output_stream_write_all (out, res, strlen (res), NULL, NULL, NULL); | |
| g_free (res); | |
| } | |
| static gboolean | |
| handler (GThreadedSocketService *service, | |
| GSocketConnection *connection, | |
| GSocketListener *listener, | |
| gpointer user_data) | |
| { | |
| GOutputStream *out; | |
| GInputStream *in; | |
| GFileInputStream *file_in; | |
| GDataInputStream *data; | |
| char *line, *escaped, *tmp, *query, *unescaped, *path, *version; | |
| GFile *f; | |
| GError *error; | |
| GFileInfo *info; | |
| GString *s; | |
| in = g_io_stream_get_input_stream (G_IO_STREAM (connection)); | |
| out = g_io_stream_get_output_stream (G_IO_STREAM (connection)); | |
| data = g_data_input_stream_new (in); | |
| /* Be tolerant of input */ | |
| g_data_input_stream_set_newline_type (data, G_DATA_STREAM_NEWLINE_TYPE_ANY); | |
| line = g_data_input_stream_read_line (data, NULL, NULL, NULL); | |
| if (line == NULL) | |
| { | |
| send_error (out, 400, "Invalid request"); | |
| goto out; | |
| } | |
| if (!g_str_has_prefix (line, "GET ")) | |
| { | |
| send_error (out, 501, "Only GET implemented"); | |
| goto out; | |
| } | |
| escaped = line + 4; /* Skip "GET " */ | |
| version = NULL; | |
| tmp = strchr (escaped, ' '); | |
| if (tmp == NULL) | |
| { | |
| send_error (out, 400, "Bad Request"); | |
| goto out; | |
| } | |
| *tmp = 0; | |
| version = tmp + 1; | |
| if (!g_str_has_prefix (version, "HTTP/1.")) | |
| { | |
| send_error(out, 505, "HTTP Version Not Supported"); | |
| goto out; | |
| } | |
| query = strchr (escaped, '?'); | |
| if (query != NULL) | |
| *query++ = 0; | |
| unescaped = g_uri_unescape_string (escaped, NULL); | |
| path = g_build_filename (root, unescaped, NULL); | |
| g_free (unescaped); | |
| f = g_file_new_for_path (path); | |
| g_free (path); | |
| error = NULL; | |
| file_in = g_file_read (f, NULL, &error); | |
| if (file_in == NULL) | |
| { | |
| send_error (out, 404, error->message); | |
| g_error_free (error); | |
| goto out; | |
| } | |
| s = g_string_new ("HTTP/1.0 200 OK\r\n"); | |
| info = g_file_input_stream_query_info (file_in, | |
| G_FILE_ATTRIBUTE_STANDARD_SIZE "," | |
| G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, | |
| NULL, NULL); | |
| if (info) | |
| { | |
| const char *content_type; | |
| char *mime_type; | |
| if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) | |
| g_string_append_printf (s, "Content-Length: %"G_GINT64_FORMAT"\r\n", | |
| g_file_info_get_size (info)); | |
| content_type = g_file_info_get_content_type (info); | |
| if (content_type) | |
| { | |
| mime_type = g_content_type_get_mime_type (content_type); | |
| if (mime_type) | |
| { | |
| g_string_append_printf (s, "Content-Type: %s\r\n", | |
| mime_type); | |
| g_free (mime_type); | |
| } | |
| } | |
| } | |
| g_string_append (s, "\r\n"); | |
| if (g_output_stream_write_all (out, | |
| s->str, s->len, | |
| NULL, NULL, NULL)) | |
| { | |
| g_output_stream_splice (out, | |
| G_INPUT_STREAM (file_in), | |
| 0, NULL, NULL); | |
| } | |
| g_string_free (s, TRUE); | |
| g_input_stream_close (G_INPUT_STREAM (file_in), NULL, NULL); | |
| g_object_unref (file_in); | |
| out: | |
| g_object_unref (data); | |
| return TRUE; | |
| } | |
| int | |
| main (int argc, char *argv[]) | |
| { | |
| GSocketService *service; | |
| GOptionContext *context; | |
| GError *error = NULL; | |
| context = g_option_context_new ("<http root dir> - Simple HTTP server"); | |
| g_option_context_add_main_entries (context, cmd_entries, NULL); | |
| if (!g_option_context_parse (context, &argc, &argv, &error)) | |
| { | |
| g_printerr ("%s: %s\n", argv[0], error->message); | |
| return 1; | |
| } | |
| if (argc != 2) | |
| { | |
| g_printerr ("Root directory not specified\n"); | |
| return 1; | |
| } | |
| root = g_strdup (argv[1]); | |
| service = g_threaded_socket_service_new (10); | |
| if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), | |
| port, | |
| NULL, | |
| &error)) | |
| { | |
| g_printerr ("%s: %s\n", argv[0], error->message); | |
| return 1; | |
| } | |
| g_print ("Http server listening on port %d\n", port); | |
| g_signal_connect (service, "run", G_CALLBACK (handler), NULL); | |
| g_main_loop_run (g_main_loop_new (NULL, FALSE)); | |
| g_assert_not_reached (); | |
| } |