-
Notifications
You must be signed in to change notification settings - Fork 22
/
SafeCurl.php
179 lines (158 loc) · 5.66 KB
/
SafeCurl.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<?php
namespace fin1te\SafeCurl;
use fin1te\SafeCurl\Exception;
use fin1te\SafeCurl\Exception\InvalidURLException;
use fin1te\SafeCurl\Exception\InvalidURLException\InvalidDomainException;
use fin1te\SafeCurl\Exception\InvalidURLException\InvalidIPException;
use fin1te\SafeCurl\Exception\InvalidURLException\InvalidPortException;
use fin1te\SafeCurl\Exception\InvalidURLException\InvalidSchemeException;
class SafeCurl {
/**
* cURL Handle
*
* @var resource
*/
private $curlHandle;
/**
* SafeCurl Options
*
* @var SafeCurl\Options
*/
private $options;
/**
* Returns new instance of SafeCurl\SafeCurl
*
* @param $curlHandle resource A valid cURL handle
* @param $options SafeCurl\Options optional
*/
public function __construct($curlHandle, Options $options = null) {
$this->setCurlHandle($curlHandle);
if ($options === null) {
$options = new Options();
}
$this->setOptions($options);
$this->init();
}
/**
* Returns cURL handle
*
* @return resource
*/
public function getCurlHandle() {
return $this->curlHandle;
}
/**
* Sets cURL handle
*
* @param $curlHandle resource
*/
public function setCurlHandle($curlHandle) {
if (!is_resource($curlHandle) || get_resource_type($curlHandle) != 'curl') {
//Need a valid cURL resource, throw exception
throw new Exception("SafeCurl expects a valid cURL resource - '" . gettype($curlHandle) . "' provided.");
}
$this->curlHandle = $curlHandle;
}
/**
* Gets Options
*
* @return SafeCurl\Options
*/
public function getOptions() {
return $this->options;
}
/**
* Sets Options
*
* @param $options SafeCurl\Options
*/
public function setOptions(Options $options) {
$this->options = $options;
}
/**
* Sets up cURL ready for executing
*/
protected function init() {
//To start with, disable FOLLOWLOCATION since we'll handle it
curl_setopt($this->curlHandle, CURLOPT_FOLLOWLOCATION, false);
//Always return the transfer
curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true);
//Force IPv4, since this class isn't yet comptible with IPv6
$curlVersion = curl_version();
if ($curlVersion['features'] & CURLOPT_IPRESOLVE) {
curl_setopt($this->curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
}
}
/**
* Exectutes a cURL request, whilst checking that the
* URL abides by our whitelists/blacklists
*
* @param $url string
* @param $curlHandle resource optional - Incase called on an object rather than statically
* @param $options SafeCurl\Options optional
*
* @return bool
*/
public static function execute($url, $curlHandle = null, Options $options = null) {
//Check if we've been called staticly or not
if (isset($this) && get_class($this) == __CLASS__) {
$safeCurl = $this;
//Get the cURL handle, if it wasn't passed in
if (!is_resource($curlHandle) || get_resource_type($curlHandle) != 'curl') {
$curlHandle = $this->getCurlHandle();
}
} else {
$safeCurl = new SafeCurl($curlHandle, $options);
}
//Backup the existing URL
$originalUrl = $url;
//Execute, catch redirects and validate the URL
$redirected = false;
$redirectCount = 0;
$redirectLimit = $safeCurl->getOptions()->getFollowLocationLimit();
$followLocation = $safeCurl->getOptions()->getFollowLocation();
do {
//Validate the URL
$url = Url::validateUrl($url, $safeCurl->getOptions());
if ($safeCurl->getOptions()->getPinDns()) {
//Send a Host header
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array('Host: ' . $url['host']));
//The "fake" URL
curl_setopt($curlHandle, CURLOPT_URL, $url['url']);
//We also have to disable SSL cert verfication, which is not great
//Might be possible to manually check the certificate ourselves?
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, false);
} else {
curl_setopt($curlHandle, CURLOPT_URL, $url['url']);
}
//Execute the cURL request
$response = curl_exec($curlHandle);
//Check for any errors
if (curl_errno($curlHandle)) {
throw new Execption("cURL Error: " . curl_error($curlHandle));
}
//Check for an HTTP redirect
if ($followLocation) {
$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
switch ($statusCode) {
case 301:
case 302:
case 303:
case 307:
case 308:
if ($redirectLimit == 0 || ++$redirectCount < $redirectLimit) {
//Redirect received, so rinse and repeat
$url = curl_getinfo($curlHandle, CURLINFO_REDIRECT_URL);
$redirected = true;
} else {
throw new Exception("Redirect limit '$redirectLimit' hit");
}
break;
default:
$redirected = false;
}
}
} while ($redirected);
return $response;
}
}