static void server_callback (SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *client, gpointer user_data) { RemoteDisplayHost *host = user_data; RemoteDisplayHostPrivate *priv = GET_PRIVATE (host); RemoteDisplayHostFile *file; if (!client_allowed (host, client)) { g_debug ("Client %s not allowed", soup_client_context_get_host (client)); soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); return; } if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_HEAD) { g_debug ("Method is not GET or HEAD"); soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } if (path == NULL || *path != '/') { g_debug ("Invalid path '%s'requested", path); soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); return; } file = g_hash_table_lookup (priv->files, path + 1); if (!file) { soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); return; } if (!file->mapped_file) { file->mapped_file = g_mapped_file_new (file->path, FALSE, NULL); if (!file->mapped_file) { soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); return; } } if (msg->method == SOUP_METHOD_GET) { soup_message_set_response (msg, file->mime_type, SOUP_MEMORY_STATIC, g_mapped_file_get_contents (file->mapped_file), g_mapped_file_get_length (file->mapped_file)); } else { soup_message_headers_set_content_type (msg->response_headers, file->mime_type, NULL); soup_message_headers_set_content_length (msg->response_headers, g_mapped_file_get_length (file->mapped_file)); } soup_message_set_status(msg, SOUP_STATUS_OK); }
static void test_http_callback( SoupServer* server, SoupMessage* msg, const char* path, GHashTable* query, SoupClientContext* context, gpointer data) { char* uri = soup_uri_to_string(soup_message_get_uri (msg), FALSE); MMS_VERBOSE("%s %s HTTP/1.%d", msg->method, uri, soup_message_get_http_version(msg)); g_free(uri); if (msg->method == SOUP_METHOD_CONNECT) { soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED); } else { TestHttp* http = data; if (msg->request_body->length) { SoupBuffer* request = soup_message_body_flatten(msg->request_body); if (http->req_bytes) g_bytes_unref(http->req_bytes); http->req_bytes = g_bytes_new_with_free_func(request->data, request->length, (GDestroyNotify)soup_buffer_free, request); } soup_message_set_status(msg, http->resp_status); soup_message_headers_set_content_type(msg->response_headers, http->resp_content_type ? http->resp_content_type : "text/plain", NULL); soup_message_headers_append(msg->response_headers, "Accept-Ranges", "bytes"); soup_message_headers_append(msg->response_headers, "Connection", "close"); if (http->resp_file) { soup_message_headers_set_content_length(msg->response_headers, g_mapped_file_get_length(http->resp_file)); soup_message_body_append(msg->response_body, SOUP_MEMORY_TEMPORARY, g_mapped_file_get_contents(http->resp_file), g_mapped_file_get_length(http->resp_file)); } else { soup_message_headers_set_content_length(msg->response_headers, 0); } } soup_message_body_complete(msg->request_body); }
static void send_chunked_file (SoupServer * server, SoupMessage * message, DPAPRecord * record, guint64 filesize) { GInputStream *stream; const char *location; GError *error = NULL; ChunkData *cd = g_new (ChunkData, 1); g_object_get (record, "location", &location, NULL); cd->server = server; stream = G_INPUT_STREAM (dpap_record_read (record, &error)); if (error != NULL) { g_warning ("Couldn't open %s: %s.", location, error->message); g_error_free (error); soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR); g_free (cd); return; } cd->stream = stream; if (cd->stream == NULL) { g_warning ("Could not set up input stream"); g_free (cd); return; } soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CONTENT_LENGTH); soup_message_headers_set_content_length (message->response_headers, filesize); soup_message_headers_append (message->response_headers, "Connection", "Close"); soup_message_headers_append (message->response_headers, "Content-Type", "application/x-dmap-tagged"); g_signal_connect (message, "wrote_headers", G_CALLBACK (dmap_write_next_chunk), cd); g_signal_connect (message, "wrote_chunk", G_CALLBACK (dmap_write_next_chunk), cd); g_signal_connect (message, "finished", G_CALLBACK (dmap_chunked_message_finished), cd); /* NOTE: cd g_free'd by chunked_message_finished(). */ }
static void get_response_headers (SoupMessage *msg, GString *headers, SoupEncoding *encoding, gpointer user_data) { SoupEncoding claimed_encoding; SoupMessageHeadersIter iter; const char *name, *value; handle_partial_get (msg); g_string_append_printf (headers, "HTTP/1.%c %d %s\r\n", soup_message_get_http_version (msg) == SOUP_HTTP_1_0 ? '0' : '1', msg->status_code, msg->reason_phrase); claimed_encoding = soup_message_headers_get_encoding (msg->response_headers); if ((msg->method == SOUP_METHOD_HEAD || msg->status_code == SOUP_STATUS_NO_CONTENT || msg->status_code == SOUP_STATUS_NOT_MODIFIED || SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) || (msg->method == SOUP_METHOD_CONNECT && SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))) *encoding = SOUP_ENCODING_NONE; else *encoding = claimed_encoding; if (claimed_encoding == SOUP_ENCODING_CONTENT_LENGTH && !soup_message_headers_get_content_length (msg->response_headers)) { soup_message_headers_set_content_length (msg->response_headers, msg->response_body->length); } soup_message_headers_iter_init (&iter, msg->response_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) g_string_append_printf (headers, "%s: %s\r\n", name, value); g_string_append (headers, "\r\n"); }
static void do_get (OtTrivialHttpd *self, SoupServer *server, SoupMessage *msg, const char *path, SoupClientContext *context) { char *slash; int ret; struct stat stbuf; httpd_log (self, "serving %s\n", path); if (opt_expected_cookies) { GSList *cookies = soup_cookies_from_request (msg); GSList *l; int i; for (i = 0 ; opt_expected_cookies[i] != NULL; i++) { gboolean found = FALSE; gchar *k = opt_expected_cookies[i]; gchar *v = strchr (k, '=') + 1; for (l = cookies; l != NULL ; l = g_slist_next (l)) { SoupCookie *c = l->data; if (!strncmp (k, soup_cookie_get_name (c), v - k - 1) && !strcmp (v, soup_cookie_get_value (c))) { found = TRUE; break; } } if (!found) { httpd_log (self, "Expected cookie not found %s\n", k); soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); soup_cookies_free (cookies); goto out; } } soup_cookies_free (cookies); } if (opt_expected_headers) { for (int i = 0 ; opt_expected_headers[i] != NULL; i++) { const gchar *kv = opt_expected_headers[i]; const gchar *eq = strchr (kv, '='); g_assert (eq); { g_autofree char *k = g_strndup (kv, eq - kv); const gchar *expected_v = eq + 1; const gchar *found_v = soup_message_headers_get_one (msg->request_headers, k); if (!found_v) { httpd_log (self, "Expected header not found %s\n", k); soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } if (strcmp (found_v, expected_v) != 0) { httpd_log (self, "Expected header %s: %s but found %s\n", k, expected_v, found_v); soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } } } } if (strstr (path, "../") != NULL) { soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } if (opt_random_500s_percentage > 0 && emitted_random_500s_count < opt_random_500s_max && g_random_int_range (0, 100) < opt_random_500s_percentage) { emitted_random_500s_count++; soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } while (path[0] == '/') path++; do ret = fstatat (self->root_dfd, path, &stbuf, 0); while (ret == -1 && errno == EINTR); if (ret == -1) { if (errno == EPERM) soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); else if (errno == ENOENT) soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); else soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } if (!is_safe_to_access (&stbuf)) { soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } if (S_ISDIR (stbuf.st_mode)) { slash = strrchr (path, '/'); if (!slash || slash[1]) { g_autofree char *redir_uri = NULL; redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path); soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY, redir_uri); } else { g_autofree char *index_realpath = g_strconcat (path, "/index.html", NULL); if (fstatat (self->root_dfd, index_realpath, &stbuf, 0) != -1) { g_autofree char *index_path = g_strconcat (path, "/index.html", NULL); do_get (self, server, msg, index_path, context); } else { GString *listing = get_directory_listing (self->root_dfd, path); soup_message_set_response (msg, "text/html", SOUP_MEMORY_TAKE, listing->str, listing->len); soup_message_set_status (msg, SOUP_STATUS_OK); g_string_free (listing, FALSE); } } } else { if (!S_ISREG (stbuf.st_mode)) { soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } if (msg->method == SOUP_METHOD_GET) { glnx_autofd int fd = -1; g_autoptr(GMappedFile) mapping = NULL; gsize buffer_length, file_size; SoupRange *ranges; int ranges_length; gboolean have_ranges; fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC); if (fd < 0) { soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL); if (!mapping) { soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } (void) close (fd); fd = -1; file_size = g_mapped_file_get_length (mapping); have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length); if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL) { SoupSocket *sock; buffer_length = file_size/2; soup_message_headers_set_content_length (msg->response_headers, file_size); soup_message_headers_append (msg->response_headers, "Connection", "close"); /* soup-message-io will wait for us to add * another chunk after the first, to fill out * the declared Content-Length. Instead, we * forcibly close the socket at that point. */ sock = soup_client_context_get_socket (context); g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock); } else buffer_length = file_size; if (have_ranges) { if (ranges_length > 0 && ranges[0].start >= file_size) { soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE); soup_message_headers_free_ranges (msg->request_headers, ranges); goto out; } soup_message_headers_free_ranges (msg->request_headers, ranges); } if (buffer_length > 0) { SoupBuffer *buffer; buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping), buffer_length, g_mapped_file_ref (mapping), (GDestroyNotify)g_mapped_file_unref); soup_message_body_append_buffer (msg->response_body, buffer); soup_buffer_free (buffer); } } else /* msg->method == SOUP_METHOD_HEAD */ { g_autofree char *length = NULL; /* We could just use the same code for both GET and * HEAD (soup-message-server-io.c will fix things up). * But we'll optimize and avoid the extra I/O. */ length = g_strdup_printf ("%lu", (gulong)stbuf.st_size); soup_message_headers_append (msg->response_headers, "Content-Length", length); } soup_message_set_status (msg, SOUP_STATUS_OK); } out: { guint status = 0; g_autofree gchar *reason = NULL; g_object_get (msg, "status-code", &status, "reason-phrase", &reason, NULL); httpd_log (self, " status: %s (%u)\n", reason, status); } return; }
static void korva_upnp_file_server_handle_request (SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *client, gpointer user_data) { KorvaUPnPFileServer *self = KORVA_UPNP_FILE_SERVER (user_data); GMatchInfo *info; char *id; GFile *file; KorvaUPnPHostData *data; ServeData *serve_data; SoupRange *ranges = NULL; int length; const char *content_features; GError *error = NULL; goffset size; if (msg->method != SOUP_METHOD_HEAD && msg->method != SOUP_METHOD_GET) { soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED); return; } g_debug ("Got %s request for uri: %s", msg->method, path); soup_message_headers_foreach (msg->request_headers, print_header, NULL); soup_server_pause_message (server, msg); soup_message_set_status (msg, SOUP_STATUS_OK); if (!g_regex_match (self->priv->path_regex, path, 0, &info)) { soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); g_match_info_free (info); goto out; } id = g_match_info_fetch (info, 1); g_match_info_free (info); file = g_hash_table_lookup (self->priv->id_map, id); g_free (id); if (file == NULL) { soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); goto out; } data = g_hash_table_lookup (self->priv->host_data, file); if (data == NULL) { soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); goto out; } if (!korva_upnp_host_data_valid_for_peer (data, soup_client_context_get_host (client))) { soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); goto out; } serve_data = g_slice_new0 (ServeData); serve_data->host_data = data; g_object_add_weak_pointer (G_OBJECT (data), (gpointer *) &(serve_data->host_data)); korva_upnp_host_data_add_request (data); size = korva_upnp_host_data_get_size (data); if (soup_message_headers_get_ranges (msg->request_headers, size, &ranges, &length)) { goffset start, end; start = ranges[0].start; end = ranges[0].end; if (start > size || start > end || end > size) { soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE); g_slice_free (ServeData, serve_data); goto out; } else { soup_message_set_status (msg, SOUP_STATUS_PARTIAL_CONTENT); soup_message_headers_set_content_range (msg->response_headers, start, end, size); } serve_data->start = start; serve_data->end = end; } else { serve_data->start = 0; serve_data->end = size - 1; soup_message_set_status (msg, SOUP_STATUS_OK); } soup_message_headers_set_content_length (msg->response_headers, serve_data->end - serve_data->start + 1); soup_message_headers_set_content_type (msg->response_headers, korva_upnp_host_data_get_content_type (data), NULL); content_features = soup_message_headers_get_one (msg->request_headers, "getContentFeatures.dlna.org"); if (content_features != NULL && atol (content_features) == 1) { const GVariant *value; value = korva_upnp_host_data_lookup_meta_data (data, "DLNAProfile"); if (value == NULL) { soup_message_headers_append (msg->response_headers, "contentFeatures.dlna.org", "*"); } else { soup_message_headers_append (msg->response_headers, "contentFeatures.dlna.org", korva_upnp_host_data_get_protocol_info (data)); } } soup_message_headers_append (msg->response_headers, "Connection", "close"); g_debug ("Response headers:"); soup_message_headers_foreach (msg->response_headers, print_header, NULL); if (g_ascii_strcasecmp (msg->method, "HEAD") == 0) { g_debug ("Handled HEAD request of %s: %d", path, msg->status_code); g_slice_free (ServeData, serve_data); goto out; } soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CONTENT_LENGTH); soup_message_body_set_accumulate (msg->response_body, FALSE); serve_data->stream = G_INPUT_STREAM (g_file_read (file, NULL, &error)); serve_data->server = server; if (error != NULL) { g_warning ("Failed to MMAP file %s: %s", path, error->message); g_error_free (error); g_slice_free (ServeData, serve_data); soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } g_seekable_seek (G_SEEKABLE (serve_data->stream), serve_data->start, G_SEEK_SET, NULL, NULL); /* Drop timeout until the message is done */ korva_upnp_host_data_cancel_timeout (data); g_signal_connect (msg, "wrote-chunk", G_CALLBACK (korva_upnp_file_server_on_wrote_chunk), serve_data); g_signal_connect (msg, "wrote-headers", G_CALLBACK (korva_upnp_file_server_on_wrote_chunk), serve_data); g_signal_connect (msg, "finished", G_CALLBACK (korva_upnp_file_server_on_finished), serve_data); out: if (ranges != NULL) { soup_message_headers_free_ranges (msg->request_headers, ranges); } soup_server_unpause_message (server, msg); }
static void prv_soup_server_cb(SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *client, gpointer user_data) { dlr_host_file_t *hf; dlr_host_server_t *hs = user_data; const gchar *file_name; const char *hdr; if ((msg->method != SOUP_METHOD_GET) && (msg->method != SOUP_METHOD_HEAD)) { soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED); goto on_error; } hf = prv_host_server_find_file(hs, path, &file_name); if (!hf) { soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND); goto on_error; } hdr = soup_message_headers_get_one(msg->request_headers, "getContentFeatures.dlna.org"); if (hdr) { if (strcmp(hdr, "1") != 0) { soup_message_set_status(msg, SOUP_STATUS_BAD_REQUEST); goto on_error; } if ((hf->dlna_header) && strlen(hf->dlna_header) > 0) soup_message_headers_append(msg->response_headers, "contentFeatures.dlna.org", hf->dlna_header); } if (hf->mapped_file) { g_mapped_file_ref(hf->mapped_file); ++hf->mapped_count; } else { hf->mapped_file = g_mapped_file_new(file_name, FALSE, NULL); if (!hf->mapped_file) { soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND); goto on_error; } hf->mapped_count = 1; } if (msg->method == SOUP_METHOD_GET) { g_signal_connect(msg, "finished", G_CALLBACK(prv_soup_message_finished_cb), hf); soup_message_set_response( msg, hf->mime_type, SOUP_MEMORY_STATIC, g_mapped_file_get_contents(hf->mapped_file), g_mapped_file_get_length(hf->mapped_file)); } else { soup_message_headers_set_content_type(msg->response_headers, hf->mime_type, NULL); soup_message_headers_set_content_length( msg->response_headers, g_mapped_file_get_length(hf->mapped_file)); } soup_message_set_status(msg, SOUP_STATUS_OK); on_error: return; }
static void server_callback (SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *context, gpointer data) { /* The way this gets used in the tests, we don't actually * need to hold it through the whole function, so it's simpler * to just release it right away. */ g_mutex_lock (&server_mutex); g_mutex_unlock (&server_mutex); if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } if (g_str_has_prefix (path, "/content-length/")) { gboolean too_long = strcmp (path, "/content-length/long") == 0; gboolean no_close = strcmp (path, "/content-length/noclose") == 0; soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "foobar", 6); if (too_long) soup_message_headers_set_content_length (msg->response_headers, 9); soup_message_headers_append (msg->response_headers, "Connection", "close"); if (too_long) { SoupSocket *sock; /* soup-message-io will wait for us to add * another chunk after the first, to fill out * the declared Content-Length. Instead, we * forcibly close the socket at that point. */ sock = soup_client_context_get_socket (context); g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock); } else if (no_close) { /* Remove the 'Connection: close' after writing * the headers, so that when we check it after * writing the body, we'll think we aren't * supposed to close it. */ g_signal_connect (msg, "wrote-headers", G_CALLBACK (forget_close), NULL); } return; } if (!strcmp (path, "/timeout-persistent")) { SoupSocket *sock; sock = soup_client_context_get_socket (context); setup_timeout_persistent (server, sock); } soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "index", 5); return; }
static void gss_adaptive_resource_get_dash_range_fragment (GssTransaction * t, GssAdaptive * adaptive, const char *path) { gboolean have_range; SoupRange *ranges; int n_ranges; int index; GssAdaptiveLevel *level; gsize start, end; /* skip over content/ */ path += 8; if (path[0] != 'a' && path[0] != 'v') { GST_ERROR ("bad path: %s", path); return; } index = strtoul (path + 1, NULL, 10); level = NULL; if (path[0] == 'a') { if (index < adaptive->n_audio_levels) { level = &adaptive->audio_levels[index]; } } else { if (index < adaptive->n_video_levels) { level = &adaptive->video_levels[index]; } } if (level == NULL) { GST_ERROR ("bad level: %c%d from path %s", path[0], index, path); return; } if (t->msg->method == SOUP_METHOD_HEAD) { GST_DEBUG ("%s: HEAD", path); soup_message_headers_set_content_length (t->msg->response_headers, level->track->dash_size); return; } have_range = soup_message_headers_get_ranges (t->msg->request_headers, level->track->dash_size, &ranges, &n_ranges); if (have_range) { if (n_ranges != 1) { GST_ERROR ("too many ranges"); } start = ranges[0].start; end = ranges[0].end + 1; } else { start = 0; end = level->track->dash_size; } GST_DEBUG ("%s: range: %ld-%ld", path, start, end); t->start = start; t->end = end; if (have_range) { soup_message_headers_set_content_range (t->msg->response_headers, ranges[0].start, ranges[0].end, level->track->dash_size); soup_message_set_status (t->msg, SOUP_STATUS_PARTIAL_CONTENT); soup_message_headers_free_ranges (t->msg->response_headers, ranges); } else { soup_message_set_status (t->msg, SOUP_STATUS_OK); } soup_message_headers_replace (t->msg->response_headers, "Content-Type", (path[0] == 'v') ? "video/mp4" : "audio/mp4"); { GssAdaptiveQuery *query; soup_server_pause_message (t->soupserver, t->msg); query = g_malloc0 (sizeof (GssAdaptiveQuery)); query->adaptive = adaptive; query->level = level; gss_transaction_process_async (t, gss_adaptive_dash_range_async, gss_adaptive_dash_range_async_finish, query); } }
static void get_request_headers (SoupMessage *req, GString *header, SoupEncoding *encoding, gpointer user_data) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); gboolean proxy = GPOINTER_TO_UINT (user_data); SoupURI *uri = soup_message_get_uri (req); char *uri_host; char *uri_string; SoupMessageHeadersIter iter; const char *name, *value; if (strchr (uri->host, ':')) uri_host = g_strdup_printf ("[%s]", uri->host); else uri_host = uri->host; if (req->method == SOUP_METHOD_CONNECT) { /* CONNECT URI is hostname:port for tunnel destination */ uri_string = g_strdup_printf ("%s:%d", uri_host, uri->port); } else { /* Proxy expects full URI to destination. Otherwise * just the path. */ uri_string = soup_uri_to_string (uri, !proxy); } if (priv->http_version == SOUP_HTTP_1_0) { g_string_append_printf (header, "%s %s HTTP/1.0\r\n", req->method, uri_string); } else { g_string_append_printf (header, "%s %s HTTP/1.1\r\n", req->method, uri_string); if (!soup_message_headers_get_one (req->request_headers, "Host")) { if (soup_uri_uses_default_port (uri)) { g_string_append_printf (header, "Host: %s\r\n", uri_host); } else { g_string_append_printf (header, "Host: %s:%d\r\n", uri_host, uri->port); } } } g_free (uri_string); if (uri_host != uri->host) g_free (uri_host); *encoding = soup_message_headers_get_encoding (req->request_headers); if ((*encoding == SOUP_ENCODING_CONTENT_LENGTH || *encoding == SOUP_ENCODING_NONE) && (req->request_body->length > 0 || soup_message_headers_get_one (req->request_headers, "Content-Type")) && !soup_message_headers_get_content_length (req->request_headers)) { *encoding = SOUP_ENCODING_CONTENT_LENGTH; soup_message_headers_set_content_length (req->request_headers, req->request_body->length); } soup_message_headers_iter_init (&iter, req->request_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) g_string_append_printf (header, "%s: %s\r\n", name, value); g_string_append (header, "\r\n"); }
static void send_chunked_file (SoupServer * server, SoupMessage * message, DAAPRecord * record, guint64 filesize, guint64 offset, const gchar * transcode_mimetype) { gchar *format = NULL; gchar *location = NULL; GInputStream *stream = NULL; gboolean has_video; GError *error = NULL; ChunkData *cd = NULL; cd = g_new (ChunkData, 1); if (NULL == cd) { g_warning ("Error allocating chunk\n"); goto _error; } g_object_get (record, "location", &location, "has-video", &has_video, NULL); if (NULL == location) { g_warning ("Error getting location from record\n"); goto _error; } /* FIXME: This crashes on powerpc-440fp-linux-gnu: * g_debug ("Sending %s chunked from offset %" G_GUINT64_FORMAT ".", location, offset); */ cd->server = server; stream = G_INPUT_STREAM (daap_record_read (record, &error)); if (error != NULL) { g_warning ("Couldn't open %s: %s.", location, error->message); goto _error; } g_object_get (record, "format", &format, NULL); if (NULL == format) { g_warning ("Error getting format from record\n"); goto _error; } // Not presently transcoding videos (see also same comments elsewhere). if (should_transcode (format, has_video, transcode_mimetype)) { #ifdef HAVE_GSTREAMERAPP cd->stream = dmap_gst_input_stream_new (transcode_mimetype, stream); #else g_warning ("Transcode format %s not supported", transcode_mimetype); cd->stream = stream; #endif /* HAVE_GSTREAMERAPP */ } else { g_debug ("Not transcoding %s", location); cd->stream = stream; } if (cd->stream == NULL) { g_warning ("Could not set up input stream"); goto _error; } if (offset != 0) { if (g_seekable_seek (G_SEEKABLE (cd->stream), offset, G_SEEK_SET, NULL, &error) == FALSE) { g_warning ("Error seeking: %s.", error->message); goto _error; } filesize -= offset; } /* Free memory after each chunk sent out over network. */ soup_message_body_set_accumulate (message->response_body, FALSE); if (! should_transcode (format, has_video, transcode_mimetype)) { /* NOTE: iTunes seems to require this or it stops reading * video data after about 2.5MB. Perhaps this is so iTunes * knows how much data to buffer. */ g_debug ("Using HTTP 1.1 content length encoding."); soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CONTENT_LENGTH); /* NOTE: iTunes 8 (and other versions?) will not seek * properly without a Content-Length header. */ g_debug ("Content length is %" G_GUINT64_FORMAT ".", filesize); soup_message_headers_set_content_length (message->response_headers, filesize); } else if (soup_message_get_http_version (message) == SOUP_HTTP_1_0) { /* NOTE: Roku clients support only HTTP 1.0. */ #ifdef HAVE_ENCODING_EOF g_debug ("Using HTTP 1.0 encoding."); soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_EOF); #else g_warning ("Received HTTP 1.0 request, but not built with HTTP 1.0 support"); soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CHUNKED); #endif } else { /* NOTE: Can not provide Content-Length when performing * real-time transcoding. */ g_debug ("Using HTTP 1.1 chunked encoding."); soup_message_headers_set_encoding (message->response_headers, SOUP_ENCODING_CHUNKED); } soup_message_headers_append (message->response_headers, "Connection", "Close"); soup_message_headers_append (message->response_headers, "Content-Type", "application/x-dmap-tagged"); g_signal_connect (message, "wrote_headers", G_CALLBACK (dmap_write_next_chunk), cd); g_signal_connect (message, "wrote_chunk", G_CALLBACK (dmap_write_next_chunk), cd); g_signal_connect (message, "finished", G_CALLBACK (dmap_chunked_message_finished), cd); /* NOTE: cd g_free'd by chunked_message_finished(). */ return; _error: soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR); if (NULL != cd) { g_free (cd); } if (NULL != format) { g_free (format); } if (NULL != location) { g_free (location); } if (NULL != error) { g_error_free (error); } if (NULL != cd->stream) { g_input_stream_close (cd->stream, NULL, NULL); } if (NULL != stream) { g_input_stream_close (cd->stream, NULL, NULL); } return; }
static void server_callback (SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *context, gpointer data) { SoupURI *uri = soup_message_get_uri (msg); soup_message_headers_append (msg->response_headers, "X-Handled-By", "server_callback"); if (!strcmp (path, "*")) { debug_printf (1, " default server_callback got request for '*'!\n"); errors++; soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); return; } if (msg->method != SOUP_METHOD_GET) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } if (!strcmp (path, "/redirect")) { soup_message_set_status (msg, SOUP_STATUS_FOUND); soup_message_headers_append (msg->response_headers, /* Kids: don't try this at home! * RFC2616 says to use an * absolute URI! */ "Location", "/"); return; } if (g_str_has_prefix (path, "/content-length/")) { gboolean too_long = strcmp (path, "/content-length/long") == 0; gboolean no_close = strcmp (path, "/content-length/noclose") == 0; soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "foobar", 6); if (too_long) soup_message_headers_set_content_length (msg->response_headers, 9); soup_message_headers_append (msg->response_headers, "Connection", "close"); if (too_long) { SoupSocket *sock; /* soup-message-io will wait for us to add * another chunk after the first, to fill out * the declared Content-Length. Instead, we * forcibly close the socket at that point. */ sock = soup_client_context_get_socket (context); g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock); } else if (no_close) { /* Remove the 'Connection: close' after writing * the headers, so that when we check it after * writing the body, we'll think we aren't * supposed to close it. */ g_signal_connect (msg, "wrote-headers", G_CALLBACK (forget_close), NULL); } return; } if (!strcmp (path, "/timeout-persistent")) { SoupSocket *sock; /* We time out the persistent connection as soon as * the client starts writing the next request. */ sock = soup_client_context_get_socket (context); g_signal_connect (sock, "readable", G_CALLBACK (timeout_socket), NULL); } soup_message_set_status (msg, SOUP_STATUS_OK); if (!strcmp (uri->host, "foo")) { soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "foo-index", 9); return; } else { soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "index", 5); return; } }
static void do_get (OtTrivialHttpd *self, SoupServer *server, SoupMessage *msg, const char *path, SoupClientContext *context) { char *slash; int ret; struct stat stbuf; g_autofree char *safepath = NULL; httpd_log (self, "serving %s\n", path); if (strstr (path, "../") != NULL) { soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } if (opt_random_500s_percentage > 0 && emitted_random_500s_count < opt_random_500s_max && g_random_int_range (0, 100) < opt_random_500s_percentage) { emitted_random_500s_count++; soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } if (path[0] == '/') path++; safepath = g_build_filename (gs_file_get_path_cached (self->root), path, NULL); do ret = stat (safepath, &stbuf); while (ret == -1 && errno == EINTR); if (ret == -1) { if (errno == EPERM) soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); else if (errno == ENOENT) soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); else soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } if (!is_safe_to_access (&stbuf)) { soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } if (S_ISDIR (stbuf.st_mode)) { slash = strrchr (safepath, '/'); if (!slash || slash[1]) { g_autofree char *redir_uri = NULL; redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path); soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY, redir_uri); } else { g_autofree char *index_realpath = g_strconcat (safepath, "/index.html", NULL); if (stat (index_realpath, &stbuf) != -1) { g_autofree char *index_path = g_strconcat (path, "/index.html", NULL); do_get (self, server, msg, index_path, context); } else { GString *listing = get_directory_listing (safepath); soup_message_set_response (msg, "text/html", SOUP_MEMORY_TAKE, listing->str, listing->len); soup_message_set_status (msg, SOUP_STATUS_OK); g_string_free (listing, FALSE); } } } else { if (!S_ISREG (stbuf.st_mode)) { soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); goto out; } if (msg->method == SOUP_METHOD_GET) { g_autoptr(GMappedFile) mapping = NULL; gsize buffer_length, file_size; SoupRange *ranges; int ranges_length; gboolean have_ranges; mapping = g_mapped_file_new (safepath, FALSE, NULL); if (!mapping) { soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); goto out; } file_size = g_mapped_file_get_length (mapping); have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length); if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL) { SoupSocket *sock; buffer_length = file_size/2; soup_message_headers_set_content_length (msg->response_headers, file_size); soup_message_headers_append (msg->response_headers, "Connection", "close"); /* soup-message-io will wait for us to add * another chunk after the first, to fill out * the declared Content-Length. Instead, we * forcibly close the socket at that point. */ sock = soup_client_context_get_socket (context); g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock); } else buffer_length = file_size; if (have_ranges) { if (ranges_length > 0 && ranges[0].start >= file_size) { soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE); soup_message_headers_free_ranges (msg->request_headers, ranges); goto out; } soup_message_headers_free_ranges (msg->request_headers, ranges); } if (buffer_length > 0) { SoupBuffer *buffer; buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping), buffer_length, g_mapped_file_ref (mapping), (GDestroyNotify)g_mapped_file_unref); soup_message_body_append_buffer (msg->response_body, buffer); soup_buffer_free (buffer); } } else /* msg->method == SOUP_METHOD_HEAD */ { g_autofree char *length = NULL; /* We could just use the same code for both GET and * HEAD (soup-message-server-io.c will fix things up). * But we'll optimize and avoid the extra I/O. */ length = g_strdup_printf ("%lu", (gulong)stbuf.st_size); soup_message_headers_append (msg->response_headers, "Content-Length", length); } soup_message_set_status (msg, SOUP_STATUS_OK); } out: { guint status = 0; g_autofree gchar *reason = NULL; g_object_get (msg, "status-code", &status, "reason-phrase", &reason, NULL); httpd_log (self, " status: %s (%u)\n", reason, status); } return; }