2 * Copyright (C) 2008-2010 Nokia Corporation.
3 * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
5 * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
6 * <zeeshan.ali@nokia.com>
7 * Jorn Baayen <jorn.baayen@gmail.com>
9 * This file is part of Rygel.
11 * Rygel is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * Rygel is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 * Responsible for handling HTTP GET & HEAD client requests.
31 internal class Rygel.HTTPGet : HTTPRequest {
32 private const string TRANSFER_MODE_HEADER = "transferMode.dlna.org";
34 public Thumbnail thumbnail;
35 public Subtitle subtitle;
38 private int thumbnail_index;
39 private int subtitle_index;
41 public HTTPGetHandler handler;
43 public HTTPGet (HTTPServer http_server,
46 base (http_server, server, msg);
48 this.thumbnail_index = -1;
49 this.subtitle_index = -1;
52 protected override async void handle () throws Error {
53 var header = this.msg.request_headers.get_one
54 ("getcontentFeatures.dlna.org");
56 /* We only entertain 'HEAD' and 'GET' requests */
57 if ((this.msg.method != "HEAD" && this.msg.method != "GET") ||
58 (header != null && header != "1")) {
59 throw new HTTPRequestError.BAD_REQUEST (_("Invalid Request"));
62 if (uri.transcode_target != null) {
63 var transcoder = this.http_server.get_transcoder
64 (uri.transcode_target);
65 this.handler = new HTTPTranscodeHandler (transcoder,
69 if (this.handler == null) {
70 this.handler = new HTTPIdentityHandler (this.cancellable);
73 this.ensure_correct_mode ();
75 yield this.handle_item_request ();
78 protected override async void find_item () throws Error {
79 yield base.find_item ();
81 if (unlikely (this.item.place_holder)) {
82 throw new HTTPRequestError.NOT_FOUND ("Item '%s' is empty",
87 var hack = ClientHacks.create_for_headers
88 (this.msg.request_headers);
89 if (hack.is_album_art_request (this.msg) &&
90 this.item is VisualItem) {
91 var visual_item = this.item as VisualItem;
93 if (visual_item.thumbnails.size <= 0) {
94 throw new HTTPRequestError.NOT_FOUND ("No Thumbnail " +
100 this.thumbnail = visual_item.thumbnails.get (0);
104 hack.apply (this.item);
106 } catch (ClientHacksError error) {}
108 if (this.uri.thumbnail_index >= 0) {
109 if (this.item is MusicItem) {
110 var music = this.item as MusicItem;
112 this.thumbnail = music.album_art;
113 } else if (this.item is VisualItem) {
114 var visual = this.item as VisualItem;
116 this.thumbnail = visual.thumbnails.get
117 (this.uri.thumbnail_index);
119 throw new HTTPRequestError.NOT_FOUND
120 ("No Thumbnail available for item '%s",
123 } else if (this.uri.subtitle_index >= 0) {
124 if (!(this.item is VideoItem)) {
125 throw new HTTPRequestError.NOT_FOUND
126 ("No subtitles available for item '%s",
130 this.subtitle = (this.item as VideoItem).subtitles.get
131 (this.uri.subtitle_index);
135 private async void handle_item_request () throws Error {
136 var need_time_seek = HTTPTimeSeek.needed (this);
137 var need_byte_seek = HTTPByteSeek.needed (this);
139 if ((HTTPTimeSeek.requested (this) && !need_time_seek) ||
140 (HTTPByteSeek.requested (this) && !need_byte_seek)) {
141 throw new HTTPRequestError.UNACCEPTABLE ("Invalid seek request");
145 if (need_time_seek) {
146 this.seek = new HTTPTimeSeek (this);
147 } else if (need_byte_seek) {
148 this.seek = new HTTPByteSeek (this);
150 } catch (HTTPSeekError error) {
151 this.server.unpause_message (this.msg);
153 if (error is HTTPSeekError.INVALID_RANGE) {
154 this.end (Soup.KnownStatusCode.BAD_REQUEST);
155 } else if (error is HTTPSeekError.OUT_OF_RANGE) {
156 this.end (Soup.KnownStatusCode.REQUESTED_RANGE_NOT_SATISFIABLE);
165 this.handler.add_response_headers (this);
167 // Add general headers
168 if (this.msg.request_headers.get_one ("Range") != null) {
169 this.msg.set_status (Soup.KnownStatusCode.PARTIAL_CONTENT);
171 this.msg.set_status (Soup.KnownStatusCode.OK);
174 if (this.seek != null && this.seek is HTTPByteSeek) {
175 this.msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH);
177 this.msg.response_headers.set_encoding (Soup.Encoding.EOF);
180 debug ("Following HTTP headers appended to response:");
181 this.msg.response_headers.foreach ((name, value) => {
182 debug ("%s : %s", name, value);
185 if (this.msg.method == "HEAD") {
186 // Only headers requested, no need to send contents
187 this.server.unpause_message (this.msg);
192 var response = this.handler.render_body (this);
194 yield response.run ();
196 this.end (Soup.KnownStatusCode.NONE);
199 private void ensure_correct_mode () throws HTTPRequestError {
200 var mode = this.msg.request_headers.get_one (TRANSFER_MODE_HEADER);
205 correct = this.handler is HTTPTranscodeHandler ||
206 (this.item.streamable () &&
207 this.subtitle == null &&
208 this.thumbnail == null);
212 correct = this.handler is HTTPIdentityHandler &&
213 ((!this.item.is_live_stream () &&
214 !this.item.streamable ()) ||
215 (this.subtitle != null ||
216 this.thumbnail != null));
222 throw new HTTPRequestError.UNACCEPTABLE
223 ("%s mode not supported for '%s'",