Skip to content

Commit b54974e

Browse files
committed
support glob expansion for infiles, fixes #328
Glob expansion for infiles is useful in environments where there is no expansion in the shell, e.g. on Windows.
1 parent a284555 commit b54974e

4 files changed

+110
-75
lines changed

src/extract_otp_secrets.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import base64
3737
import csv
3838
import fileinput
39+
import glob
3940
import json
4041
import os
4142
import platform
@@ -527,14 +528,20 @@ def extract_otps_from_files(args: Args) -> Otps:
527528

528529
files_count = urls_count = otps_count = 0
529530
if verbose: print(f"Input files: {args.infile}")
530-
for infile in args.infile:
531-
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
532-
files_count += 1
533-
for line in get_otp_urls_from_file(infile, args):
534-
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
535-
if line.startswith('#') or line == '': continue
536-
urls_count += 1
537-
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
531+
for infile_raw in args.infile:
532+
expanded_infiles = glob.glob(infile_raw)
533+
if not expanded_infiles:
534+
expanded_infiles = [infile_raw]
535+
if verbose >= LogLevel.DEBUG: log_debug(f"Could not expand input files, fallback to infile")
536+
if verbose >= LogLevel.DEBUG: log_debug(f"Expanded input files: {expanded_infiles}")
537+
for infile in expanded_infiles:
538+
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
539+
files_count += 1
540+
for line in get_otp_urls_from_file(infile, args):
541+
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
542+
if line.startswith('#') or line == '': continue
543+
urls_count += 1
544+
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
538545
if verbose: print(f"Extracted {otps_count} otp{'s'[:otps_count != 1]} from {urls_count} otp url{'s'[:urls_count != 1]} by reading {files_count} infile{'s'[:files_count != 1]}")
539546
return otps
540547

tests/data/print_verbose_output-n-vvv.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ QReader installed: True
22
CV2 version: 4.10.0
33
QR reading mode: ZBAR
44

5-
Version: extract_otp_secrets 2.8.1.post17+git.3dc7d1c2.dirty Linux x86_64 Python 3.11.9 (CPython/called as script)
5+
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script)
66

77
Input files: ['example_export.txt']
8+
9+
DEBUG: Expanded input files: ['example_export.txt']
810
Processing infile example_export.txt
911
Reading lines of example_export.txt
1012
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/

tests/data/print_verbose_output-vvv.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ QReader installed: True
22
CV2 version: 4.10.0
33
QR reading mode: ZBAR
44

5-
Version: extract_otp_secrets 2.8.1.post17+git.3dc7d1c2.dirty Linux x86_64 Python 3.11.9 (CPython/called as script)
5+
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script)
66

77
Input files: ['example_export.txt']
8+

9+
DEBUG: Expanded input files: ['example_export.txt'] 
810
Processing infile example_export.txt
911
Reading lines of example_export.txt
1012
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/

tests/extract_otp_secrets_test.py

+89-65
Original file line numberDiff line numberDiff line change
@@ -869,19 +869,8 @@ def test_wrong_content(capsys: pytest.CaptureFixture[str]) -> None:
869869
# Assert
870870
captured = capsys.readouterr()
871871

872-
expected_stderr = '''
873-
WARN: input is not a otpauth-migration:// url
874-
source: tests/data/test_export_wrong_content.txt
875-
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
876-
Maybe a wrong file was given
877-
878-
ERROR: could not parse query parameter in input url
879-
source: tests/data/test_export_wrong_content.txt
880-
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
881-
'''
882-
883872
assert captured.out == ''
884-
assert captured.err == expected_stderr
873+
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG
885874

886875

887876
def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
@@ -891,19 +880,8 @@ def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
891880
# Assert
892881
captured = capsys.readouterr()
893882

894-
expected_stderr = '''
895-
WARN: input is not a otpauth-migration:// url
896-
source: tests/data/test_export_wrong_content.txt
897-
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
898-
Maybe a wrong file was given
899-
900-
ERROR: could not parse query parameter in input url
901-
source: tests/data/test_export_wrong_content.txt
902-
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
903-
'''
904-
905883
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
906-
assert captured.err == expected_stderr
884+
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG
907885

908886

909887
def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
@@ -913,19 +891,8 @@ def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
913891
# Assert
914892
captured = capsys.readouterr()
915893

916-
expected_stderr = f'''{colorama.Fore.RED}
917-
WARN: input is not a otpauth-migration:// url
918-
source: tests/data/test_export_wrong_content.txt
919-
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
920-
Maybe a wrong file was given{colorama.Fore.RESET}
921-
{colorama.Fore.RED}
922-
ERROR: could not parse query parameter in input url
923-
source: tests/data/test_export_wrong_content.txt
924-
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
925-
'''
926-
927894
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
928-
assert captured.err == expected_stderr
895+
assert captured.err == EXPECTED_STDERR_COLORED_OTP_URL_WRONG
929896

930897

931898
def test_one_wrong_line(capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch) -> None:
@@ -997,6 +964,46 @@ def test_img_qr_reader_from_file_happy_path(capsys: pytest.CaptureFixture[str])
997964
assert captured.err == ''
998965

999966

