Completed
Pull Request — developer (#4001)
by Thom
542:26 queued 508:45
created
libraries/SabreDAV/HTTP/Auth/AWS.php 2 patches
Braces   +6 added lines, -4 removed lines patch added patch discarded remove patch
@@ -101,11 +101,13 @@
 block discarded – undo
101 101
 
102 102
         }
103 103
 
104
-        if (!$requestDate = $this->request->getHeader('x-amz-date'))
105
-            $requestDate = $this->request->getHeader('Date');
104
+        if (!$requestDate = $this->request->getHeader('x-amz-date')) {
105
+                    $requestDate = $this->request->getHeader('Date');
106
+        }
106 107
 
107
-        if (!$this->validateRFC2616Date($requestDate))
108
-            return false;
108
+        if (!$this->validateRFC2616Date($requestDate)) {
109
+                    return false;
110
+        }
109 111
 
110 112
         $amzHeaders = $this->getAmzHeaders();
111 113
 
Please login to merge, or discard this patch.
Indentation   +215 added lines, -215 removed lines patch added patch discarded remove patch
@@ -15,220 +15,220 @@
 block discarded – undo
15 15
  */
16 16
 class AWS extends AbstractAuth {
17 17
 
18
-    /**
19
-     * The signature supplied by the HTTP client
20
-     *
21
-     * @var string
22
-     */
23
-    private $signature = null;
24
-
25
-    /**
26
-     * The accesskey supplied by the HTTP client
27
-     *
28
-     * @var string
29
-     */
30
-    private $accessKey = null;
31
-
32
-    /**
33
-     * An error code, if any
34
-     *
35
-     * This value will be filled with one of the ERR_* constants
36
-     *
37
-     * @var int
38
-     */
39
-    public $errorCode = 0;
40
-
41
-    const ERR_NOAWSHEADER = 1;
42
-    const ERR_MD5CHECKSUMWRONG = 2;
43
-    const ERR_INVALIDDATEFORMAT = 3;
44
-    const ERR_REQUESTTIMESKEWED = 4;
45
-    const ERR_INVALIDSIGNATURE = 5;
46
-
47
-    /**
48
-     * Gathers all information from the headers
49
-     *
50
-     * This method needs to be called prior to anything else.
51
-     *
52
-     * @return bool
53
-     */
54
-    public function init() {
55
-
56
-        $authHeader = $this->request->getHeader('Authorization');
57
-        $authHeader = explode(' ', $authHeader);
58
-
59
-        if ($authHeader[0] != 'AWS' || !isset($authHeader[1])) {
60
-            $this->errorCode = self::ERR_NOAWSHEADER;
61
-             return false;
62
-        }
63
-
64
-        list($this->accessKey, $this->signature) = explode(':', $authHeader[1]);
65
-
66
-        return true;
67
-
68
-    }
69
-
70
-    /**
71
-     * Returns the username for the request
72
-     *
73
-     * @return string
74
-     */
75
-    public function getAccessKey() {
76
-
77
-        return $this->accessKey;
78
-
79
-    }
80
-
81
-    /**
82
-     * Validates the signature based on the secretKey
83
-     *
84
-     * @param string $secretKey
85
-     * @return bool
86
-     */
87
-    public function validate($secretKey) {
88
-
89
-        $contentMD5 = $this->request->getHeader('Content-MD5');
90
-
91
-        if ($contentMD5) {
92
-            // We need to validate the integrity of the request
93
-            $body = $this->request->getBody();
94
-            $this->request->setBody($body);
95
-
96
-            if ($contentMD5 != base64_encode(md5($body, true))) {
97
-                // content-md5 header did not match md5 signature of body
98
-                $this->errorCode = self::ERR_MD5CHECKSUMWRONG;
99
-                return false;
100
-            }
101
-
102
-        }
103
-
104
-        if (!$requestDate = $this->request->getHeader('x-amz-date'))
105
-            $requestDate = $this->request->getHeader('Date');
106
-
107
-        if (!$this->validateRFC2616Date($requestDate))
108
-            return false;
109
-
110
-        $amzHeaders = $this->getAmzHeaders();
111
-
112
-        $signature = base64_encode(
113
-            $this->hmacsha1($secretKey,
114
-                $this->request->getMethod() . "\n" .
115
-                $contentMD5 . "\n" .
116
-                $this->request->getHeader('Content-type') . "\n" .
117
-                $requestDate . "\n" .
118
-                $amzHeaders .
119
-                $this->request->getUrl()
120
-            )
121
-        );
122
-
123
-        if ($this->signature != $signature) {
124
-
125
-            $this->errorCode = self::ERR_INVALIDSIGNATURE;
126
-            return false;
127
-
128
-        }
129
-
130
-        return true;
131
-
132
-    }
133
-
134
-
135
-    /**
136
-     * Returns an HTTP 401 header, forcing login
137
-     *
138
-     * This should be called when username and password are incorrect, or not supplied at all
139
-     *
140
-     * @return void
141
-     */
142
-    public function requireLogin() {
143
-
144
-        $this->response->addHeader('WWW-Authenticate', 'AWS');
145
-        $this->response->setStatus(401);
146
-
147
-    }
148
-
149
-    /**
150
-     * Makes sure the supplied value is a valid RFC2616 date.
151
-     *
152
-     * If we would just use strtotime to get a valid timestamp, we have no way of checking if a
153
-     * user just supplied the word 'now' for the date header.
154
-     *
155
-     * This function also makes sure the Date header is within 15 minutes of the operating
156
-     * system date, to prevent replay attacks.
157
-     *
158
-     * @param string $dateHeader
159
-     * @return bool
160
-     */
161
-    protected function validateRFC2616Date($dateHeader) {
162
-
163
-        $date = Util::parseHTTPDate($dateHeader);
164
-
165
-        // Unknown format
166
-        if (!$date) {
167
-            $this->errorCode = self::ERR_INVALIDDATEFORMAT;
168
-            return false;
169
-        }
170
-
171
-        $min = new \DateTime('-15 minutes');
172
-        $max = new \DateTime('+15 minutes');
173
-
174
-        // We allow 15 minutes around the current date/time
175
-        if ($date > $max || $date < $min) {
176
-            $this->errorCode = self::ERR_REQUESTTIMESKEWED;
177
-            return false;
178
-        }
179
-
180
-        return $date;
181
-
182
-    }
183
-
184
-    /**
185
-     * Returns a list of AMZ headers
186
-     *
187
-     * @return string
188
-     */
189
-    protected function getAmzHeaders() {
190
-
191
-        $amzHeaders = [];
192
-        $headers = $this->request->getHeaders();
193
-        foreach ($headers as $headerName => $headerValue) {
194
-            if (strpos(strtolower($headerName), 'x-amz-') === 0) {
195
-                $amzHeaders[strtolower($headerName)] = str_replace(["\r\n"], [' '], $headerValue[0]) . "\n";
196
-            }
197
-        }
198
-        ksort($amzHeaders);
199
-
200
-        $headerStr = '';
201
-        foreach ($amzHeaders as $h => $v) {
202
-            $headerStr .= $h . ':' . $v;
203
-        }
204
-
205
-        return $headerStr;
206
-
207
-    }
208
-
209
-    /**
210
-     * Generates an HMAC-SHA1 signature
211
-     *
212
-     * @param string $key
213
-     * @param string $message
214
-     * @return string
215
-     */
216
-    private function hmacsha1($key, $message) {
217
-
218
-        if (function_exists('hash_hmac')) {
219
-            return hash_hmac('sha1', $message, $key, true);
220
-        }
221
-
222
-        $blocksize = 64;
223
-        if (strlen($key) > $blocksize) {
224
-            $key = pack('H*', sha1($key));
225
-        }
226
-        $key = str_pad($key, $blocksize, chr(0x00));
227
-        $ipad = str_repeat(chr(0x36), $blocksize);
228
-        $opad = str_repeat(chr(0x5c), $blocksize);
229
-        $hmac = pack('H*', sha1(($key ^ $opad) . pack('H*', sha1(($key ^ $ipad) . $message))));
230
-        return $hmac;
231
-
232
-    }
18
+	/**
19
+	 * The signature supplied by the HTTP client
20
+	 *
21
+	 * @var string
22
+	 */
23
+	private $signature = null;
24
+
25
+	/**
26
+	 * The accesskey supplied by the HTTP client
27
+	 *
28
+	 * @var string
29
+	 */
30
+	private $accessKey = null;
31
+
32
+	/**
33
+	 * An error code, if any
34
+	 *
35
+	 * This value will be filled with one of the ERR_* constants
36
+	 *
37
+	 * @var int
38
+	 */
39
+	public $errorCode = 0;
40
+
41
+	const ERR_NOAWSHEADER = 1;
42
+	const ERR_MD5CHECKSUMWRONG = 2;
43
+	const ERR_INVALIDDATEFORMAT = 3;
44
+	const ERR_REQUESTTIMESKEWED = 4;
45
+	const ERR_INVALIDSIGNATURE = 5;
46
+
47
+	/**
48
+	 * Gathers all information from the headers
49
+	 *
50
+	 * This method needs to be called prior to anything else.
51
+	 *
52
+	 * @return bool
53
+	 */
54
+	public function init() {
55
+
56
+		$authHeader = $this->request->getHeader('Authorization');
57
+		$authHeader = explode(' ', $authHeader);
58
+
59
+		if ($authHeader[0] != 'AWS' || !isset($authHeader[1])) {
60
+			$this->errorCode = self::ERR_NOAWSHEADER;
61
+			 return false;
62
+		}
63
+
64
+		list($this->accessKey, $this->signature) = explode(':', $authHeader[1]);
65
+
66
+		return true;
67
+
68
+	}
69
+
70
+	/**
71
+	 * Returns the username for the request
72
+	 *
73
+	 * @return string
74
+	 */
75
+	public function getAccessKey() {
76
+
77
+		return $this->accessKey;
78
+
79
+	}
80
+
81
+	/**
82
+	 * Validates the signature based on the secretKey
83
+	 *
84
+	 * @param string $secretKey
85
+	 * @return bool
86
+	 */
87
+	public function validate($secretKey) {
88
+
89
+		$contentMD5 = $this->request->getHeader('Content-MD5');
90
+
91
+		if ($contentMD5) {
92
+			// We need to validate the integrity of the request
93
+			$body = $this->request->getBody();
94
+			$this->request->setBody($body);
95
+
96
+			if ($contentMD5 != base64_encode(md5($body, true))) {
97
+				// content-md5 header did not match md5 signature of body
98
+				$this->errorCode = self::ERR_MD5CHECKSUMWRONG;
99
+				return false;
100
+			}
101
+
102
+		}
103
+
104
+		if (!$requestDate = $this->request->getHeader('x-amz-date'))
105
+			$requestDate = $this->request->getHeader('Date');
106
+
107
+		if (!$this->validateRFC2616Date($requestDate))
108
+			return false;
109
+
110
+		$amzHeaders = $this->getAmzHeaders();
111
+
112
+		$signature = base64_encode(
113
+			$this->hmacsha1($secretKey,
114
+				$this->request->getMethod() . "\n" .
115
+				$contentMD5 . "\n" .
116
+				$this->request->getHeader('Content-type') . "\n" .
117
+				$requestDate . "\n" .
118
+				$amzHeaders .
119
+				$this->request->getUrl()
120
+			)
121
+		);
122
+
123
+		if ($this->signature != $signature) {
124
+
125
+			$this->errorCode = self::ERR_INVALIDSIGNATURE;
126
+			return false;
127
+
128
+		}
129
+
130
+		return true;
131
+
132
+	}
133
+
134
+
135
+	/**
136
+	 * Returns an HTTP 401 header, forcing login
137
+	 *
138
+	 * This should be called when username and password are incorrect, or not supplied at all
139
+	 *
140
+	 * @return void
141
+	 */
142
+	public function requireLogin() {
143
+
144
+		$this->response->addHeader('WWW-Authenticate', 'AWS');
145
+		$this->response->setStatus(401);
146
+
147
+	}
148
+
149
+	/**
150
+	 * Makes sure the supplied value is a valid RFC2616 date.
151
+	 *
152
+	 * If we would just use strtotime to get a valid timestamp, we have no way of checking if a
153
+	 * user just supplied the word 'now' for the date header.
154
+	 *
155
+	 * This function also makes sure the Date header is within 15 minutes of the operating
156
+	 * system date, to prevent replay attacks.
157
+	 *
158
+	 * @param string $dateHeader
159
+	 * @return bool
160
+	 */
161
+	protected function validateRFC2616Date($dateHeader) {
162
+
163
+		$date = Util::parseHTTPDate($dateHeader);
164
+
165
+		// Unknown format
166
+		if (!$date) {
167
+			$this->errorCode = self::ERR_INVALIDDATEFORMAT;
168
+			return false;
169
+		}
170
+
171
+		$min = new \DateTime('-15 minutes');
172
+		$max = new \DateTime('+15 minutes');
173
+
174
+		// We allow 15 minutes around the current date/time
175
+		if ($date > $max || $date < $min) {
176
+			$this->errorCode = self::ERR_REQUESTTIMESKEWED;
177
+			return false;
178
+		}
179
+
180
+		return $date;
181
+
182
+	}
183
+
184
+	/**
185
+	 * Returns a list of AMZ headers
186
+	 *
187
+	 * @return string
188
+	 */
189
+	protected function getAmzHeaders() {
190
+
191
+		$amzHeaders = [];
192
+		$headers = $this->request->getHeaders();
193
+		foreach ($headers as $headerName => $headerValue) {
194
+			if (strpos(strtolower($headerName), 'x-amz-') === 0) {
195
+				$amzHeaders[strtolower($headerName)] = str_replace(["\r\n"], [' '], $headerValue[0]) . "\n";
196
+			}
197
+		}
198
+		ksort($amzHeaders);
199
+
200
+		$headerStr = '';
201
+		foreach ($amzHeaders as $h => $v) {
202
+			$headerStr .= $h . ':' . $v;
203
+		}
204
+
205
+		return $headerStr;
206
+
207
+	}
208
+
209
+	/**
210
+	 * Generates an HMAC-SHA1 signature
211
+	 *
212
+	 * @param string $key
213
+	 * @param string $message
214
+	 * @return string
215
+	 */
216
+	private function hmacsha1($key, $message) {
217
+
218
+		if (function_exists('hash_hmac')) {
219
+			return hash_hmac('sha1', $message, $key, true);
220
+		}
221
+
222
+		$blocksize = 64;
223
+		if (strlen($key) > $blocksize) {
224
+			$key = pack('H*', sha1($key));
225
+		}
226
+		$key = str_pad($key, $blocksize, chr(0x00));
227
+		$ipad = str_repeat(chr(0x36), $blocksize);
228
+		$opad = str_repeat(chr(0x5c), $blocksize);
229
+		$hmac = pack('H*', sha1(($key ^ $opad) . pack('H*', sha1(($key ^ $ipad) . $message))));
230
+		return $hmac;
231
+
232
+	}
233 233
 
234 234
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/Auth/Digest.php 3 patches
Braces   +6 added lines, -2 removed lines patch added patch discarded remove patch
@@ -139,7 +139,9 @@  discard block
 block discarded – undo
139 139
 
140 140
         if ($this->digestParts['qop'] == 'auth-int') {
141 141
             // Making sure we support this qop value
142
-            if (!($this->qop & self::QOP_AUTHINT)) return false;
142
+            if (!($this->qop & self::QOP_AUTHINT)) {
143
+            	return false;
144
+            }
143 145
             // We need to add an md5 of the entire request body to the A2 part of the hash
144 146
             $body = $this->request->getBody($asString = true);
145 147
             $this->request->setBody($body);
@@ -147,7 +149,9 @@  discard block
 block discarded – undo
147 149
         } else {
148 150
 
149 151
             // We need to make sure we support this qop value
150
-            if (!($this->qop & self::QOP_AUTH)) return false;
152
+            if (!($this->qop & self::QOP_AUTH)) {
153
+            	return false;
154
+            }
151 155
         }
152 156
 
153 157
         $A2 = md5($A2);
Please login to merge, or discard this patch.
Indentation   +193 added lines, -193 removed lines patch added patch discarded remove patch
@@ -29,203 +29,203 @@
 block discarded – undo
29 29
  */
30 30
 class Digest extends AbstractAuth {
31 31
 
32
-    /**
33
-     * These constants are used in setQOP();
34
-     */
35
-    const QOP_AUTH = 1;
36
-    const QOP_AUTHINT = 2;
37
-
38
-    protected $nonce;
39
-    protected $opaque;
40
-    protected $digestParts;
41
-    protected $A1;
42
-    protected $qop = self::QOP_AUTH;
43
-
44
-    /**
45
-     * Initializes the object
46
-     */
47
-    public function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) {
48
-
49
-        $this->nonce = uniqid();
50
-        $this->opaque = md5($realm);
51
-        parent::__construct($realm, $request, $response);
52
-
53
-    }
54
-
55
-    /**
56
-     * Gathers all information from the headers
57
-     *
58
-     * This method needs to be called prior to anything else.
59
-     *
60
-     * @return void
61
-     */
62
-    public function init() {
63
-
64
-        $digest = $this->getDigest();
65
-        $this->digestParts = $this->parseDigest($digest);
66
-
67
-    }
68
-
69
-    /**
70
-     * Sets the quality of protection value.
71
-     *
72
-     * Possible values are:
73
-     *   Sabre\HTTP\DigestAuth::QOP_AUTH
74
-     *   Sabre\HTTP\DigestAuth::QOP_AUTHINT
75
-     *
76
-     * Multiple values can be specified using logical OR.
77
-     *
78
-     * QOP_AUTHINT ensures integrity of the request body, but this is not
79
-     * supported by most HTTP clients. QOP_AUTHINT also requires the entire
80
-     * request body to be md5'ed, which can put strains on CPU and memory.
81
-     *
82
-     * @param int $qop
83
-     * @return void
84
-     */
85
-    public function setQOP($qop) {
86
-
87
-        $this->qop = $qop;
88
-
89
-    }
90
-
91
-    /**
92
-     * Validates the user.
93
-     *
94
-     * The A1 parameter should be md5($username . ':' . $realm . ':' . $password);
95
-     *
96
-     * @param string $A1
97
-     * @return bool
98
-     */
99
-    public function validateA1($A1) {
100
-
101
-        $this->A1 = $A1;
102
-        return $this->validate();
103
-
104
-    }
105
-
106
-    /**
107
-     * Validates authentication through a password. The actual password must be provided here.
108
-     * It is strongly recommended not store the password in plain-text and use validateA1 instead.
109
-     *
110
-     * @param string $password
111
-     * @return bool
112
-     */
113
-    public function validatePassword($password) {
114
-
115
-        $this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password);
116
-        return $this->validate();
117
-
118
-    }
119
-
120
-    /**
121
-     * Returns the username for the request
122
-     *
123
-     * @return string
124
-     */
125
-    public function getUsername() {
126
-
127
-        return $this->digestParts['username'];
128
-
129
-    }
130
-
131
-    /**
132
-     * Validates the digest challenge
133
-     *
134
-     * @return bool
135
-     */
136
-    protected function validate() {
137
-
138
-        $A2 = $this->request->getMethod() . ':' . $this->digestParts['uri'];
139
-
140
-        if ($this->digestParts['qop'] == 'auth-int') {
141
-            // Making sure we support this qop value
142
-            if (!($this->qop & self::QOP_AUTHINT)) return false;
143
-            // We need to add an md5 of the entire request body to the A2 part of the hash
144
-            $body = $this->request->getBody($asString = true);
145
-            $this->request->setBody($body);
146
-            $A2 .= ':' . md5($body);
147
-        } else {
148
-
149
-            // We need to make sure we support this qop value
150
-            if (!($this->qop & self::QOP_AUTH)) return false;
151
-        }
152
-
153
-        $A2 = md5($A2);
154
-
155
-        $validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}");
156
-
157
-        return $this->digestParts['response'] == $validResponse;
158
-
159
-
160
-    }
161
-
162
-    /**
163
-     * Returns an HTTP 401 header, forcing login
164
-     *
165
-     * This should be called when username and password are incorrect, or not supplied at all
166
-     *
167
-     * @return void
168
-     */
169
-    public function requireLogin() {
170
-
171
-        $qop = '';
172
-        switch ($this->qop) {
173
-            case self::QOP_AUTH    :
174
-                $qop = 'auth';
175
-                break;
176
-            case self::QOP_AUTHINT :
177
-                $qop = 'auth-int';
178
-                break;
179
-            case self::QOP_AUTH | self::QOP_AUTHINT :
180
-                $qop = 'auth,auth-int';
181
-                break;
182
-        }
183
-
184
-        $this->response->addHeader('WWW-Authenticate', 'Digest realm="' . $this->realm . '",qop="' . $qop . '",nonce="' . $this->nonce . '",opaque="' . $this->opaque . '"');
185
-        $this->response->setStatus(401);
186
-
187
-    }
188
-
189
-
190
-    /**
191
-     * This method returns the full digest string.
192
-     *
193
-     * It should be compatibile with mod_php format and other webservers.
194
-     *
195
-     * If the header could not be found, null will be returned
196
-     *
197
-     * @return mixed
198
-     */
199
-    public function getDigest() {
32
+	/**
33
+	 * These constants are used in setQOP();
34
+	 */
35
+	const QOP_AUTH = 1;
36
+	const QOP_AUTHINT = 2;
37
+
38
+	protected $nonce;
39
+	protected $opaque;
40
+	protected $digestParts;
41
+	protected $A1;
42
+	protected $qop = self::QOP_AUTH;
43
+
44
+	/**
45
+	 * Initializes the object
46
+	 */
47
+	public function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) {
48
+
49
+		$this->nonce = uniqid();
50
+		$this->opaque = md5($realm);
51
+		parent::__construct($realm, $request, $response);
52
+
53
+	}
54
+
55
+	/**
56
+	 * Gathers all information from the headers
57
+	 *
58
+	 * This method needs to be called prior to anything else.
59
+	 *
60
+	 * @return void
61
+	 */
62
+	public function init() {
63
+
64
+		$digest = $this->getDigest();
65
+		$this->digestParts = $this->parseDigest($digest);
66
+
67
+	}
68
+
69
+	/**
70
+	 * Sets the quality of protection value.
71
+	 *
72
+	 * Possible values are:
73
+	 *   Sabre\HTTP\DigestAuth::QOP_AUTH
74
+	 *   Sabre\HTTP\DigestAuth::QOP_AUTHINT
75
+	 *
76
+	 * Multiple values can be specified using logical OR.
77
+	 *
78
+	 * QOP_AUTHINT ensures integrity of the request body, but this is not
79
+	 * supported by most HTTP clients. QOP_AUTHINT also requires the entire
80
+	 * request body to be md5'ed, which can put strains on CPU and memory.
81
+	 *
82
+	 * @param int $qop
83
+	 * @return void
84
+	 */
85
+	public function setQOP($qop) {
86
+
87
+		$this->qop = $qop;
88
+
89
+	}
90
+
91
+	/**
92
+	 * Validates the user.
93
+	 *
94
+	 * The A1 parameter should be md5($username . ':' . $realm . ':' . $password);
95
+	 *
96
+	 * @param string $A1
97
+	 * @return bool
98
+	 */
99
+	public function validateA1($A1) {
100
+
101
+		$this->A1 = $A1;
102
+		return $this->validate();
103
+
104
+	}
105
+
106
+	/**
107
+	 * Validates authentication through a password. The actual password must be provided here.
108
+	 * It is strongly recommended not store the password in plain-text and use validateA1 instead.
109
+	 *
110
+	 * @param string $password
111
+	 * @return bool
112
+	 */
113
+	public function validatePassword($password) {
114
+
115
+		$this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password);
116
+		return $this->validate();
117
+
118
+	}
119
+
120
+	/**
121
+	 * Returns the username for the request
122
+	 *
123
+	 * @return string
124
+	 */
125
+	public function getUsername() {
126
+
127
+		return $this->digestParts['username'];
128
+
129
+	}
130
+
131
+	/**
132
+	 * Validates the digest challenge
133
+	 *
134
+	 * @return bool
135
+	 */
136
+	protected function validate() {
137
+
138
+		$A2 = $this->request->getMethod() . ':' . $this->digestParts['uri'];
139
+
140
+		if ($this->digestParts['qop'] == 'auth-int') {
141
+			// Making sure we support this qop value
142
+			if (!($this->qop & self::QOP_AUTHINT)) return false;
143
+			// We need to add an md5 of the entire request body to the A2 part of the hash
144
+			$body = $this->request->getBody($asString = true);
145
+			$this->request->setBody($body);
146
+			$A2 .= ':' . md5($body);
147
+		} else {
148
+
149
+			// We need to make sure we support this qop value
150
+			if (!($this->qop & self::QOP_AUTH)) return false;
151
+		}
152
+
153
+		$A2 = md5($A2);
154
+
155
+		$validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}");
156
+
157
+		return $this->digestParts['response'] == $validResponse;
158
+
159
+
160
+	}
161
+
162
+	/**
163
+	 * Returns an HTTP 401 header, forcing login
164
+	 *
165
+	 * This should be called when username and password are incorrect, or not supplied at all
166
+	 *
167
+	 * @return void
168
+	 */
169
+	public function requireLogin() {
170
+
171
+		$qop = '';
172
+		switch ($this->qop) {
173
+			case self::QOP_AUTH    :
174
+				$qop = 'auth';
175
+				break;
176
+			case self::QOP_AUTHINT :
177
+				$qop = 'auth-int';
178
+				break;
179
+			case self::QOP_AUTH | self::QOP_AUTHINT :
180
+				$qop = 'auth,auth-int';
181
+				break;
182
+		}
183
+
184
+		$this->response->addHeader('WWW-Authenticate', 'Digest realm="' . $this->realm . '",qop="' . $qop . '",nonce="' . $this->nonce . '",opaque="' . $this->opaque . '"');
185
+		$this->response->setStatus(401);
186
+
187
+	}
188
+
189
+
190
+	/**
191
+	 * This method returns the full digest string.
192
+	 *
193
+	 * It should be compatibile with mod_php format and other webservers.
194
+	 *
195
+	 * If the header could not be found, null will be returned
196
+	 *
197
+	 * @return mixed
198
+	 */
199
+	public function getDigest() {
200 200
 
201
-        return $this->request->getHeader('Authorization');
202
-
203
-    }
204
-
205
-
206
-    /**
207
-     * Parses the different pieces of the digest string into an array.
208
-     *
209
-     * This method returns false if an incomplete digest was supplied
210
-     *
211
-     * @param string $digest
212
-     * @return mixed
213
-     */
214
-    protected function parseDigest($digest) {
201
+		return $this->request->getHeader('Authorization');
202
+
203
+	}
204
+
205
+
206
+	/**
207
+	 * Parses the different pieces of the digest string into an array.
208
+	 *
209
+	 * This method returns false if an incomplete digest was supplied
210
+	 *
211
+	 * @param string $digest
212
+	 * @return mixed
213
+	 */
214
+	protected function parseDigest($digest) {
215 215
 
216
-        // protect against missing data
217
-        $needed_parts = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1];
218
-        $data = [];
216
+		// protect against missing data
217
+		$needed_parts = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1];
218
+		$data = [];
219 219
 
