123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- """ Port of sentry.api.endpoints.debug_files.DifAssembleEndpoint """
- import io
- import re
- import tempfile
- import zipfile
- from hashlib import sha1
- from django.core.files import File as DjangoFile
- from django.db import transaction
- from django.shortcuts import get_object_or_404
- from rest_framework import exceptions, status, views
- from rest_framework.response import Response
- from symbolic import ProguardMapper
- from files.models import File, FileBlob
- from organizations_ext.models import Organization
- from projects.models import Project
- from .models import DebugInformationFile
- from .permissions import (
- DifsAssemblePermission,
- DymsPermission,
- ProjectReprocessingPermission,
- )
- from .tasks import DIF_STATE_CREATED, DIF_STATE_NOT_FOUND, DIF_STATE_OK, difs_assemble
- MAX_UPLOAD_BLOB_SIZE = 32 * 1024 * 1024 # 32MB
- class DifsAssembleAPIView(views.APIView):
- permission_classes = [DifsAssemblePermission]
- def post(self, request, organization_slug, project_slug):
- organization = get_object_or_404(
- Organization, slug=organization_slug.lower(), users=self.request.user
- )
- self.check_object_permissions(request, organization)
- project = get_object_or_404(Project, slug=project_slug.lower())
- if project.organization.id != organization.id:
- raise exceptions.PermissionDenied(
- "The project is not under this organization"
- )
- responses = {}
- files = request.data.items()
- for checksum, file in files:
- chunks = file.get("chunks", [])
- name = file.get("name", None)
- debug_id = file.get("debug_id", None)
- file = (
- DebugInformationFile.objects.filter(
- project__slug=project_slug, file__checksum=checksum
- )
- .select_related("file")
- .first()
- )
- if file is not None:
- responses[checksum] = {
- "state": DIF_STATE_OK,
- "missingChunks": [],
- }
- continue
- existed_chunks = FileBlob.objects.filter(checksum__in=chunks).values_list(
- "checksum", flat=True
- )
- missing_chunks = list(set(chunks) - set(existed_chunks))
- if len(missing_chunks) != 0:
- responses[checksum] = {
- "state": DIF_STATE_NOT_FOUND,
- "missingChunks": missing_chunks,
- }
- continue
- responses[checksum] = {"state": DIF_STATE_CREATED, "missingChunks": []}
- difs_assemble.delay(project_slug, name, checksum, chunks, debug_id)
- return Response(responses)
- class ProjectReprocessingAPIView(views.APIView):
- """
- Not implemented. It is a dummy API to keep `sentry-cli upload-dif` happy
- """
- permission_classes = [ProjectReprocessingPermission]
- def post(self, request, organization_slug, project_slug):
- return Response()
- def extract_proguard_id(name):
- match = re.search("proguard/([-a-fA-F0-9]+).txt", name)
- if match is None:
- return
- return match.group(1)
- def extract_proguard_metadata(proguard_file):
- try:
- mapper = ProguardMapper.open(proguard_file)
- if mapper is None:
- return
- metadata = {"arch": "any", "feature": "mapping"}
- return metadata
- except Exception:
- pass
- class DsymsAPIView(views.APIView):
- """
- Implementation of /files/dsyms API View
- """
- permission_classes = [DymsPermission]
- def post(self, request, organization_slug, project_slug):
- organization = get_object_or_404(
- Organization, slug=organization_slug.lower(), users=self.request.user
- )
- self.check_object_permissions(request, organization)
- project = get_object_or_404(Project, slug=project_slug.lower())
- if project.organization.id != organization.id:
- raise exceptions.PermissionDenied(
- "The project is not under this organization"
- )
- if "file" not in request.data:
- return Response(
- {"error": "No file uploaded"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- try:
- file = request.data["file"]
- if file.size > MAX_UPLOAD_BLOB_SIZE:
- return Response(
- {"error": "File size too large"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- content = file.read()
- buffer = io.BytesIO(content)
- if zipfile.is_zipfile(buffer) is False:
- return Response(
- {"error": "Invalid file type uploaded"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- results = []
- with zipfile.ZipFile(buffer) as uploaded_zip_file:
- for filename in uploaded_zip_file.namelist():
- proguard_id = extract_proguard_id(filename)
- if proguard_id is None:
- return Response(
- {"error": "Invalid proguard mapping file uploaded"}, # noqa
- status=status.HTTP_400_BAD_REQUEST,
- )
- with uploaded_zip_file.open(filename) as proguard_file:
- result = self.create_dif_from_read_only_file(
- proguard_file, project, proguard_id, filename
- )
- if result is None:
- return Response(
- {
- "error": "Invalid proguard mapping file uploaded"
- }, # noqa
- status=status.HTTP_400_BAD_REQUEST,
- )
- results.append(result)
- return Response(results)
- except Exception as err:
- return Response(
- {"error": str(err)},
- status=status.HTTP_400_BAD_REQUEST,
- )
- def create_dif_from_read_only_file(
- self, proguard_file, project, proguard_id, filename
- ):
- with tempfile.NamedTemporaryFile("br+") as tmp:
- content = proguard_file.read()
- tmp.write(content)
- tmp.flush()
- metadata = extract_proguard_metadata(tmp.name)
- if metadata is None:
- return None
- checksum = sha1(content).hexdigest()
- with transaction.atomic():
- size = len(content)
- blob = FileBlob.objects.filter(checksum=checksum).first()
- if blob is None:
- blob = FileBlob(checksum=checksum, size=size) # noqa
- blob.blob.save(filename, DjangoFile(tmp))
- blob.save()
- fileobj = File.objects.filter(checksum=checksum).first()
- if fileobj is None:
- fileobj = File()
- fileobj.name = filename
- fileobj.headers = {}
- fileobj.checksum = checksum
- fileobj.size = size
- fileobj.blob = blob
- fileobj.save()
- dif = DebugInformationFile.objects.filter(
- file__checksum=checksum, project=project
- ).first()
- if dif is None:
- dif = DebugInformationFile()
- dif.name = filename
- dif.project = project
- dif.file = fileobj
- dif.data = {
- "arch": metadata["arch"],
- "debug_id": proguard_id,
- "symbol_type": "proguard",
- "features": ["mapping"],
- }
- dif.save()
- result = {
- "id": dif.id,
- "debugId": proguard_id,
- "cpuName": "any",
- "objectName": "proguard-mapping",
- "symbolType": "proguard",
- "size": size,
- "sha1": checksum,
- "data": {"features": ["mapping"]},
- "headers": {"Content-Type": "text/x-proguard+plain"},
- "dateCreated": fileobj.created,
- }
- return result
|