mirror of https://github.com/CIRCL/lookyloo
New: upload a capture via the API
parent
8fb2e2e695
commit
c3fc91a868
|
@ -3,11 +3,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import gzip
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from uuid import uuid4
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
import flask_login # type: ignore[import-untyped]
|
import flask_login # type: ignore[import-untyped]
|
||||||
|
@ -441,6 +444,77 @@ class CaptureReport(Resource): # type: ignore[misc]
|
||||||
return lookyloo.send_mail(capture_uuid, parameters.get('email', ''), parameters.get('comment'))
|
return lookyloo.send_mail(capture_uuid, parameters.get('email', ''), parameters.get('comment'))
|
||||||
|
|
||||||
|
|
||||||
|
@api.route('/json/upload')
|
||||||
|
@api.doc(description='Submits a capture from another instance')
|
||||||
|
class UploadCapture(Resource): # type: ignore[misc]
|
||||||
|
def post(self) -> str | tuple[dict[str, Any], int]:
|
||||||
|
parameters: dict[str, Any] = request.get_json(force=True)
|
||||||
|
uuid = str(uuid4()) # NOTE: new UUID, because we do not want duplicates
|
||||||
|
listing = True if parameters['listing'] else False
|
||||||
|
har: dict[str, Any] | None = None
|
||||||
|
html: str | None = None
|
||||||
|
last_redirected_url: str | None = None
|
||||||
|
screenshot: bytes | None = None
|
||||||
|
|
||||||
|
if 'har_file' in parameters and parameters.get('har_file'):
|
||||||
|
try:
|
||||||
|
har_decoded = base64.b64decode(parameters['har_file'])
|
||||||
|
except Exception as e:
|
||||||
|
return {'error': "Invalid base64-encoding"}, 400
|
||||||
|
har = json.loads(gzip.decompress(har_decoded))
|
||||||
|
last_redirected_url = parameters.get('landing_page')
|
||||||
|
if 'screenshot_file' in parameters:
|
||||||
|
screenshot = base64.b64decode(parameters['screenshot_file'])
|
||||||
|
if 'html_file' in parameters:
|
||||||
|
html = base64.b64decode(parameters['html_file']).decode()
|
||||||
|
lookyloo.store_capture(uuid, is_public=listing, har=har,
|
||||||
|
last_redirected_url=last_redirected_url,
|
||||||
|
png=screenshot, html=html)
|
||||||
|
return uuid
|
||||||
|
|
||||||
|
elif 'full_capture' in parameters and parameters.get('full_capture'):
|
||||||
|
try:
|
||||||
|
zipped_capture = base64.b64decode(parameters['full_capture'].encode())
|
||||||
|
except Exception as e:
|
||||||
|
return {'error': "Invalid base64-encoding"}, 400
|
||||||
|
# it *only* accepts a lookyloo export.
|
||||||
|
cookies: list[dict[str, str]] | None = None
|
||||||
|
has_error = False
|
||||||
|
with ZipFile(BytesIO(zipped_capture), 'r') as lookyloo_capture:
|
||||||
|
potential_favicons = set()
|
||||||
|
for filename in lookyloo_capture.namelist():
|
||||||
|
if filename.endswith('0.har.gz'):
|
||||||
|
# new formal
|
||||||
|
har = json.loads(gzip.decompress(lookyloo_capture.read(filename)))
|
||||||
|
elif filename.endswith('0.har'):
|
||||||
|
# old format
|
||||||
|
har = json.loads(lookyloo_capture.read(filename))
|
||||||
|
elif filename.endswith('0.html'):
|
||||||
|
html = lookyloo_capture.read(filename).decode()
|
||||||
|
elif filename.endswith('0.last_redirect.txt'):
|
||||||
|
last_redirected_url = lookyloo_capture.read(filename).decode()
|
||||||
|
elif filename.endswith('0.png'):
|
||||||
|
screenshot = lookyloo_capture.read(filename)
|
||||||
|
elif filename.endswith('0.cookies.json'):
|
||||||
|
# Not required
|
||||||
|
cookies = json.loads(lookyloo_capture.read(filename))
|
||||||
|
elif filename.endswith('potential_favicons.ico'):
|
||||||
|
# We may have more than one favicon
|
||||||
|
potential_favicons.add(lookyloo_capture.read(filename))
|
||||||
|
if not har or not html or not last_redirected_url or not screenshot:
|
||||||
|
has_error = True
|
||||||
|
if not has_error:
|
||||||
|
lookyloo.store_capture(uuid, is_public=listing, har=har,
|
||||||
|
last_redirected_url=last_redirected_url,
|
||||||
|
png=screenshot, html=html, cookies=cookies,
|
||||||
|
potential_favicons=potential_favicons)
|
||||||
|
return uuid
|
||||||
|
return {'error': "Capture has error"}, 400
|
||||||
|
|
||||||
|
else:
|
||||||
|
return {'error': "Full capture or at least har-file is required"}, 400
|
||||||
|
|
||||||
|
|
||||||
auto_report_model = api.model('AutoReportModel', {
|
auto_report_model = api.model('AutoReportModel', {
|
||||||
'email': fields.String(description="Email of the reporter, used by the analyst to get in touch.", example=''),
|
'email': fields.String(description="Email of the reporter, used by the analyst to get in touch.", example=''),
|
||||||
'comment': fields.String(description="Description of the URL, will be given to the analyst.", example='')
|
'comment': fields.String(description="Description of the URL, will be given to the analyst.", example='')
|
||||||
|
|
Loading…
Reference in New Issue