220
-        preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $digest, $matches, PREG_SET_ORDER);
221
-
222
-        foreach ($matches as $m) {
223
-            $data[$m[1]] = $m[2] ? $m[2] : $m[3];
224
-            unset($needed_parts[$m[1]]);
225
-        }
220
+		preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $digest, $matches, PREG_SET_ORDER);
221
+
222
+		foreach ($matches as $m) {
223
+			$data[$m[1]] = $m[2] ? $m[2] : $m[3];
224
+			unset($needed_parts[$m[1]]);
225
+		}
226 226
 
227
-        return $needed_parts ? false : $data;
227
+		return $needed_parts ? false : $data;
228 228
 
229
-    }
229
+	}
230 230
 
231 231
 }
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -194,7 +194,7 @@
 block discarded – undo
194 194
      *
195 195
      * If the header could not be found, null will be returned
196 196
      *
197
-     * @return mixed
197
+     * @return string|null
198 198
      */
199 199
     public function getDigest() {
200 200
 
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/Version.php 1 patch
Indentation   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -11,9 +11,9 @@
 block discarded – undo
11 11
  */
12 12
 class Version {
13 13
 
14
-    /**
15
-     * Full version number
16
-     */
17
-    const VERSION = '4.2.1';
14
+	/**
15
+	 * Full version number
16
+	 */
17
+	const VERSION = '4.2.1';
18 18
 
19 19
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/Request.php 3 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -301,7 +301,7 @@
 block discarded – undo
301 301
             foreach ($value as $v) {
302 302
                 if ($key === 'Authorization') {
303 303
                     list($v) = explode(' ', $v, 2);
304
-                    $v  .= ' REDACTED';
304
+                    $v .= ' REDACTED';
305 305
                 }
306 306
                 $out .= $key . ": " . $v . "\r\n";
307 307
             }
Please login to merge, or discard this patch.
Braces   +12 added lines, -4 removed lines patch added patch discarded remove patch
@@ -44,10 +44,18 @@
 block discarded – undo
44 44
         if (is_array($method)) {
45 45
             throw new InvalidArgumentException('The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?');
46 46
         }
47
-        if (!is_null($method))      $this->setMethod($method);
48
-        if (!is_null($url))         $this->setUrl($url);
49
-        if (!is_null($headers))     $this->setHeaders($headers);
50
-        if (!is_null($body))        $this->setBody($body);
47
+        if (!is_null($method)) {
48
+        	$this->setMethod($method);
49
+        }
50
+        if (!is_null($url)) {
51
+        	$this->setUrl($url);
52
+        }
53
+        if (!is_null($headers)) {
54
+        	$this->setHeaders($headers);
55
+        }
56
+        if (!is_null($body)) {
57
+        	$this->setBody($body);
58
+        }
51 59
 
52 60
     }
53 61
 
Please login to merge, or discard this patch.
Indentation   +295 added lines, -295 removed lines patch added patch discarded remove patch
@@ -17,300 +17,300 @@
 block discarded – undo
17 17
  */
18 18
 class Request extends Message implements RequestInterface {
19 19
 
20
-    /**
21
-     * HTTP Method
22
-     *
23
-     * @var string
24
-     */
25
-    protected $method;
26
-
27
-    /**
28
-     * Request Url
29
-     *
30
-     * @var string
31
-     */
32
-    protected $url;
33
-
34
-    /**
35
-     * Creates the request object
36
-     *
37
-     * @param string $method
38
-     * @param string $url
39
-     * @param array $headers
40
-     * @param resource $body
41
-     */
42
-    public function __construct($method = null, $url = null, array $headers = null, $body = null) {
43
-
44
-        if (is_array($method)) {
45
-            throw new InvalidArgumentException('The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?');
46
-        }
47
-        if (!is_null($method))      $this->setMethod($method);
48
-        if (!is_null($url))         $this->setUrl($url);
49
-        if (!is_null($headers))     $this->setHeaders($headers);
50
-        if (!is_null($body))        $this->setBody($body);
51
-
52
-    }
53
-
54
-    /**
55
-     * Returns the current HTTP method
56
-     *
57
-     * @return string
58
-     */
59
-    public function getMethod() {
60
-
61
-        return $this->method;
62
-
63
-    }
64
-
65
-    /**
66
-     * Sets the HTTP method
67
-     *
68
-     * @param string $method
69
-     * @return void
70
-     */
71
-    public function setMethod($method) {
72
-
73
-        $this->method = $method;
74
-
75
-    }
76
-
77
-    /**
78
-     * Returns the request url.
79
-     *
80
-     * @return string
81
-     */
82
-    public function getUrl() {
83
-
84
-        return $this->url;
85
-
86
-    }
87
-
88
-    /**
89
-     * Sets the request url.
90
-     *
91
-     * @param string $url
92
-     * @return void
93
-     */
94
-    public function setUrl($url) {
95
-
96
-        $this->url = $url;
97
-
98
-    }
99
-
100
-    /**
101
-     * Returns the list of query parameters.
102
-     *
103
-     * This is equivalent to PHP's $_GET superglobal.
104
-     *
105
-     * @return array
106
-     */
107
-    public function getQueryParameters() {
108
-
109
-        $url = $this->getUrl();
110
-        if (($index = strpos($url, '?')) === false) {
111
-            return [];
112
-        } else {
113
-            parse_str(substr($url, $index + 1), $queryParams);
114
-            return $queryParams;
115
-        }
116
-
117
-    }
118
-
119
-    /**
120
-     * Sets the absolute url.
121
-     *
122
-     * @param string $url
123
-     * @return void
124
-     */
125
-    public function setAbsoluteUrl($url) {
126
-
127
-        $this->absoluteUrl = $url;
128
-
129
-    }
130
-
131
-    /**
132
-     * Returns the absolute url.
133
-     *
134
-     * @return string
135
-     */
136
-    public function getAbsoluteUrl() {
137
-
138
-        return $this->absoluteUrl;
139
-
140
-    }
141
-
142
-    /**
143
-     * Base url
144
-     *
145
-     * @var string
146
-     */
147
-    protected $baseUrl = '/';
148
-
149
-    /**
150
-     * Sets a base url.
151
-     *
152
-     * This url is used for relative path calculations.
153
-     *
154
-     * @param string $url
155
-     * @return void
156
-     */
157
-    public function setBaseUrl($url) {
158
-
159
-        $this->baseUrl = $url;
160
-
161
-    }
162
-
163
-    /**
164
-     * Returns the current base url.
165
-     *
166
-     * @return string
167
-     */
168
-    public function getBaseUrl() {
169
-
170
-        return $this->baseUrl;
171
-
172
-    }
173
-
174
-    /**
175
-     * Returns the relative path.
176
-     *
177
-     * This is being calculated using the base url. This path will not start
178
-     * with a slash, so it will always return something like
179
-     * 'example/path.html'.
180
-     *
181
-     * If the full path is equal to the base url, this method will return an
182
-     * empty string.
183
-     *
184
-     * This method will also urldecode the path, and if the url was incoded as
185
-     * ISO-8859-1, it will convert it to UTF-8.
186
-     *
187
-     * If the path is outside of the base url, a LogicException will be thrown.
188
-     *
189
-     * @return string
190
-     */
191
-    public function getPath() {
192
-
193
-        // Removing duplicated slashes.
194
-        $uri = str_replace('//', '/', $this->getUrl());
195
-
196
-        $uri = Uri\normalize($uri);
197
-        $baseUri = Uri\normalize($this->getBaseUrl());
198
-
199
-        if (strpos($uri, $baseUri) === 0) {
200
-
201
-            // We're not interested in the query part (everything after the ?).
202
-            list($uri) = explode('?', $uri);
203
-            return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/');
204
-
205
-        }
206
-        // A special case, if the baseUri was accessed without a trailing
207
-        // slash, we'll accept it as well.
208
-        elseif ($uri . '/' === $baseUri) {
209
-
210
-            return '';
211
-
212
-        }
213
-
214
-        throw new \LogicException('Requested uri (' . $this->getUrl() . ') is out of base uri (' . $this->getBaseUrl() . ')');
215
-    }
216
-
217
-    /**
218
-     * Equivalent of PHP's $_POST.
219
-     *
220
-     * @var array
221
-     */
222
-    protected $postData = [];
223
-
224
-    /**
225
-     * Sets the post data.
226
-     *
227
-     * This is equivalent to PHP's $_POST superglobal.
228
-     *
229
-     * This would not have been needed, if POST data was accessible as
230
-     * php://input, but unfortunately we need to special case it.
231
-     *
232
-     * @param array $postData
233
-     * @return void
234
-     */
235
-    public function setPostData(array $postData) {
236
-
237
-        $this->postData = $postData;
238
-
239
-    }
240
-
241
-    /**
242
-     * Returns the POST data.
243
-     *
244
-     * This is equivalent to PHP's $_POST superglobal.
245
-     *
246
-     * @return array
247
-     */
248
-    public function getPostData() {
249
-
250
-        return $this->postData;
251
-
252
-    }
253
-
254
-    /**
255
-     * An array containing the raw _SERVER array.
256
-     *
257
-     * @var array
258
-     */
259
-    protected $rawServerData;
260
-
261
-    /**
262
-     * Returns an item from the _SERVER array.
263
-     *
264
-     * If the value does not exist in the array, null is returned.
265
-     *
266
-     * @param string $valueName
267
-     * @return string|null
268
-     */
269
-    public function getRawServerValue($valueName) {
270
-
271
-        if (isset($this->rawServerData[$valueName])) {
272
-            return $this->rawServerData[$valueName];
273
-        }
274
-
275
-    }
276
-
277
-    /**
278
-     * Sets the _SERVER array.
279
-     *
280
-     * @param array $data
281
-     * @return void
282
-     */
283
-    public function setRawServerData(array $data) {
284
-
285
-        $this->rawServerData = $data;
286
-
287
-    }
288
-
289
-    /**
290
-     * Serializes the request object as a string.
291
-     *
292
-     * This is useful for debugging purposes.
293
-     *
294
-     * @return string
295
-     */
296
-    public function __toString() {
297
-
298
-        $out = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n";
299
-
300
-        foreach ($this->getHeaders() as $key => $value) {
301
-            foreach ($value as $v) {
302
-                if ($key === 'Authorization') {
303
-                    list($v) = explode(' ', $v, 2);
304
-                    $v  .= ' REDACTED';
305
-                }
306
-                $out .= $key . ": " . $v . "\r\n";
307
-            }
308
-        }
309
-        $out .= "\r\n";
310
-        $out .= $this->getBodyAsString();
311
-
312
-        return $out;
313
-
314
-    }
20
+	/**
21
+	 * HTTP Method
22
+	 *
23
+	 * @var string
24
+	 */
25
+	protected $method;
26
+
27
+	/**
28
+	 * Request Url
29
+	 *
30
+	 * @var string
31
+	 */
32
+	protected $url;
33
+
34
+	/**
35
+	 * Creates the request object
36
+	 *
37
+	 * @param string $method
38
+	 * @param string $url
39
+	 * @param array $headers
40
+	 * @param resource $body
41
+	 */
42
+	public function __construct($method = null, $url = null, array $headers = null, $body = null) {
43
+
44
+		if (is_array($method)) {
45
+			throw new InvalidArgumentException('The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?');
46
+		}
47
+		if (!is_null($method))      $this->setMethod($method);
48
+		if (!is_null($url))         $this->setUrl($url);
49
+		if (!is_null($headers))     $this->setHeaders($headers);
50
+		if (!is_null($body))        $this->setBody($body);
51
+
52
+	}
53
+
54
+	/**
55
+	 * Returns the current HTTP method
56
+	 *
57
+	 * @return string
58
+	 */
59
+	public function getMethod() {
60
+
61
+		return $this->method;
62
+
63
+	}
64
+
65
+	/**
66
+	 * Sets the HTTP method
67
+	 *
68
+	 * @param string $method
69
+	 * @return void
70
+	 */
71
+	public function setMethod($method) {
72
+
73
+		$this->method = $method;
74
+
75
+	}
76
+
77
+	/**
78
+	 * Returns the request url.
79
+	 *
80
+	 * @return string
81
+	 */
82
+	public function getUrl() {
83
+
84
+		return $this->url;
85
+
86
+	}
87
+
88
+	/**
89
+	 * Sets the request url.
90
+	 *
91
+	 * @param string $url
92
+	 * @return void
93
+	 */
94
+	public function setUrl($url) {
95
+
96
+		$this->url = $url;
97
+
98
+	}
99
+
100
+	/**
101
+	 * Returns the list of query parameters.
102
+	 *
103
+	 * This is equivalent to PHP's $_GET superglobal.
104
+	 *
105
+	 * @return array
106
+	 */
107
+	public function getQueryParameters() {
108
+
109
+		$url = $this->getUrl();
110
+		if (($index = strpos($url, '?')) === false) {
111
+			return [];
112
+		} else {
113
+			parse_str(substr($url, $index + 1), $queryParams);
114
+			return $queryParams;
115
+		}
116
+
117
+	}
118
+
119
+	/**
120
+	 * Sets the absolute url.
121
+	 *
122
+	 * @param string $url
123
+	 * @return void
124
+	 */
125
+	public function setAbsoluteUrl($url) {
126
+
127
+		$this->absoluteUrl = $url;
128
+
129
+	}
130
+
131
+	/**
132
+	 * Returns the absolute url.
133
+	 *
134
+	 * @return string
135
+	 */
136
+	public function getAbsoluteUrl() {
137
+
138
+		return $this->absoluteUrl;
139
+
140
+	}
141
+
142
+	/**
143
+	 * Base url
144
+	 *
145
+	 * @var string
146
+	 */
147
+	protected $baseUrl = '/';
148
+
149
+	/**
150
+	 * Sets a base url.
151
+	 *
152
+	 * This url is used for relative path calculations.
153
+	 *
154
+	 * @param string $url
155
+	 * @return void
156
+	 */
157
+	public function setBaseUrl($url) {
158
+
159
+		$this->baseUrl = $url;
160
+
161
+	}
162
+
163
+	/**
164
+	 * Returns the current base url.
165
+	 *
166
+	 * @return string
167
+	 */
168
+	public function getBaseUrl() {
169
+
170
+		return $this->baseUrl;
171
+
172
+	}
173
+
174
+	/**
175
+	 * Returns the relative path.
176
+	 *
177
+	 * This is being calculated using the base url. This path will not start
178
+	 * with a slash, so it will always return something like
179
+	 * 'example/path.html'.
180
+	 *
181
+	 * If the full path is equal to the base url, this method will return an
182
+	 * empty string.
183
+	 *
184
+	 * This method will also urldecode the path, and if the url was incoded as
185
+	 * ISO-8859-1, it will convert it to UTF-8.
186
+	 *
187
+	 * If the path is outside of the base url, a LogicException will be thrown.
188
+	 *
189
+	 * @return string
190
+	 */
191
+	public function getPath() {
192
+
193
+		// Removing duplicated slashes.
194
+		$uri = str_replace('//', '/', $this->getUrl());
195
+
196
+		$uri = Uri\normalize($uri);
197
+		$baseUri = Uri\normalize($this->getBaseUrl());
198
+
199
+		if (strpos($uri, $baseUri) === 0) {
200
+
201
+			// We're not interested in the query part (everything after the ?).
202
+			list($uri) = explode('?', $uri);
203
+			return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/');
204
+
205
+		}
206
+		// A special case, if the baseUri was accessed without a trailing
207
+		// slash, we'll accept it as well.
208
+		elseif ($uri . '/' === $baseUri) {
209
+
210
+			return '';
211
+
212
+		}
213
+
214
+		throw new \LogicException('Requested uri (' . $this->getUrl() . ') is out of base uri (' . $this->getBaseUrl() . ')');
215
+	}
216
+
217
+	/**
218
+	 * Equivalent of PHP's $_POST.
219
+	 *
220
+	 * @var array
221
+	 */
222
+	protected $postData = [];
223
+
224
+	/**
225
+	 * Sets the post data.
226
+	 *
227
+	 * This is equivalent to PHP's $_POST superglobal.
228
+	 *
229
+	 * This would not have been needed, if POST data was accessible as
230
+	 * php://input, but unfortunately we need to special case it.
231
+	 *
232
+	 * @param array $postData
233
+	 * @return void
234
+	 */
235
+	public function setPostData(array $postData) {
236
+
237
+		$this->postData = $postData;
238
+
239
+	}
240
+
241
+	/**
242
+	 * Returns the POST data.
243
+	 *
244
+	 * This is equivalent to PHP's $_POST superglobal.
245
+	 *
246
+	 * @return array
247
+	 */
248
+	public function getPostData() {
249
+
250
+		return $this->postData;
251
+
252
+	}
253
+
254
+	/**
255
+	 * An array containing the raw _SERVER array.
256
+	 *
257
+	 * @var array
258
+	 */
259
+	protected $rawServerData;
260
+
261
+	/**
262
+	 * Returns an item from the _SERVER array.
263
+	 *
264
+	 * If the value does not exist in the array, null is returned.
265
+	 *
266
+	 * @param string $valueName
267
+	 * @return string|null
268
+	 */
269
+	public function getRawServerValue($valueName) {
270
+
271
+		if (isset($this->rawServerData[$valueName])) {
272
+			return $this->rawServerData[$valueName];
273
+		}
274
+
275
+	}
276
+
277
+	/**
278
+	 * Sets the _SERVER array.
279
+	 *
280
+	 * @param array $data
281
+	 * @return void
282
+	 */
283
+	public function setRawServerData(array $data) {
284
+
285
+		$this->rawServerData = $data;
286
+
287
+	}
288
+
289
+	/**
290
+	 * Serializes the request object as a string.
291
+	 *
292
+	 * This is useful for debugging purposes.
293
+	 *
294
+	 * @return string
295
+	 */
296
+	public function __toString() {
297
+
298
+		$out = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n";
299
+
300
+		foreach ($this->getHeaders() as $key => $value) {
301
+			foreach ($value as $v) {
302
+				if ($key === 'Authorization') {
303
+					list($v) = explode(' ', $v, 2);
304
+					$v  .= ' REDACTED';
305
+				}
306
+				$out .= $key . ": " . $v . "\r\n";
307
+			}
308
+		}
309
+		$out .= "\r\n";
310
+		$out .= $this->getBodyAsString();
311
+
312
+		return $out;
313
+
314
+	}
315 315
 
316 316
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/Client.php 2 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -102,7 +102,7 @@  discard block
 block discarded – undo
102 102
 
103 103
                 $response = $this->doRequest($request);
104 104
 
105
-                $code = (int)$response->getStatus();
105
+                $code = (int) $response->getStatus();
106 106
 
107 107
                 // We are doing in-PHP redirects, because curl's
108 108
                 // FOLLOW_LOCATION throws errors when PHP is configured with
@@ -422,7 +422,7 @@  discard block
 block discarded – undo
422 422
                     // For security we cast this to a string. If somehow an array could
423 423
                     // be passed here, it would be possible for an attacker to use @ to
424 424
                     // post local files.
425
-                    $settings[CURLOPT_POSTFIELDS] = (string)$body;
425
+                    $settings[CURLOPT_POSTFIELDS] = (string) $body;
426 426
                 }
427 427
                 $settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
428 428
                 break;
Please login to merge, or discard this patch.
Indentation   +519 added lines, -519 removed lines patch added patch discarded remove patch
@@ -43,557 +43,557 @@
 block discarded – undo
43 43
  */
44 44
 class Client extends EventEmitter {
45 45
 
46
-    /**
47
-     * List of curl settings
48
-     *
49
-     * @var array
50
-     */
51
-    protected $curlSettings = [];
46
+	/**
47
+	 * List of curl settings
48
+	 *
49
+	 * @var array
50
+	 */
51
+	protected $curlSettings = [];
52 52
 
53
-    /**
54
-     * Wether or not exceptions should be thrown when a HTTP error is returned.
55
-     *
56
-     * @var bool
57
-     */
58
-    protected $throwExceptions = false;
53
+	/**
54
+	 * Wether or not exceptions should be thrown when a HTTP error is returned.
55
+	 *
56
+	 * @var bool
57
+	 */
58
+	protected $throwExceptions = false;
59 59
 
60
-    /**
61
-     * The maximum number of times we'll follow a redirect.
62
-     *
63
-     * @var int
64
-     */
65
-    protected $maxRedirects = 5;
60
+	/**
61
+	 * The maximum number of times we'll follow a redirect.
62
+	 *
63
+	 * @var int
64
+	 */
65
+	protected $maxRedirects = 5;
66 66
 
67
-    /**
68
-     * Initializes the client.
69
-     *
70
-     * @return void
71
-     */
72
-    public function __construct() {
67
+	/**
68
+	 * Initializes the client.
69
+	 *
70
+	 * @return void
71
+	 */
72
+	public function __construct() {
73 73
 
74
-        $this->curlSettings = [
75
-            CURLOPT_RETURNTRANSFER => true,
76
-            CURLOPT_HEADER         => true,
77
-            CURLOPT_NOBODY         => false,
78
-            CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
79
-        ];
74
+		$this->curlSettings = [
75
+			CURLOPT_RETURNTRANSFER => true,
76
+			CURLOPT_HEADER         => true,
77
+			CURLOPT_NOBODY         => false,
78
+			CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
79
+		];
80 80
 
81
-    }
81
+	}
82 82
 
83
-    /**
84
-     * Sends a request to a HTTP server, and returns a response.
85
-     *
86
-     * @param RequestInterface $request
87
-     * @return ResponseInterface
88
-     */
89
-    public function send(RequestInterface $request) {
83
+	/**
84
+	 * Sends a request to a HTTP server, and returns a response.
85
+	 *
86
+	 * @param RequestInterface $request
87
+	 * @return ResponseInterface
88
+	 */
89
+	public function send(RequestInterface $request) {
90 90
 
91
-        $this->emit('beforeRequest', [$request]);
91
+		$this->emit('beforeRequest', [$request]);
92 92
 
93
-        $retryCount = 0;
94
-        $redirects = 0;
93
+		$retryCount = 0;
94
+		$redirects = 0;
95 95
 
96
-        do {
97
-
98
-            $doRedirect = false;
99
-            $retry = false;
96
+		do {
97
+
98
+			$doRedirect = false;
99
+			$retry = false;
100 100
 
101
-            try {
102
-
103
-                $response = $this->doRequest($request);
104
-
105
-                $code = (int)$response->getStatus();
106
-
107
-                // We are doing in-PHP redirects, because curl's
108
-                // FOLLOW_LOCATION throws errors when PHP is configured with
109
-                // open_basedir.
110
-                //
111
-                // https://github.com/fruux/sabre-http/issues/12
112
-                if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) {
113
-
114
-                    $oldLocation = $request->getUrl();
115
-
116
-                    // Creating a new instance of the request object.
117
-                    $request = clone $request;
118
-
119
-                    // Setting the new location
120
-                    $request->setUrl(Uri\resolve(
121
-                        $oldLocation,
122
-                        $response->getHeader('Location')
123
-                    ));
101
+			try {
102
+
103
+				$response = $this->doRequest($request);
104
+
105
+				$code = (int)$response->getStatus();
106
+
107
+				// We are doing in-PHP redirects, because curl's
108
+				// FOLLOW_LOCATION throws errors when PHP is configured with
109
+				// open_basedir.
110
+				//
111
+				// https://github.com/fruux/sabre-http/issues/12
112
+				if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) {
113
+
114
+					$oldLocation = $request->getUrl();
115
+
116
+					// Creating a new instance of the request object.
117
+					$request = clone $request;
118
+
119
+					// Setting the new location
120
+					$request->setUrl(Uri\resolve(
121
+						$oldLocation,
122
+						$response->getHeader('Location')
123
+					));
124 124
 
125
-                    $doRedirect = true;
126
-                    $redirects++;
127
-
128
-                }
129
-
130
-                // This was a HTTP error
131
-                if ($code >= 400) {
132
-
133
-                    $this->emit('error', [$request, $response, &$retry, $retryCount]);
134
-                    $this->emit('error:' . $code, [$request, $response, &$retry, $retryCount]);
135
-
136
-                }
137
-
138
-            } catch (ClientException $e) {
139
-
140
-                $this->emit('exception', [$request, $e, &$retry, $retryCount]);
141
-
142
-                // If retry was still set to false, it means no event handler
143
-                // dealt with the problem. In this case we just re-throw the
144
-                // exception.
145
-                if (!$retry) {
146
-                    throw $e;
147
-                }
148
-
149
-            }
150
-
151
-            if ($retry) {
152
-                $retryCount++;
153
-            }
125
+					$doRedirect = true;
126
+					$redirects++;
127
+
128
+				}
129
+
130
+				// This was a HTTP error
131
+				if ($code >= 400) {
132
+
133
+					$this->emit('error', [$request, $response, &$retry, $retryCount]);
134
+					$this->emit('error:' . $code, [$request, $response, &$retry, $retryCount]);
135
+
136
+				}
137
+
138
+			} catch (ClientException $e) {
139
+
140
+				$this->emit('exception', [$request, $e, &$retry, $retryCount]);
141
+
142
+				// If retry was still set to false, it means no event handler
143
+				// dealt with the problem. In this case we just re-throw the
144
+				// exception.
145
+				if (!$retry) {
146
+					throw $e;
147
+				}
148
+
149
+			}
150
+
151
+			if ($retry) {
152
+				$retryCount++;
153
+			}
154 154
 
155
-        } while ($retry || $doRedirect);
155
+		} while ($retry || $doRedirect);
156 156
 
157
-        $this->emit('afterRequest', [$request, $response]);
157
+		$this->emit('afterRequest', [$request, $response]);
158 158
 
159
-        if ($this->throwExceptions && $code >= 400) {
160
-            throw new ClientHttpException($response);
161
-        }
159
+		if ($this->throwExceptions && $code >= 400) {
160
+			throw new ClientHttpException($response);
161
+		}
162 162
 
163
-        return $response;
163
+		return $response;
164 164
 
165
-    }
165
+	}
166 166
 
