Skip to content

Commit

Permalink
Support password-protected PDF #155 #765
Browse files Browse the repository at this point in the history
QL will display a UI prompting for a password. Upon entering the correct password, the PDF file will be reopened.
  • Loading branch information
emako committed Dec 13, 2024
1 parent d09e9c4 commit cb59a3d
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 13 deletions.
55 changes: 55 additions & 0 deletions QuickLook.Plugin/QuickLook.Plugin.PDFViewer/PasswordControl.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<UserControl x:Class="QuickLook.Plugin.PDFViewer.PasswordControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:QuickLook.Plugin.PDFViewer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{DynamicResource CardBackground}"
BorderBrush="{DynamicResource CardBorderBrush}"
BorderThickness="1"
CornerRadius="5">
<Grid Margin="24">
<StackPanel>
<TextBlock x:Name="titleTextBlock"
FontSize="18"
FontWeight="DemiBold"
Text="Enter Password" />
<TextBlock x:Name="hintTextBlock"
Margin="0,20,0,0"
Text="This file is password protected. Please enter the password to open the file." />
<PasswordBox x:Name="passwordBox"
MinWidth="400"
Margin="0,16,0,0" />
<TextBlock x:Name="passwordErrorTextBlock"
MinWidth="400"
Margin="0,16,0,0"
Foreground="Red"
Text="Check your password"
Visibility="Collapsed" />
<Grid MinWidth="400" Margin="0,16,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="openFileButton"
Grid.Column="0"
Margin="0,0,4,0"
HorizontalAlignment="Stretch"
Content="Open File" />
<Button x:Name="cancelButton"
Grid.Column="1"
Margin="4,0,0,0"
HorizontalAlignment="Stretch"
Content="Cancel" />
</Grid>
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright © 2024 ema
//
// This file is part of QuickLook program.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using QuickLook.Common.Helpers;
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;

namespace QuickLook.Plugin.PDFViewer;

public partial class PasswordControl : UserControl
{
public event Func<string, bool> PasswordRequested;

public bool Result { get; private set; } = false;
public string Password => passwordBox.Dispatcher.Invoke(() => passwordBox.Password);

public PasswordControl()
{
InitializeComponent();

string domain = Assembly.GetExecutingAssembly().GetName().Name;
titleTextBlock.Text = TranslationHelper.Get("PW_Title", domain: domain);
hintTextBlock.Text = TranslationHelper.Get("Pw_Hint", domain: domain);
passwordErrorTextBlock.Text = TranslationHelper.Get("PW_Error", domain: domain);
openFileButton.Content = TranslationHelper.Get("BTN_OpenFile", domain: domain);
cancelButton.Content = TranslationHelper.Get("BTN_Cancel", domain: domain);
openFileButton.Click += OpenFileButton_Click;
cancelButton.Click += CancelButton_Click;
}

private void OpenFileButton_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(Password))
{
Result = true;

if (PasswordRequested != null)
{
bool accepted = PasswordRequested.Invoke(Password);

if (!accepted)
{
passwordErrorTextBlock.Dispatcher.Invoke(() => passwordErrorTextBlock.Visibility = Visibility.Visible);
}
}
}
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
Result = false;

