Ajde_Http_Curl::download()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
class Ajde_Http_Curl
4
{
5
    /**
6
     * @param string $value
7
     * @param string $key
8
     *
9
     * @return string
10
     */
11
    private static function rawURLEncodeCallback($value, $key)
12
    {
13
        return "$key=".rawurlencode($value);
14
    }
15
16
    /**
17
     * @param string $url
18
     * @param array  $postData
19
     *
20
     * @deprecated
21
     *
22
     * @throws Ajde_Core_Exception_Deprecated
23
     */
24
    public static function doPostRequest($url, $postData)
0 ignored issues
show
Unused Code introduced by
The parameter $url is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $postData is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
25
    {
26
        // TODO:
27
        throw new Ajde_Core_Exception_Deprecated();
28
    }
29
30
    /**
31
     * @param string $url
32
     * @param array  $postData
33
     * @param string $postType
34
     * @param array  $headers
35
     * @param string $method
36
     *
37
     * @throws Exception
38
     *
39
     * @return string
40
     */
41
    public static function post($url, $postData, $postType = 'form-urlencoded', $headers = [], $method = 'post')
42
    {
43
        if ($postType == 'form-urlencoded') {
44
            $encodedVariables = array_map(['Ajde_Http_Curl', 'rawURLEncodeCallback'], $postData, array_keys($postData));
45
46
            $postContent = implode('&', $encodedVariables);
47
            $postContentLen = strlen($postContent);
48
49
            $headers = array_merge([
50
                'Content-Type'   => 'application/x-www-form-urlencoded',
51
                'Content-Length' => $postContentLen,
52
            ], $headers);
53
        } else {
54
            if ($postType == 'json') {
55
                $postContent = json_encode($postData);
56
                $postContentLen = strlen($postContent);
57
58
                $headers = array_merge([
59
                    'Content-Type'   => 'application/json',
60
                    'Content-Length' => $postContentLen,
61
                ], $headers);
62
            }
63
        }
64
65
        $sendHeaders = [];
66
        foreach ($headers as $k => $v) {
67
            $sendHeaders[] = $k.': '.$v;
68
        }
69
70
        $output = false;
71
72
        try {
73
            $ch = curl_init();
74
75 View Code Duplication
            if ($method == 'post') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
76
                curl_setopt($ch, CURLOPT_POST, 1);
77
            } else {
78
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
79
            }
80
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postContent);
0 ignored issues
show
Bug introduced by
The variable $postContent does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
81
            curl_setopt($ch, CURLOPT_URL,
82
                $url);            // The URL to fetch. This can also be set when initializing a session with curl_init().
83
            curl_setopt($ch, CURLOPT_RETURNTRANSFER,
84
                true);    // TRUE to return the transfer as a string of the return value of curl_exec() instead of outputting it out directly.
85
            curl_setopt($ch, CURLOPT_HEADER, false);        // TRUE to include the header in the output.
86
87
            // Not possible in SAFE_MODE
88
            //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // TRUE to follow any "Location: " header that the server sends as part of the HTTP header (note this is recursive, PHP will follow as many "Location: " headers that it is sent, unless CURLOPT_MAXREDIRS is set).
89
90
            curl_setopt($ch, CURLOPT_MAXREDIRS,
91
                10);        // The maximum amount of HTTP redirections to follow. Use this option alongside CURLOPT_FOLLOWLOCATION.
92
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,
93
                5);    // The number of seconds to wait while trying to connect. Use 0 to wait indefinitely.
94
            curl_setopt($ch, CURLOPT_TIMEOUT,
95
                5);            // The maximum number of seconds to allow cURL functions to execute.
96
            curl_setopt($ch, CURLOPT_USERAGENT,
97
                "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36')"); // The contents of the "User-Agent: " header to be used in a HTTP request.
98
            curl_setopt($ch, CURLOPT_ENCODING,
99
                '');            // The contents of the "Accept-Encoding: " header. This enables decoding of the response. Supported encodings are "identity", "deflate", and "gzip". If an empty string, "", is set, a header containing all supported encoding types is sent.