167
-    /**
168
-     * Sends a HTTP request asynchronously.
169
-     *
170
-     * Due to the nature of PHP, you must from time to time poll to see if any
171
-     * new responses came in.
172
-     *
173
-     * After calling sendAsync, you must therefore occasionally call the poll()
174
-     * method, or wait().
175
-     *
176
-     * @param RequestInterface $request
177
-     * @param callable $success
178
-     * @param callable $error
179
-     * @return void
180
-     */
181
-    public function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) {
167
+	/**
168
+	 * Sends a HTTP request asynchronously.
169
+	 *
170
+	 * Due to the nature of PHP, you must from time to time poll to see if any
171
+	 * new responses came in.
172
+	 *
173
+	 * After calling sendAsync, you must therefore occasionally call the poll()
174
+	 * method, or wait().
175
+	 *
176
+	 * @param RequestInterface $request
177
+	 * @param callable $success
178
+	 * @param callable $error
179
+	 * @return void
180
+	 */
181
+	public function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) {
182 182
 
183
-        $this->emit('beforeRequest', [$request]);
184
-        $this->sendAsyncInternal($request, $success, $error);
185
-        $this->poll();
183
+		$this->emit('beforeRequest', [$request]);
184
+		$this->sendAsyncInternal($request, $success, $error);
185
+		$this->poll();
186 186
 
187
-    }
187
+	}
188 188
 
