<commit_before>/*
 * Copyright IBM Corporation 2016
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import KituraNet
import SwiftyJSON

import Foundation
import LoggerAPI

// MARK: RouterResponse

/// Router response that the server sends as a reply to `RouterRequest`.
public class RouterResponse {

    struct State {

        /// Whether the response has ended
        var invokedEnd = false

        /// Whether data has been added to buffer
        var invokedSend = false
    }

    struct Lifecycle {

        /// Lifecycle hook called on end()
        var onEndInvoked: LifecycleHandler = {}

        /// Current pre-write lifecycle handler
        var writtenDataFilter: WrittenDataFilter = { body in
            return body
        }
    }

    /// The server response
    let response: ServerResponse

    /// The router
    unowned let router: Router

    /// The associated request
    let request: RouterRequest

    /// The buffer used for output
    private let buffer = BufferList()

    /// State of the request
    var state = State()

    private var lifecycle = Lifecycle()

    /// Set of cookies to return with the response.
    public var cookies = [String: HTTPCookie]()
    
    /// Optional error value.
    public var error: Swift.Error?

    /// HTTP headers of the response.
    public var headers: Headers

    /// HTTP status code of the response.
    public var statusCode: HTTPStatusCode {
        get {
            return response.statusCode ?? .unknown
        }

        set(newValue) {
            response.statusCode = newValue
        }
    }

    /// Initialize a `RouterResponse` instance
    ///
    /// - Parameter response: The `ServerResponse` object to work with
    /// - Parameter router: The `Router` instance that this `RouterResponse` is
    ///                    working with.
    /// - Parameter request: The `RouterRequest` object that is paired with this
    ///                     `RouterResponse` object.
    init(response: ServerResponse, router: Router, request: RouterRequest) {
        self.response = response
        self.router = router
        self.request = request
        headers = Headers(headers: response.headers)
        statusCode = .unknown
    }

    /// End the response.
    ///
    /// - Throws: Socket.Error if an error occurred while writing to a socket.
    @discardableResult
    public func end() throws {
        lifecycle.onEndInvoked()

        // Sets status code if unset
        if statusCode == .unknown {
            statusCode = .OK
        }

        let content = lifecycle.writtenDataFilter(buffer.data)
        let contentLength = headers["Content-Length"]
        if  contentLength == nil {
            headers["Content-Length"] = String(content.count)
        }
        addCookies()

        if  request.method != .head {
            try response.write(from: content)
        }
        state.invokedEnd = true
        try response.end()
    }

    /// Add Set-Cookie headers
    private func addCookies() {
        var cookieStrings = [String]()

        for  (_, cookie) in cookies {
            var cookieString = cookie.name + "=" + cookie.value + "; path=" + cookie.path + "; domain=" + cookie.domain
            if  let expiresDate = cookie.expiresDate {
                cookieString += "; expires=" + SPIUtils.httpDate(expiresDate)
            }

            if  cookie.isSecure {
                cookieString += "; secure; HTTPOnly"
            }

            cookieStrings.append(cookieString)
        }
        response.headers.append("Set-Cookie", value: cookieStrings)<commit_msg>Eliminate potential reference loops within RouterResponse lifecycle functions (#911)<commit_after>/*
 * Copyright IBM Corporation 2016
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import KituraNet
import SwiftyJSON

import Foundation
import LoggerAPI

// MARK: RouterResponse

/// Router response that the server sends as a reply to `RouterRequest`.
public class RouterResponse {

    struct State {

        /// Whether the response has ended
        var invokedEnd = false

        /// Whether data has been added to buffer
        var invokedSend = false
    }

    /// A set of functions called during the life cycle of a Request.
    /// As The life cycle functions/closures may capture various things including the
    /// response object in question, each life cycle function needs a reset function
    /// to clear out any reference cycles that may have occurred.
    struct Lifecycle {

        /// Lifecycle hook called on end()
        var onEndInvoked: LifecycleHandler = {}

        /// Current pre-write lifecycle handler
        var writtenDataFilter: WrittenDataFilter = { body in
            return body
        }
        
        mutating func resetOnEndInvoked() {
            onEndInvoked = {}
        }
        
        mutating func resetWrittenDataFilter() {
            writtenDataFilter = { body in
                return body
            }
        }
    }

    /// The server response
    let response: ServerResponse

    /// The router
    unowned let router: Router

    /// The associated request
    let request: RouterRequest

    /// The buffer used for output
    private let buffer = BufferList()

    /// State of the request
    var state = State()

    private var lifecycle = Lifecycle()

    /// Set of cookies to return with the response.
    public var cookies = [String: HTTPCookie]()
    
    /// Optional error value.
    public var error: Swift.Error?

    /// HTTP headers of the response.
    public var headers: Headers

    /// HTTP status code of the response.
    public var statusCode: HTTPStatusCode {
        get {
            return response.statusCode ?? .unknown
        }

        set(newValue) {
            response.statusCode = newValue
        }
    }

    /// Initialize a `RouterResponse` instance
    ///
    /// - Parameter response: The `ServerResponse` object to work with
    /// - Parameter router: The `Router` instance that this `RouterResponse` is
    ///                    working with.
    /// - Parameter request: The `RouterRequest` object that is paired with this
    ///                     `RouterResponse` object.
    init(response: ServerResponse, router: Router, request: RouterRequest) {
        self.response = response
        self.router = router
        self.request = request
        headers = Headers(headers: response.headers)
        statusCode = .unknown
    }

    /// End the response.
    ///
    /// - Throws: Socket.Error if an error occurred while writing to a socket.
    @discardableResult
    public func end() throws {
        lifecycle.onEndInvoked()
        lifecycle.resetOnEndInvoked()

        // Sets status code if unset
        if statusCode == .unknown {
            statusCode = .OK
        }

        let content = lifecycle.writtenDataFilter(buffer.data)
        lifecycle.resetWrittenDataFilter()
        
        let contentLength = headers["Content-Length"]
        if  contentLength == nil {
            headers["Content-Length"] = String(content.count)
        }
        addCookies()

        if  request.method != .head {
            try response.write(from: content)
        }
        state.invokedEnd = true
        try response.end()
    }

    /// Add Set-Cookie headers
    private func addCookies() {
        var cookieStrings = [String]()

        for  (_, cookie) in cookies {
            var cookieString = cookie.name + "=" + cookie.value + "; path=" + cookie.path + "; domain=" + cookie.domain
            if  let expiresDate = cookie.expiresDate {
                cookieString += "; expires=" + SPIUtils.httpDate(expiresDate)
            }

            if  cookie.isSecure {
                cookieString += "; secure; HTTPOnly"
            }

            cookieStrings.append(cookieString)
        }
        response.headers.append("Set-Cookie", value: cookieStrings)
{'fraction_non_alphanumeric': 0.08509922637066936, 'fraction_numerical': 0.002130283664087902, 'mean_word_length': 2.0474888964810387, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1539081', 'n_tokens_mistral': 2265, 'n_tokens_neox': 2168, 'n_words': 812}