Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pyzbar to ReadQRCode #32556

Merged
merged 12 commits into from
Feb 7, 2024
6 changes: 5 additions & 1 deletion Packs/CommonScripts/.secrets-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,8 @@ cipher=ECDHE-RSA-AES128-GCM-SHA256
07.23.05.38
multipart/signed
123.123.123.123
107.66.225.91
107.66.225.91
http://itunes.apple.com
https://www.linkedin.com
http://en.m.wikipedia.org
https://xsoar.pan.dev/docs/concepts/demisto-sdk#secrets
7 changes: 7 additions & 0 deletions Packs/CommonScripts/ReleaseNotes/1_13_35.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

#### Scripts

##### ReadQRCode

- Improved implementation by adding the `pybar` library to cover a wider range of cases.
- Updated the Docker image to: *demisto/qrcode:1.0.0.87067*.
2 changes: 1 addition & 1 deletion Packs/CommonScripts/Scripts/ReadQRCode/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

This script uses the open-source library OpenCV to extract text from QR codes.
Extract text from QR codes.
The output of this script includes the output of the script "extractIndicators" run on the text extracted from the QR code.

## Script Data
Expand Down
37 changes: 23 additions & 14 deletions Packs/CommonScripts/Scripts/ReadQRCode/ReadQRCode.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
import demistomock as demisto # noqa
from CommonServerPython import * # noqa
from pyzbar import pyzbar
import cv2
from wurlitzer import pipes
# pylint: disable=E1101 # disable pylint not recognizing cv2's attributes.


def read_qr_code(filename: str) -> str:
def read_qr_code(filename: str) -> list:

detect = cv2.QRCodeDetector()
img = cv2.imread(filename)
text, *rest = detect.detectAndDecode(img)
demisto.debug(f'QR code matrices: {rest}')
return text
debug_messages = [] # don't use demisto.debug under the context manager.
with pipes() as (out, _):
img = cv2.imread(filename)
text = [d.data.decode() for d in pyzbar.decode(img)]

if not text:
debug_messages.append("Couldn't extract text with pyzbar, retrying with cv2.")
detect = cv2.QRCodeDetector()
text, *_ = detect.detectAndDecode(img)

debug_messages.append(f'stdout: {out.read()}')

def extract_indicators_from_text(text: str) -> dict:
demisto.debug('\n'.join(debug_messages))
return text if isinstance(text, list) else [text]


def extract_indicators_from_text(text: list) -> dict:
return json.loads(demisto.executeCommand(
'extractIndicators',
{'text': text}
)[0]['Contents'])
'extractIndicators', {'text': text}
)[0]['Contents']) # type: ignore


def extract_info_from_qr_code(entry_id: str, **_) -> CommandResults:
def extract_info_from_qr_code(entry_id: str) -> CommandResults:

try:
filename = demisto.getFilePath(entry_id)['path']
text = read_qr_code(filename)
if not text:
if not any(text):
return CommandResults(readable_output='No QR code was found in the image.')
indicators = extract_indicators_from_text(text)
except cv2.error as e: # generic error raised by cv2
except (cv2.error, TypeError) as e: # generic error raised by cv2
raise DemistoException('Error parsing file. Please make sure it is a valid image file.') from e
except ValueError: # raised by demisto.getFilePath when the entry_id is not found
raise DemistoException(f'Invalid entry ID: {entry_id=}')
Expand All @@ -45,7 +54,7 @@ def extract_info_from_qr_code(entry_id: str, **_) -> CommandResults:

def main():
try:
return_results(extract_info_from_qr_code(**demisto.args()))
return_results(extract_info_from_qr_code(demisto.args()['entry_id']))
except Exception as e:
return_error(f'Failed to execute ReadQRCode. Error: {e}')

Expand Down
4 changes: 2 additions & 2 deletions Packs/CommonScripts/Scripts/ReadQRCode/ReadQRCode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ args:
name: entry_id
required: true
default: true
comment: Extracts the text from a QR code. This script uses the open-source library OpenCV and is restricted to it's capabilities. The output of this script includes the output of the script "extractIndicators" run on the text extracted from the QR code.
comment: Extracts the text from a QR code. The output of this script includes the output of the script "extractIndicators" run on the text extracted from the QR code.
commonfields:
id: ReadQRCode
version: -1
Expand All @@ -26,7 +26,7 @@ tags: []
timeout: '0'
type: python
subtype: python3
dockerimage: demisto/opencv:1.0.0.82635
dockerimage: demisto/qrcode:1.0.0.87067
fromversion: 6.10.0
tests:
- No tests (auto formatted)
32 changes: 28 additions & 4 deletions Packs/CommonScripts/Scripts/ReadQRCode/ReadQRCode_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import pytest
import demistomock as demisto
from CommonServerPython import *
from pytest_mock import MockerFixture


def test_extract_info_from_qr_code(mocker):
def test_extract_info_from_qr_code(mocker: MockerFixture):
"""
Given:
A QR code image file.
Expand All @@ -26,11 +27,11 @@ def test_extract_info_from_qr_code(mocker):
result = extract_info_from_qr_code('entry_id')

assert result.outputs_prefix == 'QRCodeReader'
assert result.outputs == {'Text': 'https://xsoar.pan.dev/', 'Domain': 'xsoar.pan.dev'}
assert result.outputs == {'Text': ['https://xsoar.pan.dev/'], 'Domain': 'xsoar.pan.dev'}
assert result.readable_output == '### QR Code Read\n|Text|\n|---|\n| https://xsoar.pan.dev/ |\n'


def test_with_non_qr_code_file(mocker):
def test_with_non_qr_code_file(mocker: MockerFixture):
"""
Given:
An image file that does not contain a QR code.
Expand All @@ -52,7 +53,7 @@ def test_with_non_qr_code_file(mocker):
assert result.readable_output == 'No QR code was found in the image.'


def test_with_non_image_file(mocker):
def test_with_non_image_file(mocker: MockerFixture):
"""
Given:
A file that is not an image.
Expand All @@ -71,3 +72,26 @@ def test_with_non_image_file(mocker):

with pytest.raises(DemistoException, match='Error parsing file. Please make sure it is a valid image file.'):
extract_info_from_qr_code('entry_id')


def test_read_qr_code_multiple_codes():
"""
Given:
An image that has multiple QR codes.

When:
- Calling the ReadQRCode script.

Then:
Return the result of all images.
"""
from ReadQRCode import read_qr_code

result = read_qr_code('test_data/multiple_codes.png')

assert result == [
'http://itunes.apple.com/us/app/encyclopaedia-britannica/id447919187?mt=8',
'http://searchmobilecomputing.techtarget.com/definition/2D-barcode',
'https://www.linkedin.com/company/1334758?trk=NUS_CMPY_TWIT',
'http://en.m.wikipedia.org'
]
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Packs/CommonScripts/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Common Scripts",
"description": "Frequently used scripts pack.",
"support": "xsoar",
"currentVersion": "1.13.34",
"currentVersion": "1.13.35",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
Expand Down