189 189
 
190
-    /**
191
-     * This method checks if any http requests have gotten results, and if so,
192
-     * call the appropriate success or error handlers.
193
-     *
194
-     * This method will return true if there are still requests waiting to
195
-     * return, and false if all the work is done.
196
-     *
197
-     * @return bool
198
-     */
199
-    public function poll() {
190
+	/**
191
+	 * This method checks if any http requests have gotten results, and if so,
192
+	 * call the appropriate success or error handlers.
193
+	 *
194
+	 * This method will return true if there are still requests waiting to
195
+	 * return, and false if all the work is done.
196
+	 *
197
+	 * @return bool
198
+	 */
199
+	public function poll() {
200 200
 
201
-        // nothing to do?
202
-        if (!$this->curlMultiMap) {
203
-            return false;
204
-        }
201
+		// nothing to do?
202
+		if (!$this->curlMultiMap) {
203
+			return false;
204
+		}
205 205
 
206
-        do {
207
-            $r = curl_multi_exec(
208
-                $this->curlMultiHandle,
209
-                $stillRunning
210
-            );
211
-        } while ($r === CURLM_CALL_MULTI_PERFORM);
206
+		do {
207
+			$r = curl_multi_exec(
208
+				$this->curlMultiHandle,
209
+				$stillRunning
210
+			);
211
+		} while ($r === CURLM_CALL_MULTI_PERFORM);
212 212
 
213
-        do {
213
+		do {
214 214
 
215
-            messageQueue:
215
+			messageQueue:
216 216
 
217
-            $status = curl_multi_info_read(
218
-                $this->curlMultiHandle,
219
-                $messagesInQueue
220
-            );
217
+			$status = curl_multi_info_read(
218
+				$this->curlMultiHandle,
219
+				$messagesInQueue
220
+			);
221 221
 
222
-            if ($status && $status['msg'] === CURLMSG_DONE) {
222
+			if ($status && $status['msg'] === CURLMSG_DONE) {
223 223
 
224
-                $resourceId = intval($status['handle']);
225
-                list(
226
-                    $request,
227
-                    $successCallback,
228
-                    $errorCallback,
229
-                    $retryCount,
230
-                ) = $this->curlMultiMap[$resourceId];
231
-                unset($this->curlMultiMap[$resourceId]);
232
-                $curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']);
233
-                $retry = false;
224
+				$resourceId = intval($status['handle']);
225
+				list(
226
+					$request,
227
+					$successCallback,
228
+					$errorCallback,
229
+					$retryCount,
230
+				) = $this->curlMultiMap[$resourceId];
231
+				unset($this->curlMultiMap[$resourceId]);
232
+				$curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']);
233
+				$retry = false;
234 234
 
235
-                if ($curlResult['status'] === self::STATUS_CURLERROR) {
235
+				if ($curlResult['status'] === self::STATUS_CURLERROR) {
236 236
 
237
-                    $e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']);
238
-                    $this->emit('exception', [$request, $e, &$retry, $retryCount]);
237
+					$e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']);
238
+					$this->emit('exception', [$request, $e, &$retry, $retryCount]);
239 239
 
240
-                    if ($retry) {
241
-                        $retryCount++;
242
-                        $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
243
-                        goto messageQueue;
244
-                    }
245
-
246
-                    $curlResult['request'] = $request;
240
+					if ($retry) {
241
+						$retryCount++;
242
+						$this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
243
+						goto messageQueue;
244
+					}
245
+
246
+					$curlResult['request'] = $request;
247 247
 
248
-                    if ($errorCallback) {
249
-                        $errorCallback($curlResult);
250
-                    }
251
-
252
-                } elseif ($curlResult['status'] === self::STATUS_HTTPERROR) {
253
-
254
-                    $this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]);
255
-                    $this->emit('error:' . $curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]);
256
-
257
-                    if ($retry) {
258
-
259
-                        $retryCount++;
260
-                        $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
261
-                        goto messageQueue;
248
+					if ($errorCallback) {
249
+						$errorCallback($curlResult);
250
+					}
251
+
252
+				} elseif ($curlResult['status'] === self::STATUS_HTTPERROR) {
253
+
254
+					$this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]);
255
+					$this->emit('error:' . $curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]);
256
+
257
+					if ($retry) {
258
+
259
+						$retryCount++;
260
+						$this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
261
+						goto messageQueue;
262 262
 
263
-                    }
264
-
265
-                    $curlResult['request'] = $request;
266
-
267
-                    if ($errorCallback) {
268
-                        $errorCallback($curlResult);
269
-                    }
270
-
271
-                } else {
272
-
273
-                    $this->emit('afterRequest', [$request, $curlResult['response']]);
274
-
275
-                    if ($successCallback) {
276
-                        $successCallback($curlResult['response']);
277
-                    }
278
-
279
-                }
280
-            }
281
-
282
-        } while ($messagesInQueue > 0);
283
-
284
-        return count($this->curlMultiMap) > 0;
285
-
286
-    }
287
-
288
-    /**
289
-     * Processes every HTTP request in the queue, and waits till they are all
290
-     * completed.
291
-     *
292
-     * @return void
293
-     */
294
-    public function wait() {
295
-
296
-        do {
297
-            curl_multi_select($this->curlMultiHandle);
298
-            $stillRunning = $this->poll();
299
-        } while ($stillRunning);
300
-
301
-    }
302
-
303
-    /**
304
-     * If this is set to true, the Client will automatically throw exceptions
305
-     * upon HTTP errors.
306
-     *
307
-     * This means that if a response came back with a status code greater than
308
-     * or equal to 400, we will throw a ClientHttpException.
309
-     *
310
-     * This only works for the send() method. Throwing exceptions for
311
-     * sendAsync() is not supported.
312
-     *
313
-     * @param bool $throwExceptions
314
-     * @return void
315
-     */
316
-    public function setThrowExceptions($throwExceptions) {
317
-
318
-        $this->throwExceptions = $throwExceptions;
319
-
320
-    }
321
-
322
-    /**
323
-     * Adds a CURL setting.
324
-     *
325
-     * These settings will be included in every HTTP request.
326
-     *
327
-     * @param int $name
328
-     * @param mixed $value
329
-     * @return void
330
-     */
331
-    public function addCurlSetting($name, $value) {
332
-
333
-        $this->curlSettings[$name] = $value;
334
-
335
-    }
336
-
337
-    /**
338
-     * This method is responsible for performing a single request.
339
-     *
340
-     * @param RequestInterface $request
341
-     * @return ResponseInterface
342
-     */
343
-    protected function doRequest(RequestInterface $request) {
344
-
345
-        $settings = $this->createCurlSettingsArray($request);
346
-
347
-        if (!$this->curlHandle) {
348
-            $this->curlHandle = curl_init();
349
-        }
350
-
351
-        curl_setopt_array($this->curlHandle, $settings);
352
-        $response = $this->curlExec($this->curlHandle);
353
-        $response = $this->parseCurlResult($response, $this->curlHandle);
354
-
355
-        if ($response['status'] === self::STATUS_CURLERROR) {
356
-            throw new ClientException($response['curl_errmsg'], $response['curl_errno']);
357
-        }
358
-
359
-        return $response['response'];
360
-
361
-    }
362
-
363
-    /**
364
-     * Cached curl handle.
365
-     *
366
-     * By keeping this resource around for the lifetime of this object, things
367
-     * like persistent connections are possible.
368
-     *
369
-     * @var resource
370
-     */
371
-    private $curlHandle;
372
-
373
-    /**
374
-     * Handler for curl_multi requests.
375
-     *
376
-     * The first time sendAsync is used, this will be created.
377
-     *
378
-     * @var resource
379
-     */
380
-    private $curlMultiHandle;
381
-
382
-    /**
383
-     * Has a list of curl handles, as well as their associated success and
384
-     * error callbacks.
385
-     *
386
-     * @var array
387
-     */
388
-    private $curlMultiMap = [];
389
-
390
-    /**
391
-     * Turns a RequestInterface object into an array with settings that can be
392
-     * fed to curl_setopt
393
-     *
394
-     * @param RequestInterface $request
395
-     * @return array
396
-     */
397
-    protected function createCurlSettingsArray(RequestInterface $request) {
398
-
399
-        $settings = $this->curlSettings;
400
-
401
-        switch ($request->getMethod()) {
402
-            case 'HEAD' :
403
-                $settings[CURLOPT_NOBODY] = true;
404
-                $settings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
405
-                $settings[CURLOPT_POSTFIELDS] = '';
406
-                $settings[CURLOPT_PUT] = false;
407
-                break;
408
-            case 'GET' :
409
-                $settings[CURLOPT_CUSTOMREQUEST] = 'GET';
410
-                $settings[CURLOPT_POSTFIELDS] = '';
411
-                $settings[CURLOPT_PUT] = false;
412
-                break;
413
-            default :
414
-                $body = $request->getBody();
415
-                if (is_resource($body)) {
416
-                    // This needs to be set to PUT, regardless of the actual
417
-                    // method used. Without it, INFILE will be ignored for some
418
-                    // reason.
419
-                    $settings[CURLOPT_PUT] = true;
420
-                    $settings[CURLOPT_INFILE] = $request->getBody();
421
-                } else {
422
-                    // For security we cast this to a string. If somehow an array could
423
-                    // be passed here, it would be possible for an attacker to use @ to
424
-                    // post local files.
425
-                    $settings[CURLOPT_POSTFIELDS] = (string)$body;
426
-                }
427
-                $settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
428
-                break;
429
-
430
-        }
431
-
432
-        $nHeaders = [];
433
-        foreach ($request->getHeaders() as $key => $values) {
434
-
435
-            foreach ($values as $value) {
436
-                $nHeaders[] = $key . ': ' . $value;
437
-            }
438
-
439
-        }
440
-        $settings[CURLOPT_HTTPHEADER] = $nHeaders;
441
-        $settings[CURLOPT_URL] = $request->getUrl();
442
-        if (defined('CURLOPT_PROTOCOLS')) {
443
-            $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
444
-        }
445
-        if (defined('CURLOPT_REDIR_PROTOCOLS')) {
446
-            $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
447
-        }
448
-
449
-        return $settings;
450
-
451
-    }
452
-
453
-    const STATUS_SUCCESS = 0;
454
-    const STATUS_CURLERROR = 1;
455
-    const STATUS_HTTPERROR = 2;
456
-
457
-    /**
458
-     * Parses the result of a curl call in a format that's a bit more
459
-     * convenient to work with.
460
-     *
461
-     * The method returns an array with the following elements:
462
-     *   * status - one of the 3 STATUS constants.
463
-     *   * curl_errno - A curl error number. Only set if status is
464
-     *                  STATUS_CURLERROR.
465
-     *   * curl_errmsg - A current error message. Only set if status is
466
-     *                   STATUS_CURLERROR.
467
-     *   * response - Response object. Only set if status is STATUS_SUCCESS, or
468
-     *                STATUS_HTTPERROR.
469
-     *   * http_code - HTTP status code, as an int. Only set if Only set if
470
-     *                 status is STATUS_SUCCESS, or STATUS_HTTPERROR
471
-     *
472
-     * @param string $response
473
-     * @param resource $curlHandle
474
-     * @return Response
475
-     */
476
-    protected function parseCurlResult($response, $curlHandle) {
477
-
478
-        list(
479
-            $curlInfo,
480
-            $curlErrNo,
481
-            $curlErrMsg
482
-        ) = $this->curlStuff($curlHandle);
483
-
484
-        if ($curlErrNo) {
485
-            return [
486
-                'status'      => self::STATUS_CURLERROR,
487
-                'curl_errno'  => $curlErrNo,
488
-                'curl_errmsg' => $curlErrMsg,
489
-            ];
490
-        }
491
-
492
-        $headerBlob = substr($response, 0, $curlInfo['header_size']);
493
-        // In the case of 204 No Content, strlen($response) == $curlInfo['header_size].
494
-        // This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL
495
-        // An exception will be thrown when calling getBodyAsString then
496
-        $responseBody = substr($response, $curlInfo['header_size']) ?: null;
497
-
498
-        unset($response);
499
-
500
-        // In the case of 100 Continue, or redirects we'll have multiple lists
501
-        // of headers for each separate HTTP response. We can easily split this
502
-        // because they are separated by \r\n\r\n
503
-        $headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n"));
504
-
505
-        // We only care about the last set of headers
506
-        $headerBlob = $headerBlob[count($headerBlob) - 1];
507
-
508
-        // Splitting headers
509
-        $headerBlob = explode("\r\n", $headerBlob);
510
-
511
-        $response = new Response();
512
-        $response->setStatus($curlInfo['http_code']);
513
-
514
-        foreach ($headerBlob as $header) {
515
-            $parts = explode(':', $header, 2);
516
-            if (count($parts) == 2) {
517
-                $response->addHeader(trim($parts[0]), trim($parts[1]));
518
-            }
519
-        }
520
-
521
-        $response->setBody($responseBody);
522
-
523
-        $httpCode = intval($response->getStatus());
524
-
525
-        return [
526
-            'status'    => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS,
527
-            'response'  => $response,
528
-            'http_code' => $httpCode,
529
-        ];
530
-
531
-    }
532
-
533
-    /**
534
-     * Sends an asynchronous HTTP request.
535
-     *
536
-     * We keep this in a separate method, so we can call it without triggering
537
-     * the beforeRequest event and don't do the poll().
538
-     *
539
-     * @param RequestInterface $request
540
-     * @param callable $success
541
-     * @param callable $error
542
-     * @param int $retryCount
543
-     */
544
-    protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $retryCount = 0) {
545
-
546
-        if (!$this->curlMultiHandle) {
547
-            $this->curlMultiHandle = curl_multi_init();
548
-        }
549
-        $curl = curl_init();
550
-        curl_setopt_array(
551
-            $curl,
552
-            $this->createCurlSettingsArray($request)
553
-        );
554
-        curl_multi_add_handle($this->curlMultiHandle, $curl);
555
-        $this->curlMultiMap[intval($curl)] = [
556
-            $request,
557
-            $success,
558
-            $error,
559
-            $retryCount
560
-        ];
561
-
562
-    }
563
-
564
-    // @codeCoverageIgnoreStart
565
-
566
-    /**
567
-     * Calls curl_exec
568
-     *
569
-     * This method exists so it can easily be overridden and mocked.
570
-     *
571
-     * @param resource $curlHandle
572
-     * @return string
573
-     */
574
-    protected function curlExec($curlHandle) {
575
-
576
-        return curl_exec($curlHandle);
577
-
578
-    }
579
-
580
-    /**
581
-     * Returns a bunch of information about a curl request.
582
-     *
583
-     * This method exists so it can easily be overridden and mocked.
584
-     *
585
-     * @param resource $curlHandle
586
-     * @return array
587
-     */
588
-    protected function curlStuff($curlHandle) {
589
-
590
-        return [
591
-            curl_getinfo($curlHandle),
592
-            curl_errno($curlHandle),
593
-            curl_error($curlHandle),
594
-        ];
595
-
596
-    }
597
-    // @codeCoverageIgnoreEnd
263
+					}
264
+
265
+					$curlResult['request'] = $request;
266
+
267
+					if ($errorCallback) {
268
+						$errorCallback($curlResult);
269
+					}
270
+
271
+				} else {
272
+
273
+					$this->emit('afterRequest', [$request, $curlResult['response']]);
274
+
275
+					if ($successCallback) {
276
+						$successCallback($curlResult['response']);
277
+					}
278
+
279
+				}
280
+			}
281
+
282
+		} while ($messagesInQueue > 0);
283
+
284
+		return count($this->curlMultiMap) > 0;
285
+
286
+	}
287
+
288
+	/**
289
+	 * Processes every HTTP request in the queue, and waits till they are all
290
+	 * completed.
291
+	 *
292
+	 * @return void
293
+	 */
294
+	public function wait() {
295
+
296
+		do {
297
+			curl_multi_select($this->curlMultiHandle);
298
+			$stillRunning = $this->poll();
299
+		} while ($stillRunning);
300
+
301
+	}
302
+
303
+	/**
304
+	 * If this is set to true, the Client will automatically throw exceptions
305
+	 * upon HTTP errors.
306
+	 *
307
+	 * This means that if a response came back with a status code greater than
308
+	 * or equal to 400, we will throw a ClientHttpException.
309
+	 *
310
+	 * This only works for the send() method. Throwing exceptions for
311
+	 * sendAsync() is not supported.
312
+	 *
313
+	 * @param bool $throwExceptions
314
+	 * @return void
315
+	 */
316
+	public function setThrowExceptions($throwExceptions) {
317
+
318
+		$this->throwExceptions = $throwExceptions;
319
+
320
+	}
321
+
322
+	/**
323
+	 * Adds a CURL setting.
324
+	 *
325
+	 * These settings will be included in every HTTP request.
326
+	 *
327
+	 * @param int $name
328
+	 * @param mixed $value
329
+	 * @return void
330
+	 */
331
+	public function addCurlSetting($name, $value) {
332
+
333
+		$this->curlSettings[$name] = $value;
334
+
335
+	}
336
+
337
+	/**
338
+	 * This method is responsible for performing a single request.
339
+	 *
340
+	 * @param RequestInterface $request
341
+	 * @return ResponseInterface
342
+	 */
343
+	protected function doRequest(RequestInterface $request) {
344
+
345
+		$settings = $this->createCurlSettingsArray($request);
346
+
347
+		if (!$this->curlHandle) {
348
+			$this->curlHandle = curl_init();
349
+		}
350
+
351
+		curl_setopt_array($this->curlHandle, $settings);
352
+		$response = $this->curlExec($this->curlHandle);
353
+		$response = $this->parseCurlResult($response, $this->curlHandle);
354
+
355
+		if ($response['status'] === self::STATUS_CURLERROR) {
356
+			throw new ClientException($response['curl_errmsg'], $response['curl_errno']);
357
+		}
358
+
359
+		return $response['response'];
360
+
361
+	}
362
+
363
+	/**
364
+	 * Cached curl handle.
365
+	 *
366
+	 * By keeping this resource around for the lifetime of this object, things
367
+	 * like persistent connections are possible.
368
+	 *
369
+	 * @var resource
370
+	 */
371
+	private $curlHandle;
372
+
373
+	/**
374
+	 * Handler for curl_multi requests.
375
+	 *
376
+	 * The first time sendAsync is used, this will be created.
377
+	 *
378
+	 * @var resource
379
+	 */
380
+	private $curlMultiHandle;
381
+
382
+	/**
383
+	 * Has a list of curl handles, as well as their associated success and
384
+	 * error callbacks.
385
+	 *
386
+	 * @var array
387
+	 */
388
+	private $curlMultiMap = [];
389
+
390
+	/**
391
+	 * Turns a RequestInterface object into an array with settings that can be
392
+	 * fed to curl_setopt
393
+	 *
394
+	 * @param RequestInterface $request
395
+	 * @return array
396
+	 */
397
+	protected function createCurlSettingsArray(RequestInterface $request) {
398
+
399
+		$settings = $this->curlSettings;
400
+
401
+		switch ($request->getMethod()) {
402
+			case 'HEAD' :
403
+				$settings[CURLOPT_NOBODY] = true;
404
+				$settings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
405
+				$settings[CURLOPT_POSTFIELDS] = '';
406
+				$settings[CURLOPT_PUT] = false;
407
+				break;
408
+			case 'GET' :
409
+				$settings[CURLOPT_CUSTOMREQUEST] = 'GET';
410
+				$settings[CURLOPT_POSTFIELDS] = '';
411
+				$settings[CURLOPT_PUT] = false;
412
+				break;
413
+			default :
414
+				$body = $request->getBody();
415
+				if (is_resource($body)) {
416
+					// This needs to be set to PUT, regardless of the actual
417
+					// method used. Without it, INFILE will be ignored for some
418
+					// reason.
419
+					$settings[CURLOPT_PUT] = true;
420
+					$settings[CURLOPT_INFILE] = $request->getBody();
421
+				} else {
422
+					// For security we cast this to a string. If somehow an array could
423
+					// be passed here, it would be possible for an attacker to use @ to
424
+					// post local files.
425
+					$settings[CURLOPT_POSTFIELDS] = (string)$body;
426
+				}
427
+				$settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
428
+				break;
429
+
430
+		}
431
+
432
+		$nHeaders = [];
433
+		foreach ($request->getHeaders() as $key => $values) {
434
+
435
+			foreach ($values as $value) {
436
+				$nHeaders[] = $key . ': ' . $value;
437
+			}
438
+
439
+		}
440
+		$settings[CURLOPT_HTTPHEADER] = $nHeaders;
441
+		$settings[CURLOPT_URL] = $request->getUrl();
442
+		if (defined('CURLOPT_PROTOCOLS')) {
443
+			$settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
444
+		}
445
+		if (defined('CURLOPT_REDIR_PROTOCOLS')) {
446
+			$settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
447
+		}
448
+
449
+		return $settings;
450
+
451
+	}
452
+
453
+	const STATUS_SUCCESS = 0;
454
+	const STATUS_CURLERROR = 1;
455
+	const STATUS_HTTPERROR = 2;
456
+
457
+	/**
458
+	 * Parses the result of a curl call in a format that's a bit more
459
+	 * convenient to work with.
460
+	 *
461
+	 * The method returns an array with the following elements:
462
+	 *   * status - one of the 3 STATUS constants.
463
+	 *   * curl_errno - A curl error number. Only set if status is
464
+	 *                  STATUS_CURLERROR.
465
+	 *   * curl_errmsg - A current error message. Only set if status is
466
+	 *                   STATUS_CURLERROR.
467
+	 *   * response - Response object. Only set if status is STATUS_SUCCESS, or
468
+	 *                STATUS_HTTPERROR.
469
+	 *   * http_code - HTTP status code, as an int. Only set if Only set if
470
+	 *                 status is STATUS_SUCCESS, or STATUS_HTTPERROR
471
+	 *
472
+	 * @param string $response
473
+	 * @param resource $curlHandle
474
+	 * @return Response
475
+	 */
476
+	protected function parseCurlResult($response, $curlHandle) {
477
+
478
+		list(
479
+			$curlInfo,
480
+			$curlErrNo,
481
+			$curlErrMsg
482
+		) = $this->curlStuff($curlHandle);
483
+
484
+		if ($curlErrNo) {
485
+			return [
486
+				'status'      => self::STATUS_CURLERROR,
487
+				'curl_errno'  => $curlErrNo,
488
+				'curl_errmsg' => $curlErrMsg,
489
+			];
490
+		}
491
+
492
+		$headerBlob = substr($response, 0, $curlInfo['header_size']);
493
+		// In the case of 204 No Content, strlen($response) == $curlInfo['header_size].
494
+		// This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL
495
+		// An exception will be thrown when calling getBodyAsString then
496
+		$responseBody = substr($response, $curlInfo['header_size']) ?: null;
497
+
498
+		unset($response);
499
+
500
+		// In the case of 100 Continue, or redirects we'll have multiple lists
501
+		// of headers for each separate HTTP response. We can easily split this
502
+		// because they are separated by \r\n\r\n
503
+		$headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n"));
504
+
505
+		// We only care about the last set of headers
506
+		$headerBlob = $headerBlob[count($headerBlob) - 1];
507
+
508
+		// Splitting headers
509
+		$headerBlob = explode("\r\n", $headerBlob);
510
+
511
+		$response = new Response();
512
+		$response->setStatus($curlInfo['http_code']);
513
+
514
+		foreach ($headerBlob as $header) {
515
+			$parts = explode(':', $header, 2);
516
+			if (count($parts) == 2) {
517
+				$response->addHeader(trim($parts[0]), trim($parts[1]));
518
+			}
519
+		}
520
+
521
+		$response->setBody($responseBody);
522
+
523
+		$httpCode = intval($response->getStatus());
524
+
525
+		return [
526
+			'status'    => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS,
527
+			'response'  => $response,
528
+			'http_code' => $httpCode,
529
+		];
530
+
531
+	}
532
+
533
+	/**
534
+	 * Sends an asynchronous HTTP request.
535
+	 *
536
+	 * We keep this in a separate method, so we can call it without triggering
537
+	 * the beforeRequest event and don't do the poll().
538
+	 *
539
+	 * @param RequestInterface $request
540
+	 * @param callable $success
541
+	 * @param callable $error
542
+	 * @param int $retryCount
543
+	 */
544
+	protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $retryCount = 0) {
545
+
546
+		if (!$this->curlMultiHandle) {
547
+			$this->curlMultiHandle = curl_multi_init();
548
+		}
549
+		$curl = curl_init();
550
+		curl_setopt_array(
551
+			$curl,
552
+			$this->createCurlSettingsArray($request)
553
+		);
554
+		curl_multi_add_handle($this->curlMultiHandle, $curl);
555
+		$this->curlMultiMap[intval($curl)] = [
556
+			$request,
557
+			$success,
558
+			$error,
559
+			$retryCount
560
+		];
561
+
562
+	}
563
+
564
+	// @codeCoverageIgnoreStart
565
+
566
+	/**
567
+	 * Calls curl_exec
568
+	 *
569
+	 * This method exists so it can easily be overridden and mocked.
570
+	 *
571
+	 * @param resource $curlHandle
572
+	 * @return string
573
+	 */
574
+	protected function curlExec($curlHandle) {
575
+
576
+		return curl_exec($curlHandle);
577
+
578
+	}
579
+
580
+	/**
581
+	 * Returns a bunch of information about a curl request.
582
+	 *
583
+	 * This method exists so it can easily be overridden and mocked.
584
+	 *
585
+	 * @param resource $curlHandle
586
+	 * @return array
587
+	 */
588
+	protected function curlStuff($curlHandle) {
589
+
590
+		return [
591
+			curl_getinfo($curlHandle),
592
+			curl_errno($curlHandle),
593
+			curl_error($curlHandle),
594
+		];
595
+
596
+	}
597
+	// @codeCoverageIgnoreEnd
598 598
 
