holomorph / transmission

Emacs interface to a Transmission session
GNU General Public License v3.0
85 stars 11 forks source link

Freezing when used with HTTP proxy #19

Closed arifer612 closed 3 years ago

arifer612 commented 3 years ago

Issue

Freezes when HTTP/HTTPS proxy is used to access a docker image of Transmission remotely behind a VPN.

Environment

OS: elementary OS 5.1.7 Hera emacs: 27.2 (spacemacs 0.300) Docker image of Transmission: linuxserver/transmission Transmission: 3.00 (bb6b5a062e)

Backtrace

Initial check:

Transmission docker image is set up with a VPN proxy and port forwarding.

The set-up was tested using the web GUI on Firefox and transmission-remote-gui, both with a manual proxy directing traffic meant towards HOST:9091 to SERVER:PORT instead and the rpc-path is still /transmission/rpc.

Changes to init.el

transmission.el settings

Since the only thing that needed to be changed was transmission-host, I did so accordingly:

(setq transmission-host "HOST")

Proxy settings

I fixed the global proxy settings for Emacs:

(setq url-proxy-services
   '(("http"     . "SERVER:PORT")
     ("https"    . "SERVER:PORT")
     ("ftp"      . "SERVER:PORT")
     ("no_proxy" . "^\\(localhost\\|10.*\\)"))
   )

Trying it out with Emacs

Running (transmission) lead to an indefinite hang that I had to cancel with C-g.

I then tried to see if the proxy setting was the issue. I did this by going to http://HOST:9091/transmission/web/ on eww. The result was that I could reach the web GUI on eww without an issue. Just to confirm, I turned off the proxy settings and tried doing the same thing on eww and could not connect, confirming that the proxy did indeed allow me to connect to Transmission from across the proxy.

Conclusion

Running (transmission) should not cause result in the package hanging since it is possible to reach Transmission with the port forwarding rules set. I wonder if the package is not retrieving the X-Transmission-Session-Id properly since there is no transmission-conflict error stopping the process.

Odd thing is that going to http://HOST:9091/transmission/rpc on eww does lead me to the 409: Conflict page with the X-Transmission-Session-Id, so I do believe that it might be an issue with how the package sends the HTTP requests.

Edit: More information in conclusion and fix typo.

holomorph commented 3 years ago

Thanks. Since transmission doesn't use url.el (eww does), it has no notion of proxy handling, and it will have to be implemented. I wonder if this very naive, untested patch will work for your case :)

diff --git a/transmission.el b/transmission.el
index 8624a40..6e27411 100644
--- a/transmission.el
+++ b/transmission.el
@@ -401,7 +401,10 @@ defun transmission-http-post (process content)
     (let ((auth (transmission--auth-string)))
       (when auth (push (cons "Authorization" auth) headers)))
     (with-temp-buffer
-      (insert (concat "POST " transmission-rpc-path " HTTP/1.1\r\n"))
+      ;; assuming a proxy, ignoring local/sockets
+      (let ((uri (format "http://%s:%s%s" transmission-host transmission-service
+                         transmission-rpc-path)))
+        (insert (concat "POST " uri " HTTP/1.1\r\n")))
       (dolist (elt headers)
         (insert (format "%s: %s\r\n" (car elt) (cdr elt))))
       (insert "\r\n" content)
@@ -437,6 +440,8 @@ defun transmission-make-network-process ()
 custom variables `transmission-host' and `transmission-service'."
   (let ((socket (when (file-name-absolute-p transmission-host)
                   (expand-file-name transmission-host)))
+        (proxy (let ((s (getenv "http_proxy")))
+                 (when s (url-generic-parse-url s))))
         buffer process)
     (unwind-protect
         (prog1
@@ -444,8 +449,10 @@ defun transmission-make-network-process ()
                   process
                   (make-network-process
                    :name "transmission" :buffer buffer
-                   :host (when (null socket) transmission-host)
-                   :service (or socket transmission-service)
+                   :host (if proxy (url-host proxy)
+                           (when (null socket) transmission-host))
+                   :service (if proxy (url-port proxy)
+                              (or socket transmission-service))
                    :family (when socket 'local) :noquery t
                    :coding 'binary :filter-multibyte nil))
           (setq buffer nil process nil))
arifer612 commented 3 years ago

This fix works perfect! I had assumed that transmission used url so I made my tweaks based on that assumption from the start. Guess I need to learn to read slowly from now on!

A point to take note of for others who might face this problem too. This fix works if the HTTP_PROXY environment is globally set, or at least set for the Emacs instance. If you'd like to have only transmission work behind a proxy maybe because you might have a few proxies that you're using, you can always set up a local variable and have it feed into transmission-make-network-process the same way. A slight fix up of holomorph's wonderful patch does that.

modified   transmission.el
@@ -90,6 +90,10 @@
                  (integer :tag "Port"))
   :link '(function-link make-network-process))

+(defcustom transmission-proxy-service nil
+  "Proxy service of the Transmission session."
+  :type 'string)
+
 (defcustom transmission-rpc-path "/transmission/rpc"
   "Path to the Transmission session RPC interface."
   :type '(choice (const :tag "Default" "/transmission/rpc")
@@ -401,7 +405,10 @@ and port default to `transmission-host' and
     (let ((auth (transmission--auth-string)))
       (when auth (push (cons "Authorization" auth) headers)))
     (with-temp-buffer
-      (insert (concat "POST " transmission-rpc-path " HTTP/1.1\r\n"))
+      ;; assuming a proxy, ignoring local/sockets
+      (let ((uri (format "http://%s:%s%s" transmission-host transmission-service
+                         transmission-rpc-path)))
+        (insert (concat "POST " uri " HTTP/1.1\r\n")))
       (dolist (elt headers)
         (insert (format "%s: %s\r\n" (car elt) (cdr elt))))
       (insert "\r\n" content)
@@ -435,8 +442,14 @@ Return JSON object parsed from content."
   "Return a network client process connected to a Transmission daemon.
 When creating a new connection, the address is determined by the
 custom variables `transmission-host' and `transmission-service'."
-  (let ((socket (when (file-name-absolute-p transmission-host)
-                  (expand-file-name transmission-host)))
+  (let* ((socket (when (file-name-absolute-p transmission-host)
+                   (expand-file-name transmission-host)))
+         (proxy (cond (transmission-proxy-service)
+                      ((assoc '"http" url-proxy-services)
+                       (format "http://%s" (cdr (assoc '"http" url-proxy-services))))
+                      ((getenv "http_proxy"))))
+         (proxy-url (when proxy
+                      (url-generic-parse-url proxy)))
         buffer process)
     (unwind-protect
         (prog1
@@ -444,8 +457,10 @@ custom variables `transmission-host' and `transmission-service'."
                   process
                   (make-network-process
                    :name "transmission" :buffer buffer
-                   :host (when (null socket) transmission-host)
-                   :service (or socket transmission-service)
+                   :host (if proxy-url (url-host proxy-url)
+                           (when (null socket) transmission-host))
+                   :service (if proxy-url (url-port proxy-url)
+                              (or socket transmission-service))
                    :family (when socket 'local) :noquery t
                    :coding 'binary :filter-multibyte nil))
           (setq buffer nil process nil))

Set the variable in your config file:

(setq transmission-proxy-service "http://SERVER:PORT")