97fa2a238e47c2719c4d3f20b4eddae1288b5f9d
[profile/ivi/rygel.git] / src / rygel / rygel-http-get.vala
1 /*
2  * Copyright (C) 2008-2010 Nokia Corporation.
3  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
4  *
5  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
6  *                               <zeeshan.ali@nokia.com>
7  *         Jorn Baayen <jorn.baayen@gmail.com>
8  *
9  * This file is part of Rygel.
10  *
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.
15  *
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.
20  *
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.
24  */
25
26 using Gst;
27
28 /**
29  * Responsible for handling HTTP GET & HEAD client requests.
30  */
31 internal class Rygel.HTTPGet : HTTPRequest {
32     private const string TRANSFER_MODE_HEADER = "transferMode.dlna.org";
33
34     public Thumbnail thumbnail;
35     public Subtitle subtitle;
36     public HTTPSeek seek;
37
38     private int thumbnail_index;
39     private int subtitle_index;
40
41     public HTTPGetHandler handler;
42
43     public HTTPGet (HTTPServer   http_server,
44                     Soup.Server  server,
45                     Soup.Message msg) {
46         base (http_server, server, msg);
47
48         this.thumbnail_index = -1;
49         this.subtitle_index = -1;
50     }
51
52     protected override async void handle () throws Error {
53         var header = this.msg.request_headers.get_one
54                                         ("getcontentFeatures.dlna.org");
55
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"));
60         }
61
62         if (uri.transcode_target != null) {
63             var transcoder = this.http_server.get_transcoder
64                                         (uri.transcode_target);
65             this.handler = new HTTPTranscodeHandler (transcoder,
66                                                      this.cancellable);
67         }
68
69         if (this.handler == null) {
70             this.handler = new HTTPIdentityHandler (this.cancellable);
71         }
72
73         this.ensure_correct_mode ();
74
75         yield this.handle_item_request ();
76     }
77
78     protected override async void find_item () throws Error {
79         yield base.find_item ();
80
81         if (unlikely (this.item.place_holder)) {
82             throw new HTTPRequestError.NOT_FOUND ("Item '%s' is empty",
83                                                   this.item.id);
84         }
85
86         try {
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;
92
93                 if (visual_item.thumbnails.size <= 0) {
94                     throw new HTTPRequestError.NOT_FOUND ("No Thumbnail " +
95                                                           "available for " +
96                                                           "item '%s'",
97                                                           visual_item.id);
98                 }
99
100                 this.thumbnail = visual_item.thumbnails.get (0);
101
102                 return;
103             } else {
104                 hack.apply (this.item);
105             }
106         } catch (ClientHacksError error) {}
107
108         if (this.uri.thumbnail_index >= 0) {
109             if (this.item is MusicItem) {
110                 var music = this.item as MusicItem;
111
112                 this.thumbnail = music.album_art;
113             } else if (this.item is VisualItem) {
114                 var visual = this.item as VisualItem;
115
116                 this.thumbnail = visual.thumbnails.get
117                                         (this.uri.thumbnail_index);
118             } else {
119                 throw new HTTPRequestError.NOT_FOUND
120                                         ("No Thumbnail available for item '%s",
121                                          this.item.id);
122             }
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",
127                                          this.item.id);
128             }
129
130             this.subtitle = (this.item as VideoItem).subtitles.get
131                                         (this.uri.subtitle_index);
132         }
133     }
134
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);
138
139         if ((HTTPTimeSeek.requested (this) && !need_time_seek) ||
140             (HTTPByteSeek.requested (this) && !need_byte_seek)) {
141             throw new HTTPRequestError.UNACCEPTABLE ("Invalid seek request");
142         }
143
144         try {
145             if (need_time_seek) {
146                 this.seek = new HTTPTimeSeek (this);
147             } else if (need_byte_seek) {
148                 this.seek = new HTTPByteSeek (this);
149             }
150         } catch (HTTPSeekError error) {
151             this.server.unpause_message (this.msg);
152
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);
157             } else {
158                 throw error;
159             }
160
161             return;
162         }
163
164         // Add headers
165         this.handler.add_response_headers (this);
166
167         // Add general headers
168         if (this.msg.request_headers.get_one ("Range") != null) {
169             this.msg.set_status (Soup.KnownStatusCode.PARTIAL_CONTENT);
170         } else {
171             this.msg.set_status (Soup.KnownStatusCode.OK);
172         }
173
174         if (this.seek != null && this.seek is HTTPByteSeek) {
175             this.msg.response_headers.set_encoding (Soup.Encoding.CONTENT_LENGTH);
176         } else {
177             this.msg.response_headers.set_encoding (Soup.Encoding.EOF);
178         }
179
180         debug ("Following HTTP headers appended to response:");
181         this.msg.response_headers.foreach ((name, value) => {
182             debug ("%s : %s", name, value);
183         });
184
185         if (this.msg.method == "HEAD") {
186             // Only headers requested, no need to send contents
187             this.server.unpause_message (this.msg);
188
189             return;
190         }
191
192         var response = this.handler.render_body (this);
193
194         yield response.run ();
195
196         this.end (Soup.KnownStatusCode.NONE);
197     }
198
199     private void ensure_correct_mode () throws HTTPRequestError {
200         var mode = this.msg.request_headers.get_one (TRANSFER_MODE_HEADER);
201         var correct = true;
202
203         switch (mode) {
204         case "Streaming":
205             correct = this.handler is HTTPTranscodeHandler ||
206                       (this.item.streamable () &&
207                        this.subtitle == null &&
208                        this.thumbnail == null);
209
210             break;
211         case "Interactive":
212             correct = this.handler is HTTPIdentityHandler &&
213                       ((!this.item.is_live_stream () &&
214                        !this.item.streamable ()) ||
215                        (this.subtitle != null ||
216                         this.thumbnail != null));
217
218             break;
219         }
220
221         if (!correct) {
222             throw new HTTPRequestError.UNACCEPTABLE
223                                         ("%s mode not supported for '%s'",
224                                          mode,
225                                          this.item.id);
226         }
227     }
228 }
229