<commit_before>//
//  WebDAVFileProvider.swift
//  FileProvider
//
//  Created by <NAME>.
//  Copyright © 2016 Mousavian. Distributed under MIT license.
//

import Foundation
import CoreGraphics

/**
 Allows accessing to WebDAV server files. This provider doesn't cache or save files internally, however you can
 set `useCache` and `cache` properties to use Foundation `NSURLCache` system.
 
 WebDAV system supported by many cloud services including [Box.com](https://www.box.com/home) 
 and [Yandex disk](https://disk.yandex.com) and [ownCloud](https://owncloud.org).
 
 - Important: Because this class uses `URLSession`, it's necessary to disable App Transport Security
     in case of using this class with unencrypted HTTP connection.
     [Read this to know how](http://iosdevtips.co/post/121756573323/ios-9-xcode-7-http-connect-server-error).
*/
open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
    override open class var type: String { return "WebDAV" }
    
    /// An enum which defines HTTP Authentication method, usually you should it default `.digest`.
    /// If the server uses OAuth authentication, credential must be set with token as `password`, like Dropbox.
    public var credentialType: URLRequest.AuthenticationType = .digest
    
    /**
     Initializes WebDAV provider.
     
     - Parameters:
       - baseURL: Location of WebDAV server.
       - credential: An `URLCredential` object with `user` and `password`.
       - cache: A URLCache to cache downloaded files and contents.
    */
    public init? (baseURL: URL, credential: URLCredential?, cache: URLCache? = nil) {
        if  !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
            return nil
        }
        let refinedBaseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent(""))
        super.init(baseURL: refinedBaseURL.absoluteURL, credential: credential, cache: cache)
    }
    
    public required convenience init?(coder aDecoder: NSCoder) {
        guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
            return nil
        }
        self.init(baseURL: baseURL,
                  credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
        self.useCache        = aDecoder.decodeBool(forKey: "useCache")
        self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
    }
    
    override open func copy(with zone: NSZone? = nil) -> Any {
        let copy = WebDAVFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
        copy.delegate = self.delegate
        copy.fileOperationDelegate = self.fileOperationDelegate
        copy.useCache = self.useCache
        copy.validatingCache = self.validatingCache
        return copy
    }
    
    override open func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
        let query = NSPredicate(format: "TRUEPREDICATE")
        _ = searchFiles(path: path, recursive: false, query: query, including: [], foundItemHandler: nil, completionHandler: completionHandler)
    }
    
    /**
     Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
     
     If the directory contains no entries or an error is occured, this method will return the empty array.
     
     - Parameter path: path to target directory. If empty, root will be iterated.
     - Parameter including: An array which determines which file properties should be considered to fetch.
     - Parameter completionHandler: a closure with result of directory entries or error.
     - Parameter contents: An array of `FileObject` identifying the the directory entries.
     - Parameter error: Error returned by system.
     */
    open func contentsOfDirectory(path: String, including: [URLResourceKey], completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
        let query = NSPredicate(format: "TRUEPREDICATE")
        _ = searchFiles(path: path, recursive: false, query: query, including: including, foundItemHandler: nil, completionHandler: completionHandler)
    }
    
    override open func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
        self.attributesOfItem(path: path, including: [], completionHandler: completionHandler)
    }
    
    /**
     Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler.
     
     If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
     
     - Parameter path: path to target directory. If empty, attributes of root will be returned.
     - Parameter including: An array which determines which file properties should be considered to fetch.
     - Parameter completionHandler: a closure with result of directory entries or error.
     - Parameter attributes: A `FileObject` containing the attributes of the item.
     - Parameter error: Error returned by system.
     */
    open func attributesOfItem(path: String, including: [URLResourceKey], completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
        let url = self.url(of: path)
        var request = URLRequest(url: url)
        request.httpMethod = "PROPFIND"
        request.setValue("0", forHTTPHeaderField: "Depth")
        request.setValue(authentication: credential, with: credentialType)
        request.setValue(contentType: .xml, charset: .utf8)
        request.httpBody = WebDavFileObject.xmlProp(including)
        runDataTask(with: request, completionHandler: { (data, response, error) in
            var responseError: FileProviderHTTPError?
            if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
                responseError = self.serverError(with: rCode, path: path, data: data)
            }
            if let data = data {
                let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
                if let attr = xresponse.first {
                    completionHandler(WebDavFileObject(attr), responseError ?? error)
                    return
                }
            }
            completionHandler(nil, responseError ?? error)
        })
    }
    
    override open func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
        // Not all WebDAV clients implements RFC2518 which allows geting storage quota.
        // In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
        // and used space is zero.
        guard let baseURL = baseURL else {
            return
        }
        var request = URLRequest(url: baseURL)
        request.httpMethod = "PROPFIND"
        request.setValue("0", forHTTPHeaderField: "Depth")
        request.setValue(authentication: credential, with: credentialType)
        request.setValue(contentType: .xml, charset: .utf8)
        request.httpBody = WebDavFileObject.xmlProp([.volumeTotalCapacityKey, .volumeAvailableCapacityKey, .creationDateKey])
        runDataTask(with: request, completionHandler: { (data, response, error) in
            guard let data = data, let attr = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL).first else {
                completionHandler(nil)
                return
            }
            
            let volume = VolumeObject(allValues: [:])
            volume.creationDate = attr.prop["creationdate"].flatMap { Date(rfcString: $0) }
            volume.availableCapacity = attr.prop["quota-available-bytes"].flatMap({ Int64($0) }) ?? 0
            if let usage = attr.prop["quota-used-bytes"].flatMap({ Int64($0) }) {
                volume.totalCapacity = volume.availableCapacity + usage
            }
            completionHandler(volume)
        })
    }
    
    open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ([FileObject], Error?) -> Void) -> Progress? {
        return searchFiles(path: path, recursive: recursive, query: query, including: [], foundItemHandler: foundItemHandler, completionHandler: completionHandler)
    }
    
    /**
     Search files inside directory using query asynchronously.
     
     - Note: Query string is limited to file name, to search based on other file attributes, use NSPredicate version.
     
     - Parameters:
       - path: location of directory to start search
       - recursive: Searching subdirectories of path
       - query: Simple string that file name begins with to be search, case-insensitive.
       - including: An array which determines which file properties should be considered to fetch.
       - foundItemHandler: Closure which is called when a file is found
       - completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
       - files: all files meat the `query` criteria.
       - error: `Error` returned by server if occured.
     */
    open func searchFiles(path: String, recursive: Bool, query: NSPredicate, including: [URLResourceKey], foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
        let url = self.url(of: path)
        var request = URLRequest(url: url)
        request.httpMethod = "PROPFIND"
        // Depth infinity is disabled on some servers. Implement workaround?!
        request.setValue(recursive ? "infinity" : "1", forHTTPHeaderField: "Depth")
        request.setValue(authentication: credential, with: credentialType)
        request.setValue(contentType: .xml, charset: .utf8)
        request.httpBody = WebDavFileObject.xmlProp([])
        let progress = Progress(totalUnitCount: -1)
        progress.setUserInfoObject(url, forKey: .fileURLKey)
        
        let queryIsTruePredicate = query.predicateFormat == "TRUEPREDICATE"
        let task = session.dataTask(with: request) { (data, response, error) in
            // FIXME: paginating results
            var responseError: FileProviderHTTPError?
            if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
                responseError = self.serverError(with: rCode, path: path, data: data)
            }
            guard let data = data else {
                completionHandler([], responseError ?? error)
                return
            }
            <commit_msg>bugfix. searchFiles "including" param was being ignored<commit_after>//
//  WebDAVFileProvider.swift
//  FileProvider
//
//  Created by <NAME>.
//  Copyright © 2016 Mousavian. Distributed under MIT license.
//

import Foundation
import CoreGraphics

/**
 Allows accessing to WebDAV server files. This provider doesn't cache or save files internally, however you can
 set `useCache` and `cache` properties to use Foundation `NSURLCache` system.
 
 WebDAV system supported by many cloud services including [Box.com](https://www.box.com/home) 
 and [Yandex disk](https://disk.yandex.com) and [ownCloud](https://owncloud.org).
 
 - Important: Because this class uses `URLSession`, it's necessary to disable App Transport Security
     in case of using this class with unencrypted HTTP connection.
     [Read this to know how](http://iosdevtips.co/post/121756573323/ios-9-xcode-7-http-connect-server-error).
*/
open class WebDAVFileProvider: HTTPFileProvider, FileProviderSharing {
    override open class var type: String { return "WebDAV" }
    
    /// An enum which defines HTTP Authentication method, usually you should it default `.digest`.
    /// If the server uses OAuth authentication, credential must be set with token as `password`, like Dropbox.
    public var credentialType: URLRequest.AuthenticationType = .digest
    
    /**
     Initializes WebDAV provider.
     
     - Parameters:
       - baseURL: Location of WebDAV server.
       - credential: An `URLCredential` object with `user` and `password`.
       - cache: A URLCache to cache downloaded files and contents.
    */
    public init? (baseURL: URL, credential: URLCredential?, cache: URLCache? = nil) {
        if  !["http", "https"].contains(baseURL.uw_scheme.lowercased()) {
            return nil
        }
        let refinedBaseURL = (baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent(""))
        super.init(baseURL: refinedBaseURL.absoluteURL, credential: credential, cache: cache)
    }
    
    public required convenience init?(coder aDecoder: NSCoder) {
        guard let baseURL = aDecoder.decodeObject(forKey: "baseURL") as? URL else {
            return nil
        }
        self.init(baseURL: baseURL,
                  credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential)
        self.useCache        = aDecoder.decodeBool(forKey: "useCache")
        self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache")
    }
    
    override open func copy(with zone: NSZone? = nil) -> Any {
        let copy = WebDAVFileProvider(baseURL: self.baseURL!, credential: self.credential, cache: self.cache)!
        copy.delegate = self.delegate
        copy.fileOperationDelegate = self.fileOperationDelegate
        copy.useCache = self.useCache
        copy.validatingCache = self.validatingCache
        return copy
    }
    
    override open func contentsOfDirectory(path: String, completionHandler: @escaping (([FileObject], Error?) -> Void)) {
        let query = NSPredicate(format: "TRUEPREDICATE")
        _ = searchFiles(path: path, recursive: false, query: query, including: [], foundItemHandler: nil, completionHandler: completionHandler)
    }
    
    /**
     Returns an Array of `FileObject`s identifying the the directory entries via asynchronous completion handler.
     
     If the directory contains no entries or an error is occured, this method will return the empty array.
     
     - Parameter path: path to target directory. If empty, root will be iterated.
     - Parameter including: An array which determines which file properties should be considered to fetch.
     - Parameter completionHandler: a closure with result of directory entries or error.
     - Parameter contents: An array of `FileObject` identifying the the directory entries.
     - Parameter error: Error returned by system.
     */
    open func contentsOfDirectory(path: String, including: [URLResourceKey], completionHandler: @escaping (_ contents: [FileObject], _ error: Error?) -> Void) {
        let query = NSPredicate(format: "TRUEPREDICATE")
        _ = searchFiles(path: path, recursive: false, query: query, including: including, foundItemHandler: nil, completionHandler: completionHandler)
    }
    
    override open func attributesOfItem(path: String, completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
        self.attributesOfItem(path: path, including: [], completionHandler: completionHandler)
    }
    
    /**
     Returns a `FileObject` containing the attributes of the item (file, directory, symlink, etc.) at the path in question via asynchronous completion handler.
     
     If the directory contains no entries or an error is occured, this method will return the empty `FileObject`.
     
     - Parameter path: path to target directory. If empty, attributes of root will be returned.
     - Parameter including: An array which determines which file properties should be considered to fetch.
     - Parameter completionHandler: a closure with result of directory entries or error.
     - Parameter attributes: A `FileObject` containing the attributes of the item.
     - Parameter error: Error returned by system.
     */
    open func attributesOfItem(path: String, including: [URLResourceKey], completionHandler: @escaping (_ attributes: FileObject?, _ error: Error?) -> Void) {
        let url = self.url(of: path)
        var request = URLRequest(url: url)
        request.httpMethod = "PROPFIND"
        request.setValue("0", forHTTPHeaderField: "Depth")
        request.setValue(authentication: credential, with: credentialType)
        request.setValue(contentType: .xml, charset: .utf8)
        request.httpBody = WebDavFileObject.xmlProp(including)
        runDataTask(with: request, completionHandler: { (data, response, error) in
            var responseError: FileProviderHTTPError?
            if let code = (response as? HTTPURLResponse)?.statusCode, code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
                responseError = self.serverError(with: rCode, path: path, data: data)
            }
            if let data = data {
                let xresponse = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL)
                if let attr = xresponse.first {
                    completionHandler(WebDavFileObject(attr), responseError ?? error)
                    return
                }
            }
            completionHandler(nil, responseError ?? error)
        })
    }
    
    override open func storageProperties(completionHandler: @escaping (_ volumeInfo: VolumeObject?) -> Void) {
        // Not all WebDAV clients implements RFC2518 which allows geting storage quota.
        // In this case you won't get error. totalSize is NSURLSessionTransferSizeUnknown
        // and used space is zero.
        guard let baseURL = baseURL else {
            return
        }
        var request = URLRequest(url: baseURL)
        request.httpMethod = "PROPFIND"
        request.setValue("0", forHTTPHeaderField: "Depth")
        request.setValue(authentication: credential, with: credentialType)
        request.setValue(contentType: .xml, charset: .utf8)
        request.httpBody = WebDavFileObject.xmlProp([.volumeTotalCapacityKey, .volumeAvailableCapacityKey, .creationDateKey])
        runDataTask(with: request, completionHandler: { (data, response, error) in
            guard let data = data, let attr = DavResponse.parse(xmlResponse: data, baseURL: self.baseURL).first else {
                completionHandler(nil)
                return
            }
            
            let volume = VolumeObject(allValues: [:])
            volume.creationDate = attr.prop["creationdate"].flatMap { Date(rfcString: $0) }
            volume.availableCapacity = attr.prop["quota-available-bytes"].flatMap({ Int64($0) }) ?? 0
            if let usage = attr.prop["quota-used-bytes"].flatMap({ Int64($0) }) {
                volume.totalCapacity = volume.availableCapacity + usage
            }
            completionHandler(volume)
        })
    }
    
    open override func searchFiles(path: String, recursive: Bool, query: NSPredicate, foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping ([FileObject], Error?) -> Void) -> Progress? {
        return searchFiles(path: path, recursive: recursive, query: query, including: [], foundItemHandler: foundItemHandler, completionHandler: completionHandler)
    }
    
    /**
     Search files inside directory using query asynchronously.
     
     - Note: Query string is limited to file name, to search based on other file attributes, use NSPredicate version.
     
     - Parameters:
       - path: location of directory to start search
       - recursive: Searching subdirectories of path
       - query: Simple string that file name begins with to be search, case-insensitive.
       - including: An array which determines which file properties should be considered to fetch.
       - foundItemHandler: Closure which is called when a file is found
       - completionHandler: Closure which will be called after finishing search. Returns an arry of `FileObject` or error if occured.
       - files: all files meat the `query` criteria.
       - error: `Error` returned by server if occured.
     */
    open func searchFiles(path: String, recursive: Bool, query: NSPredicate, including: [URLResourceKey], foundItemHandler: ((FileObject) -> Void)?, completionHandler: @escaping (_ files: [FileObject], _ error: Error?) -> Void) -> Progress? {
        let url = self.url(of: path)
        var request = URLRequest(url: url)
        request.httpMethod = "PROPFIND"
        // Depth infinity is disabled on some servers. Implement workaround?!
        request.setValue(recursive ? "infinity" : "1", forHTTPHeaderField: "Depth")
        request.setValue(authentication: credential, with: credentialType)
        request.setValue(contentType: .xml, charset: .utf8)
        request.httpBody = WebDavFileObject.xmlProp(including)
        let progress = Progress(totalUnitCount: -1)
        progress.setUserInfoObject(url, forKey: .fileURLKey)
        
        let queryIsTruePredicate = query.predicateFormat == "TRUEPREDICATE"
        let task = session.dataTask(with: request) { (data, response, error) in
            // FIXME: paginating results
            var responseError: FileProviderHTTPError?
            if let code = (response as? HTTPURLResponse)?.statusCode , code >= 300, let rCode = FileProviderHTTPErrorCode(rawValue: code) {
                responseError = self.serverError(with: rCode, path: path, data: data)
            }
            guard let data = data else {
                completionHandler([], responseError ?? error)
                return
            }
            
{'fraction_non_alphanumeric': 0.09144843568945539, 'fraction_numerical': 0.003986095017381229, 'mean_word_length': 2.925764192139738, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 33, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 16}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6178881', 'n_tokens_mistral': 5667, 'n_tokens_neox': 5508, 'n_words': 1850}