├── favicon.ico ├── static ├── img │ ├── nopic.jpg │ └── apple-touch-icon.png ├── css │ └── style.css └── js │ └── music.js ├── core ├── composer.json ├── vendor │ ├── autoload.php │ ├── composer │ │ ├── autoload_classmap.php │ │ ├── autoload_namespaces.php │ │ ├── autoload_psr4.php │ │ ├── autoload_static.php │ │ ├── LICENSE │ │ ├── autoload_real.php │ │ ├── installed.json │ │ └── ClassLoader.php │ └── php-curl-class │ │ └── php-curl-class │ │ ├── src │ │ └── Curl │ │ │ ├── StrUtil.php │ │ │ ├── Decoder.php │ │ │ ├── ArrayUtil.php │ │ │ ├── CaseInsensitiveArray.php │ │ │ ├── Url.php │ │ │ ├── MultiCurl.php │ │ │ └── Curl.php │ │ ├── composer.json │ │ ├── LICENSE │ │ ├── SECURITY.md │ │ └── README.md └── composer.lock ├── .gitignore ├── .editorconfig ├── LICENSE ├── README.md ├── CHANGELOG.md ├── index.php └── template └── index.php /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maicong/music/HEAD/favicon.ico -------------------------------------------------------------------------------- /static/img/nopic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maicong/music/HEAD/static/img/nopic.jpg -------------------------------------------------------------------------------- /core/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php-curl-class/php-curl-class": "^8.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /static/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maicong/music/HEAD/static/img/apple-touch-icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | logs/** 4 | *.lcov 5 | .idea 6 | .vscode 7 | .git/ 8 | .nyc_output 9 | npm-debug.log 10 | yarn-error.log 11 | debug.log 12 | Thumbs.db 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | 11 | [*.php] 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /core/vendor/autoload.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/php-curl-class/php-curl-class/src/Curl'), 10 | ); 11 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/src/Curl/StrUtil.php: -------------------------------------------------------------------------------- 1 | =5.3", 17 | "ext-curl": "*" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "*", 21 | "squizlabs/php_codesniffer": "*" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Curl\\": "src/Curl/" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Curl\\' => 5, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'Curl\\' => 18 | array ( 19 | 0 => __DIR__ . '/..' . '/php-curl-class/php-curl-class/src/Curl', 20 | ), 21 | ); 22 | 23 | public static function getInitializer(ClassLoader $loader) 24 | { 25 | return \Closure::bind(function () use ($loader) { 26 | $loader->prefixLengthsPsr4 = ComposerStaticInit82c91dc3983c1c2ce8fc9d4263765de4::$prefixLengthsPsr4; 27 | $loader->prefixDirsPsr4 = ComposerStaticInit82c91dc3983c1c2ce8fc9d4263765de4::$prefixDirsPsr4; 28 | 29 | }, null, ClassLoader::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Maicong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /core/vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/src/Curl/Decoder.php: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /core/vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInit82c91dc3983c1c2ce8fc9d4263765de4::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "php-curl-class/php-curl-class", 4 | "version": "8.0.0", 5 | "version_normalized": "8.0.0.0", 6 | "source": { 7 | "type": "git", 8 | "url": "https://github.com/php-curl-class/php-curl-class.git", 9 | "reference": "dc8282d086362d946520781c8908e0b084f01438" 10 | }, 11 | "dist": { 12 | "type": "zip", 13 | "url": "https://files.phpcomposer.com/files/php-curl-class/php-curl-class/dc8282d086362d946520781c8908e0b084f01438.zip", 14 | "reference": "dc8282d086362d946520781c8908e0b084f01438", 15 | "shasum": "" 16 | }, 17 | "require": { 18 | "ext-curl": "*", 19 | "php": ">=5.3" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "*", 23 | "squizlabs/php_codesniffer": "*" 24 | }, 25 | "time": "2017-11-02T06:07:25+00:00", 26 | "type": "library", 27 | "installation-source": "dist", 28 | "autoload": { 29 | "psr-4": { 30 | "Curl\\": "src/Curl/" 31 | } 32 | }, 33 | "notification-url": "https://packagist.org/downloads/", 34 | "license": [ 35 | "Unlicense" 36 | ], 37 | "authors": [ 38 | { 39 | "name": "Zach Borboa" 40 | } 41 | ], 42 | "description": "PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs.", 43 | "homepage": "https://github.com/php-curl-class/php-curl-class", 44 | "keywords": [ 45 | "api", 46 | "class", 47 | "client", 48 | "curl", 49 | "framework", 50 | "http", 51 | "http client", 52 | "json", 53 | "php", 54 | "requests", 55 | "rest", 56 | "restful", 57 | "web service", 58 | "xml" 59 | ] 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 音乐搜索器 2 | 3 | [![GitHub release](https://img.shields.io/github/release/maicong/music.svg?style=flat-square)](https://github.com/maicong/music/releases) 4 | [![PHP version](https://img.shields.io/badge/php-%3E%205.4-orange.svg)](https://github.com/php-src/php) 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](#LICENSE) 6 | 7 | ## 说明 8 | 9 | **⚠️ 本项目已暂停维护,存档代码仅供学习交流,不得用于商业用途** 10 | 11 | 多站合一音乐搜索解决方案,支持搜索试听以下网站音乐: 12 | 13 | [网易云音乐](http://music.163.com) [QQ音乐](http://y.qq.com) [酷狗音乐](http://www.kugou.com) [酷我音乐](http://www.kuwo.cn) [虾米音乐](http://www.xiami.com) [百度音乐](http://music.baidu.com) [一听音乐](http://www.1ting.com) [咪咕音乐](http://music.migu.cn) [荔枝FM](http://www.lizhi.fm) [蜻蜓FM](http://www.qingting.fm) [喜马拉雅FM](http://www.ximalaya.com) [全民K歌](http://kg.qq.com) [5sing原创](http://5sing.kugou.com/yc) [5sing翻唱](http://5sing.kugou.com/fc) 14 | 15 | 数据调用的是各网站的 API 接口,有的接口并不是开放的,随时可能失效,本项目相关代码仅供参考。 16 | 17 | ## 演示 18 | 19 | 演示站点暂停维护,2018年11月01日起不再提供演示站点服务。 20 | 21 | 如果有需要改进的地方,欢迎提交 [Pull Requests](https://github.com/maicong/music/pulls) 22 | 23 | ## 下载 24 | 25 | [📦 下载开发版](https://github.com/maicong/music/archive/master.zip) [📦 获取稳定版](https://github.com/maicong/music/releases) 26 | 27 | ## 解决方案 28 | 29 | **1. 提示数据获取失败** 30 | 31 | 方案1: 32 | 33 | ``` 34 | 修改 index.php 文件里的 MC_PROXY 为您的代理地址 35 | 将 core/music.php 里需要代理的 URL 'proxy' => false 改为 'proxy' => true 36 | ``` 37 | 38 | 方案2: 39 | 40 | ``` 41 | 在 core/music.php 里查找 setTimeout,将其后面的数值 20 改为更大。 42 | 在 static/js/music.js 里查找 `timeout`,将其数值 30000 改为更大。 43 | ``` 44 | 45 | 方案3: 46 | 47 | ``` 48 | 服务器要支持 curl。 49 | 更换服务器,选择延迟更低的服务器。 50 | ``` 51 | 52 | **2. 播放器显示 `Error happens ╥﹏╥`** 53 | 54 | 音乐链接为空 55 | 56 | ``` 57 | 1. 音乐需要付费才能收听 58 | 2. 版权限制,外站无法获取 59 | 3. 服务器 IP 所在地不在源站允许的区域 60 | 4. 音乐下架了,链接被去除 61 | ``` 62 | 63 | 音乐链接不为空 64 | 65 | ``` 66 | 1. 当前 IP 所在地因版权限制而无法播放 67 | 2. 音乐格式浏览器无法正常解析 68 | ``` 69 | 70 | **3. 国内接口优化** 71 | 72 | 如果你的网站在国内,打开 [/index.php](index.php),将 `define('MC_INTERNAL', 0);` 修改为 `define('MC_INTERNAL', 1);`,这样就可以取到咪咕和网易云音乐的 320k 音频了。 73 | 74 | ## 更新日志 75 | 76 | 请查看 [CHANGELOG.md](CHANGELOG.md) 77 | 78 | ## 免责声明 79 | 80 | 1. 本站音频文件来自各网站接口,本站不会修改任何音频文件 81 | 2. 音频版权来自各网站,本站只提供数据查询服务,不提供任何音频存储和贩卖服务 82 | 3. 本项目代码仅供学习交流,不得用于商业用途,如有侵犯与代码贡献人员无关 83 | 84 | ## 开源协议 85 | 86 | The MIT License (MIT) 87 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/src/Curl/ArrayUtil.php: -------------------------------------------------------------------------------- 1 | $value) { 54 | if (is_scalar($value)) { 55 | if ($prefix) { 56 | $return[$prefix . '[' . $key . ']'] = $value; 57 | } else { 58 | $return[$key] = $value; 59 | } 60 | } else { 61 | if ($value instanceof \CURLFile) { 62 | $return[$key] = $value; 63 | } else { 64 | $return = array_merge( 65 | $return, 66 | self::array_flatten_multidim( 67 | $value, 68 | $prefix ? $prefix . '[' . $key . ']' : $key 69 | ) 70 | ); 71 | } 72 | } 73 | } 74 | } 75 | } elseif ($array === null) { 76 | $return[$prefix] = $array; 77 | } 78 | return $return; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Considerations 2 | 3 | ### Url may point to system files 4 | 5 | * Don't blindly accept urls from users as they may point to system files. Curl supports many protocols including `FILE`. 6 | The following would show the contents of `file:///etc/passwd`. 7 | 8 | ```bash 9 | # Attacker. 10 | $ curl https://www.example.com/display_webpage.php?url=file%3A%2F%2F%2Fetc%2Fpasswd 11 | ``` 12 | 13 | ```php 14 | // display_webpage.php 15 | $url = $_GET['url']; // DANGER! 16 | $curl = new Curl(); 17 | $curl->get($url); 18 | echo $curl->response; 19 | ``` 20 | 21 | Safer: 22 | 23 | ```php 24 | function is_website_url($url, $allowed_schemes = array('http', 'https')) { 25 | $validate_url = !(filter_var($url, FILTER_VALIDATE_URL) === false); 26 | $scheme = parse_url($url, PHP_URL_SCHEME); 27 | return $validate_url && in_array($scheme, $allowed_schemes, true); 28 | } 29 | 30 | $url = $_GET['url']; 31 | if (!is_website_url($url)) { 32 | die('Unsafe url detected.'); 33 | } 34 | ``` 35 | 36 | ### Url may point to internal urls 37 | 38 | * Url may point to internal urls including those behind a firewall (e.g. http://192.168.0.1/ or ftp://192.168.0.1/). Use 39 | a whitelist to allow certain urls rather than a blacklist. 40 | 41 | ### Request data may refer to system files 42 | 43 | * Request data prefixed with the `@` character may have special interpretation and read from system files. 44 | 45 | ```bash 46 | # Attacker. 47 | $ curl https://www.example.com/upload_photo.php --data "photo=@/etc/passwd" 48 | ``` 49 | 50 | ```php 51 | // upload_photo.php 52 | $curl = new Curl(); 53 | $curl->post('http://www.anotherwebsite.com/', array( 54 | 'photo' => $_POST['photo'], // DANGER! 55 | )); 56 | ``` 57 | 58 | ### Unsafe response with redirection enabled 59 | 60 | * Requests with redirection enabled may return responses from unexpected sources. 61 | Downloading https://www.example.com/image.png may redirect and download https://www.evil.com/virus.exe 62 | 63 | ```php 64 | $curl = new Curl(); 65 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); // DANGER! 66 | $curl->download('https://www.example.com/image.png', 'my_image.png'); 67 | ``` 68 | 69 | ```php 70 | $curl = new Curl(); 71 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); // DANGER! 72 | $curl->get('https://www.example.com/image.png'); 73 | ``` 74 | 75 | ### Keep SSL protections enabled 76 | 77 | * Do not disable SSL protections. 78 | 79 | ```php 80 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // DANGER! 81 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // DANGER! 82 | ``` 83 | -------------------------------------------------------------------------------- /core/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "d03e2262635cae58cd2f5f38451aa43d", 8 | "packages": [ 9 | { 10 | "name": "php-curl-class/php-curl-class", 11 | "version": "8.0.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/php-curl-class/php-curl-class.git", 15 | "reference": "dc8282d086362d946520781c8908e0b084f01438" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://files.phpcomposer.com/files/php-curl-class/php-curl-class/dc8282d086362d946520781c8908e0b084f01438.zip", 20 | "reference": "dc8282d086362d946520781c8908e0b084f01438", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-curl": "*", 25 | "php": ">=5.3" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "*", 29 | "squizlabs/php_codesniffer": "*" 30 | }, 31 | "type": "library", 32 | "autoload": { 33 | "psr-4": { 34 | "Curl\\": "src/Curl/" 35 | } 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "Unlicense" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "Zach Borboa" 44 | } 45 | ], 46 | "description": "PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs.", 47 | "homepage": "https://github.com/php-curl-class/php-curl-class", 48 | "keywords": [ 49 | "api", 50 | "class", 51 | "client", 52 | "curl", 53 | "framework", 54 | "http", 55 | "http client", 56 | "json", 57 | "php", 58 | "requests", 59 | "rest", 60 | "restful", 61 | "web service", 62 | "xml" 63 | ], 64 | "time": "2017-11-02T06:07:25+00:00" 65 | } 66 | ], 67 | "packages-dev": [], 68 | "aliases": [], 69 | "minimum-stability": "stable", 70 | "stability-flags": [], 71 | "prefer-stable": false, 72 | "prefer-lowest": false, 73 | "platform": [], 74 | "platform-dev": [] 75 | } 76 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | 2018.09.26 `v1.6.2` 4 | 5 | - 优化酷狗搜索结果 6 | - 优化默认配置 7 | 8 | 2018.06.11 `v1.6.1` 9 | 10 | - 修复 QQ 音乐音频获取失败问题 (320k 暂时已失效) 11 | 12 | 2018.05.24 `v1.6.0` 13 | 14 | - 增加服务器国内外判断 15 | - 更新咪咕接口,支持 320k 音频 16 | - 优化网易云音乐接口 17 | 18 | 2018.05.23 `v1.5.10` 19 | 20 | - 修复 cc、top 域名使用外部资源403问题 21 | 22 | 2018.03.05 `v1.5.9` 23 | 24 | - 修复歌曲切换后相关信息显示错误问题 25 | 26 | 2018.03.02 `v1.5.8` 27 | 28 | - 修复咪咕地址解析失败问题 29 | 30 | 2018.02.23 `v1.5.7` 31 | 32 | - 增加歌词下载功能 33 | - 修复网易云音乐地址为空问题 34 | 35 | 2018.02.02 `v1.5.6` 36 | 37 | - 增加音乐链接下载功能 (如果浏览器支持的话) 38 | - 优化音乐信息显示框为不可编辑 39 | - 优化代码 40 | 41 | 2018.01.17 `v1.5.5` 42 | 43 | - 增加 URL 参数传递 44 | - 优化代码 45 | - 移除对 SoundCloud 的支持 46 | 47 | 2018.01.04 `v1.5.4` 48 | 49 | - 更新代码兼容性 50 | - 更新 meta 信息 51 | - 修复代理检测 52 | - 优化百度音乐外链地址 53 | - 修复一听封面地址403问题 54 | - 修复荔枝音频地址失效问题 55 | - 更新说明 56 | 57 | 2017.12.25 `v1.5.3` 58 | 59 | - 增加对 全民K歌 的支持 60 | - 搜索结果增加 载入更多 功能。 61 | 62 | 2017.12.09 `v1.5.2` 63 | 64 | - 更新帮助里的音乐地址 65 | - 修复网易云音乐和百度音乐解析问题 66 | 67 | 2017.12.08 `v1.5.1` 68 | 69 | - 优化代码 70 | - 更新说明 71 | 72 | 2017.12.08 `v1.5.0` 73 | 74 | - 更换播放器 75 | - 增加歌词显示 76 | - 更新接口 77 | 78 | 2017.12.05 `v1.4.5` 79 | 80 | - 修复网易云音乐音频数据不对应问题 81 | 82 | 2017.12.05 `v1.4.4` 83 | 84 | - 优化网易云音乐、虾米音乐、百度音乐接口 85 | 86 | 2017.12.05 `v1.4.3` 87 | 88 | - 优化酷狗音乐接口,支持 320k 音频 89 | 90 | 2017.12.04 `v1.4.2` 91 | 92 | - 优化代码 93 | 94 | 2017.12.04 `v1.4.1` 95 | 96 | - 优化 QQ 音乐品质 97 | 98 | 2017.11.28 `v1.4.0` 99 | 100 | - 优化接口和代码 101 | - 优化 Curl 模块加载方式 102 | 103 | 2017.09.12 `v1.3.0` 104 | 105 | - 更新 QQ 音乐 API 接口 106 | 107 | 2017.09.08 `v1.2.9` 108 | 109 | - 优化模版代码 110 | - 更新说明 111 | 112 | 2017.09.06 `v1.2.8` 113 | 114 | - 更新 5sing 接口 115 | - 优化代码 116 | 117 | 2017.09.04 `v1.2.7` 118 | 119 | - 修复低版本提示显示编码问题 120 | 121 | 2017.08.03 `v1.2.6` 122 | 123 | - 更新页脚和注释 124 | 125 | 2017.08.03 `v1.2.6` 126 | 127 | - 增加低版本提示 128 | - 优化 蜻蜓 FM 的 songid 代码 129 | 130 | 2017.08.01 `v1.2.5` 131 | 132 | - 增加对 喜马拉雅 FM 的支持 133 | - 修复 url 无法获取问题 134 | 135 | 2017.07.26 `v1.2.4` 136 | 137 | - 优化代码兼容性 138 | 139 | 2017.07.24 `v1.2.3` 140 | 141 | - 优化目录结构和模版 142 | 143 | 2017.07.20 `v1.2.2` 144 | 145 | - 优化回调代码 146 | 147 | 2017.07.20 `v1.2.1` 148 | 149 | - 更新正则匹配规则 150 | 151 | 2017.07.19 `v1.2.0` 152 | 153 | - 修复正则表达式问题 154 | 155 | 2017.07.19 `v1.1.9` 156 | 157 | - 增加对蜻蜓 FM 的支持 (resolve [#6](https://github.com/maicong/music/issues/6)) 158 | 159 | 2017.07.10 `v1.1.8` 160 | 161 | - 修复 api 请求接口问题 162 | 163 | 2017.07.05 `v1.1.7` 164 | 165 | - 增加对 荔枝 FM 的支持 166 | 167 | 2017.06.26 `v1.1.6` 168 | 169 | - 修复数组写法兼容性 170 | 171 | 2017.05.19 `v1.1.5` 172 | 173 | - 修复 网易云音乐 音乐链接失效问题 174 | 175 | 2017.04.28 `v1.1.4` 176 | 177 | - 更新 QQ 音乐 API 接口 178 | - 优化代码 179 | 180 | 2017.04.21 `v1.1.3` 181 | 182 | - 优化代码和播放器视觉 183 | 184 | 2017.04.20 `v1.1.2` 185 | 186 | - 更新音乐地址匹配规则 187 | 188 | 2017.03.24 `v1.1.1` 189 | 190 | - 移除对天天动听的支持 191 | - 修复无法获取咪咕音乐的问题 192 | - 更新 SoundCloud 的支持 client_id 193 | 194 | 2017.03.23 `v1.1.0` 195 | 196 | - 更新外链资源地址 197 | - 优化代码 198 | 199 | 2015.06.15 `v1.0.4` 200 | 201 | - 增加对 SoundCloud 的支持 202 | - 增加代理支持 203 | - 修复音乐名称识别问题 204 | - 优化代码 205 | 206 | 2015.06.13 `v1.0.3` 207 | 208 | - 增加对 天天动听、咪咕 的支持 209 | 210 | 2015.06.12 `v1.0.2` 211 | 212 | - 增加对 5sing 的支持 (开源发布) 213 | 214 | 2015.06.12 `v1.0.1` 215 | 216 | - 代码优化 217 | - 修复 BUG 218 | 219 | 2015.06.10 `v1.0.0` 220 | 221 | - 音乐搜索器上线 222 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 7 | * @link https://github.com/maicong/music 8 | * @since 1.6.2 9 | * 10 | */ 11 | 12 | // 定义核心 13 | define('MC_CORE', true); 14 | 15 | // 定义版本 16 | define('MC_VERSION', '1.6.2'); 17 | 18 | // 核心文件目录 19 | define('MC_CORE_DIR', __DIR__ . '/core'); 20 | 21 | // 模版文件目录 22 | define('MC_TEMP_DIR', __DIR__ . '/template'); 23 | 24 | // 调试模式,0为关闭,-1为打开 25 | define('MC_DEBUG', 0); 26 | 27 | // Curl 代理地址,例如:define('MC_PROXY', 'someproxy.com:9999') 28 | define('MC_PROXY', false); 29 | 30 | // Curl 代理用户名和密码,例如:define('MC_PROXYUSERPWD', 'username:password') 31 | define('MC_PROXYUSERPWD', false); 32 | 33 | // 服务器是否在国内 34 | define('MC_INTERNAL', 1); 35 | 36 | // PHP 版本判断 37 | if (version_compare(phpversion(), '5.4', '<')) { 38 | header('Content-type:text/html;charset=utf-8'); 39 | echo sprintf( 40 | '

