1 | #include <gio/gio.h> |
2 | #include <string.h> |
3 | |
4 | static int port = 8080; |
5 | static char *root = NULL; |
6 | static GOptionEntry cmd_entries[] = { |
7 | {"port" , 'p', 0, G_OPTION_ARG_INT, &port, |
8 | "Local port to bind to" , NULL}, |
9 | {NULL} |
10 | }; |
11 | |
12 | static void |
13 | send_error (GOutputStream *out, |
14 | int error_code, |
15 | const char *reason) |
16 | { |
17 | char *res; |
18 | |
19 | res = g_strdup_printf (format: "HTTP/1.0 %d %s\r\n\r\n" |
20 | "<html><head><title>%d %s</title></head>" |
21 | "<body>%s</body></html>" , |
22 | error_code, reason, |
23 | error_code, reason, |
24 | reason); |
25 | g_output_stream_write_all (stream: out, buffer: res, count: strlen (s: res), NULL, NULL, NULL); |
26 | g_free (mem: res); |
27 | } |
28 | |
29 | static gboolean |
30 | handler (GThreadedSocketService *service, |
31 | GSocketConnection *connection, |
32 | GSocketListener *listener, |
33 | gpointer user_data) |
34 | { |
35 | GOutputStream *out; |
36 | GInputStream *in; |
37 | GFileInputStream *file_in; |
38 | GDataInputStream *data; |
39 | char *line, *escaped, *tmp, *query, *unescaped, *path, *version; |
40 | GFile *f; |
41 | GError *error; |
42 | GFileInfo *info; |
43 | GString *s; |
44 | |
45 | in = g_io_stream_get_input_stream (G_IO_STREAM (connection)); |
46 | out = g_io_stream_get_output_stream (G_IO_STREAM (connection)); |
47 | |
48 | data = g_data_input_stream_new (base_stream: in); |
49 | /* Be tolerant of input */ |
50 | g_data_input_stream_set_newline_type (stream: data, type: G_DATA_STREAM_NEWLINE_TYPE_ANY); |
51 | |
52 | line = g_data_input_stream_read_line (stream: data, NULL, NULL, NULL); |
53 | |
54 | if (line == NULL) |
55 | { |
56 | send_error (out, error_code: 400, reason: "Invalid request" ); |
57 | goto out; |
58 | } |
59 | |
60 | if (!g_str_has_prefix (str: line, prefix: "GET " )) |
61 | { |
62 | send_error (out, error_code: 501, reason: "Only GET implemented" ); |
63 | goto out; |
64 | } |
65 | |
66 | escaped = line + 4; /* Skip "GET " */ |
67 | |
68 | version = NULL; |
69 | tmp = strchr (s: escaped, c: ' '); |
70 | if (tmp == NULL) |
71 | { |
72 | send_error (out, error_code: 400, reason: "Bad Request" ); |
73 | goto out; |
74 | } |
75 | *tmp = 0; |
76 | |
77 | version = tmp + 1; |
78 | if (!g_str_has_prefix (str: version, prefix: "HTTP/1." )) |
79 | { |
80 | send_error(out, error_code: 505, reason: "HTTP Version Not Supported" ); |
81 | goto out; |
82 | } |
83 | |
84 | query = strchr (s: escaped, c: '?'); |
85 | if (query != NULL) |
86 | *query++ = 0; |
87 | |
88 | unescaped = g_uri_unescape_string (escaped_string: escaped, NULL); |
89 | path = g_build_filename (first_element: root, unescaped, NULL); |
90 | g_free (mem: unescaped); |
91 | f = g_file_new_for_path (path); |
92 | g_free (mem: path); |
93 | |
94 | error = NULL; |
95 | file_in = g_file_read (file: f, NULL, error: &error); |
96 | if (file_in == NULL) |
97 | { |
98 | send_error (out, error_code: 404, reason: error->message); |
99 | g_error_free (error); |
100 | goto out; |
101 | } |
102 | |
103 | s = g_string_new (init: "HTTP/1.0 200 OK\r\n" ); |
104 | info = g_file_input_stream_query_info (stream: file_in, |
105 | G_FILE_ATTRIBUTE_STANDARD_SIZE "," |
106 | G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, |
107 | NULL, NULL); |
108 | if (info) |
109 | { |
110 | const char *content_type; |
111 | char *mime_type; |
112 | |
113 | if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) |
114 | g_string_append_printf (string: s, format: "Content-Length: %" G_GINT64_FORMAT"\r\n" , |
115 | g_file_info_get_size (info)); |
116 | content_type = g_file_info_get_content_type (info); |
117 | if (content_type) |
118 | { |
119 | mime_type = g_content_type_get_mime_type (type: content_type); |
120 | if (mime_type) |
121 | { |
122 | g_string_append_printf (string: s, format: "Content-Type: %s\r\n" , |
123 | mime_type); |
124 | g_free (mem: mime_type); |
125 | } |
126 | } |
127 | } |
128 | g_string_append (string: s, val: "\r\n" ); |
129 | |
130 | if (g_output_stream_write_all (stream: out, |
131 | buffer: s->str, count: s->len, |
132 | NULL, NULL, NULL)) |
133 | { |
134 | g_output_stream_splice (stream: out, |
135 | G_INPUT_STREAM (file_in), |
136 | flags: 0, NULL, NULL); |
137 | } |
138 | g_string_free (string: s, TRUE); |
139 | |
140 | g_input_stream_close (G_INPUT_STREAM (file_in), NULL, NULL); |
141 | g_object_unref (object: file_in); |
142 | |
143 | out: |
144 | g_object_unref (object: data); |
145 | |
146 | return TRUE; |
147 | } |
148 | |
149 | int |
150 | main (int argc, char *argv[]) |
151 | { |
152 | GSocketService *service; |
153 | GOptionContext *context; |
154 | GError *error = NULL; |
155 | |
156 | context = g_option_context_new (parameter_string: "<http root dir> - Simple HTTP server" ); |
157 | g_option_context_add_main_entries (context, entries: cmd_entries, NULL); |
158 | if (!g_option_context_parse (context, argc: &argc, argv: &argv, error: &error)) |
159 | { |
160 | g_printerr (format: "%s: %s\n" , argv[0], error->message); |
161 | return 1; |
162 | } |
163 | |
164 | if (argc != 2) |
165 | { |
166 | g_printerr (format: "Root directory not specified\n" ); |
167 | return 1; |
168 | } |
169 | |
170 | root = g_strdup (str: argv[1]); |
171 | |
172 | service = g_threaded_socket_service_new (max_threads: 10); |
173 | if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), |
174 | port, |
175 | NULL, |
176 | error: &error)) |
177 | { |
178 | g_printerr (format: "%s: %s\n" , argv[0], error->message); |
179 | return 1; |
180 | } |
181 | |
182 | g_print (format: "Http server listening on port %d\n" , port); |
183 | |
184 | g_signal_connect (service, "run" , G_CALLBACK (handler), NULL); |
185 | |
186 | g_main_loop_run (loop: g_main_loop_new (NULL, FALSE)); |
187 | g_assert_not_reached (); |
188 | } |
189 | |