599 599
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/Sapi.php 2 patches
Indentation   +160 added lines, -160 removed lines patch added patch discarded remove patch
@@ -30,165 +30,165 @@
 block discarded – undo
30 30
  */
31 31
 class Sapi {
32 32
 
33
-    /**
34
-     * This static method will create a new Request object, based on the
35
-     * current PHP request.
36
-     *
37
-     * @return Request
38
-     */
39
-    static function getRequest() {
40
-
41
-        $r = self::createFromServerArray($_SERVER);
42
-        $r->setBody(fopen('php://input', 'r'));
43
-        $r->setPostData($_POST);
44
-        return $r;
45
-
46
-    }
47
-
48
-    /**
49
-     * Sends the HTTP response back to a HTTP client.
50
-     *
51
-     * This calls php's header() function and streams the body to php://output.
52
-     *
53
-     * @param ResponseInterface $response
54
-     * @return void
55
-     */
56
-    static function sendResponse(ResponseInterface $response) {
57
-
58
-        header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText());
59
-        foreach ($response->getHeaders() as $key => $value) {
60
-
61
-            foreach ($value as $k => $v) {
62
-                if ($k === 0) {
63
-                    header($key . ': ' . $v);
64
-                } else {
65
-                    header($key . ': ' . $v, false);
66
-                }
67
-            }
68
-
69
-        }
70
-
71
-        $body = $response->getBody();
72
-        if (is_null($body)) return;
73
-
74
-        $contentLength = $response->getHeader('Content-Length');
75
-        if ($contentLength !== null) {
76
-            $output = fopen('php://output', 'wb');
77
-            if (is_resource($body) && get_resource_type($body) == 'stream') {
78
-                stream_copy_to_stream($body, $output, $contentLength);
79
-            } else {
80
-                fwrite($output, $body, $contentLength);
81
-            }
82
-        } else {
83
-            file_put_contents('php://output', $body);
84
-        }
85
-
86
-        if (is_resource($body)) {
87
-            fclose($body);
88
-        }
89
-
90
-    }
91
-
92
-    /**
93
-     * This static method will create a new Request object, based on a PHP
94
-     * $_SERVER array.
95
-     *
96
-     * @param array $serverArray
97
-     * @return Request
98
-     */
99
-    static function createFromServerArray(array $serverArray) {
100
-
101
-        $headers = [];
102
-        $method = null;
103
-        $url = null;
104
-        $httpVersion = '1.1';
105
-
106
-        $protocol = 'http';
107
-        $hostName = 'localhost';
108
-
109
-        foreach ($serverArray as $key => $value) {
110
-
111
-            switch ($key) {
112
-
113
-                case 'SERVER_PROTOCOL' :
114
-                    if ($value === 'HTTP/1.0') {
115
-                        $httpVersion = '1.0';
116
-                    }
117
-                    break;
118
-                case 'REQUEST_METHOD' :
119
-                    $method = $value;
120
-                    break;
121
-                case 'REQUEST_URI' :
122
-                    $url = $value;
123
-                    break;
124
-
125
-                // These sometimes show up without a HTTP_ prefix
126
-                case 'CONTENT_TYPE' :
127
-                    $headers['Content-Type'] = $value;
128
-                    break;
129
-                case 'CONTENT_LENGTH' :
130
-                    $headers['Content-Length'] = $value;
131
-                    break;
132
-
133
-                // mod_php on apache will put credentials in these variables.
134
-                // (fast)cgi does not usually do this, however.
135
-                case 'PHP_AUTH_USER' :
136
-                    if (isset($serverArray['PHP_AUTH_PW'])) {
137
-                        $headers['Authorization'] = 'Basic ' . base64_encode($value . ':' . $serverArray['PHP_AUTH_PW']);
138
-                    }
139
-                    break;
140
-
141
-                // Similarly, mod_php may also screw around with digest auth.
142
-                case 'PHP_AUTH_DIGEST' :
143
-                    $headers['Authorization'] = 'Digest ' . $value;
144
-                    break;
145
-
146
-                // Apache may prefix the HTTP_AUTHORIZATION header with
147
-                // REDIRECT_, if mod_rewrite was used.
148
-                case 'REDIRECT_HTTP_AUTHORIZATION' :
149
-                    $headers['Authorization'] = $value;
150
-                    break;
151
-
152
-                case 'HTTP_HOST' :
153
-                    $hostName = $value;
154
-                    $headers['Host'] = $value;
155
-                    break;
156
-
157
-                case 'HTTPS' :
158
-                    if (!empty($value) && $value !== 'off') {
159
-                        $protocol = 'https';
160
-                    }
161
-                    break;
162
-
163
-                default :
164
-                    if (substr($key, 0, 5) === 'HTTP_') {
165
-                        // It's a HTTP header
166
-
167
-                        // Normalizing it to be prettier
168
-                        $header = strtolower(substr($key, 5));
169
-
170
-                        // Transforming dashes into spaces, and uppercasing
171
-                        // every first letter.
172
-                        $header = ucwords(str_replace('_', ' ', $header));
173
-
174
-                        // Turning spaces into dashes.
175
-                        $header = str_replace(' ', '-', $header);
176
-                        $headers[$header] = $value;
177
-
178
-                    }
179
-                    break;
180
-
181
-
182
-            }
183
-
184
-        }
185
-
186
-        $r = new Request($method, $url, $headers);
187
-        $r->setHttpVersion($httpVersion);
188
-        $r->setRawServerData($serverArray);
189
-        $r->setAbsoluteUrl($protocol . '://' . $hostName . $url);
190
-        return $r;
191
-
192
-    }
33
+	/**
34
+	 * This static method will create a new Request object, based on the
35
+	 * current PHP request.
36
+	 *
37
+	 * @return Request
38
+	 */
39
+	static function getRequest() {
40
+
41
+		$r = self::createFromServerArray($_SERVER);
42
+		$r->setBody(fopen('php://input', 'r'));
43
+		$r->setPostData($_POST);
44
+		return $r;
45
+
46
+	}
47
+
48
+	/**
49
+	 * Sends the HTTP response back to a HTTP client.
50
+	 *
51
+	 * This calls php's header() function and streams the body to php://output.
52
+	 *
53
+	 * @param ResponseInterface $response
54
+	 * @return void
55
+	 */
56
+	static function sendResponse(ResponseInterface $response) {
57
+
58
+		header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText());
59
+		foreach ($response->getHeaders() as $key => $value) {
60
+
61
+			foreach ($value as $k => $v) {
62
+				if ($k === 0) {
63
+					header($key . ': ' . $v);
64
+				} else {
65
+					header($key . ': ' . $v, false);
66
+				}
67
+			}
68
+
69
+		}
70
+
71
+		$body = $response->getBody();
72
+		if (is_null($body)) return;
73
+
74
+		$contentLength = $response->getHeader('Content-Length');
75
+		if ($contentLength !== null) {
76
+			$output = fopen('php://output', 'wb');
77
+			if (is_resource($body) && get_resource_type($body) == 'stream') {
78
+				stream_copy_to_stream($body, $output, $contentLength);
79
+			} else {
80
+				fwrite($output, $body, $contentLength);
81
+			}
82
+		} else {
83
+			file_put_contents('php://output', $body);
84
+		}
85
+
86
+		if (is_resource($body)) {
87
+			fclose($body);
88
+		}
89
+
90
+	}
91
+
92
+	/**
93
+	 * This static method will create a new Request object, based on a PHP
94
+	 * $_SERVER array.
95
+	 *
96
+	 * @param array $serverArray
97
+	 * @return Request
98
+	 */
99
+	static function createFromServerArray(array $serverArray) {
100
+
101
+		$headers = [];
102
+		$method = null;
103
+		$url = null;
104
+		$httpVersion = '1.1';
105
+
106
+		$protocol = 'http';
107
+		$hostName = 'localhost';
108
+
109
+		foreach ($serverArray as $key => $value) {
110
+
111
+			switch ($key) {
112
+
113
+				case 'SERVER_PROTOCOL' :
114
+					if ($value === 'HTTP/1.0') {
115
+						$httpVersion = '1.0';
116
+					}
117
+					break;
118
+				case 'REQUEST_METHOD' :
119
+					$method = $value;
120
+					break;
121
+				case 'REQUEST_URI' :
122
+					$url = $value;
123
+					break;
124
+
125
+				// These sometimes show up without a HTTP_ prefix
126
+				case 'CONTENT_TYPE' :
127
+					$headers['Content-Type'] = $value;
128
+					break;
129
+				case 'CONTENT_LENGTH' :
130
+					$headers['Content-Length'] = $value;
131
+					break;
132
+
133
+				// mod_php on apache will put credentials in these variables.
134
+				// (fast)cgi does not usually do this, however.
135
+				case 'PHP_AUTH_USER' :
136
+					if (isset($serverArray['PHP_AUTH_PW'])) {
137
+						$headers['Authorization'] = 'Basic ' . base64_encode($value . ':' . $serverArray['PHP_AUTH_PW']);
138
+					}
139
+					break;
140
+
141
+				// Similarly, mod_php may also screw around with digest auth.
142
+				case 'PHP_AUTH_DIGEST' :
143
+					$headers['Authorization'] = 'Digest ' . $value;
144
+					break;
145
+
146
+				// Apache may prefix the HTTP_AUTHORIZATION header with
147
+				// REDIRECT_, if mod_rewrite was used.
148
+				case 'REDIRECT_HTTP_AUTHORIZATION' :
149
+					$headers['Authorization'] = $value;
150
+					break;
151
+
152
+				case 'HTTP_HOST' :
153
+					$hostName = $value;
154
+					$headers['Host'] = $value;
155
+					break;
156
+
157
+				case 'HTTPS' :
158
+					if (!empty($value) && $value !== 'off') {
159
+						$protocol = 'https';
160
+					}
161
+					break;
162
+
163
+				default :
164
+					if (substr($key, 0, 5) === 'HTTP_') {
165
+						// It's a HTTP header
166
+
167
+						// Normalizing it to be prettier
168
+						$header = strtolower(substr($key, 5));
169
+
170
+						// Transforming dashes into spaces, and uppercasing
171
+						// every first letter.
172
+						$header = ucwords(str_replace('_', ' ', $header));
173
+
174
+						// Turning spaces into dashes.
175
+						$header = str_replace(' ', '-', $header);
176
+						$headers[$header] = $value;
177
+
178
+					}
179
+					break;
180
+
181
+
182
+			}
183
+
184
+		}
185
+
186
+		$r = new Request($method, $url, $headers);
187
+		$r->setHttpVersion($httpVersion);
188
+		$r->setRawServerData($serverArray);
189
+		$r->setAbsoluteUrl($protocol . '://' . $hostName . $url);
190
+		return $r;
191
+
192
+	}
193 193
 
