server/
server.rs

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
use std::env;
use std::io::Read;
use std::sync::Arc;

use clap::Parser;
use native_tls::{Identity, TlsAcceptor as NativeTlsAcceptor};
use serde_json::json;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_native_tls::TlsAcceptor;
use tracing::{debug, error, info};

use mrc::{get_property, playlist_clear, playlist_next, playlist_prev, quit, seek, set_property};

#[derive(Parser)]
#[command(author, version, about)]
struct Config {
    /// The IP address and port to bind the server to
    #[arg(short, long, default_value = "127.0.0.1:8080")]
    bind: String,

    /// Path to MPV IPC socket
    #[arg(short, long, default_value = "/tmp/mpvsocket")]
    socket: String,
}

async fn handle_connection(
    stream: tokio::net::TcpStream,
    acceptor: Arc<TlsAcceptor>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut stream = acceptor.accept(stream).await?;
    let mut buffer = vec![0; 2048];

    let n = stream.read(&mut buffer).await?;
    let request = String::from_utf8_lossy(&buffer[..n]);

    debug!("Received request:\n{}", request);

    let headers = request.split("\r\n").collect::<Vec<&str>>();
    let token_line = headers
        .iter()
        .find(|&&line| line.starts_with("Authorization:"));
    let token = match token_line {
        Some(line) => line.split(" ").nth(1).unwrap_or_default(),
        None => "",
    };

    let auth_token = match env::var("AUTH_TOKEN") {
        Ok(token) => token,
        Err(_) => {
            error!("Authentication token is not set. Connection cannot be accepted.");
            stream.write_all(b"Authentication token not set\n").await?;

            // You know what? I do not care to panic when the authentication token is
            // missing in the environment. Start the goddamned server and hell, even
            // accept incoming connections. Authenticated requests will be refused
            // when the token is incorrect or not set, so we can simply continue here.
            return Ok(());
        }
    };

    if token != auth_token {
        stream.write_all(b"Authentication failed\n").await?;
        return Ok(());
    }

    info!("Client authenticated");
    stream.write_all(b"Authenticated\n").await?;

    let command = request.split("\r\n\r\n").last().unwrap_or("");
    info!("Received command: {}", command);

    let response = match process_command(command.trim()).await {
        Ok(response) => response,
        Err(e) => {
            error!("Error processing command: {}", e);
            format!("Error: {:?}", e)
        }
    };

    stream.write_all(response.as_bytes()).await?;
    Ok(())
}

async fn process_command(command: &str) -> Result<String, String> {
    match command {
        "pause" => {
            info!("Pausing playback");
            set_property("pause", &json!(true), None)
                .await
                .map_err(|e| format!("Failed to pause: {:?}", e))?;
            Ok("Paused playback\n".to_string())
        }

        "play" => {
            info!("Unpausing playback");
            set_property("pause", &json!(false), None)
                .await
                .map_err(|e| format!("Failed to play: {:?}", e))?;
            Ok("Resumed playback\n".to_string())
        }

        "stop" => {
            info!("Stopping playback and quitting MPV");
            quit(None)
                .await
                .map_err(|e| format!("Failed to stop: {:?}", e))?;
            Ok("Stopped playback\n".to_string())
        }

        "next" => {
            info!("Skipping to next item in the playlist");
            playlist_next(None)
                .await
                .map_err(|e| format!("Failed to skip to next: {:?}", e))?;
            Ok("Skipped to next item\n".to_string())
        }

        "prev" => {
            info!("Skipping to previous item in the playlist");
            playlist_prev(None)
                .await
                .map_err(|e| format!("Failed to skip to previous: {:?}", e))?;
            Ok("Skipped to previous item\n".to_string())
        }

        "seek" => {
            let parts: Vec<&str> = command.split_whitespace().collect();
            if let Some(seconds) = parts.get(1) {
                if let Ok(sec) = seconds.parse::<i32>() {
                    info!("Seeking to {} seconds", sec);
                    seek(sec.into(), None)
                        .await
                        .map_err(|e| format!("Failed to seek: {:?}", e))?;
                    return Ok(format!("Seeking to {} seconds\n", sec));
                }
            }
            Err("Invalid seek command".to_string())
        }

        "clear" => {
            info!("Clearing the playlist");
            playlist_clear(None)
                .await
                .map_err(|e| format!("Failed to clear playlist: {:?}", e))?;
            Ok("Cleared playlist\n".to_string())
        }

        "list" => {
            info!("Listing playlist items");
            match get_property("playlist", None).await {
                Ok(Some(data)) => Ok(format!(
                    "Playlist: {}",
                    serde_json::to_string_pretty(&data).unwrap()
                )),
                Ok(None) => Err("No playlist data available".to_string()),
                Err(e) => Err(format!("Failed to fetch playlist: {:?}", e)),
            }
        }
        _ => Err("Unknown command".to_string()),
    }
}

fn create_tls_acceptor() -> Result<TlsAcceptor, Box<dyn std::error::Error + Send + Sync>> {
    let pfx_path = env::var("TLS_PFX_PATH")
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::NotFound, "TLS_PFX_PATH not set"))?;
    let password = env::var("TLS_PASSWORD")
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::NotFound, "TLS_PASSWORD not set"))?;

    let mut file = std::fs::File::open(&pfx_path)?;
    let mut identity = vec![];
    file.read_to_end(&mut identity)?;

    let identity = Identity::from_pkcs12(&identity, &password)?;
    let native_acceptor = NativeTlsAcceptor::new(identity)?;
    Ok(TlsAcceptor::from(native_acceptor))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    tracing_subscriber::fmt::init();
    let config = Config::parse();

    if !std::path::Path::new(&config.socket).exists() {
        error!(
            "Error: MPV socket not found at '{}'. Is MPV running?",
            config.socket
        );
    }

    info!("Server is starting...");
    match create_tls_acceptor() {
        Ok(acceptor) => {
            let acceptor = Arc::new(acceptor);
            let listener = tokio::net::TcpListener::bind(&config.bind).await?;
            info!("Server is listening on {}", config.bind);

            loop {
                let (stream, _) = listener.accept().await?;
                info!("New connection accepted.");

                let acceptor = Arc::clone(&acceptor);
                tokio::spawn(handle_connection(stream, acceptor));
            }
        }

        Err(e) => {
            error!("Failed to initialize TLS: {}", e);
            return Err(e);
        }
    }
}