aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Repair/RepairMimeTypes.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Repair/RepairMimeTypes.php')
-rw-r--r--lib/private/Repair/RepairMimeTypes.php473
1 files changed, 473 insertions, 0 deletions
diff --git a/lib/private/Repair/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php
new file mode 100644
index 00000000000..3c9720b9e91
--- /dev/null
+++ b/lib/private/Repair/RepairMimeTypes.php
@@ -0,0 +1,473 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Repair;
+
+use OC\Migration\NullOutput;
+use OCP\DB\Exception;
+use OCP\DB\IResult;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IAppConfig;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class RepairMimeTypes implements IRepairStep {
+ private bool $dryRun = false;
+ private int $changeCount = 0;
+
+ /** @var int */
+ protected int $folderMimeTypeId;
+
+ public function __construct(
+ protected IConfig $config,
+ protected IAppConfig $appConfig,
+ protected IDBConnection $connection,
+ ) {
+ }
+
+ public function getName(): string {
+ return 'Repair mime types';
+ }
+
+ /**
+ * @throws Exception
+ */
+ private function updateMimetypes($updatedMimetypes): IResult|int|null {
+ if ($this->dryRun) {
+ $this->changeCount += count($updatedMimetypes);
+ return null;
+ }
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select('id')
+ ->from('mimetypes')
+ ->where($query->expr()->eq('mimetype', $query->createParameter('mimetype'), IQueryBuilder::PARAM_INT));
+ $insert = $this->connection->getQueryBuilder();
+ $insert->insert('mimetypes')
+ ->setValue('mimetype', $insert->createParameter('mimetype'));
+
+ if (empty($this->folderMimeTypeId)) {
+ $query->setParameter('mimetype', 'httpd/unix-directory');
+ $result = $query->execute();
+ $this->folderMimeTypeId = (int)$result->fetchOne();
+ $result->closeCursor();
+ }
+
+ $update = $this->connection->getQueryBuilder();
+ $update->update('filecache')
+ ->runAcrossAllShards()
+ ->set('mimetype', $update->createParameter('mimetype'))
+ ->where($update->expr()->neq('mimetype', $update->createParameter('mimetype'), IQueryBuilder::PARAM_INT))
+ ->andWhere($update->expr()->neq('mimetype', $update->createParameter('folder'), IQueryBuilder::PARAM_INT))
+ ->andWhere($update->expr()->iLike('name', $update->createParameter('name')))
+ ->setParameter('folder', $this->folderMimeTypeId);
+
+ $count = 0;
+ foreach ($updatedMimetypes as $extension => $mimetype) {
+ // get target mimetype id
+ $query->setParameter('mimetype', $mimetype);
+ $result = $query->execute();
+ $mimetypeId = (int)$result->fetchOne();
+ $result->closeCursor();
+
+ if (!$mimetypeId) {
+ // insert mimetype
+ $insert->setParameter('mimetype', $mimetype);
+ $insert->execute();
+ $mimetypeId = $insert->getLastInsertId();
+ }
+
+ // change mimetype for files with x extension
+ $update->setParameter('mimetype', $mimetypeId)
+ ->setParameter('name', '%' . $this->connection->escapeLikeParameter('.' . $extension));
+ $count += $update->execute();
+ }
+
+ return $count;
+ }
+
+ /**
+ * @throws Exception
+ * @since 12.0.0.14
+ */
+ private function introduceImageTypes(): IResult|int|null {
+ $updatedMimetypes = [
+ 'jp2' => 'image/jp2',
+ 'webp' => 'image/webp',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 12.0.0.13
+ */
+ private function introduceWindowsProgramTypes(): IResult|int|null {
+ $updatedMimetypes = [
+ 'htaccess' => 'text/plain',
+ 'bat' => 'application/x-msdos-program',
+ 'cmd' => 'application/cmd',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 13.0.0.0
+ */
+ private function introduceLocationTypes(): IResult|int|null {
+ $updatedMimetypes = [
+ 'gpx' => 'application/gpx+xml',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'tcx' => 'application/vnd.garmin.tcx+xml',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 13.0.0.3
+ */
+ private function introduceInternetShortcutTypes(): IResult|int|null {
+ $updatedMimetypes = [
+ 'url' => 'application/internet-shortcut',
+ 'webloc' => 'application/internet-shortcut'
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 13.0.0.6
+ */
+ private function introduceStreamingTypes(): IResult|int|null {
+ $updatedMimetypes = [
+ 'm3u' => 'audio/mpegurl',
+ 'm3u8' => 'audio/mpegurl',
+ 'pls' => 'audio/x-scpls'
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 14.0.0.8
+ */
+ private function introduceVisioTypes(): IResult|int|null {
+ $updatedMimetypes = [
+ 'vsdm' => 'application/vnd.visio',
+ 'vsdx' => 'application/vnd.visio',
+ 'vssm' => 'application/vnd.visio',
+ 'vssx' => 'application/vnd.visio',
+ 'vstm' => 'application/vnd.visio',
+ 'vstx' => 'application/vnd.visio',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 14.0.0.10
+ */
+ private function introduceComicbookTypes(): IResult|int|null {
+ $updatedMimetypes = [
+ 'cb7' => 'application/comicbook+7z',
+ 'cba' => 'application/comicbook+ace',
+ 'cbr' => 'application/comicbook+rar',
+ 'cbt' => 'application/comicbook+tar',
+ 'cbtc' => 'application/comicbook+truecrypt',
+ 'cbz' => 'application/comicbook+zip',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 20.0.0.5
+ */
+ private function introduceOpenDocumentTemplates(): IResult|int|null {
+ $updatedMimetypes = [
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 21.0.0.7
+ */
+ private function introduceOrgModeType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'org' => 'text/org'
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 23.0.0.2
+ */
+ private function introduceFlatOpenDocumentType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'fodt' => 'application/vnd.oasis.opendocument.text-flat-xml',
+ 'fods' => 'application/vnd.oasis.opendocument.spreadsheet-flat-xml',
+ 'fodg' => 'application/vnd.oasis.opendocument.graphics-flat-xml',
+ 'fodp' => 'application/vnd.oasis.opendocument.presentation-flat-xml',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 25.0.0.2
+ */
+ private function introduceOnlyofficeFormType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'oform' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform',
+ 'docxf' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 26.0.0.1
+ */
+ private function introduceAsciidocType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'adoc' => 'text/asciidoc',
+ 'asciidoc' => 'text/asciidoc',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 28.0.0.5
+ */
+ private function introduceEnhancedMetafileFormatType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'emf' => 'image/emf',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 29.0.0.2
+ */
+ private function introduceEmlAndMsgFormatType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'eml' => 'message/rfc822',
+ 'msg' => 'application/vnd.ms-outlook',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 29.0.0.6
+ */
+ private function introduceAacAudioType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'aac' => 'audio/aac',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 29.0.10
+ */
+ private function introduceReStructuredTextFormatType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'rst' => 'text/x-rst',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 30.0.0
+ */
+ private function introduceExcalidrawType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'excalidraw' => 'application/vnd.excalidraw+json',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+
+ /**
+ * @throws Exception
+ * @since 31.0.0
+ */
+ private function introduceZstType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'zst' => 'application/zstd',
+ 'nfo' => 'text/x-nfo',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * @throws Exception
+ * @since 32.0.0
+ */
+ private function introduceMusicxmlType(): IResult|int|null {
+ $updatedMimetypes = [
+ 'mxl' => 'application/vnd.recordare.musicxml',
+ 'musicxml' => 'application/vnd.recordare.musicxml+xml',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
+
+
+ /**
+ * Check if there are any migrations available
+ *
+ * @throws Exception
+ */
+ public function migrationsAvailable(): bool {
+ $this->dryRun = true;
+ $this->run(new NullOutput());
+ $this->dryRun = false;
+ return $this->changeCount > 0;
+ }
+
+ /**
+ * Get the current mimetype version
+ */
+ private function getMimeTypeVersion(): string {
+ $serverVersion = $this->config->getSystemValueString('version', '0.0.0');
+ // 29.0.0.10 is the last version with a mimetype migration before it was moved to a separate version number
+ if (version_compare($serverVersion, '29.0.0.10', '>')) {
+ return $this->appConfig->getValueString('files', 'mimetype_version', '29.0.0.10');
+ }
+
+ return $serverVersion;
+ }
+
+ /**
+ * Fix mime types
+ *
+ * @throws Exception
+ */
+ public function run(IOutput $out): void {
+ $serverVersion = $this->config->getSystemValueString('version', '0.0.0');
+ $mimeTypeVersion = $this->getMimeTypeVersion();
+
+ // NOTE TO DEVELOPERS: when adding new mime types, please make sure to
+ // add a version comparison to avoid doing it every time
+ // PLEASE ALSO KEEP THE LIST SORTED BY VERSION NUMBER
+
+ if (version_compare($mimeTypeVersion, '12.0.0.14', '<') && $this->introduceImageTypes()) {
+ $out->info('Fixed image mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '12.0.0.13', '<') && $this->introduceWindowsProgramTypes()) {
+ $out->info('Fixed windows program mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '13.0.0.0', '<') && $this->introduceLocationTypes()) {
+ $out->info('Fixed geospatial mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '13.0.0.3', '<') && $this->introduceInternetShortcutTypes()) {
+ $out->info('Fixed internet-shortcut mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '13.0.0.6', '<') && $this->introduceStreamingTypes()) {
+ $out->info('Fixed streaming mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '14.0.0.8', '<') && $this->introduceVisioTypes()) {
+ $out->info('Fixed visio mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '14.0.0.10', '<') && $this->introduceComicbookTypes()) {
+ $out->info('Fixed comicbook mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '20.0.0.5', '<') && $this->introduceOpenDocumentTemplates()) {
+ $out->info('Fixed OpenDocument template mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '21.0.0.7', '<') && $this->introduceOrgModeType()) {
+ $out->info('Fixed orgmode mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '23.0.0.2', '<') && $this->introduceFlatOpenDocumentType()) {
+ $out->info('Fixed Flat OpenDocument mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '25.0.0.2', '<') && $this->introduceOnlyofficeFormType()) {
+ $out->info('Fixed ONLYOFFICE Forms OpenXML mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '26.0.0.1', '<') && $this->introduceAsciidocType()) {
+ $out->info('Fixed AsciiDoc mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '28.0.0.5', '<') && $this->introduceEnhancedMetafileFormatType()) {
+ $out->info('Fixed Enhanced Metafile Format mime types');
+ }
+
+ if (version_compare($mimeTypeVersion, '29.0.0.2', '<') && $this->introduceEmlAndMsgFormatType()) {
+ $out->info('Fixed eml and msg mime type');
+ }
+
+ if (version_compare($mimeTypeVersion, '29.0.0.6', '<') && $this->introduceAacAudioType()) {
+ $out->info('Fixed aac mime type');
+ }
+
+ if (version_compare($mimeTypeVersion, '29.0.0.10', '<') && $this->introduceReStructuredTextFormatType()) {
+ $out->info('Fixed ReStructured Text mime type');
+ }
+
+ if (version_compare($mimeTypeVersion, '30.0.0.0', '<') && $this->introduceExcalidrawType()) {
+ $out->info('Fixed Excalidraw mime type');
+ }
+
+ if (version_compare($mimeTypeVersion, '31.0.0.0', '<') && $this->introduceZstType()) {
+ $out->info('Fixed zst mime type');
+ }
+
+ if (version_compare($mimeTypeVersion, '32.0.0.0', '<') && $this->introduceMusicxmlType()) {
+ $out->info('Fixed musicxml mime type');
+ }
+
+ if (!$this->dryRun) {
+ $this->appConfig->setValueString('files', 'mimetype_version', $serverVersion);
+ }
+ }
+}