summaryrefslogtreecommitdiff
path: root/src/librygel-server/rygel-http-server.vala
blob: 325739aa33ce3e99d7c657abeb9a778a69f5b57c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/*
 * Copyright (C) 2008, 2009 Nokia Corporation.
 * Copyright (C) 2012 Intel Corporation.
 * Copyright (C) 2013 Cable Television Laboratories, Inc.
 *
 * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 *                               <zeeshan.ali@nokia.com>
 *         Jens Georg <jensg@openismus.com>
 *         Doug Galligan <doug@sentosatech.com>
 *         Craig Pratt <craig@ecaspia.com>
 *
 * This file is part of Rygel.
 *
 * Rygel is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Rygel is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using GUPnP;
using Gee;

public class Rygel.HTTPServer : GLib.Object, Rygel.StateMachine {
    public string path_root { get; private set; }
    public string server_name { get; set; }

    // Reference to root container of associated ContentDirectory
    public MediaContainer root_container;
    public GUPnP.Context context;
    private ArrayList<HTTPRequest> requests;
    private bool locally_hosted;
    public HashTable<string, string> replacements;

    public Cancellable cancellable { get; set; }

    private const string SERVER_TEMPLATE = "%s/%s %s/%s DLNA/1.51 UPnP/1.0";

    public HTTPServer (ContentDirectory content_dir,
                       string           name) {
        base ();

        try {
            var config = MetaConfig.get_default ();
            this.server_name = config.get_string (name, "server-name");
        } catch (Error error) {
            this.server_name = SERVER_TEMPLATE.printf
                                        (name,
                                         BuildConfig.PACKAGE_VERSION,
                                         Environment.get_prgname (),
                                         BuildConfig.PACKAGE_VERSION);
        }

        this.root_container = content_dir.root_container;
        this.context = content_dir.context;
        this.requests = new ArrayList<HTTPRequest> ();
        this.cancellable = content_dir.cancellable;

        this.locally_hosted = this.context.get_address ().get_is_loopback ();

        this.path_root = "/" + name;
        this.replacements = new HashTable <string, string> (str_hash, str_equal);
        this.replacements.insert ("@SERVICE_ADDRESS@",
                                  this.context.address.to_string ());
        this.replacements.insert ("@ADDRESS@",
                                  this.context.address.to_string ());
        this.replacements.insert ("@SERVICE_INTERFACE@",
                                  this.context.interface);
        this.replacements.insert ("@SERVICE_PORT@",
                                  this.context.port.to_string ());
        this.replacements.insert ("@HOSTNAME@", Environment.get_host_name ());
    }

    public async void run () {
        context.add_server_handler (true, this.path_root, this.server_handler);
        context.server.request_aborted.connect (this.on_request_aborted);
        context.server.request_started.connect (this.on_request_started);
        context.server.request_read.connect (this.on_request_read);

        if (this.cancellable != null) {
            this.cancellable.cancelled.connect (this.on_cancelled);
        }
    }

    /**
     * Set or unset options the server supports/doesn't support
     *
     * Resources should be setup assuming server supports all optional
     * delivery modes
     */
    public void set_resource_delivery_options (MediaResource res) {
        res.protocol = this.get_protocol ();
        // Set this just to be safe
        res.dlna_flags |= DLNAFlags.DLNA_V15;
        // This server supports all DLNA delivery modes - so leave those flags
        // alone
     }

    public bool need_proxy (string uri) {
        return Uri.parse_scheme (uri) != "http";
    }

    private void on_cancelled (Cancellable cancellable) {
        // Cancel all state machines
        this.cancellable.cancel ();

        context.server.remove_handler (this.path_root);

        this.completed ();
    }

    internal string create_uri_for_object (MediaObject object,
                                           int         thumbnail_index,
                                           int         subtitle_index,
                                           string?     resource_name) {
        var uri = new HTTPItemURI (object,
                                   this,
                                   thumbnail_index,
                                   subtitle_index,
                                   resource_name);

        return uri.to_string ();
    }

    internal virtual string get_protocol () {
        return "http-get";
    }

    internal virtual ArrayList<ProtocolInfo> get_protocol_info () {
        return new ArrayList<ProtocolInfo>();
    }

    public HashTable<string, string> get_replacements () {
        return this.replacements;
    }

    public bool is_local () {
        return this.locally_hosted;
    }

    private void on_request_completed (StateMachine machine) {
        var request = machine as HTTPRequest;

        this.requests.remove (request);

        debug ("HTTP %s request for URI '%s' handled.",
               request.msg.get_method (),
               request.msg.get_uri ().to_string ());
    }

    private void server_handler (Soup.Server               server,
                                 Soup.ServerMessage        msg,
                                 string                    server_path,
                                 HashTable<string,string>? query) {
        if (msg.get_method () == "POST") {
            // Already handled
            return;
        }

        debug ("HTTP %s request for URI '%s'. Headers:",
               msg.get_method (),
               msg.get_uri ().to_string ());
        msg.get_request_headers ().foreach ((name, value) => {
                debug ("    %s : %s", name, value);
        });

        this.queue_request (new HTTPGet (this, server, msg));
    }

    private void on_request_aborted (Soup.Server        server,
                                     Soup.ServerMessage message) {
        foreach (var request in this.requests) {
            if (request.msg == message) {
                request.cancellable.cancel ();
                debug ("HTTP client aborted %s request for URI '%s'.",
                       request.msg.get_method (),
                       request.msg.get_uri ().to_string ());

                break;
            }
        }
    }

    private void on_request_started (Soup.Server        server,
                                     Soup.ServerMessage  message) {
        message.got_headers.connect (this.on_got_headers);
    }

    private void on_request_read (Soup.Server        server,
                                  Soup.ServerMessage message) {
        var agent = message.get_request_headers ().get_one ("User-Agent");

        if (agent == null) {
            var host = message.get_remote_host ();
            agent = this.context.guess_user_agent (host);
            if (agent != null) {
                debug ("Guessed user agent %s for %s", agent, host);
                message.get_request_headers ().append ("User-Agent", agent);
            } else {
                debug ("Could not guess user agent for ip %s.", host);
            }
        }

    }

    private void on_got_headers (Soup.ServerMessage msg) {
        if (msg.get_method () == "POST" &&
            msg.get_uri ().get_path ().has_prefix (this.path_root)) {
            debug ("HTTP POST request for URI '%s'",
                   msg.get_uri ().to_string ());

            this.queue_request (new HTTPPost (this, this.context.server, msg));
        }
    }

    private void queue_request (HTTPRequest request) {
        request.completed.connect (this.on_request_completed);
        this.requests.add (request);
        request.run.begin ();
    }
}