1<?php 2/* 3 * vim:set softtabstop=4 shiftwidth=4 expandtab: 4 * 5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later) 6 * Copyright 2001 - 2020 Ampache.org 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <https://www.gnu.org/licenses/>. 20 * 21 */ 22 23namespace Ampache\Module\Util; 24 25/** 26 * ##################################################################### 27 * # Warning # 28 * # ####### # 29 * # This external file is Ampache-adapted and probably unsynced with # 30 * # origin because abandoned by its original authors. # 31 * # # 32 * ##################################################################### 33 * 34 * This provides capability information for the current web client. 35 * 36 * Browser identification is performed by examining the HTTP_USER_AGENT 37 * environment variable provided by the web server. 38 * 39 * @TODO http://ajaxian.com/archives/parse-user-agent 40 * 41 * Copyright 1999-2013 Horde LLC (http://www.horde.org/) 42 * Copyright 2011 Paul MacIain (local changes for Ampache) 43 * 44 * See the enclosed file COPYING for license information (LGPL). If you 45 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 46 * 47 * @author Chuck Hagenbuch <chuck@horde.org> 48 * @author Jon Parise <jon@horde.org> 49 * @category Horde 50 * @license http://www.horde.org/licenses/lgpl21 LGPL 51 * @package Browser 52 */ 53class Horde_Browser 54{ 55 /** 56 * Major version number. 57 * 58 * @var integer 59 */ 60 private $_majorVersion = 0; 61 62 /** 63 * Minor version number. 64 * 65 * @var integer 66 */ 67 private $_minorVersion = 0; 68 69 /** 70 * Browser name. 71 * 72 * @var string 73 */ 74 private $_browser = ''; 75 76 /** 77 * Full user agent string. 78 * 79 * @var string 80 */ 81 private $_agent = ''; 82 83 /** 84 * Lower-case user agent string. 85 * 86 * @var string 87 */ 88 private $_lowerAgent = ''; 89 90 /** 91 * HTTP_ACCEPT string 92 * 93 * @var string 94 */ 95 private $_accept = ''; 96 97 /** 98 * Platform the browser is running on. 99 * 100 * @var string 101 */ 102 private $_platform = ''; 103 104 /** 105 * Is this a mobile browser? 106 * 107 * @var boolean 108 */ 109 private $_mobile = false; 110 111 /** 112 * Is this a tablet browser? 113 * 114 * @var boolean 115 */ 116 private $_tablet = false; 117 118 /** 119 * Features. 120 * 121 * @var array 122 */ 123 private $_features = array( 124 'frames' => true, 125 'html' => true, 126 'images' => true, 127 'java' => true, 128 'javascript' => true, 129 'tables' => true 130 ); 131 132 /** 133 * Quirks. 134 * 135 * @var array 136 */ 137 private $_quirks = array(); 138 139 /** 140 * Creates a browser instance (Constructor). 141 */ 142 public function __construct() 143 { 144 $this->match(); 145 } 146 147 /** 148 * Parses the user agent string and initializes the object with all the 149 * known features and quirks for the given browser. 150 * 151 * @param string $userAgent The browser string to parse. 152 * @param string $accept The HTTP_ACCEPT settings to use. 153 */ 154 public function match($userAgent = null, $accept = null) 155 { 156 // Set our agent string. 157 if ($userAgent == null) { 158 if (filter_has_var(INPUT_SERVER, 'HTTP_USER_AGENT')) { 159 $this->_agent = trim($_SERVER['HTTP_USER_AGENT']); 160 } 161 } else { 162 $this->_agent = $userAgent; 163 } 164 $this->_lowerAgent = strtolower($this->_agent); 165 166 // Set our accept string. 167 if ($accept === null) { 168 if (filter_has_var(INPUT_SERVER, 'HTTP_ACCEPT')) { 169 $this->_accept = strtolower(trim($_SERVER['HTTP_ACCEPT'])); 170 } 171 } else { 172 $this->_accept = strtolower($accept); 173 } 174 175 // Check for UTF support. 176 if (filter_has_var(INPUT_SERVER, 'HTTP_ACCEPT_CHARSET')) { 177 $this->setFeature('utf', strpos(strtolower($_SERVER['HTTP_ACCEPT_CHARSET']), 'utf') !== false); 178 } 179 180 if (empty($this->_agent)) { 181 return; 182 } 183 184 $this->setPlatform(); 185 186 // Use local scope for frequently accessed variables. 187 $agent = $this->_agent; 188 $lowerAgent = $this->_lowerAgent; 189 190 if (strpos($lowerAgent, 'iemobile') !== false || strpos($lowerAgent, 191 'mobileexplorer') !== false || strpos($lowerAgent, 'openwave') !== false) { 192 $this->setFeature('frames', false); 193 $this->setFeature('javascript', false); 194 $this->setQuirk('avoid_popup_windows'); 195 $this->_mobile = true; 196 197 if (preg_match('|iemobile[/ ]([0-9.]+)|', $lowerAgent, $version)) { 198 list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); 199 if ($this->_majorVersion >= 7) { 200 // Windows Phone, Modern Browser 201 $this->setBrowser('msie'); 202 $this->setFeature('javascript'); 203 $this->setFeature('xmlhttpreq'); 204 $this->setFeature('ajax'); 205 $this->setFeature('dom'); 206 $this->setFeature('utf'); 207 $this->setFeature('rte'); 208 $this->setFeature('cite'); 209 } 210 } 211 } elseif (strpos($lowerAgent, 'opera mini') !== false || strpos($lowerAgent, 'operamini') !== false) { 212 $this->setBrowser('opera'); 213 $this->setFeature('frames', false); 214 $this->setFeature('javascript'); 215 $this->setQuirk('avoid_popup_windows'); 216 $this->_mobile = true; 217 } elseif (preg_match('|Opera[/ ]([0-9.]+)|', $agent, $version)) { 218 $this->setBrowser('opera'); 219 list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); 220 $this->setFeature('javascript'); 221 $this->setQuirk('no_filename_spaces'); 222 223 /* Opera Mobile reports its screen resolution in the user 224 * agent strings. */ 225 if (preg_match('/; (120x160|240x280|240x320|320x320)\)/', $agent)) { 226 $this->_mobile = true; 227 } elseif (preg_match('|Tablet|', $agent)) { 228 $this->_mobile = true; 229 $this->_tablet = true; 230 } 231 232 if ($this->_majorVersion >= 7) { 233 if ($this->_majorVersion >= 8) { 234 $this->setFeature('xmlhttpreq'); 235 $this->setFeature('javascript', 1.5); 236 } 237 if ($this->_majorVersion >= 9) { 238 $this->setFeature('dataurl', 4100); 239 if ($this->_minorVersion >= 5) { 240 $this->setFeature('ajax'); 241 $this->setFeature('rte'); 242 } 243 } 244 $this->setFeature('dom'); 245 $this->setFeature('iframes'); 246 $this->setFeature('accesskey'); 247 $this->setFeature('optgroup'); 248 $this->setQuirk('double_linebreak_textarea'); 249 } 250 } elseif (strpos($lowerAgent, 'elaine/') !== false || strpos($lowerAgent, 251 'palmsource') !== false || strpos($lowerAgent, 'digital paths') !== false) { 252 $this->setBrowser('palm'); 253 $this->setFeature('images', false); 254 $this->setFeature('frames', false); 255 $this->setFeature('javascript', false); 256 $this->setQuirk('avoid_popup_windows'); 257 $this->_mobile = true; 258 } elseif ((preg_match('|MSIE ([0-9.]+)|', $agent, $version)) || (preg_match('|Internet Explorer/([0-9.]+)|', 259 $agent, $version))) { 260 $this->setBrowser('msie'); 261 $this->setQuirk('cache_ssl_downloads'); 262 $this->setQuirk('cache_same_url'); 263 $this->setQuirk('break_disposition_filename'); 264 265 if (strpos($version[1], '.') !== false) { 266 list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); 267 } else { 268 $this->_majorVersion = $version[1]; 269 $this->_minorVersion = 0; 270 } 271 272 /* IE (< 7) on Windows does not support alpha transparency 273 * in PNG images. */ 274 if (($this->_majorVersion < 7) && preg_match('/windows/i', $agent)) { 275 $this->setQuirk('png_transparency'); 276 } 277 278 /* Some Handhelds have their screen resolution in the user 279 * agent string, which we can use to look for mobile 280 * agents. */ 281 if (preg_match('/; (120x160|240x280|240x320|320x320)\)/', $agent)) { 282 $this->_mobile = true; 283 } 284 285 $this->setFeature('xmlhttpreq'); 286 287 switch ($this->_majorVersion) { 288 default: 289 case 10: 290 case 9: 291 case 8: 292 case 7: 293 $this->setFeature('javascript', 1.4); 294 $this->setFeature('ajax'); 295 $this->setFeature('dom'); 296 $this->setFeature('iframes'); 297 $this->setFeature('utf'); 298 $this->setFeature('rte'); 299 $this->setFeature('homepage'); 300 $this->setFeature('accesskey'); 301 $this->setFeature('optgroup'); 302 if ($this->_majorVersion != 7) { 303 $this->setFeature('cite'); 304 $this->setFeature('dataurl', ($this->_majorVersion == 8) ? 32768 : true); 305 } 306 break; 307 case 6: 308 $this->setFeature('javascript', 1.4); 309 $this->setFeature('dom'); 310 $this->setFeature('iframes'); 311 $this->setFeature('utf'); 312 $this->setFeature('rte'); 313 $this->setFeature('homepage'); 314 $this->setFeature('accesskey'); 315 $this->setFeature('optgroup'); 316 $this->setQuirk('scrollbar_in_way'); 317 $this->setQuirk('broken_multipart_form'); 318 $this->setQuirk('windowed_controls'); 319 break; 320 case 5: 321 if ($this->getPlatform() == 'mac') { 322 $this->setFeature('javascript', 1.2); 323 $this->setFeature('optgroup'); 324 $this->setFeature('xmlhttpreq', false); 325 } else { 326 // MSIE 5 for Windows. 327 $this->setFeature('javascript', 1.4); 328 $this->setFeature('dom'); 329 if ($this->_minorVersion >= 5) { 330 $this->setFeature('rte'); 331 $this->setQuirk('windowed_controls'); 332 } 333 } 334 $this->setFeature('iframes'); 335 $this->setFeature('utf'); 336 $this->setFeature('homepage'); 337 $this->setFeature('accesskey'); 338 if ($this->_minorVersion == 5) { 339 $this->setQuirk('break_disposition_header'); 340 $this->setQuirk('broken_multipart_form'); 341 } 342 break; 343 case 4: 344 $this->setFeature('javascript', 1.2); 345 $this->setFeature('accesskey'); 346 $this->setFeature('xmlhttpreq', false); 347 if ($this->_minorVersion > 0) { 348 $this->setFeature('utf'); 349 } 350 break; 351 case 3: 352 $this->setFeature('javascript', 1.1); 353 $this->setQuirk('avoid_popup_windows'); 354 $this->setFeature('xmlhttpreq', false); 355 break; 356 } 357 } elseif (preg_match('|ANTFresco/([0-9]+)|', $agent, $version)) { 358 $this->setBrowser('fresco'); 359 $this->setFeature('javascript', 1.1); 360 $this->setQuirk('avoid_popup_windows'); 361 } elseif (strpos($lowerAgent, 'avantgo') !== false) { 362 $this->setBrowser('avantgo'); 363 $this->_mobile = true; 364 } elseif (preg_match('|Konqueror/([0-9]+)\.?([0-9]+)?|', $agent, 365 $version) || preg_match('|Safari/([0-9]+)\.?([0-9]+)?|', $agent, $version)) { 366 $this->setBrowser('webkit'); 367 $this->setQuirk('empty_file_input_value'); 368 $this->setQuirk('no_hidden_overflow_tables'); 369 $this->setFeature('dataurl'); 370 371 if (strpos($agent, 'Mobile') !== false || strpos($agent, 'Android') !== false || strpos($agent, 372 'SAMSUNG-GT') !== false || ((strpos($agent, 'Nokia') !== false || strpos($agent, 373 'Symbian') !== false) && strpos($agent, 'WebKit') !== false) || (strpos($agent, 374 'N900') !== false && strpos($agent, 'Maemo Browser') !== false) || (strpos($agent, 375 'MeeGo') !== false && strpos($agent, 'NokiaN9') !== false)) { 376 // WebKit Mobile 377 $this->setFeature('frames', false); 378 $this->setFeature('javascript'); 379 $this->setQuirk('avoid_popup_windows'); 380 $this->_mobile = true; 381 } 382 383 $this->_majorVersion = $version[1]; 384 if (isset($version[2])) { 385 $this->_minorVersion = $version[2]; 386 } 387 388 if (stripos($agent, 'Chrome/') !== false || stripos($agent, 'CriOS/') !== false) { 389 // Google Chrome. 390 $this->setFeature('ischrome'); 391 $this->setFeature('rte'); 392 $this->setFeature('utf'); 393 $this->setFeature('javascript', 1.4); 394 $this->setFeature('ajax'); 395 $this->setFeature('dom'); 396 $this->setFeature('iframes'); 397 $this->setFeature('accesskey'); 398 $this->setFeature('xmlhttpreq'); 399 $this->setQuirk('empty_file_input_value', 0); 400 401 if (preg_match('|Chrome/([0-9.]+)|i', $agent, $version_string)) { 402 list($this->_majorVersion, $this->_minorVersion) = explode('.', $version_string[1], 2); 403 } 404 } elseif (stripos($agent, 'Safari/') !== false && $this->_majorVersion >= 60) { 405 // Safari. 406 $this->setFeature('issafari'); 407 408 // Truly annoying - Safari did not start putting real version 409 // numbers until Version 3. 410 if (preg_match('|Version/([0-9.]+)|', $agent, $version_string)) { 411 list($this->_majorVersion, $this->_minorVersion) = explode('.', $version_string[1], 2); 412 $this->_minorVersion = (int)($this->_minorVersion); 413 $this->setFeature('ajax'); 414 $this->setFeature('rte'); 415 } elseif ($this->_majorVersion >= 412) { 416 $this->_majorVersion = 2; 417 $this->_minorVersion = 0; 418 } else { 419 if ($this->_majorVersion >= 312) { 420 $this->_minorVersion = 3; 421 } elseif ($this->_majorVersion >= 124) { 422 $this->_minorVersion = 2; 423 } else { 424 $this->_minorVersion = 0; 425 } 426 $this->_majorVersion = 1; 427 } 428 429 $this->setFeature('utf'); 430 $this->setFeature('javascript', 1.4); 431 $this->setFeature('dom'); 432 $this->setFeature('iframes'); 433 if ($this->_majorVersion > 1 || $this->_minorVersion > 2) { 434 // As of Safari 1.3 435 $this->setFeature('accesskey'); 436 $this->setFeature('xmlhttpreq'); 437 } 438 } else { 439 // Konqueror. 440 $this->setFeature('javascript', 1.1); 441 $this->setFeature('iskonqueror'); 442 switch ($this->_majorVersion) { 443 case 4: 444 case 3: 445 $this->setFeature('dom'); 446 $this->setFeature('iframes'); 447 if ($this->_minorVersion >= 5 || $this->_majorVersion == 4) { 448 $this->setFeature('accesskey'); 449 $this->setFeature('xmlhttpreq'); 450 } 451 break; 452 } 453 } 454 } elseif (preg_match('|Mozilla/([0-9.]+)|', $agent, $version)) { 455 $this->setBrowser('mozilla'); 456 $this->setQuirk('must_cache_forms'); 457 458 list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); 459 switch ($this->_majorVersion) { 460 default: 461 case 5: 462 if ($this->getPlatform() == 'win') { 463 $this->setQuirk('break_disposition_filename'); 464 } 465 $this->setFeature('javascript', 1.4); 466 $this->setFeature('ajax'); 467 $this->setFeature('dom'); 468 $this->setFeature('accesskey'); 469 $this->setFeature('optgroup'); 470 $this->setFeature('xmlhttpreq'); 471 $this->setFeature('cite'); 472 if (preg_match('|rv:(.*)\)|', $agent, $revision)) { 473 if (version_compare($revision[1], '1', '>=')) { 474 $this->setFeature('iframes'); 475 } 476 if (version_compare($revision[1], '1.3', '>=')) { 477 $this->setFeature('rte'); 478 } 479 if (version_compare($revision[1], '1.8.1', '>=')) { 480 $this->setFeature('dataurl'); 481 } 482 if (version_compare($revision[1], '10.0', '>=')) { 483 $this->setFeature('utf'); 484 } 485 } 486 if (stripos($agent, 'mobile') !== false) { 487 $this->_mobile = true; 488 } elseif (stripos($agent, 'tablet') !== false) { 489 $this->_tablet = true; 490 $this->_mobile = true; 491 } 492 break; 493 case 4: 494 $this->setFeature('javascript', 1.3); 495 $this->setQuirk('buggy_compression'); 496 break; 497 case 3: 498 case 2: 499 case 1: 500 case 0: 501 $this->setFeature('javascript', 1); 502 $this->setQuirk('buggy_compression'); 503 break; 504 } 505 } elseif (preg_match('|Lynx/([0-9]+)|', $agent, $version)) { 506 $this->setBrowser('lynx'); 507 $this->setFeature('images', false); 508 $this->setFeature('frames', false); 509 $this->setFeature('javascript', false); 510 $this->setQuirk('avoid_popup_windows'); 511 } elseif (preg_match('|Links \(([0-9]+)|', $agent, $version)) { 512 $this->setBrowser('links'); 513 $this->setFeature('images', false); 514 $this->setFeature('frames', false); 515 $this->setFeature('javascript', false); 516 $this->setQuirk('avoid_popup_windows'); 517 } elseif (preg_match('|HotJava/([0-9]+)|', $agent, $version)) { 518 $this->setBrowser('hotjava'); 519 $this->setFeature('javascript', false); 520 } elseif (strpos($agent, 'UP/') !== false || strpos($agent, 'UP.B') !== false || strpos($agent, 521 'UP.L') !== false) { 522 $this->setBrowser('up'); 523 $this->setFeature('html', false); 524 $this->setFeature('javascript', false); 525 $this->setFeature('hdml'); 526 $this->setFeature('wml'); 527 528 if (strpos($agent, 'GUI') !== false && strpos($agent, 'UP.Link') !== false) { 529 /* The device accepts Openwave GUI extensions for WML 530 * 1.3. Non-UP.Link gateways sometimes have problems, 531 * so exclude them. */ 532 $this->setQuirk('ow_gui_1.3'); 533 } 534 $this->_mobile = true; 535 } elseif (strpos($agent, 'Xiino/') !== false) { 536 $this->setBrowser('xiino'); 537 $this->setFeature('hdml'); 538 $this->setFeature('wml'); 539 $this->_mobile = true; 540 } elseif (strpos($agent, 'Palmscape/') !== false) { 541 $this->setBrowser('palmscape'); 542 $this->setFeature('javascript', false); 543 $this->setFeature('hdml'); 544 $this->setFeature('wml'); 545 $this->_mobile = true; 546 } elseif (strpos($agent, 'Nokia') !== false) { 547 $this->setBrowser('nokia'); 548 $this->setFeature('html', false); 549 $this->setFeature('wml'); 550 $this->setFeature('xhtml'); 551 $this->_mobile = true; 552 } elseif (strpos($agent, 'Ericsson') !== false) { 553 $this->setBrowser('ericsson'); 554 $this->setFeature('html', false); 555 $this->setFeature('wml'); 556 $this->_mobile = true; 557 } elseif (strpos($agent, 'Grundig') !== false) { 558 $this->setBrowser('grundig'); 559 $this->setFeature('xhtml'); 560 $this->setFeature('wml'); 561 $this->_mobile = true; 562 } elseif (strpos($agent, 'NetFront') !== false) { 563 $this->setBrowser('netfront'); 564 $this->setFeature('xhtml'); 565 $this->setFeature('wml'); 566 $this->_mobile = true; 567 } elseif (strpos($lowerAgent, 'wap') !== false) { 568 $this->setBrowser('wap'); 569 $this->setFeature('html', false); 570 $this->setFeature('javascript', false); 571 $this->setFeature('hdml'); 572 $this->setFeature('wml'); 573 $this->_mobile = true; 574 } elseif (strpos($lowerAgent, 'docomo') !== false || strpos($lowerAgent, 'portalmmm') !== false) { 575 $this->setBrowser('imode'); 576 $this->setFeature('images', false); 577 $this->_mobile = true; 578 } elseif (preg_match('|BlackBerry.*?/([0-9.]+)|', $agent, $version)) { 579 list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); 580 $this->setBrowser('blackberry'); 581 $this->setFeature('html', false); 582 $this->setFeature('javascript', false); 583 $this->setFeature('hdml'); 584 $this->setFeature('wml'); 585 $this->_mobile = true; 586 if ($this->_majorVersion >= 5 || ($this->_majorVersion == 4 && $this->_minorVersion >= 6)) { 587 $this->setFeature('ajax'); 588 $this->setFeature('iframes'); 589 $this->setFeature('javascript', 1.5); 590 $this->setFeature('dom'); 591 $this->setFeature('xmlhttpreq'); 592 } 593 } elseif (strpos($agent, 'MOT-') !== false) { 594 $this->setBrowser('motorola'); 595 $this->setFeature('html', false); 596 $this->setFeature('javascript', false); 597 $this->setFeature('hdml'); 598 $this->setFeature('wml'); 599 $this->_mobile = true; 600 } elseif (strpos($lowerAgent, 'j-') !== false) { 601 $this->setBrowser('mml'); 602 $this->_mobile = true; 603 } 604 } 605 606 /** 607 * Matches the platform of the browser. 608 * 609 * This is a pretty simplistic implementation, but it's intended to let us 610 * tell what line breaks to send, so it's good enough for its purpose. 611 */ 612 private function setPlatform() 613 { 614 if (strpos($this->_lowerAgent, 'wind') !== false) { 615 $this->_platform = 'win'; 616 } elseif (strpos($this->_lowerAgent, 'mac') !== false) { 617 $this->_platform = 'mac'; 618 } else { 619 $this->_platform = 'unix'; 620 } 621 } 622 623 /** 624 * Returns the currently matched platform. 625 * 626 * @return string The user's platform. 627 */ 628 private function getPlatform() 629 { 630 return $this->_platform; 631 } 632 633 /** 634 * Sets the current browser. 635 * 636 * @param string $browser The browser to set as current. 637 */ 638 private function setBrowser($browser) 639 { 640 $this->_browser = $browser; 641 } 642 643 /** 644 * Determines if the given browser is the same as the current. 645 * 646 * @param string $browser The browser to check. 647 * 648 * @return boolean Is the given browser the same as the current? 649 */ 650 public function isBrowser($browser) 651 { 652 return ($this->_browser === $browser); 653 } 654 655 /** 656 * Sets unique behavior for the current browser. 657 * 658 * @param string $quirk The behavior to set. Quirks: 659 * - avoid_popup_windows 660 * - break_disposition_header 661 * - break_disposition_filename 662 * - broken_multipart_form 663 * - buggy_compression 664 * - cache_same_url 665 * - cache_ssl_downloads 666 * - double_linebreak_textarea 667 * - empty_file_input_value 668 * - must_cache_forms 669 * - no_filename_spaces 670 * - no_hidden_overflow_tables 671 * - ow_gui_1.3 672 * - png_transparency 673 * - scrollbar_in_way 674 * - scroll_tds 675 * - windowed_controls 676 * @param boolean $value Special behavior parameter. 677 */ 678 private function setQuirk($quirk, $value = true) 679 { 680 if ($value) { 681 $this->_quirks[$quirk] = $value; 682 } else { 683 unset($this->_quirks[$quirk]); 684 } 685 } 686 687 /** 688 * Checks unique behavior for the current browser. 689 * 690 * @param string $quirk The behavior to check. 691 * 692 * @return boolean Does the browser have the behavior set? 693 */ 694 private function hasQuirk($quirk) 695 { 696 return !empty($this->_quirks[$quirk]); 697 } 698 699 /** 700 * Sets capabilities for the current browser. 701 * 702 * @param string $feature The capability to set. Features: 703 * - accesskey 704 * - ajax 705 * - cite 706 * - dataurl 707 * - dom 708 * - frames 709 * - hdml 710 * - html 711 * - homepage 712 * - iframes 713 * - images 714 * - ischrome 715 * - iskonqueror 716 * - issafari 717 * - java 718 * - javascript 719 * - optgroup 720 * - rte 721 * - tables 722 * - utf 723 * - wml 724 * - xmlhttpreq 725 * @param boolean $value Special capability parameter. 726 */ 727 public function setFeature($feature, $value = true) 728 { 729 if ($value) { 730 $this->_features[$feature] = $value; 731 } else { 732 unset($this->_features[$feature]); 733 } 734 } 735 736 /** 737 * Returns the headers for a browser download. 738 * 739 * @param string $filename The filename of the download. 740 * @param string $cType The content-type description of the file. 741 * @param boolean $inline True if inline, false if attachment. 742 * @param string $cLength The content-length of this file. 743 * 744 * @return string[] 745 */ 746 public function getDownloadHeaders( 747 $filename = 'unknown', 748 $cType = null, 749 $inline = false, 750 $cLength = null 751 ): array { 752 /* Remove linebreaks from file names. */ 753 $filename = str_replace(array("\r\n", "\r", "\n"), ' ', $filename); 754 755 /* Some browsers don't like spaces in the filename. */ 756 if ($this->hasQuirk('no_filename_spaces')) { 757 $filename = strtr($filename, ' ', '_'); 758 } 759 760 /* MSIE doesn't like multiple periods in the file name. Convert all 761 * periods (except the last one) to underscores. */ 762 if ($this->isBrowser('msie')) { 763 if (($pos = strrpos($filename, '.'))) { 764 $filename = strtr(substr($filename, 0, $pos), '.', '_') . substr($filename, $pos); 765 } 766 767 /* Encode the filename so IE downloads it correctly. (Bug #129) */ 768 $filename = rawurlencode($filename); 769 } 770 771 $headers = []; 772 773 /* Content-Type/Content-Disposition Header. */ 774 if ($inline) { 775 if ($cType !== null) { 776 $headers['Content-Type'] = trim($cType); 777 } elseif ($this->isBrowser('msie')) { 778 $headers['Content-Type'] = 'application/x-msdownload'; 779 } else { 780 $headers['Content-Type'] = 'application/octet-stream'; 781 } 782 $headers['Content-Disposition'] = 'inline; filename="' . $filename . '"'; 783 } else { 784 if ($this->isBrowser('msie')) { 785 $headers['Content-Type'] = 'application/x-msdownload'; 786 } elseif ($cType !== null) { 787 $headers['Content-Type'] = trim($cType); 788 } else { 789 $headers['Content-Type'] = 'application/octet-stream'; 790 } 791 792 if ($this->hasQuirk('break_disposition_header')) { 793 $headers['Content-Disposition'] = 'filename="' . $filename . '"'; 794 } else { 795 $headers['Content-Disposition'] = 'attachment; filename="' . $filename . '"'; 796 } 797 } 798 799 /* Content-Length Header. Only send if we are not compressing 800 * output. */ 801 if ($cLength !== null && !in_array('ob_gzhandler', ob_list_handlers())) { 802 $headers['Content-Length'] = $cLength; 803 } 804 805 /* Overwrite Pragma: and other caching headers for IE. */ 806 if ($this->hasQuirk('cache_ssl_downloads')) { 807 $headers['Expires'] = 0; 808 $headers['Cache-Control'] = 'must-revalidate'; 809 $headers['Pragma'] = 'public'; 810 } 811 812 return $headers; 813 } 814} 815