100
            curl_setopt($ch, CURLOPT_AUTOREFERER,
101
                true);    // TRUE to automatically set the Referer: field in requests where it follows a Location: redirect.
102
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,
103
                false); // FALSE to stop cURL from verifying the peer's certificate. Alternate certificates to verify against can be specified with the CURLOPT_CAINFO option or a certificate directory can be specified with the CURLOPT_CAPATH option. CURLOPT_SSL_VERIFYHOST may also need to be TRUE or FALSE if CURLOPT_SSL_VERIFYPEER is disabled (it defaults to 2).
104
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,
105
                false); // 1 to check the existence of a common name in the SSL peer certificate. 2 to check the existence of a common name and also verify that it matches the hostname provided. In production environments the value of this option should be kept at 2 (default value).
106
            curl_setopt($ch, CURLOPT_HTTPHEADER, $sendHeaders);
107
            $output = curl_exec($ch);
108
            curl_close($ch);
109
        } catch (Exception $e) {
110
            throw $e;
111
        }
112
113
        return $output;
114
    }
115
116
    /**
117
     * @param string      $url
118
     * @param bool|string $toFile
119
     * @param bool|array  $header
120
     *
121
     * @throws Exception
122
     *
123
     * @return string
124
     */
125
    public static function get($url, $toFile = false, $header = false)
126
    {
127
        $output = false;
128
        $debug = false;
129
130
        if ($debug) {
131
            Ajde_Log::_('cURL URL', Ajde_Log::CHANNEL_INFO, Ajde_Log::LEVEL_INFORMATIONAL, $url);
132
        }
133
134
        try {
135
            $ch = curl_init();
136
137
            curl_setopt($ch, CURLOPT_URL,
138
                $url);            // The URL to fetch. This can also be set when initializing a session with curl_init().
139
            curl_setopt($ch, CURLOPT_RETURNTRANSFER,
140
                true);    // TRUE to return the transfer as a string of the return value of curl_exec() instead of outputting it out directly.
141
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,
142
                5);    // The number of seconds to wait while trying to connect. Use 0 to wait indefinitely.
143
            curl_setopt($ch, CURLOPT_TIMEOUT,
144
                5);            // The maximum number of seconds to allow cURL functions to execute.
145
            curl_setopt($ch, CURLOPT_USERAGENT,
146
                'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'); // The contents of the "User-Agent: " header to be used in a HTTP request.
147
            curl_setopt($ch, CURLOPT_ENCODING,
148
                '');            // The contents of the "Accept-Encoding: " header. This enables decoding of the response. Supported encodings are "identity", "deflate", and "gzip". If an empty string, "", is set, a header containing all supported encoding types is sent.
149
            curl_setopt($ch, CURLOPT_AUTOREFERER,
150
                true);    // TRUE to automatically set the Referer: field in requests where it follows a Location: redirect.
151
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,
152
                false); // FALSE to stop cURL from verifying the peer's certificate. Alternate certificates to verify against can be specified with the CURLOPT_CAINFO option or a certificate directory can be specified with the CURLOPT_CAPATH option. CURLOPT_SSL_VERIFYHOST may also need to be TRUE or FALSE if CURLOPT_SSL_VERIFYPEER is disabled (it defaults to 2).
153
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
154
            curl_setopt($ch, CURLOPT_COOKIEFILE, '');
