Opened 7 years ago

Last modified 6 years ago

#5463 new Enhancement

add CORS in XML RPC response

Reported by: bertrand.gressier Owned by:
Priority: Normal Milestone: None Set
Component: Transmission Version: 2.82
Severity: Normal Keywords:
Cc:

Description

Hi.

Thanks for your tool. it's very powerful

I need a feature. I develop a client side application in javascript angular and I can't directly call transmission server because I have CORS exception in the brower.

My solution, I use a proxy pass and inject missed headers. Access-Control-Allow-Origin "*" Access-Control-Expose-Headers "X-Transmission-Session-Id" Access-Control-Allow-Methods "GET,POST,PUT,OPTIONS,DELETE" Header always set Access-Control-Allow-Headers "Authorization, Content-Type, X-Transmission-Session-Id"

And it works.

If you can add this header directly in the product, switch on with a flag in the config file. It would be fantastic.

tks for all

Change History (11)

comment:1 Changed 7 years ago by ibizaman

I second this request. This would be very nice.

In the meantime, can the author of the issue explain how he installed and configured the proxy?

Thanks!

comment:2 Changed 7 years ago by ibizaman

I'm currently implementing it.

Just to be sure it matches the author's will, here is what I'm planning to do:

When making a POST CORS request, the browser first sends a similar OPTION request (some headers are omitted) :

    OPTIONS /transmission/rpc HTTP/1.1
    Host: 192.168.42.30:9091
    Access-Control-Request-Method: POST
    Origin: http://192.168.42.21:8000
    Access-Control-Request-Headers: content-type

The server should then answer with at least these headers:

    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: 192.168.42.21
    Access-Control-Request-Methods: POST, GET, OPTIONS
    Access-Control-Allow-Headers: X-Transmission-Session-Id, Content-Type

Then the browser will send the real POST request and the server will answer by repeating the Access-Control-Allow-Origin header.

So first thing to implement is the handling of the OPTIONS request.

Second, on what basis does the server choose to send or not the headers, i.e. allows the request to be executed or not? I would make this decision based on the rpc-whitelist and rpc-whitelist-enabled parameters. If the rpc-whitelist is enabled and one of the rpc-whitelist mask matches the address in the origin header, then the server sends back the origin header in the Access-Control-Allow-Origin header. By sending back the origin header and not the mask, the server avoids to say too much about the configuration.

By the way, the server documentation says the rpc-whitelist must be a list of addresses. If the handling of CORS requests is based on rpc-whitelist, the configuration should allow domain names also. When browsing the rpc-server.c file, I don't see anything requesting this. Why is this imposed then?

Please leave any comments. :)

comment:3 Changed 7 years ago by ibizaman

Update on where I am:

I added this function to libtransmission/rpc-server.c:

@@ -543,8 +548,42 @@
   evbuffer_free (buf);
   tr_free (data);
 }
+
+static void
+handle_cors_headers (struct evhttp_request * req, struct tr_rpc_server * server, int headers)
+{
+  if (server->isWhitelistEnabled)
+    {
+      tr_list * l;
+
+      for (l=server->whitelist; l!=NULL; l=l->next)
+        {
+          char* mask = l->data;
+          if (tr_wildmat (req->remote_host, mask))
+            break;
+        }
+
+      if (l != NULL)
+        {
+          const char * origin = evhttp_find_header (req->input_headers, "Origin");
+          evhttp_add_header (req->output_headers, "Access-Control-Allow-Origin", origin);
+#ifdef REQUIRE_SESSION_ID
+          evhttp_add_header (req->output_headers, "Access-Control-Expose-Headers", "X-Transmission-Session-Id");
+#endif
+          evhttp_add_header (req->output_headers, "Access-Control-Allow-Methods", "GET,POST");
+        }
+    }
+}

