docs: private void putObject(String bucketName, String objectName, Long size, Object data,
        Map<String, String> headerMap, ServerSideEncryption sse)
      throws InvalidBucketNameException, NoSuchAlgorithmException, IOException,
             InvalidKeyException, NoResponseException, XmlPullParserException, ErrorResponseException,
             InternalException,
             InvalidArgumentException, InsufficientDataException {
      boolean unknownSize = false;
  
      // Add content type if not already set
      if (headerMap.get("Content-Type") == null) {
        headerMap.put("Content-Type", "application/octet-stream");
      }
  
      if (size == null) {
        unknownSize = true;
        size = MAX_OBJECT_SIZE;
      }
  
      if (size <= MIN_MULTIPART_SIZE) {
        // Single put object.
        if (sse != null) {
          sse.marshal(headerMap);
        }
        putObject(bucketName, objectName, size.intValue(), data, null, 0, headerMap);
        return;
      }
  
      /* Multipart upload */
      int[] rv = calculateMultipartSize(size);
      int partSize = rv[0];
      int partCount = rv[1];
      int lastPartSize = rv[2];
      Part[] totalParts = new Part[partCount];
  
      // if sse is requested set the necessary headers before we begin the multipart session.
      if (sse != null) {
        sse.marshal(headerMap);
      }
  
      // initiate new multipart upload.
      String uploadId = initMultipartUpload(bucketName, objectName, headerMap);
  
      try {
        int expectedReadSize = partSize;
        for (int partNumber = 1; partNumber <= partCount; partNumber++) {
          if (partNumber == partCount) {
            expectedReadSize = lastPartSize;
          }
  
          // For unknown sized stream, check available size.
          int availableSize = 0;
          if (unknownSize) {
            // Check whether data is available one byte more than expectedReadSize.
            availableSize = getAvailableSize(data, expectedReadSize + 1);
            // If availableSize is less or equal to expectedReadSize, then we reached last part.
            if (availableSize <= expectedReadSize) {
              // If it is first part, do single put object.
              if (partNumber == 1) {
                putObject(bucketName, objectName, availableSize, data, null, 0, headerMap);
                return;
              }
              expectedReadSize = availableSize;
              partCount = partNumber;
            }
          }
  
          // In multi-part uploads, Set encryption headers in the case of SSE-C.
          Map<String, String> encryptionHeaders = new HashMap<>();
          if (sse != null && sse.getType() == ServerSideEncryption.Type.SSE_C) {
            sse.marshal(encryptionHeaders);
          }
  
          String etag = putObject(bucketName, objectName, expectedReadSize, data,
                                  uploadId, partNumber, encryptionHeaders);
          totalParts[partNumber - 1] = new Part(partNumber, etag);
        }
        // All parts have been uploaded, complete the multipart upload.
        completeMultipart(bucketName, objectName, uploadId, totalParts);
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        abortMultipartUpload(bucketName, objectName, uploadId);
        throw e;
      }
    }