194 194
 }
Please login to merge, or discard this patch.
Braces   +3 added lines, -1 removed lines patch added patch discarded remove patch
@@ -69,7 +69,9 @@
 block discarded – undo
69 69
         }
70 70
 
71 71
         $body = $response->getBody();
72
-        if (is_null($body)) return;
72
+        if (is_null($body)) {
73
+        	return;
74
+        }
73 75
 
74 76
         $contentLength = $response->getHeader('Content-Length');
75 77
         if ($contentLength !== null) {
Please login to merge, or discard this patch.
libraries/SabreDAV/composer/autoload_real.php 1 patch
Indentation   +33 added lines, -33 removed lines patch added patch discarded remove patch
@@ -4,56 +4,56 @@
 block discarded – undo
4 4
 
5 5
 class ComposerAutoloaderInit15cab9ce9e53521bb4f9807a32118ed6
6 6
 {
7
-    private static $loader;
8
-
9
-    public static function loadClassLoader($class)
10
-    {
11
-        if ('Composer\Autoload\ClassLoader' === $class) {
12
-            require __DIR__ . '/ClassLoader.php';
13
-        }
14
-    }
15
-
16
-    public static function getLoader()
17
-    {
18
-        if (null !== self::$loader) {
19
-            return self::$loader;
20
-        }
21
-
22
-        spl_autoload_register(array('ComposerAutoloaderInit15cab9ce9e53521bb4f9807a32118ed6', 'loadClassLoader'), true, true);
23
-        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
-        spl_autoload_unregister(array('ComposerAutoloaderInit15cab9ce9e53521bb4f9807a32118ed6', 'loadClassLoader'));
7
+	private static $loader;
8
+
9
+	public static function loadClassLoader($class)
10
+	{
11
+		if ('Composer\Autoload\ClassLoader' === $class) {
12
+			require __DIR__ . '/ClassLoader.php';
13
+		}
14
+	}
15
+
16
+	public static function getLoader()
17
+	{
18
+		if (null !== self::$loader) {
19
+			return self::$loader;
20
+		}
21
+
22
+		spl_autoload_register(array('ComposerAutoloaderInit15cab9ce9e53521bb4f9807a32118ed6', 'loadClassLoader'), true, true);
23
+		self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+		spl_autoload_unregister(array('ComposerAutoloaderInit15cab9ce9e53521bb4f9807a32118ed6', 'loadClassLoader'));
25 25
 /*
26 26
         $map = require __DIR__ . '/autoload_namespaces.php';
27 27
         foreach ($map as $namespace => $path) {
28 28
             $loader->set($namespace, $path);
29 29
         }
30 30
 */
31
-        $map = require __DIR__ . '/autoload_psr4.php';
32
-        foreach ($map as $namespace => $path) {
33
-            $loader->setPsr4($namespace, $path);
34
-        }
31
+		$map = require __DIR__ . '/autoload_psr4.php';
32
+		foreach ($map as $namespace => $path) {
33
+			$loader->setPsr4($namespace, $path);
34
+		}
35 35
 /*
36 36
         $classMap = require __DIR__ . '/autoload_classmap.php';
37 37
         if ($classMap) {
38 38
             $loader->addClassMap($classMap);
39 39
         }
40 40
 */
41
-        $loader->register(true);
41
+		$loader->register(true);
42 42
 
43
-        $includeFiles = require __DIR__ . '/autoload_files.php';
44
-        foreach ($includeFiles as $fileIdentifier => $file) {
45
-            composerRequire15cab9ce9e53521bb4f9807a32118ed6($fileIdentifier, $file);
46
-        }
43
+		$includeFiles = require __DIR__ . '/autoload_files.php';
44
+		foreach ($includeFiles as $fileIdentifier => $file) {
45
+			composerRequire15cab9ce9e53521bb4f9807a32118ed6($fileIdentifier, $file);
46
+		}
47 47
 
48
-        return $loader;
49
-    }
48
+		return $loader;
49
+	}
50 50
 }
51 51
 
52 52
 function composerRequire15cab9ce9e53521bb4f9807a32118ed6($fileIdentifier, $file)
53 53
 {
54
-    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
55
-        require $file;
54
+	if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
55
+		require $file;
56 56
 
57
-        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
58
-    }
57
+		$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
58
+	}
59 59
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/Yeti/CalDAV_Schedule.php 1 patch
Braces   +6 added lines, -4 removed lines patch added patch discarded remove patch
@@ -42,11 +42,13 @@
 block discarded – undo
42 42
 
43 43
 		$summary = $iTipMessage->message->VEVENT->SUMMARY;
44 44
 
45
-		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto')
46
-			return;
45
+		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') {
46
+					return;
47
+		}
47 48
 
48
-		if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto')
49
-			return;
49
+		if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
50
+					return;
51
+		}
50 52
 
51 53
 		$sender = substr($iTipMessage->sender, 7);
52 54
 		$recipient = substr($iTipMessage->recipient, 7);
Please login to merge, or discard this patch.
libraries/SabreDAV/CardDAV/Backend/PDO.php 3 patches
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -461,7 +461,7 @@  discard block
 block discarded – undo
461 461
 
462 462
         // Current synctoken
463 463
         $stmt = $this->pdo->prepare(sprintf('SELECT synctoken FROM %s WHERE id = ?', $this->addressBooksTableName));
464
-        $stmt->execute([ $addressBookId ]);
464
+        $stmt->execute([$addressBookId]);
465 465
         $currentToken = $stmt->fetchColumn(0);
466 466
 
467 467
         if (is_null($currentToken)) return null;
@@ -476,7 +476,7 @@  discard block
 block discarded – undo