155
156
            if ($toFile !== false) {
157
158
                // @TODO We need SAFE_MODE to be off
159
                if (ini_get('safe_mode')) {
160
                    throw new Ajde_Exception('SAFE_MODE must be off when downloading files');
161
                }
162
163
                $fp = fopen($toFile, 'w+'); //This is the file where we save the information
164
165
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
166
                curl_setopt($ch, CURLOPT_MAXREDIRS,
167
                    20);        // The maximum amount of HTTP redirections to follow. Use this option alongside CURLOPT_FOLLOWLOCATION.
168
                curl_setopt($ch, CURLOPT_TIMEOUT, 300);
169
                curl_setopt($ch, CURLOPT_FILE, $fp); // write curl response to file
170
                curl_setopt($ch, CURLINFO_HEADER_OUT, true);
171
172
                if ($header) {
173
                    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
174
                }
175
176
                curl_exec($ch);
177
178
                fclose($fp);
179
                $output = true;
180
181
                $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
182
183
                if ($debug) {
184
                    $verbose = curl_getinfo($ch);
185
                }
186
                if ($debug) {
187
                    Ajde_Log::_('cURL result', Ajde_Log::CHANNEL_INFO, Ajde_Log::LEVEL_INFORMATIONAL,
188
                        var_export($verbose, true));
0 ignored issues
show
Bug introduced by
The variable $verbose does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
189
                }
190
191
                curl_close($ch);
192
193
                if (substr($http_status, 0, 1 == '4')) {
194
                    return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Ajde_Http_Curl::get of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
195
                }
196
            } else {
197
                // Not possible in SAFE_MODE
198
                // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // TRUE to follow any "Location: " header that the server sends as part of the HTTP header (note this is recursive, PHP will follow as many "Location: " headers that it is sent, unless CURLOPT_MAXREDIRS is set).
199
                // curl_setopt($ch, CURLOPT_HEADER, false);		// TRUE to include the header in the output.
200
                // curl_setopt($ch, CURLOPT_MAXREDIRS, 10);		// The maximum amount of HTTP redirections to follow. Use this option alongside CURLOPT_FOLLOWLOCATION.
201
                $output = self::_curl_exec_follow($ch, 10, false);
202
203
                if ($debug) {
204
                    $verbose = curl_getinfo($ch);
205
                }
206
                if ($debug) {
207
                    Ajde_Log::_('cURL result', Ajde_Log::CHANNEL_INFO, Ajde_Log::LEVEL_INFORMATIONAL,
208
                        var_export($verbose, true));
209
                }
210
211
                curl_close($ch);
212
            }
213
        } catch (Exception $e) {
214
            throw $e;
215
        }
216
217
        return $output;
218
    }
219
220
    public static function download($url, $filename)
221
    {
222
        return self::get($url, $filename);
223
    }
224
225
    /**
226
     * @source http://stackoverflow.com/a/5498992/938297
227
     */
228
    private static function _curl_exec_follow(&$ch, $redirects = 20, $curlopt_header = false)
229
    {
230
        if ((!ini_get('open_basedir') && !ini_get('safe_mode')) || $redirects < 1) {
231
            curl_setopt($ch, CURLOPT_HEADER, $curlopt_header);
232
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $redirects > 0);
233
            curl_setopt($ch, CURLOPT_MAXREDIRS, $redirects);
234
235
            return curl_exec($ch);
236
        } else {
237
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
238
            curl_setopt($ch, CURLOPT_HEADER, true);
239
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
240
            curl_setopt($ch, CURLOPT_FORBID_REUSE, false);
241
242
            do {
243
                $data = curl_exec($ch);
244
                if (curl_errno($ch)) {
245
                    break;
246
                }
247
                $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
248
                if ($code != 301 && $code != 302) {
249
                    break;
250
                }
251
                $header_start = strpos($data, "\r\n") + 2;
252
                $headers = substr($data, $header_start,
253
                    strpos($data, "\r\n\r\n", $header_start) + 2 - $header_start);
254
255
                $headers = explode(PHP_EOL, $headers);
256
                $redirectFound = false;
257
                foreach ($headers as $header) {
258
                    if (preg_match('/(?:Location|URI): (.*)/', $header, $matches)) {
259
                        $redirectFound = $matches[1];
260
                    }
261
                }
262
                if ($redirectFound === false) {
263
                    break;
264
                }
265
266
                curl_setopt($ch, CURLOPT_URL, $redirectFound);
267
            } while (--$redirects);
268
            if (!$redirects) {
269
                trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.',
270
                    E_USER_WARNING);
271
            }
272
            if (!$curlopt_header) {
273
                $data = substr($data, strpos($data, "\r\n\r\n") + 4);
274
            }
275
276
            return $data;
277
        }
278
    }
279
}
280