Пример #1
0
def validate_metadata_encoding(request_body: bytes) -> None:
    """
    Validate that the given application metadata can be base64 decoded.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: Application metadata is given and it cannot be base64
            decoded.
    """
    if not request_body:
        return

    request_text = request_body.decode()
    request_json = json.loads(request_text)
    if 'application_metadata' not in request_json:
        return

    application_metadata = request_json.get('application_metadata')

    if application_metadata is None:
        return

    try:
        decode_base64(encoded_data=application_metadata)
    except binascii.Error as exc:
        raise Fail(status_code=HTTPStatus.UNPROCESSABLE_ENTITY) from exc
Пример #2
0
def validate_metadata_size(request_body: bytes) -> None:
    """
    Validate that the given application metadata is a string or 1024 * 1024
    bytes or fewer.

    Args:
        request_body: The body of the request.

    Raises:
        MetadataTooLarge: Application metadata is given and it is too large.
    """
    if not request_body:
        return

    request_text = request_body.decode()
    request_json = json.loads(request_text)
    application_metadata = request_json.get('application_metadata')
    if application_metadata is None:
        return
    decoded = decode_base64(encoded_data=application_metadata)

    max_metadata_bytes = 1024 * 1024 - 1
    if len(decoded) <= max_metadata_bytes:
        return

    raise MetadataTooLarge
Пример #3
0
def validate_image_size(request_body: bytes) -> None:
    """
    Validate the file size of the image given to a VWS endpoint.

    Args:
        request_body: The body of the request.

    Raises:
        ImageTooLarge:  The image is given and is not under a certain file
            size threshold.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    image = json.loads(request_text).get('image')

    if image is None:
        return

    decoded = decode_base64(encoded_data=image)

    if len(decoded) <= 2359293:
        return

    raise ImageTooLarge
Пример #4
0
def validate_image_color_space(request_body: bytes) -> None:
    """
    Validate the color space of the image given to a VWS endpoint.

    Args:
        request_body: The body of the request.

    Raises:
        BadImage: The image is given and is not in either the RGB or
            greyscale color space.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    image = json.loads(request_text).get('image')

    if image is None:
        return

    decoded = decode_base64(encoded_data=image)
    image_file = io.BytesIO(decoded)
    pil_image = Image.open(image_file)

    if pil_image.mode in ('L', 'RGB'):
        return

    raise BadImage
Пример #5
0
def validate_image_format(request_body: bytes) -> None:
    """
    Validate the format of the image given to a VWS endpoint.

    Args:
        request_body: The body of the request.

    Raises:
        BadImage:  The image is given and is not either a PNG or a JPEG.
    """
    if not request_body:
        return

    request_text = request_body.decode()
    image = json.loads(request_text).get('image')

    if image is None:
        return

    decoded = decode_base64(encoded_data=image)
    image_file = io.BytesIO(decoded)
    pil_image = Image.open(image_file)

    if pil_image.format in ('PNG', 'JPEG'):
        return

    raise BadImage
Пример #6
0
def validate_image_is_image(request_body: bytes) -> None:
    """
    Validate that the given image data is actually an image file.

    Args:
        request_body: The body of the request.

    Raises:
        BadImage: Image data is given and it is not an image file.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    image = json.loads(request_text).get('image')

    if image is None:
        return

    decoded = decode_base64(encoded_data=image)
    image_file = io.BytesIO(decoded)

    try:
        Image.open(image_file)
    except OSError as exc:
        raise BadImage from exc
Пример #7
0
def validate_image_encoding(request_body: bytes) -> None:
    """
    Validate that the given image data can be base64 decoded.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: Image data is given and it cannot be base64 decoded.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    if 'image' not in json.loads(request_text):
        return

    image = json.loads(request_text).get('image')

    try:
        decode_base64(encoded_data=image)
    except binascii.Error as exc:
        raise Fail(status_code=HTTPStatus.UNPROCESSABLE_ENTITY) from exc
