override fun doHandle()

in src/main/kotlin/org/jetbrains/teamcity/github/controllers/GitHubWebHookListener.kt [89:194]


    override fun doHandle(request: HttpServletRequest, response: HttpServletResponse): ModelAndView? {
        //TODO: Check User-Agent
        // From documentation: Also, the User-Agent for the requests will have the prefix GitHub-Hookshot/.

        val eventType: String? = request.getHeader(X_GitHub_Event)
        @Suppress("IfNullToElvis")
        if (eventType == null) {
            return simpleText(response, SC_BAD_REQUEST, "'$X_GitHub_Event' header is missing")
        }

        if (!SupportedEvents.contains(eventType)) {
            LOG.info("Received unsupported event type '$eventType', ignoring")
            return simpleText(response, SC_ACCEPTED, "Unsupported event type '$eventType'")
        }

        val signature = request.getHeader(X_Hub_Signature)
        if (signature == null || signature.isBlank()) {
            LOG.warn("Received event without signature ($X_Hub_Signature header)")
            return simpleText(response, SC_BAD_REQUEST, "'$X_Hub_Signature' header is missing")
        }

        val path = WebUtil.getPathWithoutAuthenticationType(request)
        val pubKey: String? = getPubKeyFromRequestPath(path)
        if (pubKey == null) {
            LOG.warn("Received hook event with signature header but without public key part of url")
            return simpleText(response, SC_BAD_REQUEST, "'$X_Hub_Signature' is present but request url does not contains public key part")
        }

        LOG.debug("Received hook event with public key in path: $pubKey")

        val authData = getAuthData(pubKey)
        if (authData == null) {
            LOG.warn("No stored auth data (secret key) found for public key '$pubKey'")
            return simpleText(response, SC_NOT_FOUND, "No stored auth data (secret key) found for public key '$pubKey'. Seems hook created not by this TeamCity server. Reinstall hook via TeamCity UI.")
        }
        val userId = authData.userId
        val user = UserModel.findUserById(userId)
        if (user == null) {
            AuthDataStorage.removeAllForUser(userId)
            LOG.warn("User with id '$userId' not found")
            return simpleText(response, SC_NOT_FOUND, "User installed webhook no longer registered in TeamCity. Remove and reinstall webhook.")
        }

        val hookInfo = getHookInfoWithWaiting(authData, eventType)
        if (hookInfo == null) {
            // Seems local cache was cleared or it's a organization hook
            LOG.warn("No stored hook info found for public key '$pubKey' and repository '${authData.repository}'")
        }

        if (request.contentLength >= MaxPayloadSize) {
            val message = "Payload size exceed ${StringUtil.formatFileSize(MaxPayloadSize, 0)} limit"
            LOG.info("$message. Requests url: $path")
            return simpleText(response, SC_REQUEST_ENTITY_TOO_LARGE, message)
        }

        val content: ByteArray
        try {
            content = LimitInputStream(request.inputStream, MaxPayloadSize).readBytes()
        } catch(e: IOException) {
            LOG.warnAndDebugDetails("Failed to read payload of $eventType event", e)
            return simpleText(response, SC_SERVICE_UNAVAILABLE, "Failed to read payload: ${e.message}")
        } finally {
            FileUtil.close(request.inputStream)
        }
        if (content.size >= MaxPayloadSize) {
            val message = "Payload size exceed ${StringUtil.formatFileSize(MaxPayloadSize, 0)} limit"
            LOG.info("$message. Requests url: $path")
            return simpleText(response, SC_REQUEST_ENTITY_TOO_LARGE, message)
        }

        if (!HMacUtil.checkHMac(content, authData.secret.toByteArray(charset("UTF-8")), signature)) {
            LOG.warn("HMac verification failed for $eventType event")
            return simpleText(response, SC_FORBIDDEN, "Payload signature verification failed. Ensure request url, '$X_Hub_Signature' header and payload are correct")
        }

        val contentReader = BufferedReader(InputStreamReader(ByteArrayInputStream(content), request.characterEncoding ?: "UTF-8"))

        try {
            when (eventType) {
                "ping" -> {
                    val payload = GsonUtilsEx.fromJson(contentReader, PingWebHookPayload::class.java)
                    val pair = doHandlePingEvent(payload, hookInfo, user)
                    return pair?.let { simpleText(response, pair.first, pair.second) }
                }
                "push" -> {
                    val payload = GsonUtilsEx.fromJson(contentReader, PushWebHookPayload::class.java)
                    val pair = doHandlePushEvent(payload, hookInfo, user)
                    return pair?.let { simpleText(response, pair.first, pair.second) }
                }
                "pull_request" -> {
                    val payload = GsonUtilsEx.fromJson(contentReader, PullRequestPayloadEx::class.java)
                    val pair = doHandlePullRequestEvent(payload, hookInfo, user)
                    return pair?.let { simpleText(response, pair.first, pair.second) }
                }
            }
        } catch(e: Exception) {
            val message = if (e is JsonSyntaxException || e is JsonIOException) {
                "Failed to parse payload"
            } else {
                "Failed to process request (event type is '$eventType')"
            }
            LOG.warnAndDebugDetails(message, e)
            return simpleText(response, SC_SERVICE_UNAVAILABLE, message + ": ${e.message}")
        }
        return null
    }