AppleQuirksPlugin.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2023 Claus-Justus Heine
  4. *
  5. * @author Claus-Justus Heine <himself@claus-justus-heine.de>
  6. *
  7. * @license GNU AGPL version 3 or any later version
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as
  11. * published by the Free Software Foundation, either version 3 of the
  12. * License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. namespace OCA\DAV\Connector\Sabre;
  24. use Sabre\DAV\Server;
  25. use Sabre\DAV\ServerPlugin;
  26. use Sabre\HTTP\RequestInterface;
  27. use Sabre\HTTP\ResponseInterface;
  28. /**
  29. * A plugin which tries to work-around peculiarities of the MacOS DAV client
  30. * apps. The following problems are addressed:
  31. *
  32. * - OSX calendar client sends REPORT requests to a random principal
  33. * collection but expects to find all principals (forgot to set
  34. * {DAV:}principal-property-search flag?)
  35. */
  36. class AppleQuirksPlugin extends ServerPlugin {
  37. /*
  38. private const OSX_CALENDAR_AGENT = 'CalendarAgent';
  39. private const OSX_DATAACCESSD_AGENT = 'dataaccessd';
  40. private const OSX_ACCOUNTSD_AGENT = 'accountsd';
  41. private const OSX_CONTACTS_AGENT = 'AddressBookCore';
  42. */
  43. private const OSX_AGENT_PREFIX = 'macOS';
  44. /** @var bool */
  45. private $isMacOSDavAgent = false;
  46. /**
  47. * Sets up the plugin.
  48. *
  49. * This method is automatically called by the server class.
  50. *
  51. * @return void
  52. */
  53. public function initialize(Server $server) {
  54. $server->on('beforeMethod:REPORT', [$this, 'beforeReport'], 0);
  55. $server->on('report', [$this, 'report'], 0);
  56. }
  57. /**
  58. * Triggered before any method is handled.
  59. *
  60. * @return void
  61. */
  62. public function beforeReport(RequestInterface $request, ResponseInterface $response) {
  63. $userAgent = $request->getRawServerValue('HTTP_USER_AGENT') ?? 'unknown';
  64. $this->isMacOSDavAgent = $this->isMacOSUserAgent($userAgent);
  65. }
  66. /**
  67. * This method handles HTTP REPORT requests.
  68. *
  69. * @param string $reportName
  70. * @param mixed $report
  71. * @param mixed $path
  72. *
  73. * @return bool
  74. */
  75. public function report($reportName, $report, $path) {
  76. if ($reportName == '{DAV:}principal-property-search' && $this->isMacOSDavAgent) {
  77. /** @var \Sabre\DAVACL\Xml\Request\PrincipalPropertySearchReport $report */
  78. $report->applyToPrincipalCollectionSet = true;
  79. }
  80. return true;
  81. }
  82. /**
  83. * Check whether the given $userAgent string pretends to originate from OSX.
  84. *
  85. * @param string $userAgent
  86. *
  87. * @return bool
  88. */
  89. protected function isMacOSUserAgent(string $userAgent):bool {
  90. return str_starts_with(self::OSX_AGENT_PREFIX, $userAgent);
  91. }
  92. /**
  93. * Decode the given OSX DAV agent string.
  94. *
  95. * @param string $agent
  96. *
  97. * @return null|array
  98. */
  99. protected function decodeMacOSAgentString(string $userAgent):?array {
  100. // OSX agent string is like: macOS/13.2.1 (22D68) dataaccessd/1.0
  101. if (preg_match('|^' . self::OSX_AGENT_PREFIX . '/([0-9]+)\\.([0-9]+)\\.([0-9]+)\s+\((\w+)\)\s+([^/]+)/([0-9]+)(?:\\.([0-9]+))?(?:\\.([0-9]+))?$|i', $userAgent, $matches)) {
  102. return [
  103. 'macOSVersion' => [
  104. 'major' => $matches[1],
  105. 'minor' => $matches[2],
  106. 'patch' => $matches[3],
  107. ],
  108. 'macOSAgent' => $matches[5],
  109. 'macOSAgentVersion' => [
  110. 'major' => $matches[6],
  111. 'minor' => $matches[7] ?? null,
  112. 'patch' => $matches[8] ?? null,
  113. ],
  114. ];
  115. }
  116. return null;
  117. }
  118. }