1# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: 2 3# Copyright 2015-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org> 4# Copyright 2015-2018 Antoni Boucher (antoyo) <bouanto@zoho.com> 5# 6# This file is part of qutebrowser. 7# 8# qutebrowser is free software: you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation, either version 3 of the License, or 11# (at your option) any later version. 12# 13# qutebrowser is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. 20 21import os 22import dataclasses 23from typing import List 24 25import pytest 26import bs4 27from PyQt5.QtCore import QUrl 28from PyQt5.QtNetwork import QNetworkRequest 29 30from qutebrowser.browser.webkit.network import filescheme 31from qutebrowser.utils import urlutils, utils 32from helpers import testutils 33 34 35@pytest.mark.parametrize('create_file, create_dir, filterfunc, expected', [ 36 (True, False, os.path.isfile, True), 37 (True, False, os.path.isdir, False), 38 39 (False, True, os.path.isfile, False), 40 (False, True, os.path.isdir, True), 41 42 (False, False, os.path.isfile, False), 43 (False, False, os.path.isdir, False), 44]) 45def test_get_file_list(tmpdir, create_file, create_dir, filterfunc, expected): 46 """Test get_file_list.""" 47 path = tmpdir / 'foo' 48 if create_file or create_dir: 49 path.ensure(dir=create_dir) 50 51 all_files = os.listdir(str(tmpdir)) 52 53 result = filescheme.get_file_list(str(tmpdir), all_files, filterfunc) 54 item = {'name': 'foo', 'absname': str(path)} 55 assert (item in result) == expected 56 57 58class TestIsRoot: 59 60 @pytest.mark.windows 61 @pytest.mark.parametrize('directory, is_root', [ 62 ('C:\\foo\\bar', False), 63 ('C:\\foo\\', False), 64 ('C:\\foo', False), 65 ('C:\\', True) 66 ]) 67 def test_windows(self, directory, is_root): 68 assert filescheme.is_root(directory) == is_root 69 70 @pytest.mark.posix 71 @pytest.mark.parametrize('directory, is_root', [ 72 ('/foo/bar', False), 73 ('/foo/', False), 74 ('/foo', False), 75 ('/', True) 76 ]) 77 def test_posix(self, directory, is_root): 78 assert filescheme.is_root(directory) == is_root 79 80 81class TestParentDir: 82 83 @pytest.mark.windows 84 @pytest.mark.parametrize('directory, parent', [ 85 ('C:\\foo\\bar', 'C:\\foo'), 86 ('C:\\foo', 'C:\\'), 87 ('C:\\foo\\', 'C:\\'), 88 ('C:\\', 'C:\\'), 89 ]) 90 def test_windows(self, directory, parent): 91 assert filescheme.parent_dir(directory) == parent 92 93 @pytest.mark.posix 94 @pytest.mark.parametrize('directory, parent', [ 95 ('/home/foo', '/home'), 96 ('/home', '/'), 97 ('/home/', '/'), 98 ('/', '/'), 99 ]) 100 def test_posix(self, directory, parent): 101 assert filescheme.parent_dir(directory) == parent 102 103 104def _file_url(path): 105 """Return a file:// url (as string) for the given LocalPath. 106 107 Arguments: 108 path: The filepath as LocalPath (as handled by py.path) 109 """ 110 return urlutils.file_url(str(path)) 111 112 113class TestDirbrowserHtml: 114 115 @dataclasses.dataclass 116 class Parsed: 117 118 parent: str 119 folders: List[str] 120 files: List[str] 121 122 @dataclasses.dataclass 123 class Item: 124 125 link: str 126 text: str 127 128 @pytest.fixture 129 def parser(self): 130 """Provide a function to get a parsed dirbrowser document.""" 131 def parse(path): 132 html = filescheme.dirbrowser_html(path).decode('utf-8') 133 soup = bs4.BeautifulSoup(html, 'html.parser') 134 135 with testutils.ignore_bs4_warning(): 136 print(soup.prettify()) 137 138 container = soup('div', id='dirbrowserContainer')[0] 139 140 parent_elem = container('ul', class_='parent') 141 if not parent_elem: 142 parent = None 143 else: 144 parent = parent_elem[0].li.a.string 145 146 folders = [] 147 files = [] 148 149 for li in container('ul', class_='folders')[0]('li'): 150 item = self.Item(link=li.a['href'], text=str(li.a.string)) 151 folders.append(item) 152 153 for li in container('ul', class_='files')[0]('li'): 154 item = self.Item(link=li.a['href'], text=str(li.a.string)) 155 files.append(item) 156 157 return self.Parsed(parent=parent, folders=folders, files=files) 158 159 return parse 160 161 def test_basic(self): 162 html = filescheme.dirbrowser_html(os.getcwd()).decode('utf-8') 163 soup = bs4.BeautifulSoup(html, 'html.parser') 164 165 with testutils.ignore_bs4_warning(): 166 print(soup.prettify()) 167 168 container = soup.div 169 assert container['id'] == 'dirbrowserContainer' 170 title_elem = container('div', id='dirbrowserTitle')[0] 171 title_text = title_elem('p', id='dirbrowserTitleText')[0].text 172 assert title_text == 'Browse directory: {}'.format(os.getcwd()) 173 174 def test_icons(self, monkeypatch): 175 """Make sure icon paths are correct file:// URLs.""" 176 html = filescheme.dirbrowser_html(os.getcwd()).decode('utf-8') 177 soup = bs4.BeautifulSoup(html, 'html.parser') 178 179 with testutils.ignore_bs4_warning(): 180 print(soup.prettify()) 181 182 css = soup.html.head.style.string 183 assert "background-image: url('qute://resource/img/folder.svg');" in css 184 185 def test_empty(self, tmpdir, parser): 186 parsed = parser(str(tmpdir)) 187 assert parsed.parent 188 assert not parsed.folders 189 assert not parsed.files 190 191 def test_files(self, tmpdir, parser): 192 foo_file = tmpdir / 'foo' 193 bar_file = tmpdir / 'bar' 194 foo_file.ensure() 195 bar_file.ensure() 196 197 parsed = parser(str(tmpdir)) 198 assert parsed.parent 199 assert not parsed.folders 200 foo_item = self.Item(_file_url(foo_file), foo_file.relto(tmpdir)) 201 bar_item = self.Item(_file_url(bar_file), bar_file.relto(tmpdir)) 202 assert parsed.files == [bar_item, foo_item] 203 204 def test_html_special_chars(self, tmpdir, parser): 205 special_file = tmpdir / 'foo&bar' 206 special_file.ensure() 207 208 parsed = parser(str(tmpdir)) 209 item = self.Item(_file_url(special_file), special_file.relto(tmpdir)) 210 assert parsed.files == [item] 211 212 def test_dirs(self, tmpdir, parser): 213 foo_dir = tmpdir / 'foo' 214 bar_dir = tmpdir / 'bar' 215 foo_dir.ensure(dir=True) 216 bar_dir.ensure(dir=True) 217 218 parsed = parser(str(tmpdir)) 219 assert parsed.parent 220 assert not parsed.files 221 foo_item = self.Item(_file_url(foo_dir), foo_dir.relto(tmpdir)) 222 bar_item = self.Item(_file_url(bar_dir), bar_dir.relto(tmpdir)) 223 assert parsed.folders == [bar_item, foo_item] 224 225 def test_mixed(self, tmpdir, parser): 226 foo_file = tmpdir / 'foo' 227 bar_dir = tmpdir / 'bar' 228 foo_file.ensure() 229 bar_dir.ensure(dir=True) 230 231 parsed = parser(str(tmpdir)) 232 foo_item = self.Item(_file_url(foo_file), foo_file.relto(tmpdir)) 233 bar_item = self.Item(_file_url(bar_dir), bar_dir.relto(tmpdir)) 234 assert parsed.parent 235 assert parsed.files == [foo_item] 236 assert parsed.folders == [bar_item] 237 238 def test_root_dir(self, tmpdir, parser): 239 root_dir = 'C:\\' if utils.is_windows else '/' 240 parsed = parser(root_dir) 241 assert not parsed.parent 242 243 def test_oserror(self, mocker): 244 m = mocker.patch('qutebrowser.browser.webkit.network.filescheme.' 245 'os.listdir') 246 m.side_effect = OSError('Error message') 247 html = filescheme.dirbrowser_html('').decode('utf-8') 248 soup = bs4.BeautifulSoup(html, 'html.parser') 249 250 with testutils.ignore_bs4_warning(): 251 print(soup.prettify()) 252 253 error_msg = soup('p', id='error-message-text')[0].string 254 assert error_msg == 'Error message' 255 256 257class TestFileSchemeHandler: 258 259 def test_dir(self, tmpdir): 260 url = QUrl.fromLocalFile(str(tmpdir)) 261 req = QNetworkRequest(url) 262 reply = filescheme.handler(req, None, None) 263 # The URL will always use /, even on Windows - so we force this here 264 # too. 265 tmpdir_path = str(tmpdir).replace(os.sep, '/') 266 assert reply.readAll() == filescheme.dirbrowser_html(tmpdir_path) 267 268 def test_file(self, tmpdir): 269 filename = tmpdir / 'foo' 270 filename.ensure() 271 url = QUrl.fromLocalFile(str(filename)) 272 req = QNetworkRequest(url) 273 reply = filescheme.handler(req, None, None) 274 assert reply is None 275 276 def test_unicode_encode_error(self, mocker): 277 url = QUrl('file:///tmp/foo') 278 req = QNetworkRequest(url) 279 280 err = UnicodeEncodeError('ascii', '', 0, 2, 'foo') 281 mocker.patch('os.path.isdir', side_effect=err) 282 283 reply = filescheme.handler(req, None, None) 284 assert reply is None 285