Skip to content

Commit

Permalink
Add ability to trust SSL certificate with error
Browse files Browse the repository at this point in the history
- Fix crash when the user close the server selection window
- Direct stream will now fetch sampling rate from metadata if possible
to prevent incorrect sampling rate being used caused by corrupted file
header
- Add ability to get all claimed PMS instead of getting PMS which is
remote access enabled only
  • Loading branch information
brian9206 committed Apr 29, 2018
1 parent eeba08a commit fe39444
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 45 deletions.
95 changes: 95 additions & 0 deletions PlexFlux/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
using System.Reflection;
using System.Net;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Xml;
using System.IO;
using System.Collections.Generic;
using NAudio.CoreAudioApi;
using PlexLib;
using PlexFlux.UI;
Expand Down Expand Up @@ -33,8 +38,12 @@ public partial class App : System.Windows.Application

public TaskScheduler uiContext;

private static List<string> untrustedCertificates = new List<string>();

protected override void OnStartup(StartupEventArgs e)
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CertificateValidationError_Callback);

if (!appMutex.WaitOne(TimeSpan.Zero, true))
{
MessageBox.Show("PlexFlux is already running.\nOnly one instance at a time.", "PlexFlux", MessageBoxButton.OK, MessageBoxImage.Exclamation);
Expand Down Expand Up @@ -94,6 +103,92 @@ protected override void OnExit(ExitEventArgs e)
Environment.Exit(e.ApplicationExitCode);
}

private bool CertificateValidationError_Callback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// accept no error
if (sslPolicyErrors == SslPolicyErrors.None)
return true;

// for security, we do not accept anything that is unknown
if (!(sender is WebRequest))
return false;

var request = sender as WebRequest;

// we do not want to send our username and password to hacker
if (request.RequestUri.Host == "plex.tv" || request.RequestUri.Host == "app.plex.tv")
return false;

var hash = certificate.GetCertHashString();

if (untrustedCertificates.Contains(hash))
return false;

var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
#if DEBUG
"PlexFlux_d"
#else
"PlexFlux"
#endif
, "trusted_certificates.xml");

XmlDocument xml = new XmlDocument();

try
{
xml.LoadXml(File.ReadAllText(path));
}
catch
{
// File is not created or cannot be accessed
var declaration = xml.CreateXmlDeclaration("1.0", "utf-8", null);
xml.AppendChild(declaration);

var rootNode = xml.CreateElement("trusted");
xml.AppendChild(rootNode);
}

var trustedNode = xml.SelectSingleNode("/trusted");

foreach (XmlNode certificateNode in trustedNode.SelectNodes("certificate"))
{
if (certificateNode.InnerText == hash)
return true;
}