967+
@pytest.mark.qreader
968+
def test_img_qr_reader_but_no_otp_from_file(capsys: pytest.CaptureFixture[str]) -> None:
969+
# Act
970+
extract_otp_secrets.main(['-n', 'tests/data/qr_but_without_otp.png'])
971+
972+
# Assert
973+
captured = capsys.readouterr()
974+
975+
assert captured.out == ''
976+
assert captured.err == EXPECTED_STDERR_NO_OTP_URL
977+
978+
979+
@pytest.mark.qreader
980+
def test_img_qr_reader_from_wildcard(capsys: pytest.CaptureFixture[str]) -> None:
981+
# Act
982+
extract_otp_secrets.main(['-n', 'tests/data/*.png'])
983+
984+
# Assert
985+
captured = capsys.readouterr()
986+
987+
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
988+
assert normalize_testfile_path(captured.err) == EXPECTED_STDERR_NO_OTP_URL
989+
990+
991+
def normalize_testfile_path(text: str):
992+
return text.replace('tests/data\\', 'tests/data/') if sys.platform.startswith("win") else text
993+
994+
995+
@pytest.mark.qreader
996+
def test_img_qr_reader_from_multiple_files(capsys: pytest.CaptureFixture[str]) -> None:
997+
# Act
998+
extract_otp_secrets.main(['-n', 'tests/data/test_googleauth_export.png', 'tests/data/text_masquerading_as_image.jpeg'])
999+
1000+
# Assert
1001+
captured = capsys.readouterr()
1002+
1003+
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
1004+
assert captured.err == EXPECTED_STDERR_BAD_IMAGE
1005+
1006+
10001007
@pytest.mark.qreader
10011008
def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode: str) -> None:
10021009
# Act
@@ -1041,24 +1048,7 @@ def test_img_qr_reader_from_stdin(capsys: pytest.CaptureFixture[str], monkeypatc
10411048
# Assert
10421049
captured = capsys.readouterr()
10431050

1044-
expected_stdout = '''Name: Test1:test1@example1.com
1045-
Secret: JBSWY3DPEHPK3PXP
1046-
Issuer: Test1
1047-
Type: totp
1048-
1049-
Name: Test2:test2@example2.com
1050-
Secret: JBSWY3DPEHPK3PXQ
1051-
Issuer: Test2
1052-
Type: totp
1053-
1054-
Name: Test3:test3@example3.com
1055-
Secret: JBSWY3DPEHPK3PXR
1056-
Issuer: Test3
1057-
Type: totp
1058-
1059-
'''
1060-
1061-
assert captured.out == expected_stdout
1051+
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
10621052
assert captured.err == ''
10631053

10641054

@@ -1143,19 +1133,9 @@ def test_non_image_file(capsys: pytest.CaptureFixture[str]) -> None:
11431133

11441134
# Assert
11451135
captured = capsys.readouterr()
1146-
expected_stderr = '''
1147-
WARN: input is not a otpauth-migration:// url
1148-
source: tests/data/text_masquerading_as_image.jpeg
1149-
input: This is just a text file masquerading as an image file.
1150-
Maybe a wrong file was given
1151-
1152-
ERROR: could not parse query parameter in input url
1153-
source: tests/data/text_masquerading_as_image.jpeg
1154-
url: This is just a text file masquerading as an image file.
1155-
'''
11561136

1157-
assert captured.err == expected_stderr
11581137
assert captured.out == ''
1138+
assert captured.err == EXPECTED_STDERR_BAD_IMAGE
11591139

11601140

11611141
def test_next_valid_qr_mode() -> None:
@@ -1209,3 +1189,47 @@ def test_next_valid_qr_mode() -> None:
12091189
Type: totp
12101190
12111191
'''
1192+
1193+
EXPECTED_STDERR_OTP_URL_WRONG = '''
1194+
WARN: input is not a otpauth-migration:// url
1195+
source: tests/data/test_export_wrong_content.txt
1196+
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
1197+
Maybe a wrong file was given
1198+
1199+
ERROR: could not parse query parameter in input url
1200+
source: tests/data/test_export_wrong_content.txt
1201+
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
1202+
'''
1203+
1204+
EXPECTED_STDERR_COLORED_OTP_URL_WRONG = f'''{colorama.Fore.RED}
1205+
WARN: input is not a otpauth-migration:// url
1206+
source: tests/data/test_export_wrong_content.txt
1207+
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
1208+
Maybe a wrong file was given{colorama.Fore.RESET}
1209+
{colorama.Fore.RED}
1210+
ERROR: could not parse query parameter in input url
1211+
source: tests/data/test_export_wrong_content.txt
1212+
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
1213+
'''
1214+
1215+
EXPECTED_STDERR_NO_OTP_URL = '''
1216+
WARN: input is not a otpauth-migration:// url
1217+
source: tests/data/qr_but_without_otp.png
1218+
input: NOT A otpauth-migration:// URL
1219+
Maybe a wrong file was given
1220+
1221+
ERROR: could not parse query parameter in input url
1222+
source: tests/data/qr_but_without_otp.png
1223+
url: NOT A otpauth-migration:// URL
1224+
'''
1225+
1226+
EXPECTED_STDERR_BAD_IMAGE = '''
1227+
WARN: input is not a otpauth-migration:// url
1228+
source: tests/data/text_masquerading_as_image.jpeg
1229+
input: This is just a text file masquerading as an image file.
1230+
Maybe a wrong file was given
1231+
1232+
ERROR: could not parse query parameter in input url
1233+
source: tests/data/text_masquerading_as_image.jpeg
1234+
url: This is just a text file masquerading as an image file.
1235+
'''

0 commit comments

Comments
 (0)