程序运行失败:

您的 PHP 版本低于最低要求 5.4,当前版本为 %s
', 41 | phpversion() 42 | ); 43 | exit; 44 | } 45 | 46 | // 判断是否启用 Curl 47 | if (!extension_loaded('curl')) { 48 | header('Content-type:text/html;charset=utf-8'); 49 | echo '

程序运行失败:

请启用 Curl 模块
'; 50 | exit; 51 | } 52 | 53 | 54 | include_once MC_CORE_DIR . '/music.php'; 55 | 56 | // 支持的网站 57 | $music_type_list = array( 58 | 'netease' => '网易', 59 | 'qq' => 'QQ', 60 | 'kugou' => '酷狗', 61 | 'kuwo' => '酷我', 62 | 'xiami' => '虾米', 63 | 'baidu' => '百度', 64 | '1ting' => '一听', 65 | 'migu' => '咪咕', 66 | 'lizhi' => '荔枝', 67 | 'qingting' => '蜻蜓', 68 | 'ximalaya' => '喜马拉雅', 69 | 'kg' => '全民K歌', 70 | '5singyc' => '5sing原创', 71 | '5singfc' => '5sing翻唱' 72 | ); 73 | 74 | if (server('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest') { 75 | $music_input = trim(post('input')); 76 | $music_filter = post('filter'); 77 | $music_type = post('type'); 78 | $music_page = (int) post('page'); 79 | $music_valid_patterns = array( 80 | 'name' => '/^.+$/i', 81 | 'id' => '/^[\w\/\|]+$/i', 82 | 'url' => '/^https?:\/\/\S+$/i' 83 | ); 84 | 85 | if (!$music_input || !$music_filter || !$music_type) { 86 | response('', 403, '(°ー°〃) 传入的数据不对啊'); 87 | } 88 | 89 | if ($music_filter !== 'url' && !in_array($music_type, array_keys($music_type_list), true)) { 90 | response('', 403, '(°ー°〃) 目前还不支持这个网站'); 91 | } 92 | 93 | if (!preg_match($music_valid_patterns[$music_filter], $music_input)) { 94 | response('', 403, '(・-・*) 请检查您的输入是否正确'); 95 | } 96 | 97 | switch ($music_filter) { 98 | case 'name': 99 | if (!$music_page) { 100 | $music_page = 1; 101 | } 102 | $music_response = mc_get_song_by_name($music_input, $music_type, $music_page); 103 | break; 104 | case 'id': 105 | $music_response = mc_get_song_by_id($music_input, $music_type); 106 | break; 107 | case 'url': 108 | $music_response = mc_get_song_by_url($music_input); 109 | break; 110 | } 111 | 112 | if (empty($music_response)) { 113 | response('', 404, 'ㄟ( ▔, ▔ )ㄏ 没有找到相关信息'); 114 | } 115 | 116 | if ($music_response['error']) { 117 | response('', $music_response['code'], '(°ー°〃) ' . $music_response['error']); 118 | } 119 | 120 | response($music_response, 200, ''); 121 | } 122 | 123 | include_once(MC_TEMP_DIR . '/index.php'); 124 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 音乐搜索器 - CSS 文件 4 | * 5 | * @author MaiCong 6 | * @link https://github.com/maicong/music 7 | * @since 1.5.4 8 | * 9 | */ 10 | 11 | html { 12 | font-family: -apple-system, BlinkMacSystemFont, 'San Francisco', 'Microsoft YaHei', 'PingFang SC', 13 | 'Hiragino Sans GB', 'WenQuanYi Micro Hei', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 14 | 'Fira Sans', 'Droid Sans', 'Noto Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif, 15 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 16 | -webkit-text-size-adjust: 100%; 17 | -moz-text-size-adjust: 100%; 18 | -ms-text-size-adjust: 100%; 19 | text-size-adjust: 100%; 20 | } 21 | body { 22 | margin: 0; 23 | -webkit-tap-highlight-color: transparent; 24 | -webkit-font-smoothing: subpixel-antialiased; 25 | } 26 | a { 27 | -webkit-transition: all .3s; 28 | transition: all .3s; 29 | } 30 | .about { 31 | background: #fff; 32 | color: #7f8c8d; 33 | } 34 | .about-color { 35 | color: #34495e; 36 | } 37 | .about-title { 38 | font-size: 180%; 39 | text-align: center; 40 | } 41 | .main { 42 | margin: 60px 0; 43 | } 44 | .music-tabs > li > a { 45 | padding: 0.5em 1em; 46 | cursor: pointer; 47 | } 48 | .music-main { 49 | display: none; 50 | } 51 | .music-main input { 52 | text-overflow: ellipsis; 53 | } 54 | .music-main input:focus, 55 | .music-main input:hover { 56 | background: #eee; 57 | border-color: #ccc; 58 | color: #222; 59 | } 60 | .music-type .am-radio-inline, 61 | .music-type label { 62 | color: #7f8c8d; 63 | } 64 | .music-type .am-icon-unchecked, 65 | .music-type .am-icon-checked { 66 | color: #0e90d2; 67 | } 68 | .music-tips { 69 | margin-top: 50px; 70 | } 71 | .music-tips blockquote { 72 | font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; 73 | font-size: 14px; 74 | margin-top: 30px; 75 | } 76 | .music-tips p span { 77 | display: inline-block; 78 | min-width: 50px; 79 | } 80 | .music-tips p b { 81 | font-weight: 500; 82 | color: #c7254e; 83 | } 84 | .music-overflow { 85 | max-height: 200px; 86 | overflow: hidden; 87 | } 88 | .music-more { 89 | padding: 10px; 90 | text-align: center; 91 | font-size: 14px; 92 | color: #666; 93 | background: #eee; 94 | cursor: pointer; 95 | } 96 | .footer { 97 | position: relative; 98 | left: 0; 99 | bottom: 0; 100 | width: 100%; 101 | overflow: hidden; 102 | } 103 | .footer p { 104 | color: #7f8c8d; 105 | margin: 0; 106 | padding: 15px; 107 | text-align: center; 108 | background: #2d3e50; 109 | } 110 | .footer p a { 111 | color: #7f8c8d; 112 | } 113 | .footer p a:hover { 114 | color: #bbb; 115 | } 116 | .footer a { 117 | text-decoration: underline; 118 | } 119 | .am-alert { 120 | display: none; 121 | } 122 | .am-popup-bd { 123 | height: 100%; 124 | } 125 | .aplayer { 126 | padding: 10px !important; 127 | margin: 0 !important; 128 | border: 1px solid #ccc; 129 | } 130 | .aplayer .aplayer-info .aplayer-music .aplayer-title, 131 | .aplayer .aplayer-info .aplayer-music .aplayer-author { 132 | font-size: 16px !important; 133 | color: #555 !important; 134 | } 135 | 136 | .aplayer .aplayer-list ol li .aplayer-list-index { 137 | display: inline-block; 138 | width: 20px; 139 | text-align: right; 140 | } 141 | .aplayer .aplayer-lrc p { 142 | color: #0e90d2 !important; 143 | } 144 | .aplayer .aplayer-more { 145 | position: relative; 146 | font-size: 12px; 147 | padding: 8px 10px; 148 | margin-top: 10px; 149 | text-align: center; 150 | color: #888; 151 | cursor: pointer; 152 | -webkit-transition: color .3s; 153 | transition: color .3s; 154 | } 155 | .aplayer .aplayer-more:hover, 156 | .aplayer .aplayer-more:active { 157 | color: #0e90d2; 158 | } 159 | 160 | .aplayer.aplayer-withlrc .aplayer-pic { 161 | width: 120px !important; 162 | height: 120px !important; 163 | -webkit-box-shadow: inset 0 0 3px 0 rgba(0, 0, 0, .5); 164 | box-shadow: inset 0 0 3px 0 rgba(0, 0, 0, .5); 165 | } 166 | .aplayer.aplayer-withlrc .aplayer-info { 167 | margin-left: 120px !important; 168 | height: 120px !important; 169 | } 170 | .aplayer.aplayer-withlist .aplayer-info { 171 | display: -webkit-box; 172 | display: -ms-flexbox; 173 | display: flex; 174 | -webkit-box-orient: vertical; 175 | -webkit-box-direction: normal; 176 | -ms-flex-direction: column; 177 | flex-direction: column; 178 | -webkit-box-pack: justify; 179 | -ms-flex-pack: justify; 180 | justify-content: space-between; 181 | border-bottom: 0 !important; 182 | } 183 | .aplayer.aplayer-withlist .aplayer-list { 184 | height: auto !important; 185 | padding-top: 20px; 186 | -webkit-transition: none; 187 | transition: none; 188 | } 189 | .aplayer.aplayer-withlist .aplayer-list-hide { 190 | padding: 0; 191 | } 192 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/src/Curl/CaseInsensitiveArray.php: -------------------------------------------------------------------------------- 1 | $value) { 46 | $this->offsetSet($key, $value); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Offset Set 53 | * 54 | * Set data at a specified Offset. Converts the offset to lower-case, and 55 | * stores the Case-Sensitive Offset and the Data at the lower-case indexes 56 | * in $this->keys and @this->data. 57 | * 58 | * @see https://secure.php.net/manual/en/arrayaccess.offseteset.php 59 | * 60 | * @param string $offset The offset to store the data at (case-insensitive). 61 | * @param mixed $value The data to store at the specified offset. 62 | * 63 | * @return void 64 | * 65 | * @access public 66 | */ 67 | public function offsetSet($offset, $value) 68 | { 69 | if ($offset === null) { 70 | $this->data[] = $value; 71 | } else { 72 | $offsetlower = strtolower($offset); 73 | $this->data[$offsetlower] = $value; 74 | $this->keys[$offsetlower] = $offset; 75 | } 76 | } 77 | 78 | /** 79 | * Offset Exists 80 | * 81 | * Checks if the Offset exists in data storage. The index is looked up with 82 | * the lower-case version of the provided offset. 83 | * 84 | * @see https://secure.php.net/manual/en/arrayaccess.offsetexists.php 85 | * 86 | * @param string $offset Offset to check 87 | * 88 | * @return bool If the offset exists. 89 | * 90 | * @access public 91 | */ 92 | public function offsetExists($offset) 93 | { 94 | return (bool) array_key_exists(strtolower($offset), $this->data); 95 | } 96 | 97 | /** 98 | * Offset Unset 99 | * 100 | * Unsets the specified offset. Converts the provided offset to lowercase, 101 | * and unsets the Case-Sensitive Key, as well as the stored data. 102 | * 103 | * @see https://secure.php.net/manual/en/arrayaccess.offsetunset.php 104 | * 105 | * @param string $offset The offset to unset. 106 | * 107 | * @return void 108 | * 109 | * @access public 110 | */ 111 | public function offsetUnset($offset) 112 | { 113 | $offsetlower = strtolower($offset); 114 | unset($this->data[$offsetlower]); 115 | unset($this->keys[$offsetlower]); 116 | } 117 | 118 | /** 119 | * Offset Get 120 | * 121 | * Return the stored data at the provided offset. The offset is converted to 122 | * lowercase and the lookup is done on the Data store directly. 123 | * 124 | * @see https://secure.php.net/manual/en/arrayaccess.offsetget.php 125 | * 126 | * @param string $offset Offset to lookup. 127 | * 128 | * @return mixed The data stored at the offset. 129 | * 130 | * @access public 131 | */ 132 | public function offsetGet($offset) 133 | { 134 | $offsetlower = strtolower($offset); 135 | return isset($this->data[$offsetlower]) ? $this->data[$offsetlower] : null; 136 | } 137 | 138 | /** 139 | * Count 140 | * 141 | * @see https://secure.php.net/manual/en/countable.count.php 142 | * 143 | * @param void 144 | * 145 | * @return int The number of elements stored in the Array. 146 | * 147 | * @access public 148 | */ 149 | public function count() 150 | { 151 | return (int) count($this->data); 152 | } 153 | 154 | /** 155 | * Current 156 | * 157 | * @see https://secure.php.net/manual/en/iterator.current.php 158 | * 159 | * @param void 160 | * 161 | * @return mixed Data at the current position. 162 | * 163 | * @access public 164 | */ 165 | public function current() 166 | { 167 | return current($this->data); 168 | } 169 | 170 | /** 171 | * Next 172 | * 173 | * @see https://secure.php.net/manual/en/iterator.next.php 174 | * 175 | * @param void 176 | * 177 | * @return void 178 | * 179 | * @access public 180 | */ 181 | public function next() 182 | { 183 | next($this->data); 184 | } 185 | 186 | /** 187 | * Key 188 | * 189 | * @see https://secure.php.net/manual/en/iterator.key.php 190 | * 191 | * @param void 192 | * 193 | * @return mixed Case-Sensitive key at current position. 194 | * 195 | * @access public 196 | */ 197 | public function key() 198 | { 199 | $key = key($this->data); 200 | return isset($this->keys[$key]) ? $this->keys[$key] : $key; 201 | } 202 | 203 | /** 204 | * Valid 205 | * 206 | * @see https://secure.php.net/manual/en/iterator.valid.php 207 | * 208 | * @return bool If the current position is valid. 209 | * 210 | * @access public 211 | */ 212 | public function valid() 213 | { 214 | return (bool) !(key($this->data) === null); 215 | } 216 | 217 | /** 218 | * Rewind 219 | * 220 | * @see https://secure.php.net/manual/en/iterator.rewind.php 221 | * 222 | * @param void 223 | * 224 | * @return void 225 | * 226 | * @access public 227 | */ 228 | public function rewind() 229 | { 230 | reset($this->data); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/src/Curl/Url.php: -------------------------------------------------------------------------------- 1 | baseUrl = $base_url; 15 | $this->relativeUrl = $relative_url; 16 | } 17 | 18 | public function __toString() 19 | { 20 | return $this->absolutizeUrl(); 21 | } 22 | 23 | /** 24 | * Remove dot segments. 25 | * 26 | * Interpret and remove the special "." and ".." path segments from a referenced path. 27 | */ 28 | public static function removeDotSegments($input) 29 | { 30 | // 1. The input buffer is initialized with the now-appended path 31 | // components and the output buffer is initialized to the empty 32 | // string. 33 | $output = ''; 34 | 35 | // 2. While the input buffer is not empty, loop as follows: 36 | while (!empty($input)) { 37 | // A. If the input buffer begins with a prefix of "../" or "./", 38 | // then remove that prefix from the input buffer; otherwise, 39 | if (StrUtil::startsWith($input, '../')) { 40 | $input = substr($input, 3); 41 | } elseif (StrUtil::startsWith($input, './')) { 42 | $input = substr($input, 2); 43 | 44 | // B. if the input buffer begins with a prefix of "/./" or "/.", 45 | // where "." is a complete path segment, then replace that 46 | // prefix with "/" in the input buffer; otherwise, 47 | } elseif (StrUtil::startsWith($input, '/./')) { 48 | $input = substr($input, 2); 49 | } elseif ($input === '/.') { 50 | $input = '/'; 51 | 52 | // C. if the input buffer begins with a prefix of "/../" or "/..", 53 | // where ".." is a complete path segment, then replace that 54 | // prefix with "/" in the input buffer and remove the last 55 | // segment and its preceding "/" (if any) from the output 56 | // buffer; otherwise, 57 | } elseif (StrUtil::startsWith($input, '/../')) { 58 | $input = substr($input, 3); 59 | $output = substr_replace($output, '', mb_strrpos($output, '/')); 60 | } elseif ($input === '/..') { 61 | $input = '/'; 62 | $output = substr_replace($output, '', mb_strrpos($output, '/')); 63 | 64 | // D. if the input buffer consists only of "." or "..", then remove 65 | // that from the input buffer; otherwise, 66 | } elseif ($input === '.' || $input === '..') { 67 | $input = ''; 68 | 69 | // E. move the first path segment in the input buffer to the end of 70 | // the output buffer, including the initial "/" character (if 71 | // any) and any subsequent characters up to, but not including, 72 | // the next "/" character or the end of the input buffer. 73 | } elseif (!(($pos = mb_strpos($input, '/', 1)) === false)) { 74 | $output .= substr($input, 0, $pos); 75 | $input = substr_replace($input, '', 0, $pos); 76 | } else { 77 | $output .= $input; 78 | $input = ''; 79 | } 80 | } 81 | 82 | // 3. Finally, the output buffer is returned as the result of 83 | // remove_dot_segments. 84 | return $output . $input; 85 | } 86 | 87 | /** 88 | * Absolutize url. 89 | * 90 | * Combine the base and relative url into an absolute url. 91 | */ 92 | private function absolutizeUrl() 93 | { 94 | $b = $this->parseUrl($this->baseUrl); 95 | 96 | if (!($this->relativeUrl === null)) { 97 | $r = $this->parseUrl($this->relativeUrl); 98 | 99 | // Copy relative parts to base url. 100 | if (isset($r['scheme'])) { 101 | $b['scheme'] = $r['scheme']; 102 | } 103 | if (isset($r['host'])) { 104 | $b['host'] = $r['host']; 105 | } 106 | if (isset($r['port'])) { 107 | $b['port'] = $r['port']; 108 | } 109 | if (isset($r['user'])) { 110 | $b['user'] = $r['user']; 111 | } 112 | if (isset($r['pass'])) { 113 | $b['pass'] = $r['pass']; 114 | } 115 | 116 | if (!isset($r['path']) || $r['path'] === '') { 117 | $r['path'] = '/'; 118 | } 119 | // Merge relative url with base when relative url's path doesn't start with a slash. 120 | if (!(StrUtil::startsWith($r['path'], '/'))) { 121 | $base = mb_strrchr($b['path'], '/', true); 122 | if ($base === false) { 123 | $base = ''; 124 | } 125 | $r['path'] = $base . '/' . $r['path']; 126 | } 127 | $b['path'] = $r['path']; 128 | $b['path'] = $this->removeDotSegments($b['path']); 129 | 130 | if (isset($r['query'])) { 131 | $b['query'] = $r['query']; 132 | } 133 | if (isset($r['fragment'])) { 134 | $b['fragment'] = $r['fragment']; 135 | } 136 | } 137 | 138 | if (!isset($b['path'])) { 139 | $b['path'] = '/'; 140 | } 141 | 142 | $absolutized_url = $this->unparseUrl($b); 143 | return $absolutized_url; 144 | } 145 | 146 | /** 147 | * Parse url. 148 | * 149 | * Parse url into components of a URI as specified by RFC 3986. 150 | */ 151 | private function parseUrl($url) 152 | { 153 | return parse_url($url); 154 | } 155 | 156 | /** 157 | * Unparse url. 158 | * 159 | * Combine url components into a url. 160 | */ 161 | private function unparseUrl($parsed_url) { 162 | $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; 163 | $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; 164 | $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; 165 | $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; 166 | $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; 167 | $pass = ($user || $pass) ? $pass . '@' : ''; 168 | $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; 169 | $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; 170 | $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; 171 | $unparsed_url = $scheme . $user . $pass . $host . $port . $path . $query . $fragment; 172 | return $unparsed_url; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/README.md: -------------------------------------------------------------------------------- 1 | # PHP Curl Class: HTTP requests made easy 2 | 3 | [![](https://img.shields.io/github/release/php-curl-class/php-curl-class.svg)](https://github.com/php-curl-class/php-curl-class/releases/) 4 | [![](https://img.shields.io/github/license/php-curl-class/php-curl-class.svg)](https://github.com/php-curl-class/php-curl-class/blob/master/LICENSE) 5 | [![](https://img.shields.io/travis/php-curl-class/php-curl-class.svg)](https://travis-ci.org/php-curl-class/php-curl-class/) 6 | [![](https://img.shields.io/packagist/dt/php-curl-class/php-curl-class.svg)](https://github.com/php-curl-class/php-curl-class/releases/) 7 | 8 | PHP Curl Class makes it easy to send HTTP requests and integrate with web APIs. 9 | 10 | ![PHP Curl Class screencast](www/img/screencast.gif) 11 | 12 | --- 13 | 14 | - [Installation](#installation) 15 | - [Requirements](#requirements) 16 | - [Quick Start and Examples](#quick-start-and-examples) 17 | - [Available Methods](#available-methods) 18 | - [Security](#security) 19 | - [Troubleshooting](#troubleshooting) 20 | - [Run Tests](#run-tests) 21 | - [Contribute](#contribute) 22 | 23 | --- 24 | 25 | ### Installation 26 | 27 | To install PHP Curl Class, simply: 28 | 29 | $ composer require php-curl-class/php-curl-class 30 | 31 | For latest commit version: 32 | 33 | $ composer require php-curl-class/php-curl-class @dev 34 | 35 | ### Requirements 36 | 37 | PHP Curl Class works with PHP 5.3, 5.4, 5.5, 5.6, 7.0, 7.1, and HHVM. 38 | 39 | ### Quick Start and Examples 40 | 41 | More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). 42 | 43 | ```php 44 | require __DIR__ . '/vendor/autoload.php'; 45 | 46 | use \Curl\Curl; 47 | 48 | $curl = new Curl(); 49 | $curl->get('https://www.example.com/'); 50 | 51 | if ($curl->error) { 52 | echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; 53 | } else { 54 | echo 'Response:' . "\n"; 55 | var_dump($curl->response); 56 | } 57 | ``` 58 | 59 | ```php 60 | // https://www.example.com/search?q=keyword 61 | $curl = new Curl(); 62 | $curl->get('https://www.example.com/search', array( 63 | 'q' => 'keyword', 64 | )); 65 | ``` 66 | 67 | ```php 68 | $curl = new Curl(); 69 | $curl->post('https://www.example.com/login/', array( 70 | 'username' => 'myusername', 71 | 'password' => 'mypassword', 72 | )); 73 | ``` 74 | 75 | ```php 76 | $curl = new Curl(); 77 | $curl->setBasicAuthentication('username', 'password'); 78 | $curl->setUserAgent('MyUserAgent/0.0.1 (+https://www.example.com/bot.html)'); 79 | $curl->setReferrer('https://www.example.com/url?url=https%3A%2F%2Fwww.example.com%2F'); 80 | $curl->setHeader('X-Requested-With', 'XMLHttpRequest'); 81 | $curl->setCookie('key', 'value'); 82 | $curl->get('https://www.example.com/'); 83 | 84 | if ($curl->error) { 85 | echo 'Error: ' . $curl->errorCode . ': ' . $curl->errorMessage . "\n"; 86 | } else { 87 | echo 'Response:' . "\n"; 88 | var_dump($curl->response); 89 | } 90 | 91 | var_dump($curl->requestHeaders); 92 | var_dump($curl->responseHeaders); 93 | ``` 94 | 95 | ```php 96 | $curl = new Curl(); 97 | $curl->setOpt(CURLOPT_FOLLOWLOCATION, true); 98 | $curl->get('https://shortn.example.com/bHbVsP'); 99 | ``` 100 | 101 | ```php 102 | $curl = new Curl(); 103 | $curl->put('https://api.example.com/user/', array( 104 | 'first_name' => 'Zach', 105 | 'last_name' => 'Borboa', 106 | )); 107 | ``` 108 | 109 | ```php 110 | $curl = new Curl(); 111 | $curl->patch('https://api.example.com/profile/', array( 112 | 'image' => '@path/to/file.jpg', 113 | )); 114 | ``` 115 | 116 | ```php 117 | $curl = new Curl(); 118 | $curl->patch('https://api.example.com/profile/', array( 119 | 'image' => new CURLFile('path/to/file.jpg'), 120 | )); 121 | ``` 122 | 123 | ```php 124 | $curl = new Curl(); 125 | $curl->delete('https://api.example.com/user/', array( 126 | 'id' => '1234', 127 | )); 128 | ``` 129 | 130 | ```php 131 | // Enable all supported encoding types and download a file. 132 | $curl = new Curl(); 133 | $curl->setOpt(CURLOPT_ENCODING , ''); 134 | $curl->download('https://www.example.com/file.bin', '/tmp/myfile.bin'); 135 | ``` 136 | 137 | ```php 138 | // Case-insensitive access to headers. 139 | $curl = new Curl(); 140 | $curl->download('https://www.example.com/image.png', '/tmp/myimage.png'); 141 | echo $curl->responseHeaders['Content-Type'] . "\n"; // image/png 142 | echo $curl->responseHeaders['CoNTeNT-TyPE'] . "\n"; // image/png 143 | ``` 144 | 145 | ```php 146 | // Clean up. 147 | $curl->close(); 148 | ``` 149 | 150 | ```php 151 | // Example access to curl object. 152 | curl_set_opt($curl->curl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); 153 | curl_close($curl->curl); 154 | ``` 155 | 156 | ```php 157 | require __DIR__ . '/vendor/autoload.php'; 158 | 159 | use \Curl\MultiCurl; 160 | 161 | // Requests in parallel with callback functions. 162 | $multi_curl = new MultiCurl(); 163 | 164 | $multi_curl->success(function($instance) { 165 | echo 'call to "' . $instance->url . '" was successful.' . "\n"; 166 | echo 'response:' . "\n"; 167 | var_dump($instance->response); 168 | }); 169 | $multi_curl->error(function($instance) { 170 | echo 'call to "' . $instance->url . '" was unsuccessful.' . "\n"; 171 | echo 'error code: ' . $instance->errorCode . "\n"; 172 | echo 'error message: ' . $instance->errorMessage . "\n"; 173 | }); 174 | $multi_curl->complete(function($instance) { 175 | echo 'call completed' . "\n"; 176 | }); 177 | 178 | $multi_curl->addGet('https://www.google.com/search', array( 179 | 'q' => 'hello world', 180 | )); 181 | $multi_curl->addGet('https://duckduckgo.com/', array( 182 | 'q' => 'hello world', 183 | )); 184 | $multi_curl->addGet('https://www.bing.com/search', array( 185 | 'q' => 'hello world', 186 | )); 187 | 188 | $multi_curl->start(); // Blocks until all items in the queue have been processed. 189 | ``` 190 | 191 | More examples are available under [/examples](https://github.com/php-curl-class/php-curl-class/tree/master/examples). 192 | 193 | ### Available Methods 194 | ```php 195 | Curl::__construct($base_url = null) 196 | Curl::__destruct() 197 | Curl::__get($name) 198 | Curl::attemptRetry() 199 | Curl::beforeSend($callback) 200 | Curl::buildPostData($data) 201 | Curl::call() 202 | Curl::close() 203 | Curl::complete($callback) 204 | Curl::delete($url, $query_parameters = array(), $data = array()) 205 | Curl::download($url, $mixed_filename) 206 | Curl::error($callback) 207 | Curl::exec($ch = null) 208 | Curl::execDone() 209 | Curl::get($url, $data = array()) 210 | Curl::getCookie($key) 211 | Curl::getInfo($opt = null) 212 | Curl::getOpt($option) 213 | Curl::getResponseCookie($key) 214 | Curl::getResponseCookies() 215 | Curl::head($url, $data = array()) 216 | Curl::options($url, $data = array()) 217 | Curl::patch($url, $data = array()) 218 | Curl::post($url, $data = array(), $follow_303_with_post = false) 219 | Curl::progress($callback) 220 | Curl::put($url, $data = array()) 221 | Curl::removeHeader($key) 222 | Curl::search($url, $data = array()) 223 | Curl::setBasicAuthentication($username, $password = '') 224 | Curl::setConnectTimeout($seconds) 225 | Curl::setCookie($key, $value) 226 | Curl::setCookieFile($cookie_file) 227 | Curl::setCookieJar($cookie_jar) 228 | Curl::setCookieString($string) 229 | Curl::setCookies($cookies) 230 | Curl::setDefaultDecoder($mixed = 'json') 231 | Curl::setDefaultJsonDecoder() 232 | Curl::setDefaultTimeout() 233 | Curl::setDefaultUserAgent() 234 | Curl::setDefaultXmlDecoder() 235 | Curl::setDigestAuthentication($username, $password = '') 236 | Curl::setHeader($key, $value) 237 | Curl::setHeaders($headers) 238 | Curl::setJsonDecoder($mixed) 239 | Curl::setMaxFilesize($bytes) 240 | Curl::setOpt($option, $value) 241 | Curl::setOpts($options) 242 | Curl::setPort($port) 243 | Curl::setReferer($referer) 244 | Curl::setReferrer($referrer) 245 | Curl::setRetry($mixed) 246 | Curl::setTimeout($seconds) 247 | Curl::setUrl($url, $mixed_data = '') 248 | Curl::setUserAgent($user_agent) 249 | Curl::setXmlDecoder($mixed) 250 | Curl::success($callback) 251 | Curl::unsetHeader($key) 252 | Curl::verbose($on = true, $output = STDERR) 253 | MultiCurl::__construct($base_url = null) 254 | MultiCurl::__destruct() 255 | MultiCurl::addCurl(Curl $curl) 256 | MultiCurl::addDelete($url, $query_parameters = array(), $data = array()) 257 | MultiCurl::addDownload($url, $mixed_filename) 258 | MultiCurl::addGet($url, $data = array()) 259 | MultiCurl::addHead($url, $data = array()) 260 | MultiCurl::addOptions($url, $data = array()) 261 | MultiCurl::addPatch($url, $data = array()) 262 | MultiCurl::addPost($url, $data = array(), $follow_303_with_post = false) 263 | MultiCurl::addPut($url, $data = array()) 264 | MultiCurl::addSearch($url, $data = array()) 265 | MultiCurl::beforeSend($callback) 266 | MultiCurl::close() 267 | MultiCurl::complete($callback) 268 | MultiCurl::error($callback) 269 | MultiCurl::getOpt($option) 270 | MultiCurl::removeHeader($key) 271 | MultiCurl::setBasicAuthentication($username, $password = '') 272 | MultiCurl::setConcurrency($concurrency) 273 | MultiCurl::setConnectTimeout($seconds) 274 | MultiCurl::setCookie($key, $value) 275 | MultiCurl::setCookieFile($cookie_file) 276 | MultiCurl::setCookieJar($cookie_jar) 277 | MultiCurl::setCookieString($string) 278 | MultiCurl::setCookies($cookies) 279 | MultiCurl::setDigestAuthentication($username, $password = '') 280 | MultiCurl::setHeader($key, $value) 281 | MultiCurl::setHeaders($headers) 282 | MultiCurl::setJsonDecoder($mixed) 283 | MultiCurl::setOpt($option, $value) 284 | MultiCurl::setOpts($options) 285 | MultiCurl::setPort($port) 286 | MultiCurl::setReferer($referer) 287 | MultiCurl::setReferrer($referrer) 288 | MultiCurl::setRetry($mixed) 289 | MultiCurl::setTimeout($seconds) 290 | MultiCurl::setUrl($url) 291 | MultiCurl::setUserAgent($user_agent) 292 | MultiCurl::setXmlDecoder($mixed) 293 | MultiCurl::start() 294 | MultiCurl::success($callback) 295 | MultiCurl::unsetHeader($key) 296 | MultiCurl::verbose($on = true, $output = STDERR) 297 | ``` 298 | 299 | ### Security 300 | 301 | See [SECURITY](https://github.com/php-curl-class/php-curl-class/blob/master/SECURITY.md) for security considerations. 302 | 303 | ### Troubleshooting 304 | 305 | See [TROUBLESHOOTING](https://github.com/php-curl-class/php-curl-class/blob/master/TROUBLESHOOTING.md) for troubleshooting. 306 | 307 | ### Run Tests 308 | 309 | To run tests: 310 | 311 | $ git clone https://github.com/php-curl-class/php-curl-class.git 312 | $ cd php-curl-class/ 313 | $ composer update 314 | $ ./tests/run.sh 315 | 316 | ### Contribute 317 | 1. Check for open issues or open a new issue to start a discussion around a bug or feature. 318 | 1. Fork the repository on GitHub to start making your changes. 319 | 1. Write one or more tests for the new feature or that expose the bug. 320 | 1. Make code changes to implement the feature or fix the bug. 321 | 1. Send a pull request to get your changes merged and published. 322 | -------------------------------------------------------------------------------- /static/js/music.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 5 | * 音乐搜索器 - JS 文件 6 | * 7 | * @author MaiCong 8 | * @link https://github.com/maicong/music 9 | * @since 1.5.9 10 | * 11 | */ 12 | 13 | $(function() { 14 | // 获取参数 15 | function q(key) { 16 | var value = null; 17 | var tmp = []; 18 | location.search 19 | .substr(1) 20 | .split('&') 21 | .forEach(function(v) { 22 | tmp = v.split('='); 23 | if (tmp[0] === key) { 24 | value = decodeURIComponent(tmp[1]); 25 | } 26 | }); 27 | return value; 28 | } 29 | 30 | // 加入历史记录 31 | function pushState(title, link) { 32 | if (window.history && window.history.pushState) { 33 | window.history.pushState(null, title, link); 34 | } 35 | } 36 | 37 | // 获取 url 38 | function getUrl(path) { 39 | var url = location.href.split('?')[0]; 40 | return path ? url + path : url; 41 | } 42 | 43 | // 申明变量 44 | var player = null; 45 | var playerList = []; 46 | var nopic = 'static/img/nopic.jpg'; 47 | var qName = q('name'); 48 | var qId = q('id'); 49 | var qUrl = q('url'); 50 | var qType = q('type'); 51 | var siteTitle = document.title; 52 | 53 | // 如果参数存在 name/id 和 type 54 | if ((qName || qId) && qType) { 55 | setTimeout(function() { 56 | $('#j-input').val(qName || qId); 57 | $('#j-type input[value="' + qType + '"]').prop('checked', true); 58 | if (qName) { 59 | $('#j-nav [data-filter="name"]').trigger('click'); 60 | } 61 | if (qId) { 62 | $('#j-nav [data-filter="id"]').trigger('click'); 63 | } 64 | $('#j-validator').trigger('submit'); 65 | }, 0); 66 | } 67 | 68 | // 如果参数存在 url 69 | if (qUrl) { 70 | setTimeout(function() { 71 | $('#j-type').hide(); 72 | $('#j-input').val(qUrl); 73 | $('#j-nav [data-filter="url"]').trigger('click'); 74 | $('#j-validator').trigger('submit'); 75 | }, 0); 76 | } 77 | 78 | // Tab 切换 79 | $('#j-nav').on('click', 'li', function() { 80 | var holder = { 81 | name: '例如: 不要说话 陈奕迅', 82 | id: '例如: 25906124', 83 | url: '例如: http://music.163.com/#/song?id=25906124', 84 | pattern_name: '^.+$', 85 | pattern_id: '^[\\w\\/\\|]+$', 86 | pattern_url: '^https?:\\/\\/\\S+$' 87 | }; 88 | var filter = $(this).data('filter'); 89 | 90 | $(this) 91 | .addClass('am-active') 92 | .siblings('li') 93 | .removeClass('am-active'); 94 | 95 | $('#j-input') 96 | .data('filter', filter) 97 | .attr({ 98 | placeholder: holder[filter], 99 | pattern: holder['pattern_' + filter] 100 | }) 101 | .removeClass('am-field-valid am-field-error am-active') 102 | .closest('.am-form-group') 103 | .removeClass('am-form-success am-form-error') 104 | .find('.am-alert') 105 | .hide(); 106 | 107 | if (filter === 'url') { 108 | $('#j-type').hide(); 109 | } else { 110 | $('#j-type').show(); 111 | } 112 | }); 113 | 114 | // 输入验证 115 | $('#j-validator').validator({ 116 | onValid: function onValid(v) { 117 | $(v.field) 118 | .closest('.am-form-group') 119 | .find('.am-alert') 120 | .hide(); 121 | }, 122 | onInValid: function onInValid(v) { 123 | var $field = $(v.field); 124 | var $group = $field.closest('.am-form-group'); 125 | var msgs = { 126 | name: '将 名称 和 作者 一起输入可提高匹配度', 127 | id: '输入错误,请查看下面的帮助', 128 | url: '输入错误,请查看下面的帮助' 129 | }; 130 | var $alert = $group.find('.am-alert'); 131 | var msg = msgs[$field.data('filter')] || this.getValidationMessage(v); 132 | 133 | if (!$alert.length) { 134 | $alert = $( 135 | '
' 136 | ) 137 | .hide() 138 | .appendTo($group); 139 | } 140 | $alert.html(msg).show(); 141 | }, 142 | submit: function submit(v) { 143 | v.preventDefault(); 144 | if (this.isFormValid()) { 145 | var input = $.trim($('#j-input').val()); 146 | var filter = $('#j-input').data('filter'); 147 | var type = 148 | filter === 'url' ? '_' : $('input[name="music_type"]:checked').val(); 149 | var page = 1; 150 | var $more = $('
载入更多
'); 151 | var isload = false; 152 | var ajax = function ajax(input, filter, type, page) { 153 | $.ajax({ 154 | type: 'POST', 155 | url: getUrl(), 156 | timeout: 30000, 157 | data: { 158 | input: input, 159 | filter: filter, 160 | type: type, 161 | page: page 162 | }, 163 | dataType: 'json', 164 | beforeSend: function beforeSend() { 165 | isload = true; 166 | var title = document.title; 167 | switch (filter) { 168 | case 'name': 169 | pushState(title, getUrl('?name=' + input + '&type=' + type)); 170 | break; 171 | case 'id': 172 | pushState(title, getUrl('?id=' + input + '&type=' + type)); 173 | break; 174 | case 'url': 175 | pushState(title, getUrl('?url=' + encodeURIComponent(input))); 176 | break; 177 | } 178 | if (page === 1) { 179 | $('#j-input').attr('disabled', true); 180 | $('#j-submit').button('loading'); 181 | } else { 182 | $more.text('请稍后...'); 183 | } 184 | }, 185 | success: function success(result) { 186 | if (result.code === 200 && result.data) { 187 | result.data.map(function(v) { 188 | if (!v.title) v.title = '暂无'; 189 | if (!v.author) v.author = '暂无'; 190 | if (!v.pic) v.pic = nopic; 191 | if (!v.lrc) v.lrc = '[00:00.00] 暂无歌词'; 192 | if (!/\[00:(\d{2})\./.test(v.lrc)) { 193 | v.lrc = '[00:00.00] 无效歌词'; 194 | } 195 | }); 196 | var setValue = function setValue(data) { 197 | $('#j-link').val(data.link); 198 | $('#j-link-btn').attr('href', data.link); 199 | $('#j-src').val(data.url); 200 | $('#j-src-btn').attr('href', data.url); 201 | $('#j-lrc').val(data.lrc); 202 | $('#j-lrc-btn').attr( 203 | 'href', 204 | 'data:application/octet-stream;base64,' + 205 | btoa(unescape(encodeURIComponent(data.lrc))) 206 | ); 207 | if ('download' in $('#j-src-btn')[0]) { 208 | var name = data.title + '-' + data.author; 209 | $('#j-src-btn').attr('download', name + '.mp3'); 210 | $('#j-lrc-btn').attr('download', name + '.lrc'); 211 | $('#j-src-btn-icon, #j-lrc-btn-icon') 212 | .addClass('am-icon-download') 213 | .removeClass('am-icon-external-link'); 214 | } 215 | $('#j-songid').val(data.songid); 216 | $('#j-name').val(data.title); 217 | $('#j-author').val(data.author); 218 | }; 219 | 220 | if (page === 1) { 221 | if (player) { 222 | player.pause(); 223 | } 224 | 225 | playerList = result.data; 226 | 227 | setValue(playerList[0]); 228 | 229 | $('#j-validator').slideUp(); 230 | $('#j-main').slideDown(); 231 | 232 | player = new APlayer({ 233 | element: $('#j-player')[0], 234 | autoplay: false, 235 | narrow: false, 236 | showlrc: 1, 237 | mutex: false, 238 | mode: 'circulation', 239 | preload: 'metadata', 240 | theme: '#0e90d2', 241 | music: result.data 242 | }); 243 | 244 | $('#j-player').append($more); 245 | 246 | $more.on('click', function() { 247 | if (isload) return; 248 | page++; 249 | ajax(input, filter, type, page); 250 | }); 251 | } else { 252 | player.addMusic(result.data); 253 | playerList = playerList.concat(result.data); 254 | } 255 | 256 | player.on('canplay', function() { 257 | player.play(); 258 | }); 259 | player.on('play', function() { 260 | var data = playerList[player.playIndex]; 261 | var img = new Image(); 262 | img.src = data.pic; 263 | img.onerror = function() { 264 | $('.aplayer-pic').css( 265 | 'background-image', 266 | 'url(' + nopic + ')' 267 | ); 268 | }; 269 | document.title = 270 | '正在播放: ' + data.title + ' - ' + data.author; 271 | setValue(data); 272 | }); 273 | player.on('ended', function() { 274 | document.title = siteTitle; 275 | }); 276 | if (result.data.length < 10) { 277 | $more.hide(); 278 | } else { 279 | $more.text('载入更多'); 280 | } 281 | } else { 282 | if (page === 1) { 283 | $('#j-input') 284 | .closest('.am-form-group') 285 | .find('.am-alert') 286 | .html(result.error || '(°ー°〃) 服务器好像罢工了') 287 | .show(); 288 | } else { 289 | $more.text('没有了'); 290 | setTimeout(function() { 291 | $more.slideUp(); 292 | }, 1000); 293 | } 294 | } 295 | }, 296 | error: function error(e, t) { 297 | if (page === 1) { 298 | var err = '(°ー°〃) 出了点小问题,请重试'; 299 | if (t === 'timeout') { 300 | err = '(°ー°〃) 请求超时了,请稍后重试'; 301 | } 302 | $('#j-input') 303 | .closest('.am-form-group') 304 | .find('.am-alert') 305 | .html(err) 306 | .show(); 307 | } else { 308 | $more.text('(°ー°〃) 加载失败了,点击重试'); 309 | } 310 | }, 311 | complete: function complete() { 312 | isload = false; 313 | if (page === 1) { 314 | $('#j-input').attr('disabled', false); 315 | $('#j-submit').button('reset'); 316 | } 317 | } 318 | }); 319 | }; 320 | 321 | ajax(input, filter, type, page); 322 | } 323 | } 324 | }); 325 | 326 | $('#j-main input').focus(function() { 327 | $(this).select(); 328 | }); 329 | 330 | $('#j-more').on('click', function() { 331 | $(this).hide(); 332 | $('#j-quote').removeClass('music-overflow'); 333 | }); 334 | 335 | $('#j-back').on('click', function() { 336 | if (player) { 337 | player.pause(); 338 | } 339 | $('#j-validator').slideDown(); 340 | $('#j-main').slideUp(); 341 | $('#j-main input').val(''); 342 | document.title = siteTitle; 343 | }); 344 | }); 345 | -------------------------------------------------------------------------------- /template/index.php: -------------------------------------------------------------------------------- 1 | 7 | * @link https://github.com/maicong/music 8 | * @since 1.5.10 9 | * 10 | */ 11 | 12 | if (!defined('MC_CORE')) { 13 | header("Location: /"); 14 | exit(); 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 | 59 |
60 |
61 |
62 |

音乐搜索器

63 |

多站合一音乐搜索解决方案

64 |
65 |
66 |
67 |
68 |
69 | 80 |
81 | 82 |
83 |
84 |
85 | $val) { ?> 86 | 90 | '; ?> 91 | 92 |
93 | 94 |
95 |
96 |
97 | 成功 Get √ 返回继续 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 |

标红音乐 ID下划线 表示 音乐地址

162 |
    163 |
  • 蜻蜓 FM 的音乐 ID 需要使用 | (管道符) 组合,例如 158696|5266259
  • 164 |
  • 全民 K 歌的音乐名称请输入 shareuid,这是用户的 uid,搜索结果是该用户的所有公开作品
  • 165 |
  • 全民 K 歌的音乐 ID 请输入 shareid 这是单曲分享 id,搜索结果是该单曲信息
  • 166 |
167 |
168 |

网易:http://music.163.com/#/song?id=25906124

169 |

QQ:http://y.qq.com/n/yqq/song/002B2EAA3brD5b.html

170 |

酷狗:http://www.kugou.com/song/#hash=08228af3cb404e8a4e7e9871bf543ff6

171 |

酷我:http://www.kuwo.cn/yinyue/382425/

172 |

虾米:http://www.xiami.com/song/2113248

173 |

百度:http://music.baidu.com/song/266069

174 |

一听:http://www.1ting.com/player/b6/player_357838.html

175 |

咪咕:http://music.migu.cn/v2/music/song/477803

176 |

荔枝:http://www.lizhi.fm/1947925/2498707770886461446

177 |

蜻蜓:http://www.qingting.fm/channels/158696/programs/5266259

178 |

喜马拉雅:http://www.ximalaya.com/51701370/sound/24755731

179 |

全民K歌 (shareuid):http://kg.qq.com/node/personal?uid=619a958c25283e88

180 |

全民K歌 (shareid):https://kg.qq.com/node/play?s=FA3h1gFhd6Vk7Ft4

181 |

5sing原创:http://5sing.kugou.com/yc/3082899.html

182 |

5sing翻唱:http://5sing.kugou.com/fc/14369766.html

183 |
184 |
查看更多
185 |
186 |
187 |
188 |
189 |
190 |
191 |

免责声明

192 | × 193 |
194 |
195 |

本站音频文件来自各网站接口,本站不会修改任何音频文件

196 |

音频版权来自各网站,本站只提供数据查询服务,不提供任何音频存储和贩卖服务

197 |
198 |
199 |
200 |
201 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /core/vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath.'\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 383 | $length = $this->prefixLengthsPsr4[$first][$search]; 384 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/src/Curl/MultiCurl.php: -------------------------------------------------------------------------------- 1 | multiCurl = curl_multi_init(); 39 | $this->headers = new CaseInsensitiveArray(); 40 | $this->setUrl($base_url); 41 | } 42 | 43 | /** 44 | * Add Delete 45 | * 46 | * @access public 47 | * @param $url 48 | * @param $query_parameters 49 | * @param $data 50 | * 51 | * @return object 52 | */ 53 | public function addDelete($url, $query_parameters = array(), $data = array()) 54 | { 55 | if (is_array($url)) { 56 | $data = $query_parameters; 57 | $query_parameters = $url; 58 | $url = $this->baseUrl; 59 | } 60 | $curl = new Curl(); 61 | $curl->setUrl($url, $query_parameters); 62 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); 63 | $curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data)); 64 | $this->queueHandle($curl); 65 | return $curl; 66 | } 67 | 68 | /** 69 | * Add Download 70 | * 71 | * @access public 72 | * @param $url 73 | * @param $mixed_filename 74 | * 75 | * @return object 76 | */ 77 | public function addDownload($url, $mixed_filename) 78 | { 79 | $curl = new Curl(); 80 | $curl->setUrl($url); 81 | 82 | // Use tmpfile() or php://temp to avoid "Too many open files" error. 83 | if (is_callable($mixed_filename)) { 84 | $callback = $mixed_filename; 85 | $curl->downloadCompleteFunction = $callback; 86 | $curl->fileHandle = tmpfile(); 87 | } else { 88 | $filename = $mixed_filename; 89 | $curl->downloadCompleteFunction = function ($instance, $fh) use ($filename) { 90 | file_put_contents($filename, stream_get_contents($fh)); 91 | }; 92 | $curl->fileHandle = fopen('php://temp', 'wb'); 93 | } 94 | 95 | $curl->setOpt(CURLOPT_FILE, $curl->fileHandle); 96 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 97 | $curl->setOpt(CURLOPT_HTTPGET, true); 98 | $this->queueHandle($curl); 99 | return $curl; 100 | } 101 | 102 | /** 103 | * Add Get 104 | * 105 | * @access public 106 | * @param $url 107 | * @param $data 108 | * 109 | * @return object 110 | */ 111 | public function addGet($url, $data = array()) 112 | { 113 | if (is_array($url)) { 114 | $data = $url; 115 | $url = $this->baseUrl; 116 | } 117 | $curl = new Curl(); 118 | $curl->setUrl($url, $data); 119 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 120 | $curl->setOpt(CURLOPT_HTTPGET, true); 121 | $this->queueHandle($curl); 122 | return $curl; 123 | } 124 | 125 | /** 126 | * Add Head 127 | * 128 | * @access public 129 | * @param $url 130 | * @param $data 131 | * 132 | * @return object 133 | */ 134 | public function addHead($url, $data = array()) 135 | { 136 | if (is_array($url)) { 137 | $data = $url; 138 | $url = $this->baseUrl; 139 | } 140 | $curl = new Curl(); 141 | $curl->setUrl($url, $data); 142 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); 143 | $curl->setOpt(CURLOPT_NOBODY, true); 144 | $this->queueHandle($curl); 145 | return $curl; 146 | } 147 | 148 | /** 149 | * Add Options 150 | * 151 | * @access public 152 | * @param $url 153 | * @param $data 154 | * 155 | * @return object 156 | */ 157 | public function addOptions($url, $data = array()) 158 | { 159 | if (is_array($url)) { 160 | $data = $url; 161 | $url = $this->baseUrl; 162 | } 163 | $curl = new Curl(); 164 | $curl->setUrl($url, $data); 165 | $curl->removeHeader('Content-Length'); 166 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); 167 | $this->queueHandle($curl); 168 | return $curl; 169 | } 170 | 171 | /** 172 | * Add Patch 173 | * 174 | * @access public 175 | * @param $url 176 | * @param $data 177 | * 178 | * @return object 179 | */ 180 | public function addPatch($url, $data = array()) 181 | { 182 | if (is_array($url)) { 183 | $data = $url; 184 | $url = $this->baseUrl; 185 | } 186 | $curl = new Curl(); 187 | $curl->setUrl($url); 188 | $curl->removeHeader('Content-Length'); 189 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); 190 | $curl->setOpt(CURLOPT_POSTFIELDS, $data); 191 | $this->queueHandle($curl); 192 | return $curl; 193 | } 194 | 195 | /** 196 | * Add Post 197 | * 198 | * @access public 199 | * @param $url 200 | * @param $data 201 | * @param $follow_303_with_post 202 | * If true, will cause 303 redirections to be followed using GET requests (default: false). 203 | * Note: Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. 204 | * 205 | * @return object 206 | */ 207 | public function addPost($url, $data = array(), $follow_303_with_post = false) 208 | { 209 | if (is_array($url)) { 210 | $follow_303_with_post = (bool)$data; 211 | $data = $url; 212 | $url = $this->baseUrl; 213 | } 214 | 215 | $curl = new Curl(); 216 | 217 | if (is_array($data) && empty($data)) { 218 | $curl->removeHeader('Content-Length'); 219 | } 220 | 221 | $curl->setUrl($url); 222 | 223 | /* 224 | * For post-redirect-get requests, the CURLOPT_CUSTOMREQUEST option must not 225 | * be set, otherwise cURL will perform POST requests for redirections. 226 | */ 227 | if (!$follow_303_with_post) { 228 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); 229 | } 230 | 231 | $curl->setOpt(CURLOPT_POST, true); 232 | $curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data)); 233 | $this->queueHandle($curl); 234 | return $curl; 235 | } 236 | 237 | /** 238 | * Add Put 239 | * 240 | * @access public 241 | * @param $url 242 | * @param $data 243 | * 244 | * @return object 245 | */ 246 | public function addPut($url, $data = array()) 247 | { 248 | if (is_array($url)) { 249 | $data = $url; 250 | $url = $this->baseUrl; 251 | } 252 | $curl = new Curl(); 253 | $curl->setUrl($url); 254 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); 255 | $put_data = $curl->buildPostData($data); 256 | if (is_string($put_data)) { 257 | $curl->setHeader('Content-Length', strlen($put_data)); 258 | } 259 | $curl->setOpt(CURLOPT_POSTFIELDS, $put_data); 260 | $this->queueHandle($curl); 261 | return $curl; 262 | } 263 | 264 | /** 265 | * Add Search 266 | * 267 | * @access public 268 | * @param $url 269 | * @param $data 270 | * 271 | * @return object 272 | */ 273 | public function addSearch($url, $data = array()) 274 | { 275 | if (is_array($url)) { 276 | $data = $url; 277 | $url = $this->baseUrl; 278 | } 279 | $curl = new Curl(); 280 | $curl->setUrl($url); 281 | $curl->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH'); 282 | $put_data = $curl->buildPostData($data); 283 | if (is_string($put_data)) { 284 | $curl->setHeader('Content-Length', strlen($put_data)); 285 | } 286 | $curl->setOpt(CURLOPT_POSTFIELDS, $put_data); 287 | $this->queueHandle($curl); 288 | return $curl; 289 | } 290 | 291 | /** 292 | * Add Curl 293 | * 294 | * Add a Curl instance to the handle queue. 295 | * 296 | * @access public 297 | * @param $curl 298 | * 299 | * @return object 300 | */ 301 | public function addCurl(Curl $curl) 302 | { 303 | $this->queueHandle($curl); 304 | return $curl; 305 | } 306 | 307 | /** 308 | * Before Send 309 | * 310 | * @access public 311 | * @param $callback 312 | */ 313 | public function beforeSend($callback) 314 | { 315 | $this->beforeSendFunction = $callback; 316 | } 317 | 318 | /** 319 | * Close 320 | * 321 | * @access public 322 | */ 323 | public function close() 324 | { 325 | foreach ($this->curls as $curl) { 326 | $curl->close(); 327 | } 328 | 329 | if (is_resource($this->multiCurl)) { 330 | curl_multi_close($this->multiCurl); 331 | } 332 | } 333 | 334 | /** 335 | * Complete 336 | * 337 | * @access public 338 | * @param $callback 339 | */ 340 | public function complete($callback) 341 | { 342 | $this->completeFunction = $callback; 343 | } 344 | 345 | /** 346 | * Error 347 | * 348 | * @access public 349 | * @param $callback 350 | */ 351 | public function error($callback) 352 | { 353 | $this->errorFunction = $callback; 354 | } 355 | 356 | /** 357 | * Get Opt 358 | * 359 | * @access public 360 | * @param $option 361 | * 362 | * @return mixed 363 | */ 364 | public function getOpt($option) 365 | { 366 | return isset($this->options[$option]) ? $this->options[$option] : null; 367 | } 368 | 369 | /** 370 | * Set Basic Authentication 371 | * 372 | * @access public 373 | * @param $username 374 | * @param $password 375 | */ 376 | public function setBasicAuthentication($username, $password = '') 377 | { 378 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 379 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 380 | } 381 | 382 | /** 383 | * Set Concurrency 384 | * 385 | * @access public 386 | * @param $concurrency 387 | */ 388 | public function setConcurrency($concurrency) 389 | { 390 | $this->concurrency = $concurrency; 391 | } 392 | 393 | /** 394 | * Set Digest Authentication 395 | * 396 | * @access public 397 | * @param $username 398 | * @param $password 399 | */ 400 | public function setDigestAuthentication($username, $password = '') 401 | { 402 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 403 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 404 | } 405 | 406 | /** 407 | * Set Cookie 408 | * 409 | * @access public 410 | * @param $key 411 | * @param $value 412 | */ 413 | public function setCookie($key, $value) 414 | { 415 | $this->cookies[$key] = $value; 416 | } 417 | 418 | /** 419 | * Set Cookies 420 | * 421 | * @access public 422 | * @param $cookies 423 | */ 424 | public function setCookies($cookies) 425 | { 426 | foreach ($cookies as $key => $value) { 427 | $this->cookies[$key] = $value; 428 | } 429 | } 430 | 431 | /** 432 | * Set Port 433 | * 434 | * @access public 435 | * @param $port 436 | */ 437 | public function setPort($port) 438 | { 439 | $this->setOpt(CURLOPT_PORT, intval($port)); 440 | } 441 | 442 | /** 443 | * Set Connect Timeout 444 | * 445 | * @access public 446 | * @param $seconds 447 | */ 448 | public function setConnectTimeout($seconds) 449 | { 450 | $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); 451 | } 452 | 453 | /** 454 | * Set Cookie String 455 | * 456 | * @access public 457 | * @param $string 458 | */ 459 | public function setCookieString($string) 460 | { 461 | $this->setOpt(CURLOPT_COOKIE, $string); 462 | } 463 | 464 | /** 465 | * Set Cookie File 466 | * 467 | * @access public 468 | * @param $cookie_file 469 | */ 470 | public function setCookieFile($cookie_file) 471 | { 472 | $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); 473 | } 474 | 475 | /** 476 | * Set Cookie Jar 477 | * 478 | * @access public 479 | * @param $cookie_jar 480 | */ 481 | public function setCookieJar($cookie_jar) 482 | { 483 | $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); 484 | } 485 | 486 | /** 487 | * Set Header 488 | * 489 | * Add extra header to include in the request. 490 | * 491 | * @access public 492 | * @param $key 493 | * @param $value 494 | */ 495 | public function setHeader($key, $value) 496 | { 497 | $this->headers[$key] = $value; 498 | } 499 | 500 | /** 501 | * Set Headers 502 | * 503 | * Add extra headers to include in the request. 504 | * 505 | * @access public 506 | * @param $headers 507 | */ 508 | public function setHeaders($headers) 509 | { 510 | foreach ($headers as $key => $value) { 511 | $this->headers[$key] = $value; 512 | } 513 | } 514 | 515 | /** 516 | * Set JSON Decoder 517 | * 518 | * @access public 519 | * @param $mixed boolean|callable 520 | */ 521 | public function setJsonDecoder($mixed) 522 | { 523 | if ($mixed === false) { 524 | $this->jsonDecoder = false; 525 | } elseif (is_callable($mixed)) { 526 | $this->jsonDecoder = $mixed; 527 | } 528 | } 529 | 530 | /** 531 | * Set XML Decoder 532 | * 533 | * @access public 534 | * @param $mixed boolean|callable 535 | */ 536 | public function setXmlDecoder($mixed) 537 | { 538 | if ($mixed === false) { 539 | $this->xmlDecoder = false; 540 | } elseif (is_callable($mixed)) { 541 | $this->xmlDecoder = $mixed; 542 | } 543 | } 544 | 545 | /** 546 | * Set Opt 547 | * 548 | * @access public 549 | * @param $option 550 | * @param $value 551 | */ 552 | public function setOpt($option, $value) 553 | { 554 | $this->options[$option] = $value; 555 | } 556 | 557 | /** 558 | * Set Opts 559 | * 560 | * @access public 561 | * @param $options 562 | */ 563 | public function setOpts($options) 564 | { 565 | foreach ($options as $option => $value) { 566 | $this->setOpt($option, $value); 567 | } 568 | } 569 | 570 | /** 571 | * Set Referer 572 | * 573 | * @access public 574 | * @param $referer 575 | */ 576 | public function setReferer($referer) 577 | { 578 | $this->setReferrer($referer); 579 | } 580 | 581 | /** 582 | * Set Referrer 583 | * 584 | * @access public 585 | * @param $referrer 586 | */ 587 | public function setReferrer($referrer) 588 | { 589 | $this->setOpt(CURLOPT_REFERER, $referrer); 590 | } 591 | 592 | /** 593 | * Set Retry 594 | * 595 | * Number of retries to attempt or decider callable. Maximum number of 596 | * attempts is $maximum_number_of_retries + 1. 597 | * 598 | * @access public 599 | * @param $mixed 600 | */ 601 | public function setRetry($mixed) 602 | { 603 | $this->retry = $mixed; 604 | } 605 | 606 | /** 607 | * Set Timeout 608 | * 609 | * @access public 610 | * @param $seconds 611 | */ 612 | public function setTimeout($seconds) 613 | { 614 | $this->setOpt(CURLOPT_TIMEOUT, $seconds); 615 | } 616 | 617 | /** 618 | * Set Url 619 | * 620 | * @access public 621 | * @param $url 622 | */ 623 | public function setUrl($url) 624 | { 625 | $this->baseUrl = $url; 626 | } 627 | 628 | /** 629 | * Set User Agent 630 | * 631 | * @access public 632 | * @param $user_agent 633 | */ 634 | public function setUserAgent($user_agent) 635 | { 636 | $this->setOpt(CURLOPT_USERAGENT, $user_agent); 637 | } 638 | 639 | /** 640 | * Start 641 | * 642 | * @access public 643 | */ 644 | public function start() 645 | { 646 | if ($this->isStarted) { 647 | return; 648 | } 649 | 650 | $this->isStarted = true; 651 | 652 | $concurrency = $this->concurrency; 653 | if ($concurrency > count($this->curls)) { 654 | $concurrency = count($this->curls); 655 | } 656 | 657 | for ($i = 0; $i < $concurrency; $i++) { 658 | $this->initHandle(array_shift($this->curls)); 659 | } 660 | 661 | do { 662 | // Wait for activity on any curl_multi connection when curl_multi_select (libcurl) fails to correctly block. 663 | // https://bugs.php.net/bug.php?id=63411 664 | if (curl_multi_select($this->multiCurl) === -1) { 665 | usleep(100000); 666 | } 667 | 668 | curl_multi_exec($this->multiCurl, $active); 669 | 670 | while (!($info_array = curl_multi_info_read($this->multiCurl)) === false) { 671 | if ($info_array['msg'] === CURLMSG_DONE) { 672 | foreach ($this->activeCurls as $key => $ch) { 673 | if ($ch->curl === $info_array['handle']) { 674 | // Set the error code for multi handles using the "result" key in the array returned by 675 | // curl_multi_info_read(). Using curl_errno() on a multi handle will incorrectly return 0 676 | // for errors. 677 | $ch->curlErrorCode = $info_array['result']; 678 | $ch->exec($ch->curl); 679 | 680 | if ($ch->attemptRetry()) { 681 | // Remove completed handle before adding again in order to retry request. 682 | curl_multi_remove_handle($this->multiCurl, $ch->curl); 683 | 684 | $curlm_error_code = curl_multi_add_handle($this->multiCurl, $ch->curl); 685 | if (!($curlm_error_code === CURLM_OK)) { 686 | throw new \ErrorException( 687 | 'cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code) 688 | ); 689 | } 690 | } else { 691 | $ch->execDone(); 692 | 693 | // Remove completed instance from active curls. 694 | unset($this->activeCurls[$key]); 695 | 696 | // Start a new request before removing the handle of the completed one. 697 | if (count($this->curls) >= 1) { 698 | $this->initHandle(array_shift($this->curls)); 699 | } 700 | curl_multi_remove_handle($this->multiCurl, $ch->curl); 701 | 702 | // Clean up completed instance. 703 | $ch->close(); 704 | } 705 | 706 | break; 707 | } 708 | } 709 | } 710 | } 711 | 712 | if (!$active) { 713 | $active = count($this->activeCurls); 714 | } 715 | } while ($active > 0); 716 | 717 | $this->isStarted = false; 718 | } 719 | 720 | /** 721 | * Success 722 | * 723 | * @access public 724 | * @param $callback 725 | */ 726 | public function success($callback) 727 | { 728 | $this->successFunction = $callback; 729 | } 730 | 731 | /** 732 | * Unset Header 733 | * 734 | * Remove extra header previously set using Curl::setHeader(). 735 | * 736 | * @access public 737 | * @param $key 738 | */ 739 | public function unsetHeader($key) 740 | { 741 | unset($this->headers[$key]); 742 | } 743 | 744 | /** 745 | * Remove Header 746 | * 747 | * Remove an internal header from the request. 748 | * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. 749 | * 750 | * @access public 751 | * @param $key 752 | */ 753 | public function removeHeader($key) 754 | { 755 | $this->setHeader($key, ''); 756 | } 757 | 758 | /** 759 | * Verbose 760 | * 761 | * @access public 762 | * @param bool $on 763 | * @param resource $output 764 | */ 765 | public function verbose($on = true, $output = STDERR) 766 | { 767 | // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side 768 | // effect of causing Curl::requestHeaders to be empty. 769 | if ($on) { 770 | $this->setOpt(CURLINFO_HEADER_OUT, false); 771 | } 772 | $this->setOpt(CURLOPT_VERBOSE, $on); 773 | $this->setOpt(CURLOPT_STDERR, $output); 774 | } 775 | 776 | /** 777 | * Destruct 778 | * 779 | * @access public 780 | */ 781 | public function __destruct() 782 | { 783 | $this->close(); 784 | } 785 | 786 | /** 787 | * Queue Handle 788 | * 789 | * @access private 790 | * @param $curl 791 | */ 792 | private function queueHandle($curl) 793 | { 794 | // Use sequential ids to allow for ordered post processing. 795 | $curl->id = $this->nextCurlId++; 796 | $curl->isChildOfMultiCurl = true; 797 | $this->curls[$curl->id] = $curl; 798 | } 799 | 800 | /** 801 | * Init Handle 802 | * 803 | * @access private 804 | * @param $curl 805 | * @throws \ErrorException 806 | */ 807 | private function initHandle($curl) 808 | { 809 | // Set callbacks if not already individually set. 810 | if ($curl->beforeSendFunction === null) { 811 | $curl->beforeSend($this->beforeSendFunction); 812 | } 813 | if ($curl->successFunction === null) { 814 | $curl->success($this->successFunction); 815 | } 816 | if ($curl->errorFunction === null) { 817 | $curl->error($this->errorFunction); 818 | } 819 | if ($curl->completeFunction === null) { 820 | $curl->complete($this->completeFunction); 821 | } 822 | 823 | $curl->setOpts($this->options); 824 | $curl->setHeaders($this->headers); 825 | $curl->setRetry($this->retry); 826 | 827 | foreach ($this->cookies as $key => $value) { 828 | $curl->setCookie($key, $value); 829 | } 830 | 831 | $curl->setJsonDecoder($this->jsonDecoder); 832 | $curl->setXmlDecoder($this->xmlDecoder); 833 | 834 | $curlm_error_code = curl_multi_add_handle($this->multiCurl, $curl->curl); 835 | if (!($curlm_error_code === CURLM_OK)) { 836 | throw new \ErrorException('cURL multi add handle error: ' . curl_multi_strerror($curlm_error_code)); 837 | } 838 | 839 | $this->activeCurls[$curl->id] = $curl; 840 | $curl->call($curl->beforeSendFunction); 841 | } 842 | } 843 | -------------------------------------------------------------------------------- /core/vendor/php-curl-class/php-curl-class/src/Curl/Curl.php: -------------------------------------------------------------------------------- 1 | 63 | // CTL = 65 | // separators = "(" | ")" | "<" | ">" | "@" 66 | // | "," | ";" | ":" | "\" | <"> 67 | // | "/" | "[" | "]" | "?" | "=" 68 | // | "{" | "}" | SP | HT 69 | // SP = 70 | // HT = 71 | // <"> = 72 | '!', '#', '$', '%', '&', "'", '*', '+', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 73 | 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 74 | 'Y', 'Z', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 75 | 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~', 76 | ); 77 | public static $RFC6265 = array( 78 | // RFC 6265: "US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash". 79 | // %x21 80 | '!', 81 | // %x23-2B 82 | '#', '$', '%', '&', "'", '(', ')', '*', '+', 83 | // %x2D-3A 84 | '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', 85 | // %x3C-5B 86 | '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 87 | 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', 88 | // %x5D-7E 89 | ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 90 | 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 91 | ); 92 | 93 | private static $deferredProperties = array( 94 | 'effectiveUrl', 95 | 'rfc2616', 96 | 'rfc6265', 97 | 'totalTime', 98 | ); 99 | 100 | /** 101 | * Construct 102 | * 103 | * @access public 104 | * @param $base_url 105 | * @throws \ErrorException 106 | */ 107 | public function __construct($base_url = null) 108 | { 109 | if (!extension_loaded('curl')) { 110 | throw new \ErrorException('cURL library is not loaded'); 111 | } 112 | 113 | $this->curl = curl_init(); 114 | $this->id = uniqid('', true); 115 | $this->setDefaultUserAgent(); 116 | $this->setDefaultTimeout(); 117 | $this->setOpt(CURLINFO_HEADER_OUT, true); 118 | 119 | // Create a placeholder to temporarily store the header callback data. 120 | $header_callback_data = new \stdClass(); 121 | $header_callback_data->rawResponseHeaders = ''; 122 | $header_callback_data->responseCookies = array(); 123 | $this->headerCallbackData = $header_callback_data; 124 | $this->setOpt(CURLOPT_HEADERFUNCTION, $this->createHeaderCallback($header_callback_data)); 125 | 126 | $this->setOpt(CURLOPT_RETURNTRANSFER, true); 127 | $this->headers = new CaseInsensitiveArray(); 128 | $this->setUrl($base_url); 129 | } 130 | 131 | /** 132 | * Before Send 133 | * 134 | * @access public 135 | * @param $callback 136 | */ 137 | public function beforeSend($callback) 138 | { 139 | $this->beforeSendFunction = $callback; 140 | } 141 | 142 | /** 143 | * Build Post Data 144 | * 145 | * @access public 146 | * @param $data 147 | * 148 | * @return array|string 149 | */ 150 | public function buildPostData($data) 151 | { 152 | $binary_data = false; 153 | if (is_array($data)) { 154 | // Return JSON-encoded string when the request's content-type is JSON. 155 | if (isset($this->headers['Content-Type']) && 156 | preg_match($this->jsonPattern, $this->headers['Content-Type'])) { 157 | $json_str = json_encode($data); 158 | if (!($json_str === false)) { 159 | $data = $json_str; 160 | } 161 | } else { 162 | // Manually build a single-dimensional array from a multi-dimensional array as using curl_setopt($ch, 163 | // CURLOPT_POSTFIELDS, $data) doesn't correctly handle multi-dimensional arrays when files are 164 | // referenced. 165 | if (ArrayUtil::is_array_multidim($data)) { 166 | $data = ArrayUtil::array_flatten_multidim($data); 167 | } 168 | 169 | // Modify array values to ensure any referenced files are properly handled depending on the support of 170 | // the @filename API or CURLFile usage. This also fixes the warning "curl_setopt(): The usage of the 171 | // @filename API for file uploading is deprecated. Please use the CURLFile class instead". Ignore 172 | // non-file values prefixed with the @ character. 173 | foreach ($data as $key => $value) { 174 | if (is_string($value) && strpos($value, '@') === 0 && is_file(substr($value, 1))) { 175 | $binary_data = true; 176 | if (class_exists('CURLFile')) { 177 | $data[$key] = new \CURLFile(substr($value, 1)); 178 | } 179 | } elseif ($value instanceof \CURLFile) { 180 | $binary_data = true; 181 | } 182 | } 183 | } 184 | } 185 | 186 | if (!$binary_data && (is_array($data) || is_object($data))) { 187 | $data = http_build_query($data, '', '&'); 188 | } 189 | 190 | return $data; 191 | } 192 | 193 | /** 194 | * Call 195 | * 196 | * @access public 197 | */ 198 | public function call() 199 | { 200 | $args = func_get_args(); 201 | $function = array_shift($args); 202 | if (is_callable($function)) { 203 | array_unshift($args, $this); 204 | call_user_func_array($function, $args); 205 | } 206 | } 207 | 208 | /** 209 | * Close 210 | * 211 | * @access public 212 | */ 213 | public function close() 214 | { 215 | if (is_resource($this->curl)) { 216 | curl_close($this->curl); 217 | } 218 | $this->options = null; 219 | $this->jsonDecoder = null; 220 | $this->jsonDecoderArgs = null; 221 | $this->xmlDecoder = null; 222 | $this->defaultDecoder = null; 223 | } 224 | 225 | /** 226 | * Complete 227 | * 228 | * @access public 229 | * @param $callback 230 | */ 231 | public function complete($callback) 232 | { 233 | $this->completeFunction = $callback; 234 | } 235 | 236 | /** 237 | * Progress 238 | * 239 | * @access public 240 | * @param $callback 241 | */ 242 | public function progress($callback) 243 | { 244 | $this->setOpt(CURLOPT_PROGRESSFUNCTION, $callback); 245 | $this->setOpt(CURLOPT_NOPROGRESS, false); 246 | } 247 | 248 | /** 249 | * Delete 250 | * 251 | * @access public 252 | * @param $url 253 | * @param $query_parameters 254 | * @param $data 255 | * 256 | * @return mixed 257 | */ 258 | public function delete($url, $query_parameters = array(), $data = array()) 259 | { 260 | if (is_array($url)) { 261 | $data = $query_parameters; 262 | $query_parameters = $url; 263 | $url = (string)$this->url; 264 | } 265 | 266 | $this->setUrl($url, $query_parameters); 267 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); 268 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 269 | return $this->exec(); 270 | } 271 | 272 | /** 273 | * Download 274 | * 275 | * @access public 276 | * @param $url 277 | * @param $mixed_filename 278 | * 279 | * @return boolean 280 | */ 281 | public function download($url, $mixed_filename) 282 | { 283 | if (is_callable($mixed_filename)) { 284 | $this->downloadCompleteFunction = $mixed_filename; 285 | $this->fileHandle = tmpfile(); 286 | } else { 287 | $filename = $mixed_filename; 288 | 289 | // Use a temporary file when downloading. Not using a temporary file can cause an error when an existing 290 | // file has already fully completed downloading and a new download is started with the same destination save 291 | // path. The download request will include header "Range: bytes=$filesize-" which is syntactically valid, 292 | // but unsatisfiable. 293 | $download_filename = $filename . '.pccdownload'; 294 | 295 | $mode = 'wb'; 296 | // Attempt to resume download only when a temporary download file exists and is not empty. 297 | if (file_exists($download_filename) && $filesize = filesize($download_filename)) { 298 | $mode = 'ab'; 299 | $first_byte_position = $filesize; 300 | $range = $first_byte_position . '-'; 301 | $this->setOpt(CURLOPT_RANGE, $range); 302 | } 303 | $this->fileHandle = fopen($download_filename, $mode); 304 | 305 | // Move the downloaded temporary file to the destination save path. 306 | $this->downloadCompleteFunction = function ($instance, $fh) use ($download_filename, $filename) { 307 | // Close the open file handle before renaming the file. 308 | if (is_resource($fh)) { 309 | fclose($fh); 310 | } 311 | 312 | rename($download_filename, $filename); 313 | }; 314 | } 315 | 316 | $this->setOpt(CURLOPT_FILE, $this->fileHandle); 317 | $this->get($url); 318 | 319 | return ! $this->error; 320 | } 321 | 322 | /** 323 | * Error 324 | * 325 | * @access public 326 | * @param $callback 327 | */ 328 | public function error($callback) 329 | { 330 | $this->errorFunction = $callback; 331 | } 332 | 333 | /** 334 | * Exec 335 | * 336 | * @access public 337 | * @param $ch 338 | * 339 | * @return mixed Returns the value provided by parseResponse. 340 | */ 341 | public function exec($ch = null) 342 | { 343 | $this->attempts += 1; 344 | 345 | if ($ch === null) { 346 | $this->responseCookies = array(); 347 | $this->call($this->beforeSendFunction); 348 | $this->rawResponse = curl_exec($this->curl); 349 | $this->curlErrorCode = curl_errno($this->curl); 350 | $this->curlErrorMessage = curl_error($this->curl); 351 | } else { 352 | $this->rawResponse = curl_multi_getcontent($ch); 353 | $this->curlErrorMessage = curl_error($ch); 354 | } 355 | $this->curlError = !($this->curlErrorCode === 0); 356 | 357 | // Transfer the header callback data and release the temporary store to avoid memory leak. 358 | $this->rawResponseHeaders = $this->headerCallbackData->rawResponseHeaders; 359 | $this->responseCookies = $this->headerCallbackData->responseCookies; 360 | $this->headerCallbackData->rawResponseHeaders = null; 361 | $this->headerCallbackData->responseCookies = null; 362 | 363 | // Include additional error code information in error message when possible. 364 | if ($this->curlError && function_exists('curl_strerror')) { 365 | $this->curlErrorMessage = 366 | curl_strerror($this->curlErrorCode) . ( 367 | empty($this->curlErrorMessage) ? '' : ': ' . $this->curlErrorMessage 368 | ); 369 | } 370 | 371 | $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); 372 | $this->httpError = in_array(floor($this->httpStatusCode / 100), array(4, 5)); 373 | $this->error = $this->curlError || $this->httpError; 374 | $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0; 375 | 376 | // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders 377 | // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);). 378 | if ($this->getOpt(CURLINFO_HEADER_OUT) === true) { 379 | $this->requestHeaders = $this->parseRequestHeaders($this->getInfo(CURLINFO_HEADER_OUT)); 380 | } 381 | $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders); 382 | $this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse); 383 | 384 | $this->httpErrorMessage = ''; 385 | if ($this->error) { 386 | if (isset($this->responseHeaders['Status-Line'])) { 387 | $this->httpErrorMessage = $this->responseHeaders['Status-Line']; 388 | } 389 | } 390 | $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage; 391 | 392 | // Reset select deferred properties so that they may be recalculated. 393 | unset($this->effectiveUrl); 394 | unset($this->totalTime); 395 | 396 | // Reset content-length possibly set from a PUT or SEARCH request. 397 | $this->unsetHeader('Content-Length'); 398 | 399 | // Reset nobody setting possibly set from a HEAD request. 400 | $this->setOpt(CURLOPT_NOBODY, false); 401 | 402 | // Allow multicurl to attempt retry as needed. 403 | if ($this->isChildOfMultiCurl) { 404 | return; 405 | } 406 | 407 | if ($this->attemptRetry()) { 408 | return $this->exec($ch); 409 | } 410 | 411 | $this->execDone(); 412 | 413 | return $this->response; 414 | } 415 | 416 | public function execDone() 417 | { 418 | if ($this->error) { 419 | $this->call($this->errorFunction); 420 | } else { 421 | $this->call($this->successFunction); 422 | } 423 | 424 | $this->call($this->completeFunction); 425 | 426 | // Close open file handles and reset the curl instance. 427 | if (!($this->fileHandle === null)) { 428 | $this->downloadComplete($this->fileHandle); 429 | } 430 | } 431 | 432 | /** 433 | * Get 434 | * 435 | * @access public 436 | * @param $url 437 | * @param $data 438 | * 439 | * @return mixed Returns the value provided by exec. 440 | */ 441 | public function get($url, $data = array()) 442 | { 443 | if (is_array($url)) { 444 | $data = $url; 445 | $url = (string)$this->url; 446 | } 447 | $this->setUrl($url, $data); 448 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'GET'); 449 | $this->setOpt(CURLOPT_HTTPGET, true); 450 | return $this->exec(); 451 | } 452 | 453 | /** 454 | * Get Info 455 | * 456 | * @access public 457 | * @param $opt 458 | * 459 | * @return mixed 460 | */ 461 | public function getInfo($opt = null) 462 | { 463 | $args = array(); 464 | $args[] = $this->curl; 465 | 466 | if (func_num_args()) { 467 | $args[] = $opt; 468 | } 469 | 470 | return call_user_func_array('curl_getinfo', $args); 471 | } 472 | 473 | /** 474 | * Get Opt 475 | * 476 | * @access public 477 | * @param $option 478 | * 479 | * @return mixed 480 | */ 481 | public function getOpt($option) 482 | { 483 | return isset($this->options[$option]) ? $this->options[$option] : null; 484 | } 485 | 486 | /** 487 | * Head 488 | * 489 | * @access public 490 | * @param $url 491 | * @param $data 492 | * 493 | * @return mixed 494 | */ 495 | public function head($url, $data = array()) 496 | { 497 | if (is_array($url)) { 498 | $data = $url; 499 | $url = (string)$this->url; 500 | } 501 | $this->setUrl($url, $data); 502 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'HEAD'); 503 | $this->setOpt(CURLOPT_NOBODY, true); 504 | return $this->exec(); 505 | } 506 | 507 | /** 508 | * Options 509 | * 510 | * @access public 511 | * @param $url 512 | * @param $data 513 | * 514 | * @return mixed 515 | */ 516 | public function options($url, $data = array()) 517 | { 518 | if (is_array($url)) { 519 | $data = $url; 520 | $url = (string)$this->url; 521 | } 522 | $this->setUrl($url, $data); 523 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'OPTIONS'); 524 | return $this->exec(); 525 | } 526 | 527 | /** 528 | * Patch 529 | * 530 | * @access public 531 | * @param $url 532 | * @param $data 533 | * 534 | * @return mixed 535 | */ 536 | public function patch($url, $data = array()) 537 | { 538 | if (is_array($url)) { 539 | $data = $url; 540 | $url = (string)$this->url; 541 | } 542 | 543 | if (is_array($data) && empty($data)) { 544 | $this->removeHeader('Content-Length'); 545 | } 546 | 547 | $this->setUrl($url); 548 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); 549 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 550 | return $this->exec(); 551 | } 552 | 553 | /** 554 | * Post 555 | * 556 | * @access public 557 | * @param $url 558 | * @param $data 559 | * @param $follow_303_with_post 560 | * If true, will cause 303 redirections to be followed using a POST request (default: false). 561 | * Notes: 562 | * - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true. 563 | * - According to the HTTP specs (see [1]), a 303 redirection should be followed using 564 | * the GET method. 301 and 302 must not. 565 | * - In order to force a 303 redirection to be performed using the same method, the 566 | * underlying cURL object must be set in a special state (the CURLOPT_CURSTOMREQUEST 567 | * option must be set to the method to use after the redirection). Due to a limitation 568 | * of the cURL extension of PHP < 5.5.11 ([2], [3]) and of HHVM, it is not possible 569 | * to reset this option. Using these PHP engines, it is therefore impossible to 570 | * restore this behavior on an existing php-curl-class Curl object. 571 | * 572 | * @return mixed 573 | * 574 | * [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2 575 | * [2] https://github.com/php/php-src/pull/531 576 | * [3] http://php.net/ChangeLog-5.php#5.5.11 577 | */ 578 | public function post($url, $data = array(), $follow_303_with_post = false) 579 | { 580 | if (is_array($url)) { 581 | $follow_303_with_post = (bool)$data; 582 | $data = $url; 583 | $url = (string)$this->url; 584 | } 585 | 586 | $this->setUrl($url); 587 | 588 | if ($follow_303_with_post) { 589 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); 590 | } else { 591 | if (isset($this->options[CURLOPT_CUSTOMREQUEST])) { 592 | if ((version_compare(PHP_VERSION, '5.5.11') < 0) || defined('HHVM_VERSION')) { 593 | trigger_error( 594 | 'Due to technical limitations of PHP <= 5.5.11 and HHVM, it is not possible to ' 595 | . 'perform a post-redirect-get request using a php-curl-class Curl object that ' 596 | . 'has already been used to perform other types of requests. Either use a new ' 597 | . 'php-curl-class Curl object or upgrade your PHP engine.', 598 | E_USER_ERROR 599 | ); 600 | } else { 601 | $this->setOpt(CURLOPT_CUSTOMREQUEST, null); 602 | } 603 | } 604 | } 605 | 606 | $this->setOpt(CURLOPT_POST, true); 607 | $this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data)); 608 | return $this->exec(); 609 | } 610 | 611 | /** 612 | * Put 613 | * 614 | * @access public 615 | * @param $url 616 | * @param $data 617 | * 618 | * @return mixed 619 | */ 620 | public function put($url, $data = array()) 621 | { 622 | if (is_array($url)) { 623 | $data = $url; 624 | $url = (string)$this->url; 625 | } 626 | $this->setUrl($url); 627 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); 628 | $put_data = $this->buildPostData($data); 629 | if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { 630 | if (is_string($put_data)) { 631 | $this->setHeader('Content-Length', strlen($put_data)); 632 | } 633 | } 634 | if (!empty($put_data)) { 635 | $this->setOpt(CURLOPT_POSTFIELDS, $put_data); 636 | } 637 | return $this->exec(); 638 | } 639 | 640 | /** 641 | * Search 642 | * 643 | * @access public 644 | * @param $url 645 | * @param $data 646 | * 647 | * @return mixed 648 | */ 649 | public function search($url, $data = array()) 650 | { 651 | if (is_array($url)) { 652 | $data = $url; 653 | $url = (string)$this->url; 654 | } 655 | $this->setUrl($url); 656 | $this->setOpt(CURLOPT_CUSTOMREQUEST, 'SEARCH'); 657 | $put_data = $this->buildPostData($data); 658 | if (empty($this->options[CURLOPT_INFILE]) && empty($this->options[CURLOPT_INFILESIZE])) { 659 | if (is_string($put_data)) { 660 | $this->setHeader('Content-Length', strlen($put_data)); 661 | } 662 | } 663 | if (!empty($put_data)) { 664 | $this->setOpt(CURLOPT_POSTFIELDS, $put_data); 665 | } 666 | return $this->exec(); 667 | } 668 | 669 | /** 670 | * Set Basic Authentication 671 | * 672 | * @access public 673 | * @param $username 674 | * @param $password 675 | */ 676 | public function setBasicAuthentication($username, $password = '') 677 | { 678 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); 679 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 680 | } 681 | 682 | /** 683 | * Set Digest Authentication 684 | * 685 | * @access public 686 | * @param $username 687 | * @param $password 688 | */ 689 | public function setDigestAuthentication($username, $password = '') 690 | { 691 | $this->setOpt(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 692 | $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); 693 | } 694 | 695 | /** 696 | * Set Cookie 697 | * 698 | * @access public 699 | * @param $key 700 | * @param $value 701 | */ 702 | public function setCookie($key, $value) 703 | { 704 | $this->setEncodedCookie($key, $value); 705 | $this->buildCookies(); 706 | } 707 | 708 | /** 709 | * Set Cookies 710 | * 711 | * @access public 712 | * @param $cookies 713 | */ 714 | public function setCookies($cookies) 715 | { 716 | foreach ($cookies as $key => $value) { 717 | $this->setEncodedCookie($key, $value); 718 | } 719 | $this->buildCookies(); 720 | } 721 | 722 | /** 723 | * Get Cookie 724 | * 725 | * @access public 726 | * @param $key 727 | * 728 | * @return mixed 729 | */ 730 | public function getCookie($key) 731 | { 732 | return $this->getResponseCookie($key); 733 | } 734 | 735 | /** 736 | * Get Response Cookie 737 | * 738 | * @access public 739 | * @param $key 740 | * 741 | * @return mixed 742 | */ 743 | public function getResponseCookie($key) 744 | { 745 | return isset($this->responseCookies[$key]) ? $this->responseCookies[$key] : null; 746 | } 747 | 748 | /** 749 | * Get Response Cookies 750 | * 751 | * @access public 752 | * 753 | * @return array 754 | */ 755 | public function getResponseCookies() 756 | { 757 | return $this->responseCookies; 758 | } 759 | 760 | /** 761 | * Set Max Filesize 762 | * 763 | * @access public 764 | * @param $bytes 765 | */ 766 | public function setMaxFilesize($bytes) 767 | { 768 | // Make compatible with PHP version both before and after 5.5.0. PHP 5.5.0 added the cURL resource as the first 769 | // argument to the CURLOPT_PROGRESSFUNCTION callback. 770 | $gte_v550 = version_compare(PHP_VERSION, '5.5.0') >= 0; 771 | if ($gte_v550) { 772 | $callback = function ($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($bytes) { 773 | // Abort the transfer when $downloaded bytes exceeds maximum $bytes by returning a non-zero value. 774 | return $downloaded > $bytes ? 1 : 0; 775 | }; 776 | } else { 777 | $callback = function ($download_size, $downloaded, $upload_size, $uploaded) use ($bytes) { 778 | return $downloaded > $bytes ? 1 : 0; 779 | }; 780 | } 781 | 782 | $this->progress($callback); 783 | } 784 | 785 | /** 786 | * Set Port 787 | * 788 | * @access public 789 | * @param $port 790 | */ 791 | public function setPort($port) 792 | { 793 | $this->setOpt(CURLOPT_PORT, intval($port)); 794 | } 795 | 796 | /** 797 | * Set Connect Timeout 798 | * 799 | * @access public 800 | * @param $seconds 801 | */ 802 | public function setConnectTimeout($seconds) 803 | { 804 | $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); 805 | } 806 | 807 | /** 808 | * Set Cookie String 809 | * 810 | * @access public 811 | * @param $string 812 | * 813 | * @return bool 814 | */ 815 | public function setCookieString($string) 816 | { 817 | return $this->setOpt(CURLOPT_COOKIE, $string); 818 | } 819 | 820 | /** 821 | * Set Cookie File 822 | * 823 | * @access public 824 | * @param $cookie_file 825 | * 826 | * @return boolean 827 | */ 828 | public function setCookieFile($cookie_file) 829 | { 830 | return $this->setOpt(CURLOPT_COOKIEFILE, $cookie_file); 831 | } 832 | 833 | /** 834 | * Set Cookie Jar 835 | * 836 | * @access public 837 | * @param $cookie_jar 838 | * 839 | * @return boolean 840 | */ 841 | public function setCookieJar($cookie_jar) 842 | { 843 | return $this->setOpt(CURLOPT_COOKIEJAR, $cookie_jar); 844 | } 845 | 846 | /** 847 | * Set Default JSON Decoder 848 | * 849 | * @access public 850 | * @param $assoc 851 | * @param $depth 852 | * @param $options 853 | */ 854 | public function setDefaultJsonDecoder() 855 | { 856 | $this->jsonDecoder = '\Curl\Decoder::decodeJson'; 857 | $this->jsonDecoderArgs = func_get_args(); 858 | } 859 | 860 | /** 861 | * Set Default XML Decoder 862 | * 863 | * @access public 864 | */ 865 | public function setDefaultXmlDecoder() 866 | { 867 | $this->xmlDecoder = '\Curl\Decoder::decodeXml'; 868 | } 869 | 870 | /** 871 | * Set Default Decoder 872 | * 873 | * @access public 874 | * @param $mixed boolean|callable|string 875 | */ 876 | public function setDefaultDecoder($mixed = 'json') 877 | { 878 | if ($mixed === false) { 879 | $this->defaultDecoder = false; 880 | } elseif (is_callable($mixed)) { 881 | $this->defaultDecoder = $mixed; 882 | } else { 883 | if ($mixed === 'json') { 884 | $this->defaultDecoder = $this->jsonDecoder; 885 | } elseif ($mixed === 'xml') { 886 | $this->defaultDecoder = $this->xmlDecoder; 887 | } 888 | } 889 | } 890 | 891 | /** 892 | * Set Default Timeout 893 | * 894 | * @access public 895 | */ 896 | public function setDefaultTimeout() 897 | { 898 | $this->setTimeout(self::DEFAULT_TIMEOUT); 899 | } 900 | 901 | /** 902 | * Set Default User Agent 903 | * 904 | * @access public 905 | */ 906 | public function setDefaultUserAgent() 907 | { 908 | $user_agent = 'PHP-Curl-Class/' . self::VERSION . ' (+https://github.com/php-curl-class/php-curl-class)'; 909 | $user_agent .= ' PHP/' . PHP_VERSION; 910 | $curl_version = curl_version(); 911 | $user_agent .= ' curl/' . $curl_version['version']; 912 | $this->setUserAgent($user_agent); 913 | } 914 | 915 | /** 916 | * Set Header 917 | * 918 | * Add extra header to include in the request. 919 | * 920 | * @access public 921 | * @param $key 922 | * @param $value 923 | */ 924 | public function setHeader($key, $value) 925 | { 926 | $this->headers[$key] = $value; 927 | $headers = array(); 928 | foreach ($this->headers as $key => $value) { 929 | $headers[] = $key . ': ' . $value; 930 | } 931 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 932 | } 933 | 934 | /** 935 | * Set Headers 936 | * 937 | * Add extra headers to include in the request. 938 | * 939 | * @access public 940 | * @param $headers 941 | */ 942 | public function setHeaders($headers) 943 | { 944 | foreach ($headers as $key => $value) { 945 | $this->headers[$key] = $value; 946 | } 947 | 948 | $headers = array(); 949 | foreach ($this->headers as $key => $value) { 950 | $headers[] = $key . ': ' . $value; 951 | } 952 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 953 | } 954 | 955 | /** 956 | * Set JSON Decoder 957 | * 958 | * @access public 959 | * @param $mixed boolean|callable 960 | */ 961 | public function setJsonDecoder($mixed) 962 | { 963 | if ($mixed === false) { 964 | $this->jsonDecoder = false; 965 | $this->jsonDecoderArgs = array(); 966 | } elseif (is_callable($mixed)) { 967 | $this->jsonDecoder = $mixed; 968 | $this->jsonDecoderArgs = array(); 969 | } 970 | } 971 | 972 | /** 973 | * Set XML Decoder 974 | * 975 | * @access public 976 | * @param $mixed boolean|callable 977 | */ 978 | public function setXmlDecoder($mixed) 979 | { 980 | if ($mixed === false) { 981 | $this->xmlDecoder = false; 982 | } elseif (is_callable($mixed)) { 983 | $this->xmlDecoder = $mixed; 984 | } 985 | } 986 | 987 | /** 988 | * Set Opt 989 | * 990 | * @access public 991 | * @param $option 992 | * @param $value 993 | * 994 | * @return boolean 995 | */ 996 | public function setOpt($option, $value) 997 | { 998 | $required_options = array( 999 | CURLOPT_RETURNTRANSFER => 'CURLOPT_RETURNTRANSFER', 1000 | ); 1001 | 1002 | if (in_array($option, array_keys($required_options), true) && !($value === true)) { 1003 | trigger_error($required_options[$option] . ' is a required option', E_USER_WARNING); 1004 | } 1005 | 1006 | $success = curl_setopt($this->curl, $option, $value); 1007 | if ($success) { 1008 | $this->options[$option] = $value; 1009 | } 1010 | return $success; 1011 | } 1012 | 1013 | /** 1014 | * Set Opts 1015 | * 1016 | * @access public 1017 | * @param $options 1018 | * 1019 | * @return boolean 1020 | * Returns true if all options were successfully set. If an option could not be successfully set, false is 1021 | * immediately returned, ignoring any future options in the options array. Similar to curl_setopt_array(). 1022 | */ 1023 | public function setOpts($options) 1024 | { 1025 | foreach ($options as $option => $value) { 1026 | if (!$this->setOpt($option, $value)) { 1027 | return false; 1028 | } 1029 | } 1030 | return true; 1031 | } 1032 | 1033 | /** 1034 | * Set Referer 1035 | * 1036 | * @access public 1037 | * @param $referer 1038 | */ 1039 | public function setReferer($referer) 1040 | { 1041 | $this->setReferrer($referer); 1042 | } 1043 | 1044 | /** 1045 | * Set Referrer 1046 | * 1047 | * @access public 1048 | * @param $referrer 1049 | */ 1050 | public function setReferrer($referrer) 1051 | { 1052 | $this->setOpt(CURLOPT_REFERER, $referrer); 1053 | } 1054 | 1055 | /** 1056 | * Set Retry 1057 | * 1058 | * Number of retries to attempt or decider callable. Maximum number of 1059 | * attempts is $maximum_number_of_retries + 1. 1060 | * 1061 | * @access public 1062 | * @param $mixed 1063 | */ 1064 | public function setRetry($mixed) 1065 | { 1066 | if (is_callable($mixed)) { 1067 | $this->retryDecider = $mixed; 1068 | } elseif (is_int($mixed)) { 1069 | $maximum_number_of_retries = $mixed; 1070 | $this->remainingRetries = $maximum_number_of_retries; 1071 | } 1072 | } 1073 | 1074 | /** 1075 | * Set Timeout 1076 | * 1077 | * @access public 1078 | * @param $seconds 1079 | */ 1080 | public function setTimeout($seconds) 1081 | { 1082 | $this->setOpt(CURLOPT_TIMEOUT, $seconds); 1083 | } 1084 | 1085 | /** 1086 | * Set Url 1087 | * 1088 | * @access public 1089 | * @param $url 1090 | * @param $mixed_data 1091 | */ 1092 | public function setUrl($url, $mixed_data = '') 1093 | { 1094 | $built_url = $this->buildUrl($url, $mixed_data); 1095 | 1096 | if ($this->url === null) { 1097 | $this->url = (string)new Url($built_url); 1098 | } else { 1099 | $this->url = (string)new Url($this->url, $built_url); 1100 | } 1101 | 1102 | $this->setOpt(CURLOPT_URL, $this->url); 1103 | } 1104 | 1105 | /** 1106 | * Set User Agent 1107 | * 1108 | * @access public 1109 | * @param $user_agent 1110 | */ 1111 | public function setUserAgent($user_agent) 1112 | { 1113 | $this->setOpt(CURLOPT_USERAGENT, $user_agent); 1114 | } 1115 | 1116 | /** 1117 | * Attempt Retry 1118 | * 1119 | * @access public 1120 | */ 1121 | public function attemptRetry() 1122 | { 1123 | $attempt_retry = false; 1124 | if ($this->error) { 1125 | if ($this->retryDecider === null) { 1126 | $attempt_retry = $this->remainingRetries >= 1; 1127 | } else { 1128 | $attempt_retry = call_user_func($this->retryDecider, $this); 1129 | } 1130 | if ($attempt_retry) { 1131 | $this->retries += 1; 1132 | if ($this->remainingRetries) { 1133 | $this->remainingRetries -= 1; 1134 | } 1135 | } 1136 | } 1137 | return $attempt_retry; 1138 | } 1139 | 1140 | /** 1141 | * Success 1142 | * 1143 | * @access public 1144 | * @param $callback 1145 | */ 1146 | public function success($callback) 1147 | { 1148 | $this->successFunction = $callback; 1149 | } 1150 | 1151 | /** 1152 | * Unset Header 1153 | * 1154 | * Remove extra header previously set using Curl::setHeader(). 1155 | * 1156 | * @access public 1157 | * @param $key 1158 | */ 1159 | public function unsetHeader($key) 1160 | { 1161 | unset($this->headers[$key]); 1162 | $headers = array(); 1163 | foreach ($this->headers as $key => $value) { 1164 | $headers[] = $key . ': ' . $value; 1165 | } 1166 | $this->setOpt(CURLOPT_HTTPHEADER, $headers); 1167 | } 1168 | 1169 | /** 1170 | * Remove Header 1171 | * 1172 | * Remove an internal header from the request. 1173 | * Using `curl -H "Host:" ...' is equivalent to $curl->removeHeader('Host');. 1174 | * 1175 | * @access public 1176 | * @param $key 1177 | */ 1178 | public function removeHeader($key) 1179 | { 1180 | $this->setHeader($key, ''); 1181 | } 1182 | 1183 | /** 1184 | * Verbose 1185 | * 1186 | * @access public 1187 | * @param bool $on 1188 | * @param resource $output 1189 | */ 1190 | public function verbose($on = true, $output = STDERR) 1191 | { 1192 | // Turn off CURLINFO_HEADER_OUT for verbose to work. This has the side 1193 | // effect of causing Curl::requestHeaders to be empty. 1194 | if ($on) { 1195 | $this->setOpt(CURLINFO_HEADER_OUT, false); 1196 | } 1197 | $this->setOpt(CURLOPT_VERBOSE, $on); 1198 | $this->setOpt(CURLOPT_STDERR, $output); 1199 | } 1200 | 1201 | /** 1202 | * Destruct 1203 | * 1204 | * @access public 1205 | */ 1206 | public function __destruct() 1207 | { 1208 | $this->close(); 1209 | } 1210 | 1211 | public function __get($name) 1212 | { 1213 | $return = null; 1214 | if (in_array($name, self::$deferredProperties) && is_callable(array($this, $getter = '__get_' . $name))) { 1215 | $return = $this->$name = $this->$getter(); 1216 | } 1217 | return $return; 1218 | } 1219 | 1220 | /** 1221 | * Get Effective Url 1222 | * 1223 | * @access private 1224 | */ 1225 | private function __get_effectiveUrl() 1226 | { 1227 | return $this->getInfo(CURLINFO_EFFECTIVE_URL); 1228 | } 1229 | 1230 | /** 1231 | * Get RFC 2616 1232 | * 1233 | * @access private 1234 | */ 1235 | private function __get_rfc2616() 1236 | { 1237 | return array_fill_keys(self::$RFC2616, true); 1238 | } 1239 | 1240 | /** 1241 | * Get RFC 6265 1242 | * 1243 | * @access private 1244 | */ 1245 | private function __get_rfc6265() 1246 | { 1247 | return array_fill_keys(self::$RFC6265, true); 1248 | } 1249 | 1250 | /** 1251 | * Get Total Time 1252 | * 1253 | * @access private 1254 | */ 1255 | private function __get_totalTime() 1256 | { 1257 | return $this->getInfo(CURLINFO_TOTAL_TIME); 1258 | } 1259 | 1260 | /** 1261 | * Build Cookies 1262 | * 1263 | * @access private 1264 | */ 1265 | private function buildCookies() 1266 | { 1267 | // Avoid using http_build_query() as unnecessary encoding is performed. 1268 | // http_build_query($this->cookies, '', '; '); 1269 | $this->setOpt(CURLOPT_COOKIE, implode('; ', array_map(function ($k, $v) { 1270 | return $k . '=' . $v; 1271 | }, array_keys($this->cookies), array_values($this->cookies)))); 1272 | } 1273 | 1274 | /** 1275 | * Build Url 1276 | * 1277 | * @access private 1278 | * @param $url 1279 | * @param $mixed_data 1280 | * 1281 | * @return string 1282 | */ 1283 | private function buildUrl($url, $mixed_data = '') 1284 | { 1285 | $query_string = ''; 1286 | if (!empty($mixed_data)) { 1287 | $query_mark = strpos($url, '?') > 0 ? '&' : '?'; 1288 | if (is_string($mixed_data)) { 1289 | $query_string .= $query_mark . $mixed_data; 1290 | } elseif (is_array($mixed_data)) { 1291 | $query_string .= $query_mark . http_build_query($mixed_data, '', '&'); 1292 | } 1293 | } 1294 | return $url . $query_string; 1295 | } 1296 | 1297 | /** 1298 | * Create Header Callback 1299 | * 1300 | * @access private 1301 | * @param $header_callback_data 1302 | * 1303 | * @return callable 1304 | */ 1305 | private function createHeaderCallback($header_callback_data) 1306 | { 1307 | return function ($ch, $header) use ($header_callback_data) { 1308 | if (preg_match('/^Set-Cookie:\s*([^=]+)=([^;]+)/mi', $header, $cookie) === 1) { 1309 | $header_callback_data->responseCookies[$cookie[1]] = trim($cookie[2], " \n\r\t\0\x0B"); 1310 | } 1311 | $header_callback_data->rawResponseHeaders .= $header; 1312 | return strlen($header); 1313 | }; 1314 | } 1315 | 1316 | /** 1317 | * Download Complete 1318 | * 1319 | * @access private 1320 | * @param $fh 1321 | */ 1322 | private function downloadComplete($fh) 1323 | { 1324 | if (!$this->error && $this->downloadCompleteFunction) { 1325 | rewind($fh); 1326 | $this->call($this->downloadCompleteFunction, $fh); 1327 | $this->downloadCompleteFunction = null; 1328 | } 1329 | 1330 | if (is_resource($fh)) { 1331 | fclose($fh); 1332 | } 1333 | 1334 | // Fix "PHP Notice: Use of undefined constant STDOUT" when reading the 1335 | // PHP script from stdin. Using null causes "Warning: curl_setopt(): 1336 | // supplied argument is not a valid File-Handle resource". 1337 | if (!defined('STDOUT')) { 1338 | define('STDOUT', fopen('php://stdout', 'w')); 1339 | } 1340 | 1341 | // Reset CURLOPT_FILE with STDOUT to avoid: "curl_exec(): CURLOPT_FILE 1342 | // resource has gone away, resetting to default". 1343 | $this->setOpt(CURLOPT_FILE, STDOUT); 1344 | 1345 | // Reset CURLOPT_RETURNTRANSFER to tell cURL to return subsequent 1346 | // responses as the return value of curl_exec(). Without this, 1347 | // curl_exec() will revert to returning boolean values. 1348 | $this->setOpt(CURLOPT_RETURNTRANSFER, true); 1349 | } 1350 | 1351 | /** 1352 | * Parse Headers 1353 | * 1354 | * @access private 1355 | * @param $raw_headers 1356 | * 1357 | * @return array 1358 | */ 1359 | private function parseHeaders($raw_headers) 1360 | { 1361 | $raw_headers = preg_split('/\r\n/', $raw_headers, null, PREG_SPLIT_NO_EMPTY); 1362 | $http_headers = new CaseInsensitiveArray(); 1363 | 1364 | $raw_headers_count = count($raw_headers); 1365 | for ($i = 1; $i < $raw_headers_count; $i++) { 1366 | list($key, $value) = explode(':', $raw_headers[$i], 2); 1367 | $key = trim($key); 1368 | $value = trim($value); 1369 | // Use isset() as array_key_exists() and ArrayAccess are not compatible. 1370 | if (isset($http_headers[$key])) { 1371 | $http_headers[$key] .= ',' . $value; 1372 | } else { 1373 | $http_headers[$key] = $value; 1374 | } 1375 | } 1376 | 1377 | return array(isset($raw_headers['0']) ? $raw_headers['0'] : '', $http_headers); 1378 | } 1379 | 1380 | /** 1381 | * Parse Request Headers 1382 | * 1383 | * @access private 1384 | * @param $raw_headers 1385 | * 1386 | * @return \Curl\CaseInsensitiveArray 1387 | */ 1388 | private function parseRequestHeaders($raw_headers) 1389 | { 1390 | $request_headers = new CaseInsensitiveArray(); 1391 | list($first_line, $headers) = $this->parseHeaders($raw_headers); 1392 | $request_headers['Request-Line'] = $first_line; 1393 | foreach ($headers as $key => $value) { 1394 | $request_headers[$key] = $value; 1395 | } 1396 | return $request_headers; 1397 | } 1398 | 1399 | /** 1400 | * Parse Response 1401 | * 1402 | * @access private 1403 | * @param $response_headers 1404 | * @param $raw_response 1405 | * 1406 | * @return mixed 1407 | * Provided the content-type is determined to be json or xml: 1408 | * Returns stdClass object when the default json decoder is used and the content-type is json. 1409 | * Returns SimpleXMLElement object when the default xml decoder is used and the content-type is xml. 1410 | */ 1411 | private function parseResponse($response_headers, $raw_response) 1412 | { 1413 | $response = $raw_response; 1414 | if (isset($response_headers['Content-Type'])) { 1415 | if (preg_match($this->jsonPattern, $response_headers['Content-Type'])) { 1416 | if ($this->jsonDecoder) { 1417 | $args = $this->jsonDecoderArgs; 1418 | array_unshift($args, $response); 1419 | $response = call_user_func_array($this->jsonDecoder, $args); 1420 | } 1421 | } elseif (preg_match($this->xmlPattern, $response_headers['Content-Type'])) { 1422 | if ($this->xmlDecoder) { 1423 | $response = call_user_func($this->xmlDecoder, $response); 1424 | } 1425 | } else { 1426 | if ($this->defaultDecoder) { 1427 | $response = call_user_func($this->defaultDecoder, $response); 1428 | } 1429 | } 1430 | } 1431 | 1432 | return $response; 1433 | } 1434 | 1435 | /** 1436 | * Parse Response Headers 1437 | * 1438 | * @access private 1439 | * @param $raw_response_headers 1440 | * 1441 | * @return \Curl\CaseInsensitiveArray 1442 | */ 1443 | private function parseResponseHeaders($raw_response_headers) 1444 | { 1445 | $response_header_array = explode("\r\n\r\n", $raw_response_headers); 1446 | $response_header = ''; 1447 | for ($i = count($response_header_array) - 1; $i >= 0; $i--) { 1448 | if (stripos($response_header_array[$i], 'HTTP/') === 0) { 1449 | $response_header = $response_header_array[$i]; 1450 | break; 1451 | } 1452 | } 1453 | 1454 | $response_headers = new CaseInsensitiveArray(); 1455 | list($first_line, $headers) = $this->parseHeaders($response_header); 1456 | $response_headers['Status-Line'] = $first_line; 1457 | foreach ($headers as $key => $value) { 1458 | $response_headers[$key] = $value; 1459 | } 1460 | return $response_headers; 1461 | } 1462 | 1463 | /** 1464 | * Set Encoded Cookie 1465 | * 1466 | * @access private 1467 | * @param $key 1468 | * @param $value 1469 | */ 1470 | private function setEncodedCookie($key, $value) 1471 | { 1472 | $name_chars = array(); 1473 | foreach (str_split($key) as $name_char) { 1474 | if (isset($this->rfc2616[$name_char])) { 1475 | $name_chars[] = $name_char; 1476 | } else { 1477 | $name_chars[] = rawurlencode($name_char); 1478 | } 1479 | } 1480 | 1481 | $value_chars = array(); 1482 | foreach (str_split($value) as $value_char) { 1483 | if (isset($this->rfc6265[$value_char])) { 1484 | $value_chars[] = $value_char; 1485 | } else { 1486 | $value_chars[] = rawurlencode($value_char); 1487 | } 1488 | } 1489 | 1490 | $this->cookies[implode('', $name_chars)] = implode('', $value_chars); 1491 | } 1492 | } 1493 | --------------------------------------------------------------------------------