476 476
         if ($syncToken) {
477 477
 
478 478
             $query = sprintf("SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && addressbookid = ? ORDER BY synctoken", $this->addressBookChangesTableName);
479
-            if ($limit > 0) $query .= " LIMIT " . (int)$limit;
479
+            if ($limit > 0) $query .= " LIMIT " . (int) $limit;
480 480
 
481 481
             // Fetching all changes
482 482
             $stmt = $this->pdo->prepare($query);
Please login to merge, or discard this patch.
Braces   +9 added lines, -3 removed lines patch added patch discarded remove patch
@@ -255,7 +255,9 @@  discard block
 block discarded – undo
255 255
 
256 256
         $result = $stmt->fetch(\PDO::FETCH_ASSOC);
257 257
 
258
-        if (!$result) return false;
258
+        if (!$result) {
259
+        	return false;
260
+        }
259 261
 
260 262
         $result['etag'] = '"' . $result['etag'] . '"';
261 263
         return $result;
@@ -464,7 +466,9 @@  discard block
 block discarded – undo
464 466
         $stmt->execute([ $addressBookId ]);
465 467
         $currentToken = $stmt->fetchColumn(0);
466 468
 
467
-        if (is_null($currentToken)) return null;
469
+        if (is_null($currentToken)) {
470
+        	return null;
471
+        }
468 472
 
469 473
         $result = [
470 474
             'syncToken' => $currentToken,
@@ -476,7 +480,9 @@  discard block
 block discarded – undo
476 480
         if ($syncToken) {
477 481
 
478 482
             $query = sprintf("SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && addressbookid = ? ORDER BY synctoken", $this->addressBookChangesTableName);
479
-            if ($limit > 0) $query .= " LIMIT " . (int)$limit;
483
+            if ($limit > 0) {
484
+            	$query .= " LIMIT " . (int)$limit;
485
+            }
480 486
 
481 487
             // Fetching all changes
482 488
             $stmt = $this->pdo->prepare($query);
Please login to merge, or discard this patch.
Indentation   +526 added lines, -526 removed lines patch added patch discarded remove patch
@@ -16,530 +16,530 @@
 block discarded – undo
16 16
  */
17 17
 class PDO extends AbstractBackend implements SyncSupport {
18 18
 
19
-    /**
20
-     * PDO connection
21
-     *
22
-     * @var PDO
23
-     */
24
-    protected $pdo;
25
-
26
-    /**
27
-     * The PDO table name used to store addressbooks
28
-     */
29
-    public $addressBooksTableName = 'addressbooks';
30
-
31
-    /**
32
-     * The PDO table name used to store cards
33
-     */
34
-    public $cardsTableName = 'cards';
35
-
36
-    /**
37
-     * The table name that will be used for tracking changes in address books.
38
-     *
39
-     * @var string
40
-     */
41
-    public $addressBookChangesTableName = 'addressbookchanges';
42
-
43
-    /**
44
-     * Sets up the object
45
-     *
46
-     * @param \PDO $pdo
47
-     */
48
-    public function __construct(\PDO $pdo) {
49
-
50
-        $this->pdo = $pdo;
51
-
52
-    }
53
-
54
-    /**
55
-     * Returns the list of addressbooks for a specific user.
56
-     *
57
-     * @param string $principalUri
58
-     * @return array
59
-     */
60
-    public function getAddressBooksForUser($principalUri) {
61
-
62
-        $stmt = $this->pdo->prepare(sprintf('SELECT id, uri, displayname, principaluri, description, synctoken FROM %s WHERE principaluri = ?', $this->addressBooksTableName));
63
-        $stmt->execute([$principalUri]);
64
-
65
-        $addressBooks = [];
66
-
67
-        foreach ($stmt->fetchAll() as $row) {
68
-
69
-            $addressBooks[] = [
70
-                'id'                                                          => $row['id'],
71
-                'uri'                                                         => $row['uri'],
72
-                'principaluri'                                                => $row['principaluri'],
73
-                '{DAV:}displayname'                                           => $row['displayname'],
74
-                '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
75
-                '{http://calendarserver.org/ns/}getctag'                      => $row['synctoken'],
76
-                '{http://sabredav.org/ns}sync-token'                          => $row['synctoken'] ? $row['synctoken'] : '0',
77
-            ];
78
-
79
-        }
80
-
81
-        return $addressBooks;
82
-
83
-    }
84
-
85
-
86
-    /**
87
-     * Updates properties for an address book.
88
-     *
89
-     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
90
-     * To do the actual updates, you must tell this object which properties
91
-     * you're going to process with the handle() method.
92
-     *
93
-     * Calling the handle method is like telling the PropPatch object "I
94
-     * promise I can handle updating this property".
95
-     *
96
-     * Read the PropPatch documenation for more info and examples.
97
-     *
98
-     * @param string $addressBookId
99
-     * @param \Sabre\DAV\PropPatch $propPatch
100
-     * @return void
101
-     */
102
-    public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
103
-
104
-        $supportedProperties = [
105
-            '{DAV:}displayname',
106
-            '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description',
107
-        ];
108
-
109
-        $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
110
-
111
-            $updates = [];
112
-            foreach ($mutations as $property => $newValue) {
113
-
114
-                switch ($property) {
115
-                    case '{DAV:}displayname' :
116
-                        $updates['displayname'] = $newValue;
117
-                        break;
118
-                    case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
119
-                        $updates['description'] = $newValue;
120
-                        break;
121
-                }
122
-            }
123
-            $query = sprintf('UPDATE %s SET ', $this->addressBooksTableName);
124
-            $first = true;
125
-            foreach ($updates as $key => $value) {
126
-                if ($first) {
127
-                    $first = false;
128
-                } else {
129
-                    $query .= ', ';
130
-                }
131
-                $query .= ' `' . $key . '` = :' . $key . ' ';
132
-            }
133
-            $query .= ' WHERE id = :addressbookid';
134
-
135
-            $stmt = $this->pdo->prepare($query);
136
-            $updates['addressbookid'] = $addressBookId;
137
-
138
-            $stmt->execute($updates);
139
-
140
-            $this->addChange($addressBookId, "", 2);
141
-
142
-            return true;
143
-
144
-        });
145
-
146
-    }
147
-
148
-    /**
149
-     * Creates a new address book
150
-     *
151
-     * @param string $principalUri
152
-     * @param string $url Just the 'basename' of the url.
153
-     * @param array $properties
154
-     * @return int Last insert id
155
-     */
156
-    public function createAddressBook($principalUri, $url, array $properties) {
157
-
158
-        $values = [
159
-            'displayname'  => null,
160
-            'description'  => null,
161
-            'principaluri' => $principalUri,
162
-            'uri'          => $url,
163
-        ];
164
-
165
-        foreach ($properties as $property => $newValue) {
166
-
167
-            switch ($property) {
168
-                case '{DAV:}displayname' :
169
-                    $values['displayname'] = $newValue;
170
-                    break;
171
-                case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
172
-                    $values['description'] = $newValue;
173
-                    break;
174
-                default :
175
-                    throw new DAV\Exception\BadRequest('Unknown property: ' . $property);
176
-            }
177
-
178
-        }
179
-
180
-        $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
181
-        $stmt = $this->pdo->prepare($query);
182
-        $stmt->execute($values);
183
-        return $this->pdo->lastInsertId();
184
-
185
-    }
186
-
187
-    /**
188
-     * Deletes an entire addressbook and all its contents
189
-     *
190
-     * @param int $addressBookId
191
-     * @return void
192
-     */
193
-    public function deleteAddressBook($addressBookId) {
194
-
195
-        $stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE addressbookid = ?', $this->cardsTableName));
196
-        $stmt->execute([$addressBookId]);
197
-
198
-        $stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE id = ?', $this->addressBooksTableName));
199
-        $stmt->execute([$addressBookId]);
200
-
201
-        $stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE addressbookid = ?', $this->addressBookChangesTableName));
202
-        $stmt->execute([$addressBookId]);
203
-
204
-    }
205
-
206
-    /**
207
-     * Returns all cards for a specific addressbook id.
208
-     *
209
-     * This method should return the following properties for each card:
210
-     *   * carddata - raw vcard data
211
-     *   * uri - Some unique url
212
-     *   * lastmodified - A unix timestamp
213
-     *
214
-     * It's recommended to also return the following properties:
215
-     *   * etag - A unique etag. This must change every time the card changes.
216
-     *   * size - The size of the card in bytes.
217
-     *
218
-     * If these last two properties are provided, less time will be spent
219
-     * calculating them. If they are specified, you can also ommit carddata.
220
-     * This may speed up certain requests, especially with large cards.
221
-     *
222
-     * @param mixed $addressbookId
223
-     * @return array
224
-     */
225
-    public function getCards($addressbookId) {
226
-
227
-        $stmt = $this->pdo->prepare(sprintf('SELECT id, uri, lastmodified, etag, size FROM %s WHERE addressbookid = ?', $this->cardsTableName));
228
-        $stmt->execute([$addressbookId]);
229
-
230
-        $result = [];
231
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
232
-            $row['etag'] = '"' . $row['etag'] . '"';
233
-            $result[] = $row;
234
-        }
235
-        return $result;
236
-
237
-    }
238
-
239
-    /**
240
-     * Returns a specfic card.
241
-     *
242
-     * The same set of properties must be returned as with getCards. The only
243
-     * exception is that 'carddata' is absolutely required.
244
-     *
245
-     * If the card does not exist, you must return false.
246
-     *
247
-     * @param mixed $addressBookId
248
-     * @param string $cardUri
249
-     * @return array
250
-     */
251
-    public function getCard($addressBookId, $cardUri) {
252
-
253
-        $stmt = $this->pdo->prepare(sprintf('SELECT id, carddata, uri, lastmodified, etag, size FROM %s WHERE addressbookid = ? && uri = ? LIMIT 1', $this->cardsTableName));
254
-        $stmt->execute([$addressBookId, $cardUri]);
255
-
256
-        $result = $stmt->fetch(\PDO::FETCH_ASSOC);
257
-
258
-        if (!$result) return false;
259
-
260
-        $result['etag'] = '"' . $result['etag'] . '"';
261
-        return $result;
262
-
263
-    }
264
-
265
-    /**
266
-     * Returns a list of cards.
267
-     *
268
-     * This method should work identical to getCard, but instead return all the
269
-     * cards in the list as an array.
270
-     *
271
-     * If the backend supports this, it may allow for some speed-ups.
272
-     *
273
-     * @param mixed $addressBookId
274
-     * @param array $uris
275
-     * @return array
276
-     */
277
-    public function getMultipleCards($addressBookId, array $uris) {
278
-
279
-        $query = sprintf('SELECT id, uri, lastmodified, etag, size, carddata FROM %s WHERE addressbookid = ? && uri IN (', $this->cardsTableName);
280
-        // Inserting a whole bunch of question marks
281
-        $query .= implode(',', array_fill(0, count($uris), '?'));
282
-        $query .= ')';
283
-
284
-        $stmt = $this->pdo->prepare($query);
285
-        $stmt->execute(array_merge([$addressBookId], $uris));
286
-        $result = [];
287
-        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
288
-            $row['etag'] = '"' . $row['etag'] . '"';
289
-            $result[] = $row;
290
-        }
291
-        return $result;
292
-
293
-    }
294
-
295
-    /**
296
-     * Creates a new card.
297
-     *
298
-     * The addressbook id will be passed as the first argument. This is the
299
-     * same id as it is returned from the getAddressBooksForUser method.
300
-     *
301
-     * The cardUri is a base uri, and doesn't include the full path. The
302
-     * cardData argument is the vcard body, and is passed as a string.
303
-     *
304
-     * It is possible to return an ETag from this method. This ETag is for the
305
-     * newly created resource, and must be enclosed with double quotes (that
306
-     * is, the string itself must contain the double quotes).
307
-     *
308
-     * You should only return the ETag if you store the carddata as-is. If a
309
-     * subsequent GET request on the same card does not have the same body,
310
-     * byte-by-byte and you did return an ETag here, clients tend to get
311
-     * confused.
312
-     *
313
-     * If you don't return an ETag, you can just return null.
314
-     *
315
-     * @param mixed $addressBookId
316
-     * @param string $cardUri
317
-     * @param string $cardData
318
-     * @return string|null
319
-     */
320
-    public function createCard($addressBookId, $cardUri, $cardData) {
321
-
322
-        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)');
323
-
324
-        $etag = md5($cardData);
325
-
326
-        $stmt->execute([
327
-            $cardData,
328
-            $cardUri,
329
-            time(),
330
-            $addressBookId,
331
-            strlen($cardData),
332
-            $etag,
333
-        ]);
334
-
335
-        $this->addChange($addressBookId, $cardUri, 1);
336
-
337
-        return '"' . $etag . '"';
338
-
339
-    }
340
-
341
-    /**
342
-     * Updates a card.
343
-     *
344
-     * The addressbook id will be passed as the first argument. This is the
345
-     * same id as it is returned from the getAddressBooksForUser method.
346
-     *
347
-     * The cardUri is a base uri, and doesn't include the full path. The
348
-     * cardData argument is the vcard body, and is passed as a string.
349
-     *
350
-     * It is possible to return an ETag from this method. This ETag should
351
-     * match that of the updated resource, and must be enclosed with double
352
-     * quotes (that is: the string itself must contain the actual quotes).
353
-     *
354
-     * You should only return the ETag if you store the carddata as-is. If a
355
-     * subsequent GET request on the same card does not have the same body,
356
-     * byte-by-byte and you did return an ETag here, clients tend to get
357
-     * confused.
358
-     *
359
-     * If you don't return an ETag, you can just return null.
360
-     *
361
-     * @param mixed $addressBookId
362
-     * @param string $cardUri
363
-     * @param string $cardData
364
-     * @return string|null
365
-     */
366
-    public function updateCard($addressBookId, $cardUri, $cardData) {
367
-
368
-        $stmt = $this->pdo->prepare(sprintf('UPDATE %s SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? && addressbookid =?', $this->cardsTableName));
369
-
370
-        $etag = md5($cardData);
371
-        $stmt->execute([
372
-            $cardData,
373
-            time(),
374
-            strlen($cardData),
375
-            $etag,
376
-            $cardUri,
377
-            $addressBookId
378
-        ]);
379
-
380
-        $this->addChange($addressBookId, $cardUri, 2);
381
-
382
-        return '"' . $etag . '"';
383
-
384
-    }
385
-
386
-    /**
387
-     * Deletes a card
388
-     *
389
-     * @param mixed $addressBookId
390
-     * @param string $cardUri
391
-     * @return bool
392
-     */
393
-    public function deleteCard($addressBookId, $cardUri) {
394
-
395
-        $stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE addressbookid = ? && uri = ?', $this->cardsTableName));
396
-        $stmt->execute([$addressBookId, $cardUri]);
397
-
398
-        $this->addChange($addressBookId, $cardUri, 3);
399
-
400
-        return $stmt->rowCount() === 1;
401
-
402
-    }
403
-
404
-    /**
405
-     * The getChanges method returns all the changes that have happened, since
406
-     * the specified syncToken in the specified address book.
407
-     *
408
-     * This function should return an array, such as the following:
409
-     *
410
-     * [
411
-     *   'syncToken' => 'The current synctoken',
412
-     *   'added'   => [
413
-     *      'new.txt',
414
-     *   ],
415
-     *   'modified'   => [
416
-     *      'updated.txt',
417
-     *   ],
418
-     *   'deleted' => [
419
-     *      'foo.php.bak',
420
-     *      'old.txt'
421
-     *   ]
422
-     * ];
423
-     *
424
-     * The returned syncToken property should reflect the *current* syncToken
425
-     * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token
426
-     * property. This is needed here too, to ensure the operation is atomic.
427
-     *
428
-     * If the $syncToken argument is specified as null, this is an initial
429
-     * sync, and all members should be reported.
430
-     *
431
-     * The modified property is an array of nodenames that have changed since
432
-     * the last token.
433
-     *
434
-     * The deleted property is an array with nodenames, that have been deleted
435
-     * from collection.
436
-     *
437
-     * The $syncLevel argument is basically the 'depth' of the report. If it's
438
-     * 1, you only have to report changes that happened only directly in
439
-     * immediate descendants. If it's 2, it should also include changes from
440
-     * the nodes below the child collections. (grandchildren)
441
-     *
442
-     * The $limit argument allows a client to specify how many results should
443
-     * be returned at most. If the limit is not specified, it should be treated
444
-     * as infinite.
445
-     *
446
-     * If the limit (infinite or not) is higher than you're willing to return,
447
-     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
448
-     *
449
-     * If the syncToken is expired (due to data cleanup) or unknown, you must
450
-     * return null.
451
-     *
452
-     * The limit is 'suggestive'. You are free to ignore it.
453
-     *
454
-     * @param string $addressBookId
455
-     * @param string $syncToken
456
-     * @param int $syncLevel
457
-     * @param int $limit
458
-     * @return array
459
-     */
460
-    public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
461
-
462
-        // Current synctoken
463
-        $stmt = $this->pdo->prepare(sprintf('SELECT synctoken FROM %s WHERE id = ?', $this->addressBooksTableName));
464
-        $stmt->execute([ $addressBookId ]);
465
-        $currentToken = $stmt->fetchColumn(0);
466
-
467
-        if (is_null($currentToken)) return null;
468
-
469
-        $result = [
470
-            'syncToken' => $currentToken,
471
-            'added'     => [],
472
-            'modified'  => [],
473
-            'deleted'   => [],
474
-        ];
475
-
476
-        if ($syncToken) {
477
-
478
-            $query = sprintf("SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && addressbookid = ? ORDER BY synctoken", $this->addressBookChangesTableName);
479
-            if ($limit > 0) $query .= " LIMIT " . (int)$limit;
480
-
481
-            // Fetching all changes
482
-            $stmt = $this->pdo->prepare($query);
483
-            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
484
-
485
-            $changes = [];
486
-
487
-            // This loop ensures that any duplicates are overwritten, only the
488
-            // last change on a node is relevant.
489
-            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
490
-
491
-                $changes[$row['uri']] = $row['operation'];
492
-
493
-            }
494
-
495
-            foreach ($changes as $uri => $operation) {
496
-
497
-                switch ($operation) {
498
-                    case 1:
499
-                        $result['added'][] = $uri;
500
-                        break;
501
-                    case 2:
502
-                        $result['modified'][] = $uri;
503
-                        break;
504
-                    case 3:
505
-                        $result['deleted'][] = $uri;
506
-                        break;
507
-                }
508
-
509
-            }
510
-        } else {
511
-            // No synctoken supplied, this is the initial sync.
512
-            $query = sprintf("SELECT uri FROM %s WHERE addressbookid = ?", $this->cardsTableName);
513
-            $stmt = $this->pdo->prepare($query);
514
-            $stmt->execute([$addressBookId]);
515
-
516
-            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
517
-        }
518
-        return $result;
519
-
520
-    }
521
-
522
-    /**
523
-     * Adds a change record to the addressbookchanges table.
524
-     *
525
-     * @param mixed $addressBookId
526
-     * @param string $objectUri
527
-     * @param int $operation 1 = add, 2 = modify, 3 = delete
528
-     * @return void
529
-     */
530
-    protected function addChange($addressBookId, $objectUri, $operation) {
531
-
532
-        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->addressBookChangesTableName . ' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
533
-        $stmt->execute([
534
-            $objectUri,
535
-            $addressBookId,
536
-            $operation,
537
-            $addressBookId
538
-        ]);
539
-        $stmt = $this->pdo->prepare(sprintf('UPDATE %s SET synctoken = synctoken + 1 WHERE id = ?', $this->addressBooksTableName));
540
-        $stmt->execute([
541
-            $addressBookId
542
-        ]);
543
-
544
-    }
19
+	/**
20
+	 * PDO connection
21
+	 *
22
+	 * @var PDO
23
+	 */
24
+	protected $pdo;
25
+
26
+	/**
27
+	 * The PDO table name used to store addressbooks
28
+	 */
29
+	public $addressBooksTableName = 'addressbooks';
30
+
31
+	/**
32
+	 * The PDO table name used to store cards
33
+	 */
34
+	public $cardsTableName = 'cards';
35
+
36
+	/**
37
+	 * The table name that will be used for tracking changes in address books.
38
+	 *
39
+	 * @var string
40
+	 */
41
+	public $addressBookChangesTableName = 'addressbookchanges';
42
+
43
+	/**
44
+	 * Sets up the object
45
+	 *
46
+	 * @param \PDO $pdo
47
+	 */
48
+	public function __construct(\PDO $pdo) {
49
+
50
+		$this->pdo = $pdo;
51
+
52
+	}
53
+
54
+	/**
55
+	 * Returns the list of addressbooks for a specific user.
56
+	 *
57
+	 * @param string $principalUri
58
+	 * @return array
59
+	 */
60
+	public function getAddressBooksForUser($principalUri) {
61
+
62
+		$stmt = $this->pdo->prepare(sprintf('SELECT id, uri, displayname, principaluri, description, synctoken FROM %s WHERE principaluri = ?', $this->addressBooksTableName));
63
+		$stmt->execute([$principalUri]);
64
+
65
+		$addressBooks = [];
66
+
67
+		foreach ($stmt->fetchAll() as $row) {
68
+
69
+			$addressBooks[] = [
70
+				'id'                                                          => $row['id'],
71
+				'uri'                                                         => $row['uri'],
72
+				'principaluri'                                                => $row['principaluri'],
73
+				'{DAV:}displayname'                                           => $row['displayname'],
74
+				'{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
75
+				'{http://calendarserver.org/ns/}getctag'                      => $row['synctoken'],
76
+				'{http://sabredav.org/ns}sync-token'                          => $row['synctoken'] ? $row['synctoken'] : '0',
77
+			];
78
+
79
+		}
80
+
81
+		return $addressBooks;
82
+
83
+	}
84
+
85
+
86
+	/**
87
+	 * Updates properties for an address book.
88
+	 *
89
+	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
90
+	 * To do the actual updates, you must tell this object which properties
91
+	 * you're going to process with the handle() method.
92
+	 *
93
+	 * Calling the handle method is like telling the PropPatch object "I
94
+	 * promise I can handle updating this property".
95
+	 *
96
+	 * Read the PropPatch documenation for more info and examples.
97
+	 *
98
+	 * @param string $addressBookId
99
+	 * @param \Sabre\DAV\PropPatch $propPatch
100
+	 * @return void
101
+	 */
102
+	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
103
+
104
+		$supportedProperties = [
105
+			'{DAV:}displayname',
106
+			'{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description',
107
+		];
108
+
109
+		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
110
+
111
+			$updates = [];
112
+			foreach ($mutations as $property => $newValue) {
113
+
114
+				switch ($property) {
115
+					case '{DAV:}displayname' :
116
+						$updates['displayname'] = $newValue;
117
+						break;
118
+					case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
119
+						$updates['description'] = $newValue;
120
+						break;
121
+				}
122
+			}
123
+			$query = sprintf('UPDATE %s SET ', $this->addressBooksTableName);
124
+			$first = true;
125
+			foreach ($updates as $key => $value) {
126
+				if ($first) {
127
+					$first = false;
128
+				} else {
129
+					$query .= ', ';
130
+				}
131
+				$query .= ' `' . $key . '` = :' . $key . ' ';
132
+			}
133
+			$query .= ' WHERE id = :addressbookid';
134
+
135
+			$stmt = $this->pdo->prepare($query);
136
+			$updates['addressbookid'] = $addressBookId;
137
+
138
+			$stmt->execute($updates);
139
+
140
+			$this->addChange($addressBookId, "", 2);
141
+
142
+			return true;
143
+
144
+		});
145
+
146
+	}
147
+
148
+	/**
149
+	 * Creates a new address book
150
+	 *
151
+	 * @param string $principalUri
152
+	 * @param string $url Just the 'basename' of the url.
153
+	 * @param array $properties
154
+	 * @return int Last insert id
155
+	 */
156
+	public function createAddressBook($principalUri, $url, array $properties) {
157
+
158
+		$values = [
159
+			'displayname'  => null,
160
+			'description'  => null,
161
+			'principaluri' => $principalUri,
162
+			'uri'          => $url,
163
+		];
164
+
165
+		foreach ($properties as $property => $newValue) {
166
+
167
+			switch ($property) {
168
+				case '{DAV:}displayname' :
169
+					$values['displayname'] = $newValue;
170
+					break;
171
+				case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
172
+					$values['description'] = $newValue;
173
+					break;
174
+				default :
175
+					throw new DAV\Exception\BadRequest('Unknown property: ' . $property);
176
+			}
177
+
178
+		}
179
+
180
+		$query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
181
+		$stmt = $this->pdo->prepare($query);
182
+		$stmt->execute($values);
183
+		return $this->pdo->lastInsertId();
184
+
185
+	}
186
+
187
+	/**
188
+	 * Deletes an entire addressbook and all its contents
189
+	 *
190
+	 * @param int $addressBookId
191
+	 * @return void
192
+	 */
193
+	public function deleteAddressBook($addressBookId) {
194
+
195
+		$stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE addressbookid = ?', $this->cardsTableName));
196
+		$stmt->execute([$addressBookId]);
197
+
198
+		$stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE id = ?', $this->addressBooksTableName));
199
+		$stmt->execute([$addressBookId]);
200
+
201
+		$stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE addressbookid = ?', $this->addressBookChangesTableName));
202
+		$stmt->execute([$addressBookId]);
203
+
204
+	}
205
+
206
+	/**
207
+	 * Returns all cards for a specific addressbook id.
208
+	 *
209
+	 * This method should return the following properties for each card:
210
+	 *   * carddata - raw vcard data
211
+	 *   * uri - Some unique url
212
+	 *   * lastmodified - A unix timestamp
213
+	 *
214
+	 * It's recommended to also return the following properties:
215
+	 *   * etag - A unique etag. This must change every time the card changes.
216
+	 *   * size - The size of the card in bytes.
217
+	 *
218
+	 * If these last two properties are provided, less time will be spent
219
+	 * calculating them. If they are specified, you can also ommit carddata.
220
+	 * This may speed up certain requests, especially with large cards.
221
+	 *
222
+	 * @param mixed $addressbookId
223
+	 * @return array
224
+	 */
225
+	public function getCards($addressbookId) {
226
+
227
+		$stmt = $this->pdo->prepare(sprintf('SELECT id, uri, lastmodified, etag, size FROM %s WHERE addressbookid = ?', $this->cardsTableName));
228
+		$stmt->execute([$addressbookId]);
229
+
230
+		$result = [];
231
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
232
+			$row['etag'] = '"' . $row['etag'] . '"';
233
+			$result[] = $row;
234
+		}
235
+		return $result;
236
+
237
+	}
238
+
239
+	/**
240
+	 * Returns a specfic card.
241
+	 *
242
+	 * The same set of properties must be returned as with getCards. The only
243
+	 * exception is that 'carddata' is absolutely required.
244
+	 *
245
+	 * If the card does not exist, you must return false.
246
+	 *
247
+	 * @param mixed $addressBookId
248
+	 * @param string $cardUri
249
+	 * @return array
250
+	 */
251
+	public function getCard($addressBookId, $cardUri) {
252
+
253
+		$stmt = $this->pdo->prepare(sprintf('SELECT id, carddata, uri, lastmodified, etag, size FROM %s WHERE addressbookid = ? && uri = ? LIMIT 1', $this->cardsTableName));
254
+		$stmt->execute([$addressBookId, $cardUri]);
255
+
256
+		$result = $stmt->fetch(\PDO::FETCH_ASSOC);
257
+
258
+		if (!$result) return false;
259
+
260
+		$result['etag'] = '"' . $result['etag'] . '"';
261
+		return $result;
262
+
263
+	}
264
+
265
+	/**
266
+	 * Returns a list of cards.
267
+	 *
268
+	 * This method should work identical to getCard, but instead return all the
269
+	 * cards in the list as an array.
270
+	 *
271
+	 * If the backend supports this, it may allow for some speed-ups.
272
+	 *
273
+	 * @param mixed $addressBookId
274
+	 * @param array $uris
275
+	 * @return array
276
+	 */
277
+	public function getMultipleCards($addressBookId, array $uris) {
278
+
279
+		$query = sprintf('SELECT id, uri, lastmodified, etag, size, carddata FROM %s WHERE addressbookid = ? && uri IN (', $this->cardsTableName);
280
+		// Inserting a whole bunch of question marks
281
+		$query .= implode(',', array_fill(0, count($uris), '?'));
282
+		$query .= ')';
283
+
284
+		$stmt = $this->pdo->prepare($query);
285
+		$stmt->execute(array_merge([$addressBookId], $uris));
286
+		$result = [];
287
+		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
288
+			$row['etag'] = '"' . $row['etag'] . '"';
289
+			$result[] = $row;
290
+		}
291
+		return $result;
292
+
293
+	}
294
+
295
+	/**
296
+	 * Creates a new card.
297
+	 *
298
+	 * The addressbook id will be passed as the first argument. This is the
299
+	 * same id as it is returned from the getAddressBooksForUser method.
300
+	 *
301
+	 * The cardUri is a base uri, and doesn't include the full path. The
302
+	 * cardData argument is the vcard body, and is passed as a string.
303
+	 *
304
+	 * It is possible to return an ETag from this method. This ETag is for the
305
+	 * newly created resource, and must be enclosed with double quotes (that
306
+	 * is, the string itself must contain the double quotes).
307
+	 *
308
+	 * You should only return the ETag if you store the carddata as-is. If a
309
+	 * subsequent GET request on the same card does not have the same body,
310
+	 * byte-by-byte and you did return an ETag here, clients tend to get
311
+	 * confused.
312
+	 *
313
+	 * If you don't return an ETag, you can just return null.
314
+	 *
315
+	 * @param mixed $addressBookId
316
+	 * @param string $cardUri
317
+	 * @param string $cardData
318
+	 * @return string|null
319
+	 */
320
+	public function createCard($addressBookId, $cardUri, $cardData) {
321
+
322
+		$stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)');
323
+
324
+		$etag = md5($cardData);
325
+
326
+		$stmt->execute([
327
+			$cardData,
328
+			$cardUri,
329
+			time(),
330
+			$addressBookId,
331
+			strlen($cardData),
332
+			$etag,
333
+		]);
334
+
335
+		$this->addChange($addressBookId, $cardUri, 1);
336
+
337
+		return '"' . $etag . '"';
338
+
339
+	}
340
+
341
+	/**
342
+	 * Updates a card.
343
+	 *
344
+	 * The addressbook id will be passed as the first argument. This is the
345
+	 * same id as it is returned from the getAddressBooksForUser method.
346
+	 *
347
+	 * The cardUri is a base uri, and doesn't include the full path. The
348
+	 * cardData argument is the vcard body, and is passed as a string.
349
+	 *
350
+	 * It is possible to return an ETag from this method. This ETag should
351
+	 * match that of the updated resource, and must be enclosed with double
352
+	 * quotes (that is: the string itself must contain the actual quotes).
353
+	 *
354
+	 * You should only return the ETag if you store the carddata as-is. If a
355
+	 * subsequent GET request on the same card does not have the same body,
356
+	 * byte-by-byte and you did return an ETag here, clients tend to get
357
+	 * confused.
358
+	 *
359
+	 * If you don't return an ETag, you can just return null.
360
+	 *
361
+	 * @param mixed $addressBookId
362
+	 * @param string $cardUri
363
+	 * @param string $cardData
364
+	 * @return string|null
365
+	 */
366
+	public function updateCard($addressBookId, $cardUri, $cardData) {
367
+
368
+		$stmt = $this->pdo->prepare(sprintf('UPDATE %s SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? && addressbookid =?', $this->cardsTableName));
369
+
370
+		$etag = md5($cardData);
371
+		$stmt->execute([
372
+			$cardData,
373
+			time(),
374
+			strlen($cardData),
375
+			$etag,
376
+			$cardUri,
377
+			$addressBookId
378
+		]);
379
+
380
+		$this->addChange($addressBookId, $cardUri, 2);
381
+
382
+		return '"' . $etag . '"';
383
+
384
+	}
385
+
386
+	/**
387
+	 * Deletes a card
388
+	 *
389
+	 * @param mixed $addressBookId
390
+	 * @param string $cardUri
391
+	 * @return bool
392
+	 */
393
+	public function deleteCard($addressBookId, $cardUri) {
394
+
395
+		$stmt = $this->pdo->prepare(sprintf('DELETE FROM %s WHERE addressbookid = ? && uri = ?', $this->cardsTableName));
396
+		$stmt->execute([$addressBookId, $cardUri]);
397
+
398
+		$this->addChange($addressBookId, $cardUri, 3);
399
+
400
+		return $stmt->rowCount() === 1;
401
+
402
+	}
403
+
404
+	/**
405
+	 * The getChanges method returns all the changes that have happened, since
406
+	 * the specified syncToken in the specified address book.
407
+	 *
408
+	 * This function should return an array, such as the following:
409
+	 *
410
+	 * [
411
+	 *   'syncToken' => 'The current synctoken',
412
+	 *   'added'   => [
413
+	 *      'new.txt',
414
+	 *   ],
415
+	 *   'modified'   => [
416
+	 *      'updated.txt',
417
+	 *   ],
418
+	 *   'deleted' => [
419
+	 *      'foo.php.bak',
420
+	 *      'old.txt'
421
+	 *   ]
422
+	 * ];
423
+	 *
424
+	 * The returned syncToken property should reflect the *current* syncToken
425
+	 * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token
426
+	 * property. This is needed here too, to ensure the operation is atomic.
427
+	 *
428
+	 * If the $syncToken argument is specified as null, this is an initial
429
+	 * sync, and all members should be reported.
430
+	 *
431
+	 * The modified property is an array of nodenames that have changed since
432
+	 * the last token.
433
+	 *
434
+	 * The deleted property is an array with nodenames, that have been deleted
435
+	 * from collection.
436
+	 *
437
+	 * The $syncLevel argument is basically the 'depth' of the report. If it's
438
+	 * 1, you only have to report changes that happened only directly in
439
+	 * immediate descendants. If it's 2, it should also include changes from
440
+	 * the nodes below the child collections. (grandchildren)
441
+	 *
442
+	 * The $limit argument allows a client to specify how many results should
443
+	 * be returned at most. If the limit is not specified, it should be treated
444
+	 * as infinite.
445
+	 *
446
+	 * If the limit (infinite or not) is higher than you're willing to return,
447
+	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
448
+	 *
449
+	 * If the syncToken is expired (due to data cleanup) or unknown, you must
450
+	 * return null.
451
+	 *
452
+	 * The limit is 'suggestive'. You are free to ignore it.
453
+	 *
454
+	 * @param string $addressBookId
455
+	 * @param string $syncToken
456
+	 * @param int $syncLevel
457
+	 * @param int $limit
458
+	 * @return array
459
+	 */
460
+	public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
461
+
462
+		// Current synctoken
463
+		$stmt = $this->pdo->prepare(sprintf('SELECT synctoken FROM %s WHERE id = ?', $this->addressBooksTableName));
464
+		$stmt->execute([ $addressBookId ]);
465
+		$currentToken = $stmt->fetchColumn(0);
466
+
467
+		if (is_null($currentToken)) return null;
468
+
469
+		$result = [
470
+			'syncToken' => $currentToken,
471
+			'added'     => [],
472
+			'modified'  => [],
473
+			'deleted'   => [],
474
+		];
475
+
476
+		if ($syncToken) {
477
+
478
+			$query = sprintf("SELECT uri, operation FROM %s WHERE synctoken >= ? && synctoken < ? && addressbookid = ? ORDER BY synctoken", $this->addressBookChangesTableName);
479
+			if ($limit > 0) $query .= " LIMIT " . (int)$limit;
480
+
481
+			// Fetching all changes
482
+			$stmt = $this->pdo->prepare($query);
483
+			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
484
+
485
+			$changes = [];
486
+
487
+			// This loop ensures that any duplicates are overwritten, only the
488
+			// last change on a node is relevant.
489
+			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
490
+
491
+				$changes[$row['uri']] = $row['operation'];
492
+
493
+			}
494
+
495
+			foreach ($changes as $uri => $operation) {
496
+
497
+				switch ($operation) {
498
+					case 1:
499
+						$result['added'][] = $uri;
500
+						break;
501
+					case 2:
502
+						$result['modified'][] = $uri;
503
+						break;
504
+					case 3:
505
+						$result['deleted'][] = $uri;
506
+						break;
507
+				}
508
+
509
+			}
510
+		} else {
511
+			// No synctoken supplied, this is the initial sync.
512
+			$query = sprintf("SELECT uri FROM %s WHERE addressbookid = ?", $this->cardsTableName);
513
+			$stmt = $this->pdo->prepare($query);
514
+			$stmt->execute([$addressBookId]);
515
+
516
+			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
517
+		}
518
+		return $result;
519
+
520
+	}
521
+
522
+	/**
523
+	 * Adds a change record to the addressbookchanges table.
524
+	 *
525
+	 * @param mixed $addressBookId
526
+	 * @param string $objectUri
527
+	 * @param int $operation 1 = add, 2 = modify, 3 = delete
528
+	 * @return void
529
+	 */
530
+	protected function addChange($addressBookId, $objectUri, $operation) {
531
+
532
+		$stmt = $this->pdo->prepare('INSERT INTO ' . $this->addressBookChangesTableName . ' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
533
+		$stmt->execute([
534
+			$objectUri,
535
+			$addressBookId,
536
+			$operation,
537
+			$addressBookId
538
+		]);
539
+		$stmt = $this->pdo->prepare(sprintf('UPDATE %s SET synctoken = synctoken + 1 WHERE id = ?', $this->addressBooksTableName));
540
+		$stmt->execute([
541
+			$addressBookId
542
+		]);
543
+
544
+	}
545 545
 }
Please login to merge, or discard this patch.