Siosm's blog

Some thoughts from a systemd, Rust and security aficionado

My first program in Rust and an update on my RSS reading habits

I’ve finally found the time to make my first program in Rust and I definitely enjoyed it!

(Last updated on 2014-07-29)

Rust

If you don’t know about the Rust language started by a Mozilla employed developer (and now developed with help from Samsung and the community), have a look at those links:

So here is my first Rust program. It’s a simple daemon waiting for input on localhost:7777 (Unix socket support was not completed when I started programming in Rust). If it receives an URL, it opens it in a new tab in Firefox.

Update: This code is the first version I made for Rust 0.9 so it is completely outdated. I’ve removed the Gist and created a proper repository with the updated code.

(firefox_tab_opener.rs) download
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
extern mod extra;

use std::from_str::from_str;
use std::io::net::ip::SocketAddr;
use std::io::net::tcp::{TcpListener,TcpStream};
use std::io::{Acceptor,Listener};
use std::run::{Process,ProcessOptions};

use extra::url::Url;

// Note: Error handling should be improved
fn main() {
	let browserCommand = "firefox";
	let address = "127.0.0.1:7777";

	// Prepare a socket listening on localhost:7777
	let addr: SocketAddr = from_str(address).expect(format!("Invalid address: {}", address));
	let listener = TcpListener::bind(addr).expect(format!("Failed to bind to: {}", address));
	let mut acceptor = listener.listen().expect("Could not listen");

	// Infinite loop to keep handling new connections.
	loop {
		let tcpStream = acceptor.accept().expect("Could not accept connection");
		debug!("Accepted new connection");
		do spawn {
			handleClient(tcpStream, browserCommand)
		}
	}
}

// Accept a new connection and listen for URLs
fn handleClient(tcpStream: TcpStream, browserCommand: &'static str) {
	debug!("Spawned new task");

	let mut tcpStream = tcpStream;

	// Note that as soon as read_to_str() returns, everything sent
	// to the socket after this point will be discarded as once
	// we're done working with the content we've just read, the
	// tcpStream will be freed.
	let message = tcpStream.read_to_str();

	// Iterate over the lines in the received message
	for line in message.lines() {
		debug!("Current line is: {}", line);

		// This tries to convert the line to a Url struct
		let url: Option<Url> = from_str(line);

		// If this fails, it returns None, else we got a valid
		// URL
		match url {
			None     => { info!("No Url found") }
			Some(u)  => {
				debug!("Found Url in: {}", line);
				if checkUrl(&u) {
					spawnProcess(&u, browserCommand)
				}
			}
		}
	}
}

// Check that the URL is actually usable
fn checkUrl(u: &Url) -> bool {
	(u.scheme == ~"http" || u.scheme == ~"https" )
		&& u.host != ~""
}

// Spawn a browser to access the URL
fn spawnProcess(u: &Url, command: &'static str) {
	debug!("Spawning process {} {}", command, u.to_str());
	let pOptions = ProcessOptions::new();
	let mut child = Process::new(command, [u.to_str()], pOptions).expect("Could not fork process");
	child.finish();
}

Integration with Newsbeuter

I’ve stopped using the Leselys feed reader because it looks like it isn’t being developed anymore, is using mongodb which isn’t really lightweight and I encountered some annoying bugs (which I have not reported yet because I cannot find a reliable way to reproduce them).

Thus I was looking for an alternative and I remembered that I had stumbled upon a nice ncurses RSS aggregator during my previous searches for the Holy Grail of RSS Reader.

In order to get fresh news all the time and to not loose any if I were to not look at them during long periods of time (from week ends to weeks for example), I have to run this in a tmux on my dedicated server. I can easily access it remotely but it makes a simple task not obvious anymore: opening articles in the browser on the client side.

First I tried tunneling ssh connections through the initial connection, but I had to get Firefox to launch and open a tab with X environment variables hard coded:

1
2
#!/usr/bin/env bash
ssh -p 2222 localhost "DISPLAY=:0 XAUTHORITY=/tmp/kde-tim/xauth-1000-_0 firefox ${1}" &> /dev/null &

So this became tedious and I didn’t really like having the remote server allowed direct access to my computers.

So I made this simple Rust program to run on the client side with a simple ssh port forwarding to allow the RSS aggregator on the server to request a tab to be opened on the client.

1
ssh my.server.com -R 7777:localhost:7777

This is my .confg/newsbeuter/config config with custom key bindings close to vim ones:

newsbeuter configuration (newsbeuter_config) download
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
# Use auto-reload, but do no reload at startup
auto-reload yes
suppress-first-reload yes
refresh-on-startup no

# Keep articles for 30 days
keep-articles-days 30

# Reload articles every hour
reload-time 60

# Set custom script as open in browser
browser "/home/tim/remote_firefox.bash %u"

# Unbind most keys to avoid mistakes
unbind-key ENTER
unbind-key r
unbind-key R
unbind-key A
unbind-key C
unbind-key s
unbind-key n
unbind-key p
unbind-key J
unbind-key K
unbind-key ^K
unbind-key o
unbind-key O
unbind-key ^U
unbind-key N
unbind-key l
unbind-key u
unbind-key ^T
unbind-key t
unbind-key e
unbind-key E
unbind-key ^R
unbind-key ^L
unbind-key F
unbind-key f
unbind-key ^F
unbind-key ^B
unbind-key ^E
unbind-key ^N
unbind-key ^P
unbind-key j
unbind-key k
unbind-key D
unbind-key $
unbind-key v
unbind-key ^X
unbind-key ^V
unbind-key ^G
unbind-key |
unbind-key g
unbind-key G
unbind-key UP
unbind-key DOWN
unbind-key PPAGE
unbind-key NPAGE

# Vim-like bindings
bind-key i open

bind-key r reload-all

bind-key a mark-feed-read
bind-key n toogle-article-read

bind-key o open-in-browser-and-mark-read

bind-key k up
bind-key j down

bind-key h next-unread-feed
bind-key l prev-unread-feed

# Set some colors
color listnormal color244 color234
color listfocus color166 color235
color info color136 color235
color background color244 color234
color article color244 color234

And the simple script remote_firefox.bash used to ask for a new tab:

1
2
#!/usr/bin/env bash
echo -n "${1}" | nc localhost 7777

And finally the full script/shell function I’m using to automate those steps:

1
2
3
4
/home/tim/ffto/ffto &
local FIRERUST_PID=${!}
ssh my.server.com -t -R 7777:localhost:7777 -- tmux attach-session -t newsbeuter
kill -term ${FIRERUST_PID}

Comments