It is used in handle_rpc_from_json:

 static void
 handle_rpc_from_json (struct evhttp_request * req,
                       struct tr_rpc_server  * server,
                       const char            * json,
@@ -552,6 +591,9 @@
 {
   struct rpc_response_data * data;

+  handle_cors_headers (req, server);
+

And in handle_request when issuing a 403 (i.e. get new api key):

@@ -682,16 +732,19 @@
             "<p><code>%s: %s</code></p>",
             TR_RPC_SESSION_ID_HEADER, sessionId);
           evhttp_add_header (req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId);
+          handle_cors_headers (req, server);

With this, I think I covered nearly every cases:

  • Handling of REQUIRE_SESSION_ID,
  • Handling of 403 request

I'm still battling with the handling of the OPTIONS request. I really don't know what's going on since when receiving that request, the code doesn't even go in the handle_request function of rpc_server.c and the client gets a 501 request. The only file in which this error is related is third-party/libevent/http.c but it's a third-party library so I should not touch it, right?

This is the function I'm talking about, I added the OPTIONS handling (see commented line), did a make install in the third-party/libevent folder and a make clean && make in the root transmission folder but it does not work.

static struct evhttp*
evhttp_new_object(void)
{
        struct evhttp *http = NULL;

        if ((http = mm_calloc(1, sizeof(struct evhttp))) == NULL) {
                event_warn("%s: calloc", __func__);
                return (NULL);
        }

        http->timeout = -1;
        evhttp_set_max_headers_size(http, EV_SIZE_MAX);
        evhttp_set_max_body_size(http, EV_SIZE_MAX);
        evhttp_set_allowed_methods(http,
            EVHTTP_REQ_GET |
            EVHTTP_REQ_POST |
            EVHTTP_REQ_HEAD |
            EVHTTP_REQ_PUT |
            EVHTTP_REQ_DELETE |
            EVHTTP_REQ_OPTIONS); // added this

        TAILQ_INIT(&http->sockets);
        TAILQ_INIT(&http->callbacks);
        TAILQ_INIT(&http->connections);
        TAILQ_INIT(&http->virtualhosts);
        TAILQ_INIT(&http->aliases);

        return (http);
}

I'm a little bit lost here and don't know if I'm on the right path here.

comment:4 Changed 7 years ago by mike.dld

Couldn't you try adding EVHTTP_REQ_OPTIONS later, in startServer () (libtransmission/rpc-server.c)? Something like

  // ...
  server->httpd = evhttp_new (server->session->event_base); // this line is already there
  evhttp_set_allowed_methods (server->httpd, ... | EVHTTP_REQ_OPTIONS); // this is what you add
  // ...

comment:5 Changed 7 years ago by bertrand.gressier

Hello,

Tks to work on this feature.

Have you find a solution to your problem ?

I'm very happy to see this feature in the next release. This solution open the door to new client-side application

If I can help, warn me

comment:6 Changed 7 years ago by bertrand.gressier

  • Milestone changed from None Set to Sometime

comment:7 Changed 7 years ago by livings124

  • Milestone changed from Sometime to None Set

comment:8 Changed 7 years ago by ibizaman

Hi!

I did manage to make it kind of work. Kind of since it was not thoroughly tested. I must also say I didn't work on this lately.

About testing, I have a major concern: how to automate it since it implies using a web browser? Obviously the behavior of a client using CORS could be mocked but I don't think I'll be able to do it reliably.

Here is the current state of modifications: http://pastebin.com/iAkAcrGy

comment:9 Changed 6 years ago by BathZ

Hi, I'm very interested in this ticket.

I'm currently running a Lighttpd instance to proxy the request to transmission RPC. the config is a little hack-ish : https://gist.github.com/bathizte/80f40f27f02452f98fd6. Nginx until now and without the "more headers" module does not allow to add headers on every response such as a 409.

PhantomJS can be used to test the RPC responses to a web browser.

Last edited 6 years ago by BathZ (previous) (diff)

comment:10 Changed 6 years ago by jordan

ibizaman, are you still working on this?

comment:11 Changed 6 years ago by ibizaman

Jordan, I'm sorry but not really. Although the code I've posted above have some quirks, it's a good start. Do you plan taking over?

Note: See TracTickets for help on using tickets.