iamfreee /
laracasts-downloader
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * Http functions |
||
| 4 | */ |
||
| 5 | |||
| 6 | namespace App\Http; |
||
| 7 | |||
| 8 | use App\Downloader; |
||
| 9 | use App\Exceptions\NoDownloadLinkException; |
||
| 10 | use App\Exceptions\SubscriptionNotActiveException; |
||
| 11 | use App\Html\Parser; |
||
| 12 | use App\Utils\Utils; |
||
| 13 | use GuzzleHttp\Client; |
||
| 14 | use GuzzleHttp\Cookie\CookieJar; |
||
| 15 | use GuzzleHttp\Event\ProgressEvent; |
||
| 16 | use Ubench; |
||
| 17 | |||
| 18 | /** |
||
| 19 | * Class Resolver |
||
| 20 | * @package App\Http |
||
| 21 | */ |
||
| 22 | class Resolver |
||
| 23 | { |
||
| 24 | /** |
||
| 25 | * Guzzle client |
||
| 26 | * @var Client |
||
| 27 | */ |
||
| 28 | private $client; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * Guzzle cookie |
||
| 32 | * @var CookieJar |
||
| 33 | */ |
||
| 34 | private $cookie; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * Ubench lib |
||
| 38 | * @var Ubench |
||
| 39 | */ |
||
| 40 | private $bench; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * Retry download on connection fail |
||
| 44 | * @var int |
||
| 45 | */ |
||
| 46 | private $retryDownload = false; |
||
| 47 | |||
| 48 | /** |
||
| 49 | * Receives dependencies |
||
| 50 | * |
||
| 51 | * @param Client $client |
||
| 52 | * @param Ubench $bench |
||
| 53 | * @param bool $retryDownload |
||
| 54 | */ |
||
| 55 | public function __construct(Client $client, Ubench $bench, $retryDownload = false) |
||
| 56 | { |
||
| 57 | $this->client = $client; |
||
| 58 | $this->cookie = new CookieJar(); |
||
| 59 | $this->bench = $bench; |
||
| 60 | $this->retryDownload = $retryDownload; |
||
|
0 ignored issues
–
show
|
|||
| 61 | } |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Grabs all lessons & series from the website. |
||
| 65 | */ |
||
| 66 | public function getAllLessons() |
||
| 67 | { |
||
| 68 | $array = []; |
||
| 69 | $html = $this->getAllPage(); |
||
| 70 | Parser::getAllLessons($html, $array); |
||
| 71 | |||
| 72 | while ($nextPage = Parser::hasNextPage($html)) { |
||
| 73 | $html = $this->client->get($nextPage, ['verify' => false])->getBody()->getContents(); |
||
| 74 | Parser::getAllLessons($html, $array); |
||
| 75 | } |
||
| 76 | |||
| 77 | Downloader::$currentLessonNumber = count($array['lessons']); |
||
| 78 | |||
| 79 | return $array; |
||
| 80 | } |
||
| 81 | |||
| 82 | /** |
||
| 83 | * Gets the latest lessons only. |
||
| 84 | * |
||
| 85 | * @return array |
||
| 86 | * |
||
| 87 | * @throws \Exception |
||
| 88 | */ |
||
| 89 | public function getLatestLessons() |
||
| 90 | { |
||
| 91 | $array = []; |
||
| 92 | |||
| 93 | $html = $this->getAllPage(); |
||
| 94 | Parser::getAllLessons($html, $array); |
||
| 95 | |||
| 96 | return $array; |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Gets the html from the all page. |
||
| 101 | * |
||
| 102 | * @return string |
||
| 103 | * |
||
| 104 | * @throws \Exception |
||
| 105 | */ |
||
| 106 | private function getAllPage() |
||
| 107 | { |
||
| 108 | $response = $this->client->get(LARACASTS_ALL_PATH, ['verify' => false]); |
||
| 109 | |||
| 110 | return $response->getBody()->getContents(); |
||
| 111 | } |
||
| 112 | |||
| 113 | /** |
||
| 114 | * Tries to auth. |
||
| 115 | * |
||
| 116 | * @param $email |
||
| 117 | * @param $password |
||
| 118 | * |
||
| 119 | * @return bool |
||
| 120 | * @throws SubscriptionNotActiveException |
||
| 121 | */ |
||
| 122 | public function doAuth($email, $password) |
||
| 123 | { |
||
| 124 | $response = $this->client->get(LARACASTS_LOGIN_PATH, [ |
||
| 125 | 'cookies' => $this->cookie, |
||
| 126 | 'verify' => false |
||
| 127 | ]); |
||
| 128 | |||
| 129 | $token = Parser::getToken($response->getBody()->getContents()); |
||
| 130 | |||
| 131 | $response = $this->client->post(LARACASTS_POST_LOGIN_PATH, [ |
||
| 132 | 'cookies' => $this->cookie, |
||
| 133 | 'body' => [ |
||
| 134 | 'email' => $email, |
||
| 135 | 'password' => $password, |
||
| 136 | '_token' => $token, |
||
| 137 | 'remember' => 1, |
||
| 138 | ], |
||
| 139 | 'verify' => false |
||
| 140 | ]); |
||
| 141 | |||
| 142 | $html = $response->getBody()->getContents(); |
||
| 143 | |||
| 144 | if (strpos($html, "Reactivate") !== FALSE) { |
||
| 145 | throw new SubscriptionNotActiveException(); |
||
| 146 | } |
||
| 147 | |||
| 148 | if(strpos($html, "The email must be a valid email address.") !== FALSE) { |
||
| 149 | return false; |
||
| 150 | } |
||
| 151 | |||
| 152 | // user doesnt provided an email in the .env |
||
| 153 | // laracasts redirects to login page again |
||
| 154 | if(strpos($html, 'name="password"') !== FALSE) { |
||
| 155 | return false; |
||
| 156 | } |
||
| 157 | |||
| 158 | return strpos($html, "verify your credentials.") === FALSE; |
||
| 159 | } |
||
| 160 | |||
| 161 | /** |
||
| 162 | * Download the episode of the serie. |
||
| 163 | * |
||
| 164 | * @param $serie |
||
| 165 | * @param $episode |
||
| 166 | * @return bool |
||
| 167 | */ |
||
| 168 | public function downloadSerieEpisode($serie, $episode) |
||
| 169 | { |
||
| 170 | $path = LARACASTS_SERIES_PATH . '/' . $serie . '/episodes/' . $episode; |
||
| 171 | $episodePage = $this->getPage($path); |
||
| 172 | $name = $this->getNameOfEpisode($episodePage, $path); |
||
| 173 | $number = sprintf("%02d", $episode); |
||
| 174 | $saveTo = BASE_FOLDER . '/' . SERIES_FOLDER . '/' . $serie . '/' . $number . '-' . $name . '.mp4'; |
||
| 175 | Utils::writeln(sprintf("Download started: %s . . . . Saving on " . SERIES_FOLDER . '/' . $serie . ' folder.', |
||
| 176 | $number . ' - ' . $name |
||
| 177 | )); |
||
| 178 | |||
| 179 | return $this->downloadLessonFromPath($episodePage, $saveTo); |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * Downloads the lesson. |
||
| 184 | * |
||
| 185 | * @param $lesson |
||
| 186 | * @return bool |
||
| 187 | */ |
||
| 188 | public function downloadLesson($lesson) |
||
| 189 | { |
||
| 190 | $path = LARACASTS_LESSONS_PATH . '/' . $lesson; |
||
| 191 | $number = sprintf("%04d", Downloader::$totalLocalLessons + Downloader::$currentLessonNumber--); |
||
| 192 | $saveTo = BASE_FOLDER . '/' . LESSONS_FOLDER . '/' . $number . '-' . $lesson . '.mp4'; |
||
| 193 | |||
| 194 | Utils::writeln(sprintf("Download started: %s . . . . Saving on " . LESSONS_FOLDER . ' folder.', |
||
| 195 | $lesson |
||
| 196 | )); |
||
| 197 | $html = $this->getPage($path); |
||
| 198 | return $this->downloadLessonFromPath($html, $saveTo); |
||
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * Helper function to get html of a page |
||
| 203 | * @param $path |
||
| 204 | * @return string |
||
| 205 | */ |
||
| 206 | private function getPage($path) { |
||
| 207 | return $this->client |
||
| 208 | ->get($path, ['cookies' => $this->cookie, 'verify' => false]) |
||
| 209 | ->getBody() |
||
| 210 | ->getContents(); |
||
| 211 | } |
||
| 212 | |||
| 213 | /** |
||
| 214 | * Helper to get the Location header. |
||
| 215 | * |
||
| 216 | * @param $url |
||
| 217 | * |
||
| 218 | * @return string |
||
| 219 | */ |
||
| 220 | private function getRedirectUrl($url) |
||
| 221 | { |
||
| 222 | $response = $this->client->get($url, [ |
||
| 223 | 'cookies' => $this->cookie, |
||
| 224 | 'allow_redirects' => FALSE, |
||
| 225 | 'verify' => false |
||
| 226 | ]); |
||
| 227 | |||
| 228 | return $response->getHeader('Location'); |
||
| 229 | } |
||
| 230 | |||
| 231 | /** |
||
| 232 | * Gets the name of the serie episode. |
||
| 233 | * |
||
| 234 | * @param $html |
||
| 235 | * |
||
| 236 | * @param $path |
||
| 237 | * @return string |
||
| 238 | */ |
||
| 239 | private function getNameOfEpisode($html, $path) |
||
| 240 | { |
||
| 241 | $name = Parser::getNameOfEpisode($html, $path); |
||
| 242 | |||
| 243 | return Utils::parseEpisodeName($name); |
||
| 244 | } |
||
| 245 | |||
| 246 | /** |
||
| 247 | * Helper to download the video. |
||
| 248 | * |
||
| 249 | * @param $html |
||
| 250 | * @param $saveTo |
||
| 251 | * @return bool |
||
| 252 | */ |
||
| 253 | private function downloadLessonFromPath($html, $saveTo) |
||
| 254 | { |
||
| 255 | try { |
||
| 256 | $downloadUrl = Parser::getDownloadLink($html); |
||
| 257 | $viemoUrl = $this->getRedirectUrl($downloadUrl); |
||
| 258 | $finalUrl = $this->getRedirectUrl($viemoUrl); |
||
| 259 | } catch(NoDownloadLinkException $e) { |
||
| 260 | Utils::write(sprintf("Can't download this lesson! :( No download button")); |
||
| 261 | |||
| 262 | try { |
||
| 263 | Utils::write(sprintf("Tring to find a Wistia.net video")); |
||
| 264 | $Wistia = new Wistia($html,$this->bench); |
||
| 265 | $finalUrl = $Wistia->getDownloadUrl(); |
||
| 266 | } catch(NoDownloadLinkException $e) { |
||
| 267 | return false; |
||
| 268 | } |
||
| 269 | |||
| 270 | } |
||
| 271 | |||
| 272 | $this->bench->start(); |
||
| 273 | |||
| 274 | $retries = 0; |
||
| 275 | while (true) { |
||
| 276 | try { |
||
| 277 | $downloadedBytes = file_exists($saveTo) ? filesize($saveTo) : 0; |
||
| 278 | $req = $this->client->createRequest('GET', $finalUrl, [ |
||
| 279 | 'save_to' => fopen($saveTo, 'a'), |
||
| 280 | 'verify' => false, |
||
| 281 | 'headers' => [ |
||
| 282 | 'Range' => 'bytes=' . $downloadedBytes . '-' |
||
| 283 | ] |
||
| 284 | ]); |
||
| 285 | |||
| 286 | if (php_sapi_name() == "cli") { //on cli show progress |
||
| 287 | $req->getEmitter()->on('progress', function (ProgressEvent $e) use ($downloadedBytes) { |
||
| 288 | printf("> Total: %d%% Downloaded: %s of %s \r", |
||
| 289 | Utils::getPercentage($e->downloaded + $downloadedBytes, $e->downloadSize), |
||
| 290 | Utils::formatBytes($e->downloaded + $downloadedBytes), |
||
| 291 | Utils::formatBytes($e->downloadSize)); |
||
| 292 | }); |
||
| 293 | } |
||
| 294 | |||
| 295 | $response = $this->client->send($req); |
||
| 296 | |||
| 297 | if(strpos($response->getHeader('Content-Type'), 'text/html') !== FALSE) { |
||
| 298 | Utils::writeln(sprintf("Got HTML instead of the video file, the subscription is probably inactive")); |
||
| 299 | throw new SubscriptionNotActiveException(); |
||
| 300 | } |
||
| 301 | |||
| 302 | break; |
||
| 303 | } catch (\Exception $e) { |
||
| 304 | if (is_a($e, SubscriptionNotActiveException::class) || !$this->retryDownload || ($this->retryDownload && $retries >= 3)) { |
||
| 305 | throw $e; |
||
| 306 | } |
||
| 307 | ++$retries; |
||
| 308 | Utils::writeln(sprintf("Retry download after connection fail! ")); |
||
| 309 | continue; |
||
| 310 | } |
||
| 311 | } |
||
| 312 | |||
| 313 | $this->bench->end(); |
||
| 314 | |||
| 315 | Utils::write(sprintf("Elapsed time: %s, Memory: %s ", |
||
| 316 | $this->bench->getTime(), |
||
| 317 | $this->bench->getMemoryUsage() |
||
| 318 | )); |
||
| 319 | |||
| 320 | return true; |
||
| 321 | } |
||
| 322 | } |
||
| 323 |
This check looks for assignments to scalar types that may be of the wrong type.
To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.