if (Window.GetWindow(this) is Window window)
{
window.Close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ namespace QuickLook.Plugin.PDFViewer;

public class PdfDocumentWrapper : IDisposable
{
public PdfDocumentWrapper(Stream stream)
public PdfDocumentWrapper(Stream stream, string password = null)
{
PdfStream = new MemoryStream((int)stream.Length);
stream.CopyTo(PdfStream);

PdfDocument = PdfDocument.Load(PdfStream);
PdfDocument = PdfDocument.Load(PdfStream, password);
}

public PdfDocument PdfDocument { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,13 @@ private void UpdatePageViewWhenSelectionChanged(object sender, SelectionChangedE
}
}

public static Size GetDesiredControlSizeByFirstPage(string path)
public static Size GetDesiredControlSizeByFirstPage(string path, string password = null)
{
Size size;

using (var s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var tempHandle = PdfDocument.Load(s))
using (var tempHandle = PdfDocument.Load(s, password))
{
size = new Size(0, 0);
tempHandle.PageSizes.Take(5).ForEach(p =>
Expand All @@ -236,10 +236,10 @@ public static Size GetDesiredControlSizeByFirstPage(string path)
return new Size(size.Width * 3, size.Height * 3);
}

public void LoadPdf(string path)
public void LoadPdf(string path, string password = null)
{
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
PdfDocumentWrapper = new PdfDocumentWrapper(stream);
PdfDocumentWrapper = new PdfDocumentWrapper(stream, password);
_pdfLoaded = true;

if (PdfDocumentWrapper.PdfDocument.PageCount < 2)
Expand Down
59 changes: 52 additions & 7 deletions QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using PdfiumViewer;
using QuickLook.Common.Plugin;
using System;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Windows;
using System.Windows.Threading;

namespace QuickLook.Plugin.PDFViewer;
Expand All @@ -29,6 +31,7 @@ public class Plugin : IViewer
private ContextObject _context;
private string _path;
private PdfViewerControl _pdfControl;
private PasswordControl _passwordControl;

public int Priority => 0;

Expand All @@ -55,9 +58,16 @@ public void Prepare(string path, ContextObject context)
_context = context;
_path = path;

var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(path);

context.SetPreferredSizeFit(desiredSize, 0.9);
try
{
var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(path);
context.SetPreferredSizeFit(desiredSize, 0.9);
}
catch (PdfException ex) when (ex.Message == "Password required or incorrect password")
{
// Fallback to a size to request the password
context.PreferredSize = new Size { Width = 800, Height = 600 };
}
}

public void View(string path, ContextObject context)
Expand All @@ -67,7 +77,7 @@ public void View(string path, ContextObject context)

Exception exception = null;

_pdfControl.Dispatcher.BeginInvoke(new Action(() =>
_ = _pdfControl.Dispatcher.BeginInvoke(() =>
{
try
{
Expand All @@ -78,11 +88,46 @@ public void View(string path, ContextObject context)
_pdfControl.CurrentPageChanged += UpdateWindowCaption;
context.IsBusy = false;
}
catch (Exception e)
catch (PdfException ex) when (ex.Message == "Password required or incorrect password")
{
// Fallback to request a password
_passwordControl = new PasswordControl();
_passwordControl.PasswordRequested += (string password) =>
{
try
{
var desiredSize = PdfViewerControl.GetDesiredControlSizeByFirstPage(path, password);
context.SetPreferredSizeFit(desiredSize, 0.9); // Actually it is no longer effective here

context.ViewerContent = _pdfControl;

context.IsBusy = true;
_pdfControl.LoadPdf(path, password);

context.Title = $"1 / {_pdfControl.TotalPages}: {Path.GetFileName(path)}";

_pdfControl.CurrentPageChanged += UpdateWindowCaption;
context.IsBusy = false;
}
catch (PdfException ex) when (ex.Message == "Password required or incorrect password")
{
// This password is not accepted
return false;
}

// This password is accepted
return true;
};

context.ViewerContent = _passwordControl;
context.Title = $"[PASSWORD PROTECTED] {Path.GetFileName(path)}";
context.IsBusy = false;
}
catch (Exception ex)
{
exception = e;
exception = ex;
}
}), DispatcherPriority.Loaded).Wait();
}, DispatcherPriority.Loaded).Wait();

if (exception != null)
ExceptionDispatchInfo.Capture(exception).Throw();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@
<Resource Include="Resources\loading.png" />
</ItemGroup>

<ItemGroup>
<None Include="Translations.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<Compile Include="..\..\GitVersion.cs">
<Link>Properties\GitVersion.cs</Link>
Expand Down
32 changes: 32 additions & 0 deletions QuickLook.Plugin/QuickLook.Plugin.PDFViewer/Translations.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>

<Translations>
<en>
<PW_Title>Enter Password</PW_Title>
<Pw_Hint>This file is password protected. Please enter the password to open the file.</Pw_Hint>
<PW_Error>Check your password</PW_Error>
<BTN_OpenFile>Open File</BTN_OpenFile>
<BTN_Cancel>BTN_Cancel</BTN_Cancel>
</en>
<ja-JP>
<PW_Title>パスワード入力</PW_Title>
<Pw_Hint>このファイルではパスワードで保護されています。ファイルを開くにはパスワードを入力してください。</Pw_Hint>
<PW_Error>パスワードを確認してください</PW_Error>
<BTN_OpenFile>ファイルを開く</BTN_OpenFile>
<BTN_Cancel>キャンセル</BTN_Cancel>
</ja-JP>
<zh-CN>
<PW_Title>输入密码</PW_Title>
<Pw_Hint>此文件受密码保护。请输入密码以打开文件。</Pw_Hint>
<PW_Error>检查您的密码</PW_Error>
<BTN_OpenFile>打开文件</BTN_OpenFile>
<BTN_Cancel>取消</BTN_Cancel>
</zh-CN>
<zh-TW>
<PW_Title>輸入密碼</PW_Title>
<Pw_Hint>此檔案受密碼保護。請輸入密碼以開啟檔案。</Pw_Hint>
<PW_Error>檢查您的密碼</PW_Error>
<BTN_OpenFile>開啟檔案</BTN_OpenFile>
<BTN_Cancel>取消</BTN_Cancel>
</zh-TW>
</Translations>

0 comments on commit cb59a3d

Please sign in to comment.