// not listed in trusted certificate
if (MessageBox.Show("Do you want to trust the following SSL certificate?\n\n" +
"[Connecting Host]\n" +
request.RequestUri.Host + "\n\n" +
certificate.ToString() + "\n" +
"--- WARNING ---\nIf you are not sure, you should not trust any certificate as your information can be evaspdropped by hacker.", "SSL Policy", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
{
lock (untrustedCertificates)
untrustedCertificates.Add(hash);

plexConnection = null;
plexClient = null;

return false;
}

var certNode = xml.CreateElement("certificate");
certNode.InnerText = hash;

trustedNode.AppendChild(certNode);

// save it to path
var dir = Path.GetDirectoryName(path);

if (Directory.Exists(dir))
Directory.CreateDirectory(dir);

using (var file = File.Open(path, System.IO.FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
xml.Save(file);

return true;
}

public async Task<PlexServer[]> GetPlexServers()
{
PlexServer[] servers = null;
Expand Down
17 changes: 15 additions & 2 deletions PlexFlux/PlaybackManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,24 +161,37 @@ private PlaybackManager()

private Task PlaybackTask()
{
return Task.Run(() =>
return Task.Run(async () =>
{
var app = (App)Application.Current;

// make transcode URL
var url = app.plexClient.GetMusicTranscodeUrl(Track, app.config.TranscodeBitrate < 0 ? 320 : app.config.TranscodeBitrate);
int samplingRate = 0;

if (app.config.TranscodeBitrate < 0)
{
// try to find mp3 so no transcode is needed if found
var media = Track.FindByFormat("mp3");

if (media != null)
{
try
{
samplingRate = await app.plexClient.GetSamplingRate(Track);
}
catch
{
// auto detect
samplingRate = 0;
}

url = app.plexConnection.BuildRequestUrl(media.Url);
}
}

// start streaming
streaming = new Mp3Streaming(app.plexConnection.CreateRequest(url));
streaming = new Mp3Streaming(app.plexConnection.CreateRequest(url), samplingRate);
var sourceProvider = streaming.Start();

// check if we have aborted
Expand Down
4 changes: 2 additions & 2 deletions PlexFlux/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.6.0.*")]
[assembly: AssemblyFileVersion("1.6.0.0")]
[assembly: AssemblyVersion("1.6.1.*")]
[assembly: AssemblyFileVersion("1.6.1.0")]
8 changes: 5 additions & 3 deletions PlexFlux/Streaming/Mp3Streaming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Mp3Streaming : IDisposable
private StreamingWaveProvider waveProvider;
private ManualResetEvent instantiateWaitHandle;
private TimeSpan startTime;
private int samplingRate;

public bool Started
{
Expand Down Expand Up @@ -68,13 +69,14 @@ public bool IsBuffering
);
}

public Mp3Streaming(HttpWebRequest request)
public Mp3Streaming(HttpWebRequest request, int samplingRate)
{
this.request = request;
sourceStream = null;
waveProvider = null;
instantiateWaitHandle = new ManualResetEvent(false);
Current = TimeSpan.Zero;
this.samplingRate = samplingRate;

// set timeout
this.request.Timeout = 10 * 1000;
Expand Down Expand Up @@ -153,7 +155,7 @@ private void StreamFromHttp()

if (decompressor == null)
{
WaveFormat waveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
WaveFormat waveFormat = new Mp3WaveFormat(samplingRate == 0 ? frame.SampleRate : samplingRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
decompressor = new AcmMp3FrameDecompressor(waveFormat);

var app = (App)Application.Current;
Expand All @@ -162,7 +164,7 @@ private void StreamFromHttp()
if (!app.config.DisableDiskCaching)
{
// calculate decompressed wave size
waveSize = (ulong)(waveFormat.SampleRate * 16 * waveFormat.Channels * PlaybackManager.GetInstance().Track.Duration / 8); // workaround: 16 = waveFormat.BitsPerSample, sometimes waveFormat.BitsPerSample is equals to 0
waveSize = (ulong)((samplingRate == 0 ? frame.SampleRate : samplingRate) * 16 * waveFormat.Channels * PlaybackManager.GetInstance().Track.Duration / 8); // workaround: 16 = waveFormat.BitsPerSample, sometimes waveFormat.BitsPerSample is equals to 0
}

waveProvider = new StreamingWaveProvider(decompressor.OutputFormat, waveSize)
Expand Down
11 changes: 9 additions & 2 deletions PlexFlux/UI/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ public MainWindow()
var playQueue = PlayQueueManager.GetInstance();
playQueue.TrackChanged += PlayQueue_TrackChanged;

Initialize();
new Func<object>(() => Initialize())();
}

private async void Initialize()
private async Task Initialize()
{
var app = (App)Application.Current;
var playQueue = PlayQueueManager.GetInstance();
Expand All @@ -163,6 +163,13 @@ private async void Initialize()
if (server == null)
server = app.SelectPlexServer();

if (server == null)
{
app.myPlexClient = new MyPlexClient(app.deviceInfo);
await Initialize();
return;
}

await app.ConnectToPlexServer(server);

Server = server;
Expand Down
2 changes: 1 addition & 1 deletion PlexFlux/UI/Pages/BrowseAlbum.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ private async void MenuItem_AddToPlaylist_Click(object sender, RoutedEventArgs e
var mainWindow = MainWindow.GetInstance();
mainWindow.FlashPlaylist(playlist);
}
catch
catch (Exception exx)
{
MessageBox.Show("Could not fetch data from remote server.", "PlexFlux", MessageBoxButton.OK, MessageBoxImage.Error);
}
Expand Down
46 changes: 37 additions & 9 deletions PlexLib/MyPlexClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Xml;
using System.IO;
Expand Down Expand Up @@ -62,7 +63,7 @@ public async Task SignIn(string username, string password)

public async Task<PlexServer[]> GetServers()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://plex.tv/pms/servers.xml?X-Plex-Token=" + HttpUtility.UrlEncode(AuthenticationToken));
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://plex.tv/devices.xml?X-Plex-Token=" + HttpUtility.UrlEncode(AuthenticationToken));
request.UserAgent = deviceInfo.UserAgent;

var response = await request.GetResponseAsync();
Expand All @@ -75,16 +76,43 @@ public async Task<PlexServer[]> GetServers()

var retval = new List<PlexServer>();

foreach (XmlNode server in xml.SelectNodes("/MediaContainer/Server"))
foreach (XmlNode device in xml.SelectNodes("/MediaContainer/Device"))
{
// search for PMS only
if (device.Attributes["product"] != null && device.Attributes["product"].Value != "Plex Media Server")
continue;

// find connection
string address = null;
var localAddresses = new List<string>();

// rfc1918 regex
var rfc1918 = new Regex(@"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)");

foreach (XmlNode connection in device.SelectNodes("Connection"))
{
Uri uri = new Uri(connection.Attributes["uri"].Value);

if (uri.AbsolutePath != "/")
continue;

// check rfc1918
if (rfc1918.Match(uri.Host).Success)
{
localAddresses.Add(uri.ToString());
}
else if (address == null)
{
address = uri.ToString();
}
}

var plexServer = new PlexServer();
plexServer.Name = server.Attributes["name"].InnerText;
plexServer.Address = server.Attributes["address"].InnerText;
plexServer.LocalAddressList = server.Attributes["localAddresses"].InnerText.Split(',');
plexServer.Port = int.Parse(server.Attributes["port"].InnerText);
plexServer.Scheme = server.Attributes["scheme"].InnerText;
plexServer.AccessToken = server.Attributes["accessToken"].InnerText;
plexServer.MachineIdentifier = server.Attributes["machineIdentifier"].InnerText;
plexServer.Name = device.Attributes["name"].InnerText;
plexServer.Address = address;
plexServer.LocalAddressList = localAddresses.ToArray();
plexServer.AccessToken = device.Attributes["token"].InnerText;
plexServer.MachineIdentifier = device.Attributes["clientIdentifier"].InnerText;

retval.Add(plexServer);
}
Expand Down
11 changes: 11 additions & 0 deletions PlexLib/PlexClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,16 @@ public Uri GetMusicTranscodeUrl(PlexTrack track, int maxAudioBitrate)
{ "directPlay", "0" }
});
}

public async Task<int> GetSamplingRate(PlexTrack track)
{
var response = await connection.RequestXml(track.MetadataUrl);
var streamNode = response.SelectSingleNode("/MediaContainer/Track/Media/Part/Stream");

if (streamNode == null || streamNode.Attributes == null || streamNode.Attributes["samplingRate"] == null)
return 0;

return int.Parse(streamNode.Attributes["samplingRate"].Value);
}
}
}
Loading

0 comments on commit fe39444

Please sign in to comment.