├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── src ├── ContentTypes.php ├── CurlDownloader.php └── HeaderHandler.php └── tests └── DownloadTest.php /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | run: 7 | runs-on: ${{ matrix.operating-system }} 8 | strategy: 9 | matrix: 10 | operating-system: [ubuntu-latest] 11 | php-versions: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] 12 | name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-versions }} 22 | extensions: mbstring, intl, zip, xml 23 | coverage: none 24 | 25 | - name: Install dependencies 26 | run: composer install --prefer-dist --no-progress 27 | 28 | - name: Static analysis 29 | run: | 30 | composer require --dev phpstan/phpstan 31 | vendor/bin/phpstan analyse src tests 32 | 33 | - name: Run test suite 34 | run: vendor/bin/phpunit tests/ 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | Thumbs.db 6 | /phpunit.xml 7 | /.idea 8 | /.vscode 9 | .phpunit.result.cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Athlon1600 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Supported PHP Versions](https://img.shields.io/badge/PHP-7.3,%208.0,%208.1,%208.2-blue)](https://github.com/Athlon1600/php-curl-file-downloader) 2 | ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/Athlon1600/php-curl-file-downloader/build.yml) 3 | ![](https://img.shields.io/github/last-commit/Athlon1600/php-curl-file-downloader.svg) 4 | ![Packagist Downloads (custom server)](https://img.shields.io/packagist/dm/Athlon1600/php-curl-file-downloader) 5 | 6 | # Download large files using PHP and cURL 7 | 8 | There's too many code snippets on the Internet on how to do this, but not enough libraries. 9 | 10 | This will allow you to download files of any size using cURL without ever running out of memory. That's it. 11 | 12 | ```php 13 | download("https://download.ccleaner.com/cctrialsetup.exe", function ($filename) { 22 | return './2020-06-07-' . $filename; 23 | }); 24 | 25 | if ($response->status == 200) { 26 | // 28,851,928 bytes downloaded in 20.041231 seconds 27 | echo number_format($response->info->size_download) . ' bytes downloaded in ' . $response->info->total_time . ' seconds'; 28 | } 29 | ``` 30 | 31 | It will automatically guess filename of the resource being downloaded (just like web-browsers do it!) with an option to override it if needed. 32 | 33 | ## Installation 34 | 35 | ```bash 36 | composer require athlon1600/php-curl-file-downloader "^1.0" 37 | ``` 38 | 39 | ### Links 40 | 41 | - https://github.com/Athlon1600/php-curl-client 42 | - https://demo.borland.com/testsite/download_testpage.php 43 | 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "athlon1600/php-curl-file-downloader", 3 | "require": { 4 | "athlon1600/php-curl-client": "^1.0", 5 | "ext-curl": "*" 6 | }, 7 | "require-dev": { 8 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 9 | }, 10 | "autoload": { 11 | "psr-4": { 12 | "CurlDownloader\\": "src/" 13 | } 14 | }, 15 | "autoload-dev": { 16 | "psr-4": { 17 | "CurlDownloader\\Tests\\": "tests" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ContentTypes.php: -------------------------------------------------------------------------------- 1 | 'ez', 10 | 'application/applixware' => 'aw', 11 | 'application/atom+xml' => 'atom', 12 | 'application/atomcat+xml' => 'atomcat', 13 | 'application/atomsvc+xml' => 'atomsvc', 14 | 'application/ccxml+xml' => 'ccxml', 15 | 'application/cdmi-capability' => 'cdmia', 16 | 'application/cdmi-container' => 'cdmic', 17 | 'application/cdmi-domain' => 'cdmid', 18 | 'application/cdmi-object' => 'cdmio', 19 | 'application/cdmi-queue' => 'cdmiq', 20 | 'application/cu-seeme' => 'cu', 21 | 'application/davmount+xml' => 'davmount', 22 | 'application/docbook+xml' => 'dbk', 23 | 'application/dssc+der' => 'dssc', 24 | 'application/dssc+xml' => 'xdssc', 25 | 'application/ecmascript' => 'ecma', 26 | 'application/emma+xml' => 'emma', 27 | 'application/epub+zip' => 'epub', 28 | 'application/exi' => 'exi', 29 | 'application/font-tdpfr' => 'pfr', 30 | 'application/gml+xml' => 'gml', 31 | 'application/gpx+xml' => 'gpx', 32 | 'application/gxf' => 'gxf', 33 | 'application/hyperstudio' => 'stk', 34 | 'application/inkml+xml' => 'ink', 35 | 'application/ipfix' => 'ipfix', 36 | 'application/java-archive' => 'jar', 37 | 'application/java-serialized-object' => 'ser', 38 | 'application/java-vm' => 'class', 39 | 'application/javascript' => 'js', 40 | 'application/json' => 'json', 41 | 'application/jsonml+json' => 'jsonml', 42 | 'application/lost+xml' => 'lostxml', 43 | 'application/mac-binhex40' => 'hqx', 44 | 'application/mac-compactpro' => 'cpt', 45 | 'application/mads+xml' => 'mads', 46 | 'application/marc' => 'mrc', 47 | 'application/marcxml+xml' => 'mrcx', 48 | 'application/mathematica' => 'ma', 49 | 'application/mathml+xml' => 'mathml', 50 | 'application/mbox' => 'mbox', 51 | 'application/mediaservercontrol+xml' => 'mscml', 52 | 'application/metalink+xml' => 'metalink', 53 | 'application/metalink4+xml' => 'meta4', 54 | 'application/mets+xml' => 'mets', 55 | 'application/mods+xml' => 'mods', 56 | 'application/mp21' => 'm21', 57 | 'application/mp4' => 'mp4s', 58 | 'application/msword' => 'doc', 59 | 'application/mxf' => 'mxf', 60 | 'application/octet-stream' => 'bin', 61 | 'application/oda' => 'oda', 62 | 'application/oebps-package+xml' => 'opf', 63 | 'application/ogg' => 'ogx', 64 | 'application/omdoc+xml' => 'omdoc', 65 | 'application/onenote' => 'onetoc', 66 | 'application/oxps' => 'oxps', 67 | 'application/patch-ops-error+xml' => 'xer', 68 | 'application/pdf' => 'pdf', 69 | 'application/pgp-encrypted' => 'pgp', 70 | 'application/pgp-signature' => 'asc', 71 | 'application/pics-rules' => 'prf', 72 | 'application/pkcs10' => 'p10', 73 | 'application/pkcs7-mime' => 'p7m', 74 | 'application/pkcs7-signature' => 'p7s', 75 | 'application/pkcs8' => 'p8', 76 | 'application/pkix-attr-cert' => 'ac', 77 | 'application/pkix-cert' => 'cer', 78 | 'application/pkix-crl' => 'crl', 79 | 'application/pkix-pkipath' => 'pkipath', 80 | 'application/pkixcmp' => 'pki', 81 | 'application/pls+xml' => 'pls', 82 | 'application/postscript' => 'ai', 83 | 'application/prs.cww' => 'cww', 84 | 'application/pskc+xml' => 'pskcxml', 85 | 'application/rdf+xml' => 'rdf', 86 | 'application/reginfo+xml' => 'rif', 87 | 'application/relax-ng-compact-syntax' => 'rnc', 88 | 'application/resource-lists+xml' => 'rl', 89 | 'application/resource-lists-diff+xml' => 'rld', 90 | 'application/rls-services+xml' => 'rs', 91 | 'application/rpki-ghostbusters' => 'gbr', 92 | 'application/rpki-manifest' => 'mft', 93 | 'application/rpki-roa' => 'roa', 94 | 'application/rsd+xml' => 'rsd', 95 | 'application/rss+xml' => 'rss', 96 | 'application/rtf' => 'rtf', 97 | 'application/sbml+xml' => 'sbml', 98 | 'application/scvp-cv-request' => 'scq', 99 | 'application/scvp-cv-response' => 'scs', 100 | 'application/scvp-vp-request' => 'spq', 101 | 'application/scvp-vp-response' => 'spp', 102 | 'application/sdp' => 'sdp', 103 | 'application/set-payment-initiation' => 'setpay', 104 | 'application/set-registration-initiation' => 'setreg', 105 | 'application/shf+xml' => 'shf', 106 | 'application/smil+xml' => 'smi', 107 | 'application/sparql-query' => 'rq', 108 | 'application/sparql-results+xml' => 'srx', 109 | 'application/srgs' => 'gram', 110 | 'application/srgs+xml' => 'grxml', 111 | 'application/sru+xml' => 'sru', 112 | 'application/ssdl+xml' => 'ssdl', 113 | 'application/ssml+xml' => 'ssml', 114 | 'application/tei+xml' => 'tei', 115 | 'application/thraud+xml' => 'tfi', 116 | 'application/timestamped-data' => 'tsd', 117 | 'application/vnd.3gpp.pic-bw-large' => 'plb', 118 | 'application/vnd.3gpp.pic-bw-small' => 'psb', 119 | 'application/vnd.3gpp.pic-bw-var' => 'pvb', 120 | 'application/vnd.3gpp2.tcap' => 'tcap', 121 | 'application/vnd.3m.post-it-notes' => 'pwn', 122 | 'application/vnd.accpac.simply.aso' => 'aso', 123 | 'application/vnd.accpac.simply.imp' => 'imp', 124 | 'application/vnd.acucobol' => 'acu', 125 | 'application/vnd.acucorp' => 'atc', 126 | 'application/vnd.adobe.air-application-installer-package+zip' => 'air', 127 | 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', 128 | 'application/vnd.adobe.fxp' => 'fxp', 129 | 'application/vnd.adobe.xdp+xml' => 'xdp', 130 | 'application/vnd.adobe.xfdf' => 'xfdf', 131 | 'application/vnd.ahead.space' => 'ahead', 132 | 'application/vnd.airzip.filesecure.azf' => 'azf', 133 | 'application/vnd.airzip.filesecure.azs' => 'azs', 134 | 'application/vnd.amazon.ebook' => 'azw', 135 | 'application/vnd.americandynamics.acc' => 'acc', 136 | 'application/vnd.amiga.ami' => 'ami', 137 | 'application/vnd.android.package-archive' => 'apk', 138 | 'application/vnd.anser-web-certificate-issue-initiation' => 'cii', 139 | 'application/vnd.anser-web-funds-transfer-initiation' => 'fti', 140 | 'application/vnd.antix.game-component' => 'atx', 141 | 'application/vnd.apple.installer+xml' => 'mpkg', 142 | 'application/vnd.apple.mpegurl' => 'm3u8', 143 | 'application/vnd.aristanetworks.swi' => 'swi', 144 | 'application/vnd.astraea-software.iota' => 'iota', 145 | 'application/vnd.audiograph' => 'aep', 146 | 'application/vnd.blueice.multipass' => 'mpm', 147 | 'application/vnd.bmi' => 'bmi', 148 | 'application/vnd.businessobjects' => 'rep', 149 | 'application/vnd.chemdraw+xml' => 'cdxml', 150 | 'application/vnd.chipnuts.karaoke-mmd' => 'mmd', 151 | 'application/vnd.cinderella' => 'cdy', 152 | 'application/vnd.claymore' => 'cla', 153 | 'application/vnd.cloanto.rp9' => 'rp9', 154 | 'application/vnd.clonk.c4group' => 'c4g', 155 | 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', 156 | 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', 157 | 'application/vnd.commonspace' => 'csp', 158 | 'application/vnd.contact.cmsg' => 'cdbcmsg', 159 | 'application/vnd.cosmocaller' => 'cmc', 160 | 'application/vnd.crick.clicker' => 'clkx', 161 | 'application/vnd.crick.clicker.keyboard' => 'clkk', 162 | 'application/vnd.crick.clicker.palette' => 'clkp', 163 | 'application/vnd.crick.clicker.template' => 'clkt', 164 | 'application/vnd.crick.clicker.wordbank' => 'clkw', 165 | 'application/vnd.criticaltools.wbs+xml' => 'wbs', 166 | 'application/vnd.ctc-posml' => 'pml', 167 | 'application/vnd.cups-ppd' => 'ppd', 168 | 'application/vnd.curl.car' => 'car', 169 | 'application/vnd.curl.pcurl' => 'pcurl', 170 | 'application/vnd.dart' => 'dart', 171 | 'application/vnd.data-vision.rdz' => 'rdz', 172 | 'application/vnd.dece.data' => 'uvf', 173 | 'application/vnd.dece.ttml+xml' => 'uvt', 174 | 'application/vnd.dece.unspecified' => 'uvx', 175 | 'application/vnd.dece.zip' => 'uvz', 176 | 'application/vnd.denovo.fcselayout-link' => 'fe_launch', 177 | 'application/vnd.dna' => 'dna', 178 | 'application/vnd.dolby.mlp' => 'mlp', 179 | 'application/vnd.dpgraph' => 'dpg', 180 | 'application/vnd.dreamfactory' => 'dfac', 181 | 'application/vnd.ds-keypoint' => 'kpxx', 182 | 'application/vnd.dvb.ait' => 'ait', 183 | 'application/vnd.dvb.service' => 'svc', 184 | 'application/vnd.dynageo' => 'geo', 185 | 'application/vnd.ecowin.chart' => 'mag', 186 | 'application/vnd.enliven' => 'nml', 187 | 'application/vnd.epson.esf' => 'esf', 188 | 'application/vnd.epson.msf' => 'msf', 189 | 'application/vnd.epson.quickanime' => 'qam', 190 | 'application/vnd.epson.salt' => 'slt', 191 | 'application/vnd.epson.ssf' => 'ssf', 192 | 'application/vnd.eszigno3+xml' => 'es3', 193 | 'application/vnd.ezpix-album' => 'ez2', 194 | 'application/vnd.ezpix-package' => 'ez3', 195 | 'application/vnd.fdf' => 'fdf', 196 | 'application/vnd.fdsn.mseed' => 'mseed', 197 | 'application/vnd.fdsn.seed' => 'seed', 198 | 'application/vnd.flographit' => 'gph', 199 | 'application/vnd.fluxtime.clip' => 'ftc', 200 | 'application/vnd.framemaker' => 'fm', 201 | 'application/vnd.frogans.fnc' => 'fnc', 202 | 'application/vnd.frogans.ltf' => 'ltf', 203 | 'application/vnd.fsc.weblaunch' => 'fsc', 204 | 'application/vnd.fujitsu.oasys' => 'oas', 205 | 'application/vnd.fujitsu.oasys2' => 'oa2', 206 | 'application/vnd.fujitsu.oasys3' => 'oa3', 207 | 'application/vnd.fujitsu.oasysgp' => 'fg5', 208 | 'application/vnd.fujitsu.oasysprs' => 'bh2', 209 | 'application/vnd.fujixerox.ddd' => 'ddd', 210 | 'application/vnd.fujixerox.docuworks' => 'xdw', 211 | 'application/vnd.fujixerox.docuworks.binder' => 'xbd', 212 | 'application/vnd.fuzzysheet' => 'fzs', 213 | 'application/vnd.genomatix.tuxedo' => 'txd', 214 | 'application/vnd.geogebra.file' => 'ggb', 215 | 'application/vnd.geogebra.tool' => 'ggt', 216 | 'application/vnd.geometry-explorer' => 'gex', 217 | 'application/vnd.geonext' => 'gxt', 218 | 'application/vnd.geoplan' => 'g2w', 219 | 'application/vnd.geospace' => 'g3w', 220 | 'application/vnd.gmx' => 'gmx', 221 | 'application/vnd.google-earth.kml+xml' => 'kml', 222 | 'application/vnd.google-earth.kmz' => 'kmz', 223 | 'application/vnd.grafeq' => 'gqf', 224 | 'application/vnd.groove-account' => 'gac', 225 | 'application/vnd.groove-help' => 'ghf', 226 | 'application/vnd.groove-identity-message' => 'gim', 227 | 'application/vnd.groove-injector' => 'grv', 228 | 'application/vnd.groove-tool-message' => 'gtm', 229 | 'application/vnd.groove-tool-template' => 'tpl', 230 | 'application/vnd.groove-vcard' => 'vcg', 231 | 'application/vnd.hal+xml' => 'hal', 232 | 'application/vnd.handheld-entertainment+xml' => 'zmm', 233 | 'application/vnd.hbci' => 'hbci', 234 | 'application/vnd.hhe.lesson-player' => 'les', 235 | 'application/vnd.hp-hpgl' => 'hpgl', 236 | 'application/vnd.hp-hpid' => 'hpid', 237 | 'application/vnd.hp-hps' => 'hps', 238 | 'application/vnd.hp-jlyt' => 'jlt', 239 | 'application/vnd.hp-pcl' => 'pcl', 240 | 'application/vnd.hp-pclxl' => 'pclxl', 241 | 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', 242 | 'application/vnd.ibm.minipay' => 'mpy', 243 | 'application/vnd.ibm.modcap' => 'afp', 244 | 'application/vnd.ibm.rights-management' => 'irm', 245 | 'application/vnd.ibm.secure-container' => 'sc', 246 | 'application/vnd.iccprofile' => 'icc', 247 | 'application/vnd.igloader' => 'igl', 248 | 'application/vnd.immervision-ivp' => 'ivp', 249 | 'application/vnd.immervision-ivu' => 'ivu', 250 | 'application/vnd.insors.igm' => 'igm', 251 | 'application/vnd.intercon.formnet' => 'xpw', 252 | 'application/vnd.intergeo' => 'i2g', 253 | 'application/vnd.intu.qbo' => 'qbo', 254 | 'application/vnd.intu.qfx' => 'qfx', 255 | 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', 256 | 'application/vnd.irepository.package+xml' => 'irp', 257 | 'application/vnd.is-xpr' => 'xpr', 258 | 'application/vnd.isac.fcs' => 'fcs', 259 | 'application/vnd.jam' => 'jam', 260 | 'application/vnd.jcp.javame.midlet-rms' => 'rms', 261 | 'application/vnd.jisp' => 'jisp', 262 | 'application/vnd.joost.joda-archive' => 'joda', 263 | 'application/vnd.kahootz' => 'ktz', 264 | 'application/vnd.kde.karbon' => 'karbon', 265 | 'application/vnd.kde.kchart' => 'chrt', 266 | 'application/vnd.kde.kformula' => 'kfo', 267 | 'application/vnd.kde.kivio' => 'flw', 268 | 'application/vnd.kde.kontour' => 'kon', 269 | 'application/vnd.kde.kpresenter' => 'kpr', 270 | 'application/vnd.kde.kspread' => 'ksp', 271 | 'application/vnd.kde.kword' => 'kwd', 272 | 'application/vnd.kenameaapp' => 'htke', 273 | 'application/vnd.kidspiration' => 'kia', 274 | 'application/vnd.kinar' => 'kne', 275 | 'application/vnd.koan' => 'skp', 276 | 'application/vnd.kodak-descriptor' => 'sse', 277 | 'application/vnd.las.las+xml' => 'lasxml', 278 | 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', 279 | 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', 280 | 'application/vnd.lotus-1-2-3' => '123', 281 | 'application/vnd.lotus-approach' => 'apr', 282 | 'application/vnd.lotus-freelance' => 'pre', 283 | 'application/vnd.lotus-notes' => 'nsf', 284 | 'application/vnd.lotus-organizer' => 'org', 285 | 'application/vnd.lotus-screencam' => 'scm', 286 | 'application/vnd.lotus-wordpro' => 'lwp', 287 | 'application/vnd.macports.portpkg' => 'portpkg', 288 | 'application/vnd.mcd' => 'mcd', 289 | 'application/vnd.medcalcdata' => 'mc1', 290 | 'application/vnd.mediastation.cdkey' => 'cdkey', 291 | 'application/vnd.mfer' => 'mwf', 292 | 'application/vnd.mfmp' => 'mfm', 293 | 'application/vnd.micrografx.flo' => 'flo', 294 | 'application/vnd.micrografx.igx' => 'igx', 295 | 'application/vnd.mif' => 'mif', 296 | 'application/vnd.mobius.daf' => 'daf', 297 | 'application/vnd.mobius.dis' => 'dis', 298 | 'application/vnd.mobius.mbk' => 'mbk', 299 | 'application/vnd.mobius.mqy' => 'mqy', 300 | 'application/vnd.mobius.msl' => 'msl', 301 | 'application/vnd.mobius.plc' => 'plc', 302 | 'application/vnd.mobius.txf' => 'txf', 303 | 'application/vnd.mophun.application' => 'mpn', 304 | 'application/vnd.mophun.certificate' => 'mpc', 305 | 'application/vnd.mozilla.xul+xml' => 'xul', 306 | 'application/vnd.ms-artgalry' => 'cil', 307 | 'application/vnd.ms-cab-compressed' => 'cab', 308 | 'application/vnd.ms-excel' => 'xls', 309 | 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', 310 | 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', 311 | 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', 312 | 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', 313 | 'application/vnd.ms-fontobject' => 'eot', 314 | 'application/vnd.ms-htmlhelp' => 'chm', 315 | 'application/vnd.ms-ims' => 'ims', 316 | 'application/vnd.ms-lrm' => 'lrm', 317 | 'application/vnd.ms-officetheme' => 'thmx', 318 | 'application/vnd.ms-pki.seccat' => 'cat', 319 | 'application/vnd.ms-pki.stl' => 'stl', 320 | 'application/vnd.ms-powerpoint' => 'ppt', 321 | 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', 322 | 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', 323 | 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', 324 | 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', 325 | 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', 326 | 'application/vnd.ms-project' => 'mpp', 327 | 'application/vnd.ms-word.document.macroenabled.12' => 'docm', 328 | 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', 329 | 'application/vnd.ms-works' => 'wps', 330 | 'application/vnd.ms-wpl' => 'wpl', 331 | 'application/vnd.ms-xpsdocument' => 'xps', 332 | 'application/vnd.mseq' => 'mseq', 333 | 'application/vnd.musician' => 'mus', 334 | 'application/vnd.muvee.style' => 'msty', 335 | 'application/vnd.mynfc' => 'taglet', 336 | 'application/vnd.neurolanguage.nlu' => 'nlu', 337 | 'application/vnd.nitf' => 'ntf', 338 | 'application/vnd.noblenet-directory' => 'nnd', 339 | 'application/vnd.noblenet-sealer' => 'nns', 340 | 'application/vnd.noblenet-web' => 'nnw', 341 | 'application/vnd.nokia.n-gage.data' => 'ngdat', 342 | 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage', 343 | 'application/vnd.nokia.radio-preset' => 'rpst', 344 | 'application/vnd.nokia.radio-presets' => 'rpss', 345 | 'application/vnd.novadigm.edm' => 'edm', 346 | 'application/vnd.novadigm.edx' => 'edx', 347 | 'application/vnd.novadigm.ext' => 'ext', 348 | 'application/vnd.oasis.opendocument.chart' => 'odc', 349 | 'application/vnd.oasis.opendocument.chart-template' => 'otc', 350 | 'application/vnd.oasis.opendocument.database' => 'odb', 351 | 'application/vnd.oasis.opendocument.formula' => 'odf', 352 | 'application/vnd.oasis.opendocument.formula-template' => 'odft', 353 | 'application/vnd.oasis.opendocument.graphics' => 'odg', 354 | 'application/vnd.oasis.opendocument.graphics-template' => 'otg', 355 | 'application/vnd.oasis.opendocument.image' => 'odi', 356 | 'application/vnd.oasis.opendocument.image-template' => 'oti', 357 | 'application/vnd.oasis.opendocument.presentation' => 'odp', 358 | 'application/vnd.oasis.opendocument.presentation-template' => 'otp', 359 | 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', 360 | 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', 361 | 'application/vnd.oasis.opendocument.text' => 'odt', 362 | 'application/vnd.oasis.opendocument.text-master' => 'odm', 363 | 'application/vnd.oasis.opendocument.text-template' => 'ott', 364 | 'application/vnd.oasis.opendocument.text-web' => 'oth', 365 | 'application/vnd.olpc-sugar' => 'xo', 366 | 'application/vnd.oma.dd2+xml' => 'dd2', 367 | 'application/vnd.openofficeorg.extension' => 'oxt', 368 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 369 | 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', 370 | 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', 371 | 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', 372 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 373 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', 374 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 375 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', 376 | 'application/vnd.osgeo.mapguide.package' => 'mgp', 377 | 'application/vnd.osgi.dp' => 'dp', 378 | 'application/vnd.osgi.subsystem' => 'esa', 379 | 'application/vnd.palm' => 'pdb', 380 | 'application/vnd.pawaafile' => 'paw', 381 | 'application/vnd.pg.format' => 'str', 382 | 'application/vnd.pg.osasli' => 'ei6', 383 | 'application/vnd.picsel' => 'efif', 384 | 'application/vnd.pmi.widget' => 'wg', 385 | 'application/vnd.pocketlearn' => 'plf', 386 | 'application/vnd.powerbuilder6' => 'pbd', 387 | 'application/vnd.previewsystems.box' => 'box', 388 | 'application/vnd.proteus.magazine' => 'mgz', 389 | 'application/vnd.publishare-delta-tree' => 'qps', 390 | 'application/vnd.pvi.ptid1' => 'ptid', 391 | 'application/vnd.quark.quarkxpress' => 'qxd', 392 | 'application/vnd.realvnc.bed' => 'bed', 393 | 'application/vnd.recordare.musicxml' => 'mxl', 394 | 'application/vnd.recordare.musicxml+xml' => 'musicxml', 395 | 'application/vnd.rig.cryptonote' => 'cryptonote', 396 | 'application/vnd.rim.cod' => 'cod', 397 | 'application/vnd.rn-realmedia' => 'rm', 398 | 'application/vnd.rn-realmedia-vbr' => 'rmvb', 399 | 'application/vnd.route66.link66+xml' => 'link66', 400 | 'application/vnd.sailingtracker.track' => 'st', 401 | 'application/vnd.seemail' => 'see', 402 | 'application/vnd.sema' => 'sema', 403 | 'application/vnd.semd' => 'semd', 404 | 'application/vnd.semf' => 'semf', 405 | 'application/vnd.shana.informed.formdata' => 'ifm', 406 | 'application/vnd.shana.informed.formtemplate' => 'itp', 407 | 'application/vnd.shana.informed.interchange' => 'iif', 408 | 'application/vnd.shana.informed.package' => 'ipk', 409 | 'application/vnd.simtech-mindmapper' => 'twd', 410 | 'application/vnd.smaf' => 'mmf', 411 | 'application/vnd.smart.teacher' => 'teacher', 412 | 'application/vnd.solent.sdkm+xml' => 'sdkm', 413 | 'application/vnd.spotfire.dxp' => 'dxp', 414 | 'application/vnd.spotfire.sfs' => 'sfs', 415 | 'application/vnd.stardivision.calc' => 'sdc', 416 | 'application/vnd.stardivision.draw' => 'sda', 417 | 'application/vnd.stardivision.impress' => 'sdd', 418 | 'application/vnd.stardivision.math' => 'smf', 419 | 'application/vnd.stardivision.writer' => 'sdw', 420 | 'application/vnd.stardivision.writer-global' => 'sgl', 421 | 'application/vnd.stepmania.package' => 'smzip', 422 | 'application/vnd.stepmania.stepchart' => 'sm', 423 | 'application/vnd.sun.xml.calc' => 'sxc', 424 | 'application/vnd.sun.xml.calc.template' => 'stc', 425 | 'application/vnd.sun.xml.draw' => 'sxd', 426 | 'application/vnd.sun.xml.draw.template' => 'std', 427 | 'application/vnd.sun.xml.impress' => 'sxi', 428 | 'application/vnd.sun.xml.impress.template' => 'sti', 429 | 'application/vnd.sun.xml.math' => 'sxm', 430 | 'application/vnd.sun.xml.writer' => 'sxw', 431 | 'application/vnd.sun.xml.writer.global' => 'sxg', 432 | 'application/vnd.sun.xml.writer.template' => 'stw', 433 | 'application/vnd.sus-calendar' => 'sus', 434 | 'application/vnd.svd' => 'svd', 435 | 'application/vnd.symbian.install' => 'sis', 436 | 'application/vnd.syncml+xml' => 'xsm', 437 | 'application/vnd.syncml.dm+wbxml' => 'bdm', 438 | 'application/vnd.syncml.dm+xml' => 'xdm', 439 | 'application/vnd.tao.intent-module-archive' => 'tao', 440 | 'application/vnd.tcpdump.pcap' => 'pcap', 441 | 'application/vnd.tmobile-livetv' => 'tmo', 442 | 'application/vnd.trid.tpt' => 'tpt', 443 | 'application/vnd.triscape.mxs' => 'mxs', 444 | 'application/vnd.trueapp' => 'tra', 445 | 'application/vnd.ufdl' => 'ufd', 446 | 'application/vnd.uiq.theme' => 'utz', 447 | 'application/vnd.umajin' => 'umj', 448 | 'application/vnd.unity' => 'unityweb', 449 | 'application/vnd.uoml+xml' => 'uoml', 450 | 'application/vnd.vcx' => 'vcx', 451 | 'application/vnd.visio' => 'vsd', 452 | 'application/vnd.visionary' => 'vis', 453 | 'application/vnd.vsf' => 'vsf', 454 | 'application/vnd.wap.wbxml' => 'wbxml', 455 | 'application/vnd.wap.wmlc' => 'wmlc', 456 | 'application/vnd.wap.wmlscriptc' => 'wmlsc', 457 | 'application/vnd.webturbo' => 'wtb', 458 | 'application/vnd.wolfram.player' => 'nbp', 459 | 'application/vnd.wordperfect' => 'wpd', 460 | 'application/vnd.wqd' => 'wqd', 461 | 'application/vnd.wt.stf' => 'stf', 462 | 'application/vnd.xara' => 'xar', 463 | 'application/vnd.xfdl' => 'xfdl', 464 | 'application/vnd.yamaha.hv-dic' => 'hvd', 465 | 'application/vnd.yamaha.hv-script' => 'hvs', 466 | 'application/vnd.yamaha.hv-voice' => 'hvp', 467 | 'application/vnd.yamaha.openscoreformat' => 'osf', 468 | 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg', 469 | 'application/vnd.yamaha.smaf-audio' => 'saf', 470 | 'application/vnd.yamaha.smaf-phrase' => 'spf', 471 | 'application/vnd.yellowriver-custom-menu' => 'cmp', 472 | 'application/vnd.zul' => 'zir', 473 | 'application/vnd.zzazz.deck+xml' => 'zaz', 474 | 'application/voicexml+xml' => 'vxml', 475 | 'application/widget' => 'wgt', 476 | 'application/winhlp' => 'hlp', 477 | 'application/wsdl+xml' => 'wsdl', 478 | 'application/wspolicy+xml' => 'wspolicy', 479 | 'application/x-7z-compressed' => '7z', 480 | 'application/x-abiword' => 'abw', 481 | 'application/x-ace-compressed' => 'ace', 482 | 'application/x-apple-diskimage' => 'dmg', 483 | 'application/x-authorware-bin' => 'aab', 484 | 'application/x-authorware-map' => 'aam', 485 | 'application/x-authorware-seg' => 'aas', 486 | 'application/x-bcpio' => 'bcpio', 487 | 'application/x-bittorrent' => 'torrent', 488 | 'application/x-blorb' => 'blb', 489 | 'application/x-bzip' => 'bz', 490 | 'application/x-bzip2' => 'bz2', 491 | 'application/x-cbr' => 'cbr', 492 | 'application/x-cdlink' => 'vcd', 493 | 'application/x-cfs-compressed' => 'cfs', 494 | 'application/x-chat' => 'chat', 495 | 'application/x-chess-pgn' => 'pgn', 496 | 'application/x-conference' => 'nsc', 497 | 'application/x-cpio' => 'cpio', 498 | 'application/x-csh' => 'csh', 499 | 'application/x-debian-package' => 'deb', 500 | 'application/x-dgc-compressed' => 'dgc', 501 | 'application/x-director' => 'dir', 502 | 'application/x-doom' => 'wad', 503 | 'application/x-dtbncx+xml' => 'ncx', 504 | 'application/x-dtbook+xml' => 'dtb', 505 | 'application/x-dtbresource+xml' => 'res', 506 | 'application/x-dvi' => 'dvi', 507 | 'application/x-envoy' => 'evy', 508 | 'application/x-eva' => 'eva', 509 | 'application/x-font-bdf' => 'bdf', 510 | 'application/x-font-ghostscript' => 'gsf', 511 | 'application/x-font-linux-psf' => 'psf', 512 | 'application/x-font-pcf' => 'pcf', 513 | 'application/x-font-snf' => 'snf', 514 | 'application/x-font-type1' => 'pfa', 515 | 'application/x-freearc' => 'arc', 516 | 'application/x-futuresplash' => 'spl', 517 | 'application/x-gca-compressed' => 'gca', 518 | 'application/x-glulx' => 'ulx', 519 | 'application/x-gnumeric' => 'gnumeric', 520 | 'application/x-gramps-xml' => 'gramps', 521 | 'application/x-gtar' => 'gtar', 522 | 'application/x-hdf' => 'hdf', 523 | 'application/x-install-instructions' => 'install', 524 | 'application/x-iso9660-image' => 'iso', 525 | 'application/x-java-jnlp-file' => 'jnlp', 526 | 'application/x-latex' => 'latex', 527 | 'application/x-lzh-compressed' => 'lzh', 528 | 'application/x-mie' => 'mie', 529 | 'application/x-mobipocket-ebook' => 'prc', 530 | 'application/x-ms-application' => 'application', 531 | 'application/x-ms-shortcut' => 'lnk', 532 | 'application/x-ms-wmd' => 'wmd', 533 | 'application/x-ms-wmz' => 'wmz', 534 | 'application/x-ms-xbap' => 'xbap', 535 | 'application/x-msaccess' => 'mdb', 536 | 'application/x-msbinder' => 'obd', 537 | 'application/x-mscardfile' => 'crd', 538 | 'application/x-msclip' => 'clp', 539 | 'application/x-msdownload' => 'exe', 540 | 'application/x-msmediaview' => 'mvb', 541 | 'application/x-msmetafile' => 'wmf', 542 | 'application/x-msmoney' => 'mny', 543 | 'application/x-mspublisher' => 'pub', 544 | 'application/x-msschedule' => 'scd', 545 | 'application/x-msterminal' => 'trm', 546 | 'application/x-mswrite' => 'wri', 547 | 'application/x-netcdf' => 'nc', 548 | 'application/x-nzb' => 'nzb', 549 | 'application/x-pkcs12' => 'p12', 550 | 'application/x-pkcs7-certificates' => 'p7b', 551 | 'application/x-pkcs7-certreqresp' => 'p7r', 552 | 'application/x-rar-compressed' => 'rar', 553 | 'application/x-research-info-systems' => 'ris', 554 | 'application/x-sh' => 'sh', 555 | 'application/x-shar' => 'shar', 556 | 'application/x-shockwave-flash' => 'swf', 557 | 'application/x-silverlight-app' => 'xap', 558 | 'application/x-sql' => 'sql', 559 | 'application/x-stuffit' => 'sit', 560 | 'application/x-stuffitx' => 'sitx', 561 | 'application/x-subrip' => 'srt', 562 | 'application/x-sv4cpio' => 'sv4cpio', 563 | 'application/x-sv4crc' => 'sv4crc', 564 | 'application/x-t3vm-image' => 't3', 565 | 'application/x-tads' => 'gam', 566 | 'application/x-tar' => 'tar', 567 | 'application/x-tcl' => 'tcl', 568 | 'application/x-tex' => 'tex', 569 | 'application/x-tex-tfm' => 'tfm', 570 | 'application/x-texinfo' => 'texinfo', 571 | 'application/x-tgif' => 'obj', 572 | 'application/x-ustar' => 'ustar', 573 | 'application/x-wais-source' => 'src', 574 | 'application/x-x509-ca-cert' => 'der', 575 | 'application/x-xfig' => 'fig', 576 | 'application/x-xliff+xml' => 'xlf', 577 | 'application/x-xpinstall' => 'xpi', 578 | 'application/x-xz' => 'xz', 579 | 'application/x-zmachine' => 'z1', 580 | 'application/xaml+xml' => 'xaml', 581 | 'application/xcap-diff+xml' => 'xdf', 582 | 'application/xenc+xml' => 'xenc', 583 | 'application/xhtml+xml' => 'xhtml', 584 | 'application/xml' => 'xml', 585 | 'application/xml-dtd' => 'dtd', 586 | 'application/xop+xml' => 'xop', 587 | 'application/xproc+xml' => 'xpl', 588 | 'application/xslt+xml' => 'xslt', 589 | 'application/xspf+xml' => 'xspf', 590 | 'application/xv+xml' => 'mxml', 591 | 'application/yang' => 'yang', 592 | 'application/yin+xml' => 'yin', 593 | 'application/zip' => 'zip', 594 | 'audio/adpcm' => 'adp', 595 | 'audio/basic' => 'au', 596 | 'audio/midi' => 'mid', 597 | 'audio/mp4' => 'm4a', 598 | 'audio/mpeg' => 'mpga', 599 | 'audio/ogg' => 'oga', 600 | 'audio/s3m' => 's3m', 601 | 'audio/silk' => 'sil', 602 | 'audio/vnd.dece.audio' => 'uva', 603 | 'audio/vnd.digital-winds' => 'eol', 604 | 'audio/vnd.dra' => 'dra', 605 | 'audio/vnd.dts' => 'dts', 606 | 'audio/vnd.dts.hd' => 'dtshd', 607 | 'audio/vnd.lucent.voice' => 'lvp', 608 | 'audio/vnd.ms-playready.media.pya' => 'pya', 609 | 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', 610 | 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', 611 | 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', 612 | 'audio/vnd.rip' => 'rip', 613 | 'audio/webm' => 'weba', 614 | 'audio/x-aac' => 'aac', 615 | 'audio/x-aiff' => 'aif', 616 | 'audio/x-caf' => 'caf', 617 | 'audio/x-flac' => 'flac', 618 | 'audio/x-matroska' => 'mka', 619 | 'audio/x-mpegurl' => 'm3u', 620 | 'audio/x-ms-wax' => 'wax', 621 | 'audio/x-ms-wma' => 'wma', 622 | 'audio/x-pn-realaudio' => 'ram', 623 | 'audio/x-pn-realaudio-plugin' => 'rmp', 624 | 'audio/x-wav' => 'wav', 625 | 'audio/xm' => 'xm', 626 | 'chemical/x-cdx' => 'cdx', 627 | 'chemical/x-cif' => 'cif', 628 | 'chemical/x-cmdf' => 'cmdf', 629 | 'chemical/x-cml' => 'cml', 630 | 'chemical/x-csml' => 'csml', 631 | 'chemical/x-xyz' => 'xyz', 632 | 'font/collection' => 'ttc', 633 | 'font/otf' => 'otf', 634 | 'font/ttf' => 'ttf', 635 | 'font/woff' => 'woff', 636 | 'font/woff2' => 'woff2', 637 | 'image/bmp' => 'bmp', 638 | 'image/cgm' => 'cgm', 639 | 'image/g3fax' => 'g3', 640 | 'image/gif' => 'gif', 641 | 'image/ief' => 'ief', 642 | 'image/jpeg' => 'jpeg', 643 | 'image/ktx' => 'ktx', 644 | 'image/png' => 'png', 645 | 'image/prs.btif' => 'btif', 646 | 'image/sgi' => 'sgi', 647 | 'image/svg+xml' => 'svg', 648 | 'image/tiff' => 'tiff', 649 | 'image/vnd.adobe.photoshop' => 'psd', 650 | 'image/vnd.dece.graphic' => 'uvi', 651 | 'image/vnd.djvu' => 'djvu', 652 | 'image/vnd.dvb.subtitle' => 'sub', 653 | 'image/vnd.dwg' => 'dwg', 654 | 'image/vnd.dxf' => 'dxf', 655 | 'image/vnd.fastbidsheet' => 'fbs', 656 | 'image/vnd.fpx' => 'fpx', 657 | 'image/vnd.fst' => 'fst', 658 | 'image/vnd.fujixerox.edmics-mmr' => 'mmr', 659 | 'image/vnd.fujixerox.edmics-rlc' => 'rlc', 660 | 'image/vnd.ms-modi' => 'mdi', 661 | 'image/vnd.ms-photo' => 'wdp', 662 | 'image/vnd.net-fpx' => 'npx', 663 | 'image/vnd.wap.wbmp' => 'wbmp', 664 | 'image/vnd.xiff' => 'xif', 665 | 'image/webp' => 'webp', 666 | 'image/x-3ds' => '3ds', 667 | 'image/x-cmu-raster' => 'ras', 668 | 'image/x-cmx' => 'cmx', 669 | 'image/x-freehand' => 'fh', 670 | 'image/x-icon' => 'ico', 671 | 'image/x-mrsid-image' => 'sid', 672 | 'image/x-pcx' => 'pcx', 673 | 'image/x-pict' => 'pic', 674 | 'image/x-portable-anymap' => 'pnm', 675 | 'image/x-portable-bitmap' => 'pbm', 676 | 'image/x-portable-graymap' => 'pgm', 677 | 'image/x-portable-pixmap' => 'ppm', 678 | 'image/x-rgb' => 'rgb', 679 | 'image/x-tga' => 'tga', 680 | 'image/x-xbitmap' => 'xbm', 681 | 'image/x-xpixmap' => 'xpm', 682 | 'image/x-xwindowdump' => 'xwd', 683 | 'message/rfc822' => 'eml', 684 | 'model/iges' => 'igs', 685 | 'model/mesh' => 'msh', 686 | 'model/vnd.collada+xml' => 'dae', 687 | 'model/vnd.dwf' => 'dwf', 688 | 'model/vnd.gdl' => 'gdl', 689 | 'model/vnd.gtw' => 'gtw', 690 | 'model/vnd.mts' => 'mts', 691 | 'model/vnd.vtu' => 'vtu', 692 | 'model/vrml' => 'wrl', 693 | 'model/x3d+binary' => 'x3db', 694 | 'model/x3d+vrml' => 'x3dv', 695 | 'model/x3d+xml' => 'x3d', 696 | 'text/cache-manifest' => 'appcache', 697 | 'text/calendar' => 'ics', 698 | 'text/css' => 'css', 699 | 'text/csv' => 'csv', 700 | 'text/html' => 'html', 701 | 'text/n3' => 'n3', 702 | 'text/plain' => 'txt', 703 | 'text/prs.lines.tag' => 'dsc', 704 | 'text/richtext' => 'rtx', 705 | 'text/sgml' => 'sgml', 706 | 'text/tab-separated-values' => 'tsv', 707 | 'text/troff' => 't', 708 | 'text/turtle' => 'ttl', 709 | 'text/uri-list' => 'uri', 710 | 'text/vcard' => 'vcard', 711 | 'text/vnd.curl' => 'curl', 712 | 'text/vnd.curl.dcurl' => 'dcurl', 713 | 'text/vnd.curl.mcurl' => 'mcurl', 714 | 'text/vnd.curl.scurl' => 'scurl', 715 | 'text/vnd.dvb.subtitle' => 'sub', 716 | 'text/vnd.fly' => 'fly', 717 | 'text/vnd.fmi.flexstor' => 'flx', 718 | 'text/vnd.graphviz' => 'gv', 719 | 'text/vnd.in3d.3dml' => '3dml', 720 | 'text/vnd.in3d.spot' => 'spot', 721 | 'text/vnd.sun.j2me.app-descriptor' => 'jad', 722 | 'text/vnd.wap.wml' => 'wml', 723 | 'text/vnd.wap.wmlscript' => 'wmls', 724 | 'text/x-asm' => 's', 725 | 'text/x-c' => 'c', 726 | 'text/x-fortran' => 'f', 727 | 'text/x-java-source' => 'java', 728 | 'text/x-nfo' => 'nfo', 729 | 'text/x-opml' => 'opml', 730 | 'text/x-pascal' => 'p', 731 | 'text/x-setext' => 'etx', 732 | 'text/x-sfv' => 'sfv', 733 | 'text/x-uuencode' => 'uu', 734 | 'text/x-vcalendar' => 'vcs', 735 | 'text/x-vcard' => 'vcf', 736 | 'video/3gpp' => '3gp', 737 | 'video/3gpp2' => '3g2', 738 | 'video/h261' => 'h261', 739 | 'video/h263' => 'h263', 740 | 'video/h264' => 'h264', 741 | 'video/jpeg' => 'jpgv', 742 | 'video/jpm' => 'jpm', 743 | 'video/mj2' => 'mj2', 744 | 'video/mp4' => 'mp4', 745 | 'video/mpeg' => 'mpeg', 746 | 'video/ogg' => 'ogv', 747 | 'video/quicktime' => 'qt', 748 | 'video/vnd.dece.hd' => 'uvh', 749 | 'video/vnd.dece.mobile' => 'uvm', 750 | 'video/vnd.dece.pd' => 'uvp', 751 | 'video/vnd.dece.sd' => 'uvs', 752 | 'video/vnd.dece.video' => 'uvv', 753 | 'video/vnd.dvb.file' => 'dvb', 754 | 'video/vnd.fvt' => 'fvt', 755 | 'video/vnd.mpegurl' => 'mxu', 756 | 'video/vnd.ms-playready.media.pyv' => 'pyv', 757 | 'video/vnd.uvvu.mp4' => 'uvu', 758 | 'video/vnd.vivo' => 'viv', 759 | 'video/webm' => 'webm', 760 | 'video/x-f4v' => 'f4v', 761 | 'video/x-fli' => 'fli', 762 | 'video/x-flv' => 'flv', 763 | 'video/x-m4v' => 'm4v', 764 | 'video/x-matroska' => 'mkv', 765 | 'video/x-mng' => 'mng', 766 | 'video/x-ms-asf' => 'asf', 767 | 'video/x-ms-vob' => 'vob', 768 | 'video/x-ms-wm' => 'wm', 769 | 'video/x-ms-wmv' => 'wmv', 770 | 'video/x-ms-wmx' => 'wmx', 771 | 'video/x-ms-wvx' => 'wvx', 772 | 'video/x-msvideo' => 'avi', 773 | 'video/x-sgi-movie' => 'movie', 774 | 'video/x-smv' => 'smv', 775 | 'x-conference/x-cooltalk' => 'ice' 776 | ); 777 | 778 | public static function getExtension($content_type, $default = null) 779 | { 780 | $content_type = static::cleanContentType($content_type); 781 | 782 | if (array_key_exists($content_type, static::$map)) { 783 | return static::$map[$content_type]; 784 | } 785 | 786 | return $default; 787 | } 788 | 789 | /** 790 | * Will translate: "text/html; charset=UTF-8" into just "text/html" 791 | * @param $content_type 792 | * @return string 793 | */ 794 | public static function cleanContentType($content_type) 795 | { 796 | return trim(preg_replace('/;.*/', '', $content_type)); 797 | } 798 | } -------------------------------------------------------------------------------- /src/CurlDownloader.php: -------------------------------------------------------------------------------- 1 | client = $client; 18 | } 19 | 20 | protected function createTempFile() 21 | { 22 | return tempnam(sys_get_temp_dir(), uniqid()); 23 | } 24 | 25 | protected function getPathFromUrl($url) 26 | { 27 | return parse_url($url, PHP_URL_PATH); 28 | } 29 | 30 | protected function getFilenameFromUrl($url) 31 | { 32 | // equivalent to: pathinfo with PATHINFO_FILENAME 33 | return basename($this->getPathFromUrl($url)); 34 | } 35 | 36 | protected function getExtensionFromUrl($url) 37 | { 38 | return pathinfo($this->getFilenameFromUrl($url), PATHINFO_EXTENSION); 39 | } 40 | 41 | /** 42 | * @param $url 43 | * @param $destination 44 | * @return \Curl\Response 45 | */ 46 | public function download($url, $destination) 47 | { 48 | $handler = new HeaderHandler(); 49 | 50 | // Will download file to temp for now 51 | $temp_filename = $this->createTempFile(); 52 | 53 | $handle = fopen($temp_filename, 'w+'); 54 | 55 | $response = $this->client->request('GET', $url, [], [], [ 56 | CURLOPT_FILE => $handle, 57 | CURLOPT_HEADERFUNCTION => $handler->callback(), 58 | CURLOPT_TIMEOUT => $this->max_timeout 59 | ]); 60 | 61 | // TODO: refactor this whole filename logic into its own class 62 | if ($response->info->http_code === 200) { 63 | $filename = $handler->getContentDispositionFilename(); 64 | 65 | if (empty($filename)) { 66 | $url = $response->info->url; 67 | 68 | $filename = $this->getFilenameFromUrl($url); 69 | 70 | $extension_from_url = $this->getExtensionFromUrl($url); 71 | $extension_from_content_type = ContentTypes::getExtension($handler->getContentType()); 72 | 73 | // E.g: https://www.google.com/ 74 | if (empty($filename)) { 75 | $filename = 'index.' . ($extension_from_content_type ? $extension_from_content_type : 'html'); 76 | } else { 77 | 78 | // in case filename in url is like `videoplayback` with `content-type: video/mp4` 79 | if (empty($extension_from_url) && $extension_from_content_type) { 80 | $filename = ($filename . '.' . $extension_from_content_type); 81 | } 82 | } 83 | } 84 | 85 | $save_to = call_user_func($destination, $filename); 86 | 87 | rename($temp_filename, $save_to); 88 | } 89 | 90 | @fclose($handle); 91 | @unlink($temp_filename); 92 | 93 | return $response; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/HeaderHandler.php: -------------------------------------------------------------------------------- 1 | \\*?)=\\"(?.+)\\"@'; 14 | const REQUEST_HEADER_FILENAME_REGEX = '/filename\s*=\s*["\']*(?[^"\']+)/'; 15 | 16 | public function callback() 17 | { 18 | $oThis = $this; 19 | 20 | $headers = array(); 21 | $first_line_sent = false; 22 | 23 | return function ($ch, $data) use ($oThis, &$first_line_sent, &$headers) { 24 | $line = trim($data); 25 | 26 | if ($first_line_sent == false) { 27 | $first_line_sent = true; 28 | } elseif ($line === '') { 29 | $oThis->sendHeaders(); 30 | } else { 31 | 32 | $parts = explode(':', $line, 2); 33 | 34 | // Despite that headers may be retrieved case-insensitively, the original case MUST be preserved by the implementation 35 | // Non-conforming HTTP applications may depend on a certain case, 36 | // so it is useful for a user to be able to dictate the case of the HTTP headers when creating a request or response. 37 | 38 | // TODO: 39 | // Multiple message-header fields with the same field-name may be present in a message 40 | // if and only if the entire field-value for that header field is defined as a comma-separated list 41 | $oThis->headers[trim($parts[0])] = isset($parts[1]) ? trim($parts[1]) : null; 42 | } 43 | 44 | return strlen($data); 45 | }; 46 | } 47 | 48 | protected function sendHeaders() 49 | { 50 | if (is_callable($this->callback)) { 51 | call_user_func($this->callback, $this); 52 | } 53 | } 54 | 55 | /** 56 | * @param callable $callback 57 | */ 58 | public function onHeadersReceived($callback) 59 | { 60 | $this->callback = $callback; 61 | } 62 | 63 | // While header names are not case-sensitive, getHeaders() will preserve the exact case in which headers were originally specified. 64 | public function getHeaders() 65 | { 66 | return $this->headers; 67 | } 68 | 69 | public function getContentDispositionFilename() 70 | { 71 | $normalized = array_change_key_case($this->headers, CASE_LOWER); 72 | $header = isset($normalized['content-disposition']) ? $normalized['content-disposition'] : null; 73 | 74 | if ($header && preg_match(static::REQUEST_HEADER_FILENAME_REGEX, $header, $matches)) { 75 | return $matches['filename']; 76 | } 77 | 78 | return null; 79 | } 80 | 81 | public function getContentType() 82 | { 83 | $normalized = array_change_key_case($this->headers, CASE_LOWER); 84 | return isset($normalized['content-type']) ? $normalized['content-type'] : null; 85 | } 86 | } -------------------------------------------------------------------------------- /tests/DownloadTest.php: -------------------------------------------------------------------------------- 1 | client = new CurlDownloader(new Client()); 17 | } 18 | 19 | public function test_download_directly() 20 | { 21 | $this->client->download("https://demo.borland.com/testsite/downloads/Small.zip", function ($filename) { 22 | return './' . $filename; 23 | }); 24 | 25 | $this->assertTrue(file_exists('./Small.zip')); 26 | 27 | unlink('./Small.zip'); 28 | } 29 | 30 | public function test_download_content_disposition() 31 | { 32 | $this->client->download("https://demo.borland.com/testsite/downloads/downloadfile.php?file=Data1KB.dat&cd=attachment+filename", function ($filename) { 33 | return './' . $filename; 34 | }); 35 | 36 | $this->assertTrue(file_exists('./Data1KB.dat')); 37 | 38 | unlink('./Data1KB.dat'); 39 | } 40 | 41 | public function test_download_content_disposition_github() 42 | { 43 | $this->client->download("https://github.com/guzzle/guzzle/releases/download/6.5.4/guzzle.zip", function ($filename) { 44 | return './' . $filename; 45 | }); 46 | 47 | $this->assertTrue(file_exists('./guzzle.zip')); 48 | 49 | unlink('./guzzle.zip'); 50 | } 51 | 52 | public function test_download_content_disposition_custom_filename() 53 | { 54 | $this->client->download("https://demo.borland.com/testsite/downloads/downloadfile.php?file=Data1KB.dat&cd=attachment+filename", function ($filename) { 55 | return './2020-06-07-' . $filename; 56 | }); 57 | 58 | $this->assertTrue(file_exists('./2020-06-07-Data1KB.dat')); 59 | 60 | unlink('./2020-06-07-Data1KB.dat'); 61 | } 62 | 63 | public function test_download_guess_filename_from_content_type() 64 | { 65 | $this->client->download("https://www.google.com/", function ($filename) { 66 | return './' . $filename; 67 | }); 68 | 69 | $this->assertTrue(file_exists('./index.html')); 70 | 71 | unlink('./index.html'); 72 | } 73 | 74 | public function test_download_file_no_extension() 75 | { 76 | $this->client->download("https://cdn.proxynova.com/tests/content_type_text_html", function ($filename) { 77 | return './' . $filename; 78 | }); 79 | 80 | $this->assertTrue(file_exists('./content_type_text_html.html')); 81 | @unlink('./content_type_text_html.html'); 82 | } 83 | } 84 | --------------------------------------------------------------------------------