Пример #8
0
def get_query_match_response_text(
    request_headers: Dict[str, str],
    request_body: bytes,
    request_method: str,
    request_path: str,
    databases: Set[VuforiaDatabase],
    query_processes_deletion_seconds: Union[int, float],
    query_recognizes_deletion_seconds: Union[int, float],
) -> str:
    """
    Args:
        request_path: The path of the request.
        request_headers: The headers sent with the request.
        request_body: The body of the request.
        request_method: The HTTP method of the request.
        databases: All Vuforia databases.
        query_recognizes_deletion_seconds: The number of seconds after a target
            has been deleted that the query endpoint will still recognize the
            target for.
        query_processes_deletion_seconds: The number of seconds after a target
            deletion is recognized that the query endpoint will return a 500
            response on a match.

    Returns:
        The response text for a query endpoint request.

    Raises:
        MatchingTargetsWithProcessingStatus: There is at least one matching
            target which has the status 'processing'.
        ActiveMatchingTargetsDeleteProcessing: There is at least one active
            target which matches and was recently deleted.
    """
    body_file = io.BytesIO(request_body)

    _, pdict = cgi.parse_header(request_headers['Content-Type'])
    parsed = cgi.parse_multipart(
        fp=body_file,
        pdict={
            'boundary': pdict['boundary'].encode(),
        },
    )

    [max_num_results] = parsed.get('max_num_results', ['1'])

    [include_target_data] = parsed.get('include_target_data', ['top'])
    include_target_data = include_target_data.lower()

    [image_bytes] = parsed['image']
    assert isinstance(image_bytes, bytes)
    image = io.BytesIO(image_bytes)
    gmt = ZoneInfo('GMT')
    now = datetime.datetime.now(tz=gmt)

    processing_timedelta = datetime.timedelta(
        seconds=query_processes_deletion_seconds,
    )

    recognition_timedelta = datetime.timedelta(
        seconds=query_recognizes_deletion_seconds,
    )

    database = get_database_matching_client_keys(
        request_headers=request_headers,
        request_body=request_body,
        request_method=request_method,
        request_path=request_path,
        databases=databases,
    )

    assert isinstance(database, VuforiaDatabase)

    matching_targets = [
        target
        for target in database.targets
        if _images_match(image=target.image, another_image=image)
    ]

    not_deleted_matches = [
        target
        for target in matching_targets
        if target.active_flag
        and not target.delete_date
        and target.status == TargetStatuses.SUCCESS.value
    ]

    deletion_not_recognized_matches = [
        target
        for target in matching_targets
        if target.active_flag
        and target.delete_date
        and (now - target.delete_date) < recognition_timedelta
    ]

    matching_targets_with_processing_status = [
        target
        for target in matching_targets
        if target.status == TargetStatuses.PROCESSING.value
    ]

    active_matching_targets_delete_processing = [
        target
        for target in matching_targets
        if target.active_flag
        and target.delete_date
        and (now - target.delete_date)
        < (recognition_timedelta + processing_timedelta)
        and target not in deletion_not_recognized_matches
    ]

    if matching_targets_with_processing_status:
        raise MatchingTargetsWithProcessingStatus

    if active_matching_targets_delete_processing:
        raise ActiveMatchingTargetsDeleteProcessing

    matches = not_deleted_matches + deletion_not_recognized_matches

    results: List[Dict[str, Any]] = []
    for target in matches:
        target_timestamp = target.last_modified_date.timestamp()
        if target.application_metadata is None:
            application_metadata = None
        else:
            application_metadata = base64.b64encode(
                decode_base64(encoded_data=target.application_metadata),
            ).decode('ascii')
        target_data = {
            'target_timestamp': int(target_timestamp),
            'name': target.name,
            'application_metadata': application_metadata,
        }

        if include_target_data == 'all':
            result = {
                'target_id': target.target_id,
                'target_data': target_data,
            }
        elif include_target_data == 'top' and not results:
            result = {
                'target_id': target.target_id,
                'target_data': target_data,
            }
        else:
            result = {
                'target_id': target.target_id,
            }

        results.append(result)

    results = results[: int(max_num_results)]
    body = {
        'result_code': ResultCodes.SUCCESS.value,
        'results': results,
        'query_id': uuid.uuid4().hex,
    }

    value = json_dump(body)
    return value