├── .github └── workflows │ └── ci.yml ├── .gitignore ├── APACHE-LICENSE ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── rake ├── data ├── custom.xml └── tika.xml ├── lib ├── marcel.rb └── marcel │ ├── magic.rb │ ├── mime_type.rb │ ├── mime_type │ └── definitions.rb │ ├── tables.rb │ └── version.rb ├── marcel.gemspec ├── script ├── download_tika_data.rb └── generate_tables.rb └── test ├── declared_type_test.rb ├── extension_test.rb ├── fixtures ├── image.gif ├── magic │ ├── application │ │ ├── msword │ │ │ └── msword.doc │ │ ├── pdf │ │ │ ├── illustrator-seen-as-pdf.ai │ │ │ └── pdf.pdf │ │ ├── postscript │ │ │ ├── illustrator.eps │ │ │ └── photoshop.eps │ │ ├── rtf │ │ │ └── rtf.rtf │ │ ├── vnd.adobe.flash.movie │ │ │ └── flash.swf │ │ ├── vnd.ms-access │ │ │ ├── db.accdb │ │ │ ├── db.accde │ │ │ ├── db.mdb │ │ │ └── db.mde │ │ ├── vnd.oasis.opendocument.spreadsheet │ │ │ └── vnd.oasis.opendocument.spreadsheet.ods │ │ ├── vnd.oasis.opendocument.text │ │ │ └── vnd.oasis.opendocument.text.odt │ │ ├── vnd.openxmlformats-officedocument.presentationml.presentation │ │ │ ├── converted_from_google_slide.pptx │ │ │ └── vnd.openxmlformats-officedocument.presentationml.presentation.pptx │ │ ├── vnd.openxmlformats-officedocument.spreadsheetml.sheet │ │ │ ├── converted_from_google_sheet.xlsx │ │ │ └── vnd.openxmlformats-officedocument.spreadsheetml.sheet.xlsx │ │ ├── vnd.openxmlformats-officedocument.wordprocessingml.document │ │ │ ├── converted_from_google_doc.docx │ │ │ └── vnd.openxmlformats-officedocument.wordprocessingml.document.docx │ │ ├── x-ole-storage │ │ │ └── oft.oft │ │ ├── x-x509-ca-cert │ │ │ └── x-x509-ca-cert.pem │ │ ├── xml │ │ │ └── xml.xml │ │ └── zip │ │ │ ├── sketch.sketch │ │ │ └── zip.zip │ ├── audio │ │ ├── flac.flac │ │ ├── mp4 │ │ │ ├── m4a.m4a │ │ │ └── mp4.m4a │ │ ├── mpc │ │ │ └── musepack.mpc │ │ ├── mpeg │ │ │ └── mpeg.mp3 │ │ ├── ogg.ogg │ │ ├── webm │ │ │ └── webm.webm │ │ └── x-wav.wav │ ├── font │ │ ├── otf.otf │ │ ├── ttf.ttf │ │ ├── woff.woff │ │ └── woff2.woff2 │ ├── image │ │ ├── avif │ │ │ └── avif.avif │ │ ├── bmp │ │ │ └── bmp.bmp │ │ ├── gif │ │ │ └── gif.gif │ │ ├── jpeg │ │ │ └── jpeg.jpg │ │ ├── png │ │ │ └── png.png │ │ ├── svg+xml │ │ │ ├── svg.svg │ │ │ ├── svg_with_comment.svg │ │ │ ├── svg_with_document_type_declaration.svg │ │ │ ├── svg_with_newline_in_svg_tag.svg │ │ │ └── svg_with_xml_declaration.svg │ │ ├── tiff │ │ │ └── tiff.tif │ │ ├── vnd.adobe.photoshop │ │ │ ├── photoshop-large-document-format.psb │ │ │ └── vnd.adobe.photoshop.psd │ │ ├── vnd.dwg │ │ │ └── x-dwg.dwg │ │ ├── vnd.microsoft.icon │ │ │ └── ico.ico │ │ └── webp │ │ │ └── webp.webp │ ├── text │ │ ├── html │ │ │ ├── html.html │ │ │ └── html_with_svg.html │ │ └── vcard.vcf │ └── video │ │ ├── mp4 │ │ ├── m4v.m4v │ │ └── mp4.mp4 │ │ ├── quicktime │ │ └── quicktime.mov │ │ └── webm │ │ └── webm.mkv └── name │ ├── application │ ├── illustrator │ │ └── illustrator.ai │ ├── javascript │ │ └── javascript.js │ ├── json │ │ └── json.json │ ├── msword │ │ └── msword.doc │ ├── pdf │ │ └── pdf.pdf │ ├── postscript │ │ ├── illustrator.eps │ │ └── photoshop.eps │ ├── rtf │ │ └── rtf.rtf │ ├── sql │ │ └── sql.sql │ ├── vnd.adobe.flash.movie │ │ └── flash.swf │ ├── vnd.android.package-archive │ │ └── vnd.android.package-archive.apk │ ├── vnd.apple.keynote │ │ └── vnd.apple.keynote.key │ ├── vnd.apple.numbers │ │ └── vnd.apple.numbers.numbers │ ├── vnd.apple.pages │ │ └── vnd.apple.pages.pages │ ├── vnd.ms-access │ │ ├── db.accdb │ │ ├── db.accde │ │ ├── db.mdb │ │ └── db.mde │ ├── vnd.ms-excel.sheet.binary.macroenabled.12 │ │ └── vnd.ms-excel.sheet.binary.macroenabled.12.xlsb │ ├── vnd.ms-excel.sheet.macroenabled.12 │ │ └── vnd.ms-excel.sheet.macroenabled.12.xlsm │ ├── vnd.ms-excel.template.macroenabled.12 │ │ └── vnd.ms-excel.template.macroenabled.12.xltm │ ├── vnd.ms-excel │ │ └── vnd.ms-excel.xls │ ├── vnd.ms-powerpoint.presentation.macroenabled.12 │ │ └── vnd.ms-powerpoint.presentation.macroenabled.12.pptm │ ├── vnd.ms-powerpoint.slideshow.macroenabled.12 │ │ └── vnd.ms-powerpoint.slideshow.macroenabled.12.ppsm │ ├── vnd.ms-powerpoint.template.macroenabled.12 │ │ └── vnd.ms-powerpoint.template.macroenabled.12.potm │ ├── vnd.ms-powerpoint │ │ └── vnd.ms-powerpoint.ppt │ ├── vnd.ms-word.document.macroenabled.12 │ │ └── vnd.ms-word.document.macroenabled.12.docm │ ├── vnd.ms-word.template.macroenabled.12 │ │ └── vnd.ms-word.template.macroenabled.12.dotm │ ├── vnd.openxmlformats-officedocument.presentationml.presentation │ │ └── vnd.openxmlformats-officedocument.presentationml.presentation.pptx │ ├── vnd.openxmlformats-officedocument.presentationml.slideshow │ │ └── vnd.openxmlformats-officedocument.presentationml.slideshow.ppsx │ ├── vnd.openxmlformats-officedocument.presentationml.template │ │ └── vnd.openxmlformats-officedocument.presentationml.template.potx │ ├── vnd.openxmlformats-officedocument.spreadsheetml.sheet │ │ ├── downloaded-from-google-docs.xlsx │ │ └── vnd.openxmlformats-officedocument.spreadsheetml.sheet.xlsx │ ├── vnd.openxmlformats-officedocument.wordprocessingml.document │ │ └── vnd.openxmlformats-officedocument.wordprocessingml.document.docx │ ├── vnd.openxmlformats-officedocument.wordprocessingml.template │ │ └── vnd.openxmlformats-officedocument.wordprocessingml.template.dotx │ ├── x-x509-ca-cert │ │ └── x-x509-ca-cert.pem │ └── zip │ │ └── zip.zip │ ├── audio │ ├── aac.aac │ ├── flac.flac │ ├── mp4 │ │ └── mp4.m4a │ ├── mpc │ │ └── musepack.mpc │ ├── mpeg │ │ └── mpeg.mp3 │ ├── ogg.ogg │ └── x-wav.wav │ ├── font │ ├── otf.otf │ ├── ttf.ttf │ ├── woff.woff │ └── woff2.woff2 │ ├── image │ ├── bmp │ │ └── bmp.bmp │ ├── gif │ │ └── gif.gif │ ├── heic.heic │ ├── heif.heif │ ├── jpeg │ │ └── jpeg.jpg │ ├── png │ │ └── png.png │ ├── svg+xml │ │ └── svg.svg │ ├── tiff │ │ ├── tiff.tif │ │ └── tiff.tiff │ ├── vnd.adobe.photoshop │ │ ├── saved-as-large-document-format.psb │ │ └── vnd.adobe.photoshop.psd │ ├── x-raw-canon │ │ └── cr2.cr2 │ └── x-raw-sony │ │ └── arw.arw │ ├── message │ └── rfc822 │ │ └── rfc822.eml │ ├── text │ ├── css.css │ ├── csv │ │ └── csv.csv │ ├── html │ │ └── html.html │ ├── markdown │ │ ├── sample.markdown │ │ ├── sample.md │ │ ├── sample.mdtext │ │ └── sample.mkd │ ├── plain │ │ ├── plain.asc │ │ └── plain.txt │ ├── vcard.vcf │ ├── x-log │ │ └── x-log.log │ ├── x-sass │ │ └── sass.sass │ └── x-scss │ │ └── scss.scss │ └── video │ ├── mp4 │ └── mp4.mp4 │ └── quicktime │ └── quicktime.mov ├── illustrator_test.rb ├── magic_and_declared_type_test.rb ├── magic_and_name_test.rb ├── magic_test.rb ├── mime_type_test.rb ├── name_test.rb └── test_helper.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | continue-on-error: ${{ matrix.experimental }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | ruby: ["2.7", "3.0", "3.1", "3.2"] 11 | experimental: [false] 12 | include: 13 | - ruby: head 14 | experimental: true 15 | - ruby: jruby 16 | experimental: true 17 | - ruby: jruby-head 18 | experimental: true 19 | - ruby: truffleruby 20 | experimental: true 21 | - ruby: truffleruby-head 22 | experimental: true 23 | 24 | name: ${{ matrix.ruby }} 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | 29 | - name: Set up Ruby 30 | uses: ruby/setup-ruby@v1 31 | with: 32 | ruby-version: ${{ matrix.ruby }} 33 | bundler-cache: true 34 | 35 | - name: Run tests 36 | run: bundle exec rake 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | *.gem 3 | .byebug_history 4 | -------------------------------------------------------------------------------- /APACHE-LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | if RUBY_VERSION >= "2.7" 4 | gem "irb" 5 | gem "debug", platform: :mri 6 | end 7 | 8 | gemspec 9 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Tom Ward 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Marcel 2 | 3 | Marcel chooses the most appropriate content type for a file by inspecting its contents, the declared MIME type (perhaps passed as a Content-Type header), and the file extension. 4 | 5 | Marcel checks, in order: 6 | 7 | 1. The "magic bytes" sniffed from the file contents. 8 | 2. The declared type, typically provided in a Content-Type header on an uploaded file, unless it's the `application/octet-stream` default. 9 | 3. The filename extension. 10 | 4. Safe fallback to the indeterminate `application/octet-stream` default. 11 | 12 | At each step, the most specific MIME subtype is selected. This allows the declared type and file extension to refine the parent type sniffed from the file contents, but not conflict with it. For example, if "file.csv" has declared type `text/plain`, `text/csv` is returned since it's a more specific subtype of `text/plain`. Similarly, Adobe Illustrator files are PDFs internally, so magic byte sniffing indicates `application/pdf` which is refined to `application/illustrator` by the `ai` file extension. But a PDF named "image.png" will still be detected as `application/pdf` since `image/png` is not a subtype. 13 | 14 | ## Usage 15 | 16 | ```ruby 17 | # Magic bytes sniffing alone 18 | Marcel::MimeType.for Pathname.new("example.gif") 19 | # => "image/gif" 20 | 21 | File.open "example.gif" do |file| 22 | Marcel::MimeType.for file 23 | end 24 | # => "image/gif" 25 | 26 | # Magic bytes with filename fallback 27 | Marcel::MimeType.for Pathname.new("unrecognisable-data"), name: "example.pdf" 28 | # => "application/pdf" 29 | 30 | # File extension alone 31 | Marcel::MimeType.for extension: ".pdf" 32 | # => "application/pdf" 33 | 34 | # Magic bytes, declared type, and filename together 35 | Marcel::MimeType.for Pathname.new("unrecognisable-data"), name: "example", declared_type: "image/png" 36 | # => "image/png" 37 | 38 | # Safe fallback to application/octet-stream 39 | Marcel::MimeType.for StringIO.new(File.read "unrecognisable-data") 40 | # => "application/octet-stream" 41 | ``` 42 | 43 | ## Extending 44 | 45 | Custom file types may be added with `Marcel::MimeType.extend`: 46 | 47 | ```ruby 48 | Marcel::MimeType.extend "text/custom", extensions: %w( customtxt ) 49 | Marcel::MimeType.for name: "file.customtxt" 50 | # => "text/custom" 51 | ``` 52 | 53 | ## Motivation 54 | 55 | Marcel was extracted from Basecamp's file detection heuristics. The aim is provide sensible, safe, "do what I expect" results for typical file handling. Test fixtures have been added for many common file types, including those typically encountered by Basecamp. 56 | 57 | 58 | ## Contributing 59 | 60 | Marcel generates MIME lookup tables with `bundle exec rake update`. MIME types are seeded from data found in `data/*.xml`. Custom MIMEs may be added to `data/custom.xml`, while overrides to the standard MIME database may be added to `lib/marcel/mime_type/definitions.rb`. 61 | 62 | Marcel follows the same contributing guidelines as [rails/rails](https://github.com/rails/rails#contributing). 63 | 64 | 65 | ## Testing 66 | 67 | The main test fixture files are split into two folders, those that can be recognised by magic bytes, and those that can only be recognised by name. Even though strictly unnecessary, the fixtures in both folders should all be valid files of the type they represent. 68 | 69 | 70 | ## License 71 | 72 | Marcel itself is released under the terms of the MIT License. See the MIT-LICENSE file for details. 73 | 74 | Portions of Marcel are adapted from the [mimemagic] gem, released under the terms of the MIT License. 75 | 76 | Marcel's magic signature data is adapted from [Apache Tika](https://tika.apache.org), released under the terms of the Apache License. See the APACHE-LICENSE file for details. 77 | 78 | [mimemagic]: https://github.com/mimemagicrb/mimemagic 79 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | 4 | task default: :test 5 | 6 | Rake::TestTask.new :test do |t| 7 | t.libs << "test" 8 | t.test_files = FileList['test/**/*_test.rb'] 9 | end 10 | 11 | namespace :test do 12 | task tables: [ :tables, :test ] 13 | task update: [ :update, :test ] 14 | end 15 | 16 | 17 | task :types do 18 | fixture_path = File.expand_path("../test/fixtures", __FILE__) 19 | 20 | tested_by_data = Dir["#{fixture_path}/magic/*/*"].map do |path| 21 | type = path.split("#{fixture_path}/magic/").last 22 | end 23 | 24 | tested_by_filename = Dir["#{fixture_path}/name/*/*"].sort.map do |path| 25 | type = path.split("#{fixture_path}/name/").last 26 | extensions = Dir["#{path}/*.*"].map { |file| File.extname(file) } 27 | [type, extensions] 28 | end 29 | 30 | puts "Test fixtures exist for the following types: " 31 | 32 | tested_by_filename.each do |(type, extensions)| 33 | if tested_by_data.include?(type) 34 | puts " #{type} by (#{extensions.join(", ")}) and by file data" 35 | else 36 | puts " #{type} by (#{extensions.join(", ")})" 37 | end 38 | end 39 | end 40 | 41 | desc "Download latest Tika data and update data tables" 42 | task update: [ "tika:download", "tables" ] 43 | 44 | desc "Generate data tables" 45 | task tables: "lib/marcel/tables.rb" 46 | file "lib/marcel/tables.rb" => %w[ data/tika.xml data/custom.xml ] do |target| 47 | sh "script/generate_tables.rb", *target.prerequisites, out: target.name 48 | end 49 | 50 | namespace :tika do 51 | desc "Download latest data/tika.xml" 52 | task :download do 53 | sh "script/download_tika_data.rb", out: "data/tika.xml" 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "bundler/setup" 3 | require "irb" 4 | begin 5 | require "debug" 6 | rescue StandardError, LoadError 7 | end 8 | require "marcel" 9 | 10 | IRB.start 11 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "bundler/setup" 3 | require "rake" 4 | 5 | Rake.application.run 6 | -------------------------------------------------------------------------------- /data/custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 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 | -------------------------------------------------------------------------------- /lib/marcel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Marcel 4 | require "marcel/version" 5 | require "marcel/magic" 6 | require "marcel/mime_type" 7 | end 8 | -------------------------------------------------------------------------------- /lib/marcel/magic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Code in this file adapted from the mimemagic gem, released under the MIT License. 4 | # Copyright (c) 2011 Daniel Mendler. Available at https://github.com/mimemagicrb/mimemagic. 5 | 6 | require 'marcel/tables' 7 | 8 | require 'stringio' 9 | 10 | module Marcel 11 | # Mime type detection 12 | class Magic 13 | attr_reader :type, :mediatype, :subtype 14 | 15 | # Mime type by type string 16 | def initialize(type) 17 | @type = type 18 | @mediatype, @subtype = type.split('/', 2) 19 | end 20 | 21 | # Add custom mime type. Arguments: 22 | # * type: Mime type 23 | # * options: Options hash 24 | # 25 | # Option keys: 26 | # * :extensions: String list or single string of file extensions 27 | # * :parents: String list or single string of parent mime types 28 | # * :magic: Mime magic specification 29 | # * :comment: Comment string 30 | def self.add(type, options) 31 | extensions = [options[:extensions]].flatten.compact 32 | TYPE_EXTS[type] = extensions 33 | parents = [options[:parents]].flatten.compact 34 | TYPE_PARENTS[type] = parents unless parents.empty? 35 | extensions.each {|ext| EXTENSIONS[ext] = type } 36 | MAGIC.unshift [type, options[:magic]] if options[:magic] 37 | end 38 | 39 | # Removes a mime type from the dictionary. You might want to do this if 40 | # you're seeing impossible conflicts (for instance, application/x-gmc-link). 41 | # * type: The mime type to remove. All associated extensions and magic are removed too. 42 | def self.remove(type) 43 | EXTENSIONS.delete_if {|ext, t| t == type } 44 | MAGIC.delete_if {|t, m| t == type } 45 | TYPE_EXTS.delete(type) 46 | TYPE_PARENTS.delete(type) 47 | end 48 | 49 | # Returns true if type is a text format 50 | def text?; mediatype == 'text' || child_of?('text/plain'); end 51 | 52 | # Mediatype shortcuts 53 | def image?; mediatype == 'image'; end 54 | def audio?; mediatype == 'audio'; end 55 | def video?; mediatype == 'video'; end 56 | 57 | # Returns true if type is child of parent type 58 | def child_of?(parent) 59 | self.class.child?(type, parent) 60 | end 61 | 62 | # Get string list of file extensions 63 | def extensions 64 | TYPE_EXTS[type] || [] 65 | end 66 | 67 | # Get mime comment 68 | def comment 69 | nil # deprecated 70 | end 71 | 72 | # Lookup mime type by file extension 73 | def self.by_extension(ext) 74 | ext = ext.to_s.downcase 75 | mime = ext[0..0] == '.' ? EXTENSIONS[ext[1..-1]] : EXTENSIONS[ext] 76 | mime && new(mime) 77 | end 78 | 79 | # Lookup mime type by filename 80 | def self.by_path(path) 81 | by_extension(File.extname(path)) 82 | end 83 | 84 | # Lookup mime type by magic content analysis. 85 | # This is a slow operation. 86 | def self.by_magic(io) 87 | mime = magic_match(io, :find) 88 | mime && new(mime[0]) 89 | end 90 | 91 | # Lookup all mime types by magic content analysis. 92 | # This is a slower operation. 93 | def self.all_by_magic(io) 94 | magic_match(io, :select).map { |mime| new(mime[0]) } 95 | end 96 | 97 | # Return type as string 98 | def to_s 99 | type 100 | end 101 | 102 | # Allow comparison with string 103 | def eql?(other) 104 | type == other.to_s 105 | end 106 | 107 | def hash 108 | type.hash 109 | end 110 | 111 | alias == eql? 112 | 113 | def self.child?(child, parent) 114 | child == parent || TYPE_PARENTS[child]&.any? {|p| child?(p, parent) } 115 | end 116 | 117 | def self.magic_match(io, method) 118 | return magic_match(StringIO.new(io.to_s), method) unless io.respond_to?(:read) 119 | 120 | io.binmode if io.respond_to?(:binmode) 121 | io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding) 122 | buffer = "".encode(Encoding::BINARY) 123 | 124 | MAGIC.send(method) { |type, matches| magic_match_io(io, matches, buffer) } 125 | end 126 | 127 | def self.magic_match_io(io, matches, buffer) 128 | matches.any? do |offset, value, children| 129 | match = 130 | if value 131 | if Range === offset 132 | io.read(offset.begin, buffer) 133 | x = io.read(offset.end - offset.begin + value.bytesize, buffer) 134 | x && x.include?(value) 135 | else 136 | io.read(offset, buffer) 137 | io.read(value.bytesize, buffer) == value 138 | end 139 | end 140 | 141 | io.rewind 142 | match && (!children || magic_match_io(io, children, buffer)) 143 | end 144 | end 145 | 146 | private_class_method :magic_match, :magic_match_io 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /lib/marcel/mime_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Marcel 4 | class MimeType 5 | BINARY = "application/octet-stream" 6 | 7 | class << self 8 | def extend(type, extensions: [], parents: [], magic: nil) 9 | extensions = (Array(extensions) + Array(Marcel::TYPE_EXTS[type])).uniq 10 | parents = (Array(parents) + Array(Marcel::TYPE_PARENTS[type])).uniq 11 | Magic.add(type, extensions: extensions, magic: magic, parents: parents) 12 | end 13 | 14 | # Returns the most appropriate content type for the given file. 15 | # 16 | # The first argument should be a +Pathname+ or an +IO+. If it is a +Pathname+, the specified 17 | # file will be opened first. 18 | # 19 | # Optional parameters: 20 | # * +name+: file name, if known 21 | # * +extension+: file extension, if known 22 | # * +declared_type+: MIME type, if known 23 | # 24 | # The most appropriate type is determined by the following: 25 | # * type declared by binary magic number data 26 | # * type declared by the first of file name, file extension, or declared MIME type 27 | # 28 | # If no type can be determined, then +application/octet-stream+ is returned. 29 | def for(pathname_or_io = nil, name: nil, extension: nil, declared_type: nil) 30 | filename_type = for_name(name) || for_extension(extension) 31 | most_specific_type for_data(pathname_or_io), for_declared_type(declared_type), filename_type, BINARY 32 | end 33 | 34 | private 35 | 36 | def for_data(pathname_or_io) 37 | if pathname_or_io 38 | with_io(pathname_or_io) do |io| 39 | if magic = Marcel::Magic.by_magic(io) 40 | magic.type.downcase 41 | end 42 | end 43 | end 44 | end 45 | 46 | def for_name(name) 47 | if name 48 | if magic = Marcel::Magic.by_path(name) 49 | magic.type.downcase 50 | end 51 | end 52 | end 53 | 54 | def for_extension(extension) 55 | if extension 56 | if magic = Marcel::Magic.by_extension(extension) 57 | magic.type.downcase 58 | end 59 | end 60 | end 61 | 62 | def for_declared_type(declared_type) 63 | type = parse_media_type(declared_type) 64 | 65 | # application/octet-stream is treated as an undeclared/missing type, 66 | # allowing the type to be inferred from the filename. If there's no 67 | # filename extension, then the type falls back to binary anyway. 68 | type unless type == BINARY 69 | end 70 | 71 | def with_io(pathname_or_io, &block) 72 | if defined?(Pathname) && pathname_or_io.is_a?(Pathname) 73 | pathname_or_io.open(&block) 74 | else 75 | yield pathname_or_io 76 | end 77 | end 78 | 79 | def parse_media_type(content_type) 80 | if content_type 81 | result = content_type.downcase.split(/[;,\s]/, 2).first 82 | result if result && result.index("/") 83 | end 84 | end 85 | 86 | # For some document types (notably Microsoft Office) we recognise the main content 87 | # type with magic, but not the specific subclass. In this situation, if we can get a more 88 | # specific class using either the name or declared_type, we should use that in preference 89 | def most_specific_type(*candidates) 90 | candidates.compact.uniq.reduce do |type, candidate| 91 | Marcel::Magic.child?(candidate, type) ? candidate : type 92 | end 93 | end 94 | end 95 | end 96 | end 97 | 98 | require 'marcel/mime_type/definitions' 99 | -------------------------------------------------------------------------------- /lib/marcel/mime_type/definitions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Marcel::MimeType.extend "text/plain", extensions: %w( txt asc ) 4 | 5 | Marcel::MimeType.extend "application/illustrator", parents: "application/pdf" 6 | Marcel::MimeType.extend "image/vnd.adobe.photoshop", magic: [[0, "8BPS"]], extensions: %w( psd psb ) 7 | 8 | Marcel::MimeType.extend "application/vnd.ms-excel", parents: "application/x-ole-storage" 9 | Marcel::MimeType.extend "application/vnd.ms-powerpoint", parents: "application/x-ole-storage" 10 | 11 | Marcel::MimeType.extend "application/vnd.openxmlformats-officedocument.wordprocessingml.document", parents: "application/zip" 12 | Marcel::MimeType.extend "application/vnd.openxmlformats-officedocument.wordprocessingml.template", parents: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" 13 | Marcel::MimeType.extend "application/vnd.ms-word.document.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" 14 | Marcel::MimeType.extend "application/vnd.ms-word.template.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" 15 | 16 | Marcel::MimeType.extend "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", parents: "application/zip" 17 | Marcel::MimeType.extend "application/vnd.openxmlformats-officedocument.spreadsheetml.template", parents: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 18 | Marcel::MimeType.extend "application/vnd.ms-excel.sheet.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 19 | Marcel::MimeType.extend "application/vnd.ms-excel.template.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 20 | Marcel::MimeType.extend "application/vnd.ms-excel.addin.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 21 | Marcel::MimeType.extend "application/vnd.ms-excel.sheet.binary.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 22 | 23 | Marcel::MimeType.extend "application/vnd.openxmlformats-officedocument.presentationml.presentation", parents: "application/zip" 24 | Marcel::MimeType.extend "application/vnd.openxmlformats-officedocument.presentationml.template", parents: "application/vnd.openxmlformats-officedocument.presentationml.presentation" 25 | Marcel::MimeType.extend "application/vnd.openxmlformats-officedocument.presentationml.slideshow", parents: "application/vnd.openxmlformats-officedocument.presentationml.presentation" 26 | Marcel::MimeType.extend "application/vnd.ms-powerpoint.addin.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.presentationml.presentation" 27 | Marcel::MimeType.extend "application/vnd.ms-powerpoint.presentation.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.presentationml.presentation" 28 | Marcel::MimeType.extend "application/vnd.ms-powerpoint.template.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.presentationml.presentation" 29 | Marcel::MimeType.extend "application/vnd.ms-powerpoint.slideshow.macroenabled.12", parents: "application/vnd.openxmlformats-officedocument.presentationml.presentation" 30 | 31 | Marcel::MimeType.extend "application/vnd.apple.pages", extensions: %w( pages ), parents: "application/zip" 32 | Marcel::MimeType.extend "application/vnd.apple.numbers", extensions: %w( numbers ), parents: "application/zip" 33 | Marcel::MimeType.extend "application/vnd.apple.keynote", extensions: %w( key ), parents: "application/zip" 34 | 35 | Marcel::MimeType.extend "audio/aac", extensions: %w( aac ), parents: "audio/x-aac" 36 | Marcel::MimeType.extend("audio/ogg", extensions: %w( ogg oga ), magic: [[0, 'OggS', [[29, 'vorbis']]]]) 37 | 38 | Marcel::MimeType.extend "image/vnd.dwg", magic: [[0, "AC10"]] 39 | 40 | Marcel::MimeType.extend "application/x-x509-ca-cert", magic: [[0, '-----BEGIN CERTIFICATE-----']], extensions: %w( pem ), parents: "application/x-x509-cert;format=pem" 41 | 42 | Marcel::MimeType.extend "image/avif", magic: [[4, "ftypavif"]], extensions: %w( avif ) 43 | Marcel::MimeType.extend "image/heif", magic: [[4, "ftypmif1"]], extensions: %w( heif ) 44 | Marcel::MimeType.extend "image/heic", magic: [[4, "ftypheic"]], extensions: %w( heic ) 45 | 46 | Marcel::MimeType.extend "image/x-raw-sony", extensions: %w( arw ), parents: "image/tiff" 47 | Marcel::MimeType.extend "image/x-raw-canon", extensions: %w( cr2 crw ), parents: "image/tiff" 48 | 49 | Marcel::MimeType.extend "video/mp4", magic: [[4, "ftypisom"], [4, "ftypM4V "]], extensions: %w( mp4 m4v ) 50 | 51 | Marcel::MimeType.extend "audio/flac", magic: [[0, 'fLaC']], extensions: %w( flac ), parents: "audio/x-flac" 52 | Marcel::MimeType.extend "audio/x-wav", magic: [[0, 'RIFF', [[8, 'WAVE']]]], extensions: %w( wav ), parents: "audio/vnd.wav" 53 | Marcel::MimeType.extend "audio/mpc", magic: [[0, "MPCKSH"]], extensions: %w( mpc ) 54 | 55 | Marcel::MimeType.extend "font/ttf", magic: [[0, "\x00\x01\x00\x00"]], extensions: %w( ttf ttc ) 56 | Marcel::MimeType.extend "font/otf", magic: [[0, "OTTO"]], extensions: %w( otf ), parents: "font/ttf" 57 | Marcel::MimeType.extend "application/vnd.adobe.flash.movie", magic: [[0, "FWS"], [0, "CWS"]], extensions: %w( swf ) 58 | Marcel::MimeType.extend "application/sql", extensions: %w( sql ) 59 | Marcel::MimeType.extend "text/vcard", magic: [[0, "BEGIN:VCARD"]], extensions: %w( vcf ), parents: "text/plain" 60 | 61 | Marcel::MimeType.extend( 62 | "application/vnd.ms-access", 63 | extensions: %w( mdb mde accdb accde ), 64 | magic: [ 65 | [0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x4a\x65\x74\x20\x44\x42"], # "\x00\x01\x00Standard Jet DB" 66 | [0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x41\x43\x45\x20\x44\x42"], # "\x00\x01\x00Standard ACE DB" 67 | ], 68 | parents: "application/x-msaccess" 69 | ) 70 | 71 | Marcel::MimeType.extend "text/markdown", extensions: %w(md mdtext markdown mkd), parents: "text/x-web-markdown" 72 | -------------------------------------------------------------------------------- /lib/marcel/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Marcel 4 | VERSION = "1.0.4" 5 | end 6 | -------------------------------------------------------------------------------- /marcel.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'marcel/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'marcel' 8 | spec.version = Marcel::VERSION 9 | spec.authors = ['Tom Ward'] 10 | spec.email = ['tom@basecamp.com'] 11 | spec.summary = %q{Simple mime type detection using magic numbers, filenames, and extensions} 12 | spec.homepage = 'https://github.com/rails/marcel' 13 | spec.licenses = %w[ MIT Apache-2.0 ] 14 | 15 | spec.files = Dir['lib/**/*', 'MIT-LICENSE', 'APACHE-LICENSE', 'README.md'] 16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ['lib'] 19 | 20 | spec.required_ruby_version = '>= 2.3' 21 | 22 | spec.add_development_dependency 'minitest', '~> 5.11' 23 | spec.add_development_dependency 'bundler', '>= 1.7' 24 | spec.add_development_dependency 'rake', '>= 13.0' 25 | spec.add_development_dependency 'rack', '>= 2' 26 | spec.add_development_dependency 'nokogiri', '>= 1.9.1' 27 | end 28 | -------------------------------------------------------------------------------- /script/download_tika_data.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'net/http' 3 | require 'uri' 4 | 5 | url = URI("https://raw.githubusercontent.com/apache/tika/main/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml") 6 | data = Net::HTTP.get_response(url).tap(&:value).body 7 | 8 | # Insert a comment indicating provenance 9 | data.sub! /\A(?:<\?xml.+\?>\n)?/, "\\&\n" 10 | 11 | puts data 12 | -------------------------------------------------------------------------------- /script/generate_tables.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Code in this file adapted from the mimemagic gem, released under the MIT License. 4 | # Copyright (c) 2011 Daniel Mendler. Available at https://github.com/mimemagicrb/mimemagic. 5 | 6 | require 'nokogiri' 7 | 8 | class String 9 | alias inspect_old inspect 10 | 11 | def inspect 12 | str = b.inspect_old.gsub(/\\x([0-9a-f]{2})/i) do 13 | '\\%03o' % $1.to_i(16) 14 | end 15 | str.gsub!('"', '\'') unless str.match?(/[\\']/) 16 | str 17 | end 18 | end 19 | 20 | class BinaryString 21 | def initialize(string) 22 | @string = string 23 | end 24 | 25 | def inspect 26 | "b[#{@string.inspect}]" 27 | end 28 | end 29 | 30 | def str2int(s) 31 | return s.to_i(16) if s[0..1].downcase == '0x' 32 | return s.to_i(8) if s[0..0].downcase == '0' 33 | s.to_i(10) 34 | end 35 | 36 | def binary_strings(object) 37 | case object 38 | when Array 39 | object.map { |o| binary_strings(o) } 40 | when String 41 | BinaryString.new(object) 42 | when Numeric, Range, nil 43 | object 44 | else 45 | raise TypeError, "unexpected #{object.class}" 46 | end 47 | end 48 | 49 | def get_matches(mime, parent) 50 | parent.elements.map {|match| 51 | children = get_matches(mime, match) 52 | 53 | type = match['type'] 54 | value = match['value'] 55 | offset = match['offset'] || '0' 56 | offset = offset.split(':').map {|x| x.to_i } 57 | 58 | mask = match['mask'] 59 | 60 | # We only support masks of whole bytes against a string type 61 | if mask && (!mask.match?(/\A0x(FF|00)*\z/) || type != 'string') 62 | warn "#{mime['type']}: unsupported mask #{match.to_s}" 63 | next nil 64 | end 65 | 66 | offset = offset.size == 2 ? offset[0]..offset[1] : offset[0] 67 | case type 68 | when 'string', 'stringignorecase' 69 | value.gsub!(/\A0x([0-9a-f]+)\z/i) { [$1].pack('H*') } 70 | value.gsub!(/\\(x[\dA-Fa-f]{1,2}|0\d{1,3}|\d{1,3}|.)/) { eval("\"\\#{$1}\"") } 71 | 72 | if mask 73 | segments = [] 74 | mask.scan(/(?:FF)+/) do 75 | match = $~ 76 | match_offset = match.offset(0) 77 | mask_offset = (match_offset[0] - 2) / 2 78 | mask_length = (match_offset[1] - match_offset[0]) / 2 79 | segments << [mask_offset, mask_length] 80 | end 81 | 82 | chain = children 83 | segments.reverse_each do |(mask_offset, mask_length)| 84 | masked_value = value[mask_offset, mask_length] 85 | if chain.empty? 86 | chain = [[mask_offset, masked_value]] 87 | else 88 | chain = [[mask_offset, masked_value, chain]] 89 | end 90 | end 91 | next chain[0] 92 | end 93 | when 'big16' 94 | value = str2int(value) 95 | value = ((value >> 8).chr + (value & 0xFF).chr) 96 | when 'big32' 97 | value = str2int(value) 98 | value = (((value >> 24) & 0xFF).chr + ((value >> 16) & 0xFF).chr + ((value >> 8) & 0xFF).chr + (value & 0xFF).chr) 99 | when 'little16' 100 | value = str2int(value) 101 | value = ((value & 0xFF).chr + (value >> 8).chr) 102 | when 'little32' 103 | value = str2int(value) 104 | value = ((value & 0xFF).chr + ((value >> 8) & 0xFF).chr + ((value >> 16) & 0xFF).chr + ((value >> 24) & 0xFF).chr) 105 | when 'host16' # use little endian 106 | value = str2int(value) 107 | value = ((value & 0xFF).chr + (value >> 8).chr) 108 | when 'host32' # use little endian 109 | value = str2int(value) 110 | value = ((value & 0xFF).chr + ((value >> 8) & 0xFF).chr + ((value >> 16) & 0xFF).chr + ((value >> 24) & 0xFF).chr) 111 | when 'byte' 112 | value = str2int(value) 113 | value = value.chr 114 | when nil 115 | nil 116 | else 117 | warn "#{mime['type']}: unsupported #{type} match: #{match.to_s}" 118 | end 119 | 120 | children.empty? ? [offset, value] : [offset, value, children] 121 | }.compact 122 | end 123 | 124 | if ARGV.size == 0 125 | puts "Usage: #{$0} path/to/data.xml" 126 | exit 1 127 | end 128 | 129 | extensions = {} 130 | types = {} 131 | magics = [] 132 | 133 | ARGV.each do |path| 134 | file = File.new(path) 135 | doc = Nokogiri::XML(file) 136 | 137 | (doc/'mime-info/mime-type').each do |mime| 138 | comments = Hash[*(mime/'_comment').map {|comment| [comment['xml:lang'], comment.inner_text] }.flatten] 139 | type = mime['type'] 140 | subclass = (mime/'sub-class-of').map{|x| x['type']} 141 | exts = (mime/'glob').map{|x| x['pattern'] =~ /^\*\.([^\[\]]+)$/ ? $1.downcase : nil }.compact 142 | (mime/'magic').each do |magic| 143 | priority = (magic['priority'] || '50').to_i 144 | matches = get_matches(mime, magic) 145 | magics << [priority, type, matches] 146 | end 147 | if !exts.empty? 148 | exts.each{|x| 149 | extensions[x] = type if !extensions.include?(x) 150 | } 151 | types[type] = [exts,subclass,comments[nil]] 152 | end 153 | end 154 | end 155 | 156 | magics = magics.sort_by { |priority, type| [-priority, type] } 157 | 158 | common_types = [ 159 | "image/jpeg", # .jpg 160 | "image/png", # .png 161 | "image/gif", # .gif 162 | "image/tiff", # .tiff 163 | "image/bmp", # .bmp 164 | "image/vnd.adobe.photoshop", # .psd 165 | "image/webp", # .webp 166 | "text/html", # .html 167 | "image/svg+xml", # .svg 168 | 169 | "video/x-msvideo", # .avi 170 | "video/x-ms-wmv", # .wmv 171 | "video/mp4", # .mp4, .m4v 172 | "audio/mp4", # .m4a 173 | "video/quicktime", # .mov 174 | "video/mpeg", # .mpeg 175 | "video/ogg", # .ogv 176 | "video/webm", # .webm 177 | "video/x-matroska", # .mkv 178 | "video/x-flv", # .flv 179 | 180 | "audio/mpeg", # .mp3 181 | "audio/x-wav", # .wav 182 | "audio/aac", # .aac 183 | "audio/flac", # .flac 184 | "audio/ogg", # .ogg 185 | 186 | "application/pdf", # .pdf 187 | "application/msword", # .doc 188 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # .docx 189 | "application/vnd.ms-powerpoint", # .pps 190 | "application/vnd.openxmlformats-officedocument.presentationml.slideshow", # .ppsx 191 | "application/vnd.openxmlformats-officedocument.presentationml.presentation", # .pptx 192 | "application/vnd.ms-excel", # .xls 193 | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # .xlsx 194 | ] 195 | 196 | common_magics = common_types.map do |common_type| 197 | magics.find { |_, type, _| type == common_type } 198 | end 199 | 200 | magics = (common_magics.compact + magics).uniq 201 | 202 | puts "# frozen_string_literal: true" 203 | puts "" 204 | puts "# This file is auto-generated. Instead of editing this file, please" 205 | puts "# add MIMEs to data/custom.xml or lib/marcel/mime_type/definitions.rb." 206 | puts "" 207 | puts "module Marcel" 208 | puts " # @private" 209 | puts " # :nodoc:" 210 | puts " EXTENSIONS = {" 211 | extensions.keys.sort.each do |key| 212 | puts " '#{key}' => '#{extensions[key]}'," 213 | end 214 | puts " }" 215 | puts " # @private" 216 | puts " # :nodoc:" 217 | puts " TYPE_EXTS = {" 218 | types.keys.sort.each do |key| 219 | exts = types[key][0].join(' ') 220 | comment = types[key][2] 221 | comment = " # #{comment.tr("\n", " ")}" if comment 222 | puts " '#{key}' => %w(#{exts}),#{comment}" 223 | end 224 | puts " }" 225 | puts " TYPE_PARENTS = {" 226 | types.keys.sort.each do |key| 227 | parents = types[key][1].sort.join(' ') 228 | unless parents.empty? 229 | puts " '#{key}' => %w(#{parents})," 230 | end 231 | end 232 | puts " }" 233 | puts " b = Hash.new { |h, k| h[k] = k.b.freeze }" 234 | puts " # @private" 235 | puts " # :nodoc:" 236 | puts " MAGIC = [" 237 | magics.each do |priority, type, matches| 238 | puts " ['#{type}', #{binary_strings(matches).inspect}]," 239 | end 240 | puts " ]" 241 | puts "end" 242 | -------------------------------------------------------------------------------- /test/declared_type_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeType::DeclaredTypeTest < Marcel::TestCase 5 | test "prefers declared type over filename extension" do 6 | assert_equal "text/html", Marcel::MimeType.for(name: "file.txt", declared_type: "text/html") 7 | end 8 | 9 | test "prefers filename extension over binary type" do 10 | assert_equal "text/plain", Marcel::MimeType.for(name: "file.txt", declared_type: "application/octet-stream") 11 | end 12 | 13 | test "defaults to binary if declared type is unrecognized" do 14 | assert_equal "application/octet-stream", Marcel::MimeType.for(declared_type: nil) 15 | assert_equal "application/octet-stream", Marcel::MimeType.for(declared_type: "") 16 | assert_equal "application/octet-stream", Marcel::MimeType.for(declared_type: "unrecognised") 17 | end 18 | 19 | test "ignores charset declarations" do 20 | assert_equal "text/html", Marcel::MimeType.for(declared_type: "text/html; charset=utf-8") 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/extension_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeType::ExtensionTest < Marcel::TestCase 5 | test "ignores case and any preceding dot" do 6 | assert_equal "application/pdf", Marcel::MimeType.for(extension: "PDF") 7 | assert_equal "application/pdf", Marcel::MimeType.for(extension: ".PDF") 8 | assert_equal "application/pdf", Marcel::MimeType.for(extension: "pdf") 9 | assert_equal "application/pdf", Marcel::MimeType.for(extension: ".pdf") 10 | end 11 | 12 | extensions = [] 13 | 14 | each_content_type_fixture('name') do |file, name, content_type| 15 | extension = File.extname(name) 16 | 17 | unless extensions.include?(extension) 18 | test "gets type for #{content_type} given file extension #{extension}" do 19 | assert_equal content_type, Marcel::MimeType.for(extension: extension) 20 | end 21 | 22 | extensions << extension 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/fixtures/image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/image.gif -------------------------------------------------------------------------------- /test/fixtures/magic/application/msword/msword.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/msword/msword.doc -------------------------------------------------------------------------------- /test/fixtures/magic/application/pdf/illustrator-seen-as-pdf.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/pdf/illustrator-seen-as-pdf.ai -------------------------------------------------------------------------------- /test/fixtures/magic/application/pdf/pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/pdf/pdf.pdf -------------------------------------------------------------------------------- /test/fixtures/magic/application/postscript/illustrator.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/postscript/illustrator.eps -------------------------------------------------------------------------------- /test/fixtures/magic/application/postscript/photoshop.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/postscript/photoshop.eps -------------------------------------------------------------------------------- /test/fixtures/magic/application/rtf/rtf.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1} -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.adobe.flash.movie/flash.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.adobe.flash.movie/flash.swf -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.ms-access/db.accdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.ms-access/db.accdb -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.ms-access/db.accde: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.ms-access/db.accde -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.ms-access/db.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.ms-access/db.mdb -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.ms-access/db.mde: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.ms-access/db.mde -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.oasis.opendocument.spreadsheet/vnd.oasis.opendocument.spreadsheet.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.oasis.opendocument.spreadsheet/vnd.oasis.opendocument.spreadsheet.ods -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.oasis.opendocument.text/vnd.oasis.opendocument.text.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.oasis.opendocument.text/vnd.oasis.opendocument.text.odt -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.openxmlformats-officedocument.presentationml.presentation/converted_from_google_slide.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.openxmlformats-officedocument.presentationml.presentation/converted_from_google_slide.pptx -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.openxmlformats-officedocument.presentationml.presentation/vnd.openxmlformats-officedocument.presentationml.presentation.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.openxmlformats-officedocument.presentationml.presentation/vnd.openxmlformats-officedocument.presentationml.presentation.pptx -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/converted_from_google_sheet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/converted_from_google_sheet.xlsx -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/vnd.openxmlformats-officedocument.spreadsheetml.sheet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/vnd.openxmlformats-officedocument.spreadsheetml.sheet.xlsx -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.openxmlformats-officedocument.wordprocessingml.document/converted_from_google_doc.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.openxmlformats-officedocument.wordprocessingml.document/converted_from_google_doc.docx -------------------------------------------------------------------------------- /test/fixtures/magic/application/vnd.openxmlformats-officedocument.wordprocessingml.document/vnd.openxmlformats-officedocument.wordprocessingml.document.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/vnd.openxmlformats-officedocument.wordprocessingml.document/vnd.openxmlformats-officedocument.wordprocessingml.document.docx -------------------------------------------------------------------------------- /test/fixtures/magic/application/x-ole-storage/oft.oft: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/x-ole-storage/oft.oft -------------------------------------------------------------------------------- /test/fixtures/magic/application/x-x509-ca-cert/x-x509-ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | -------------------------------------------------------------------------------- /test/fixtures/magic/application/xml/xml.xml: -------------------------------------------------------------------------------- 1 | <_/> -------------------------------------------------------------------------------- /test/fixtures/magic/application/zip/sketch.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/zip/sketch.sketch -------------------------------------------------------------------------------- /test/fixtures/magic/application/zip/zip.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/application/zip/zip.zip -------------------------------------------------------------------------------- /test/fixtures/magic/audio/flac.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/flac.flac -------------------------------------------------------------------------------- /test/fixtures/magic/audio/mp4/m4a.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/mp4/m4a.m4a -------------------------------------------------------------------------------- /test/fixtures/magic/audio/mp4/mp4.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/mp4/mp4.m4a -------------------------------------------------------------------------------- /test/fixtures/magic/audio/mpc/musepack.mpc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/mpc/musepack.mpc -------------------------------------------------------------------------------- /test/fixtures/magic/audio/mpeg/mpeg.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/mpeg/mpeg.mp3 -------------------------------------------------------------------------------- /test/fixtures/magic/audio/ogg.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/ogg.ogg -------------------------------------------------------------------------------- /test/fixtures/magic/audio/webm/webm.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/webm/webm.webm -------------------------------------------------------------------------------- /test/fixtures/magic/audio/x-wav.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/audio/x-wav.wav -------------------------------------------------------------------------------- /test/fixtures/magic/font/otf.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/font/otf.otf -------------------------------------------------------------------------------- /test/fixtures/magic/font/ttf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/font/ttf.ttf -------------------------------------------------------------------------------- /test/fixtures/magic/font/woff.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/font/woff.woff -------------------------------------------------------------------------------- /test/fixtures/magic/font/woff2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/font/woff2.woff2 -------------------------------------------------------------------------------- /test/fixtures/magic/image/avif/avif.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/avif/avif.avif -------------------------------------------------------------------------------- /test/fixtures/magic/image/bmp/bmp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/bmp/bmp.bmp -------------------------------------------------------------------------------- /test/fixtures/magic/image/gif/gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/gif/gif.gif -------------------------------------------------------------------------------- /test/fixtures/magic/image/jpeg/jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/jpeg/jpeg.jpg -------------------------------------------------------------------------------- /test/fixtures/magic/image/png/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/png/png.png -------------------------------------------------------------------------------- /test/fixtures/magic/image/svg+xml/svg.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/magic/image/svg+xml/svg_with_comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/magic/image/svg+xml/svg_with_document_type_declaration.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/magic/image/svg+xml/svg_with_newline_in_svg_tag.svg: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /test/fixtures/magic/image/svg+xml/svg_with_xml_declaration.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/fixtures/magic/image/tiff/tiff.tif: -------------------------------------------------------------------------------- 1 | MM*dd by alok -------------------------------------------------------------------------------- /test/fixtures/magic/image/vnd.adobe.photoshop/photoshop-large-document-format.psb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/vnd.adobe.photoshop/photoshop-large-document-format.psb -------------------------------------------------------------------------------- /test/fixtures/magic/image/vnd.adobe.photoshop/vnd.adobe.photoshop.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/vnd.adobe.photoshop/vnd.adobe.photoshop.psd -------------------------------------------------------------------------------- /test/fixtures/magic/image/vnd.dwg/x-dwg.dwg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/vnd.dwg/x-dwg.dwg -------------------------------------------------------------------------------- /test/fixtures/magic/image/vnd.microsoft.icon/ico.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/image/vnd.microsoft.icon/ico.ico -------------------------------------------------------------------------------- /test/fixtures/magic/image/webp/webp.webp: -------------------------------------------------------------------------------- 1 | RIFFWEBPVP8L/Alok -------------------------------------------------------------------------------- /test/fixtures/magic/text/html/html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/magic/text/html/html_with_svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/magic/text/vcard.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | VERSION:3.0 3 | END:VCARD 4 | -------------------------------------------------------------------------------- /test/fixtures/magic/video/mp4/m4v.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/video/mp4/m4v.m4v -------------------------------------------------------------------------------- /test/fixtures/magic/video/mp4/mp4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/video/mp4/mp4.mp4 -------------------------------------------------------------------------------- /test/fixtures/magic/video/quicktime/quicktime.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/video/quicktime/quicktime.mov -------------------------------------------------------------------------------- /test/fixtures/magic/video/webm/webm.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/magic/video/webm/webm.mkv -------------------------------------------------------------------------------- /test/fixtures/name/application/illustrator/illustrator.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/illustrator/illustrator.ai -------------------------------------------------------------------------------- /test/fixtures/name/application/javascript/javascript.js: -------------------------------------------------------------------------------- 1 | function() { 2 | alert("test") 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/name/application/json/json.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JSON" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/name/application/msword/msword.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/msword/msword.doc -------------------------------------------------------------------------------- /test/fixtures/name/application/pdf/pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/pdf/pdf.pdf -------------------------------------------------------------------------------- /test/fixtures/name/application/postscript/illustrator.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/postscript/illustrator.eps -------------------------------------------------------------------------------- /test/fixtures/name/application/postscript/photoshop.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/postscript/photoshop.eps -------------------------------------------------------------------------------- /test/fixtures/name/application/rtf/rtf.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1} -------------------------------------------------------------------------------- /test/fixtures/name/application/sql/sql.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/sql/sql.sql -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.adobe.flash.movie/flash.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.adobe.flash.movie/flash.swf -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.android.package-archive/vnd.android.package-archive.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.android.package-archive/vnd.android.package-archive.apk -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.apple.keynote/vnd.apple.keynote.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.apple.keynote/vnd.apple.keynote.key -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.apple.numbers/vnd.apple.numbers.numbers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.apple.numbers/vnd.apple.numbers.numbers -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.apple.pages/vnd.apple.pages.pages: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.apple.pages/vnd.apple.pages.pages -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-access/db.accdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-access/db.accdb -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-access/db.accde: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-access/db.accde -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-access/db.mdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-access/db.mdb -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-access/db.mde: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-access/db.mde -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-excel.sheet.binary.macroenabled.12/vnd.ms-excel.sheet.binary.macroenabled.12.xlsb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-excel.sheet.binary.macroenabled.12/vnd.ms-excel.sheet.binary.macroenabled.12.xlsb -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-excel.sheet.macroenabled.12/vnd.ms-excel.sheet.macroenabled.12.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-excel.sheet.macroenabled.12/vnd.ms-excel.sheet.macroenabled.12.xlsm -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-excel.template.macroenabled.12/vnd.ms-excel.template.macroenabled.12.xltm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-excel.template.macroenabled.12/vnd.ms-excel.template.macroenabled.12.xltm -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-excel/vnd.ms-excel.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-excel/vnd.ms-excel.xls -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-powerpoint.presentation.macroenabled.12/vnd.ms-powerpoint.presentation.macroenabled.12.pptm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-powerpoint.presentation.macroenabled.12/vnd.ms-powerpoint.presentation.macroenabled.12.pptm -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-powerpoint.slideshow.macroenabled.12/vnd.ms-powerpoint.slideshow.macroenabled.12.ppsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-powerpoint.slideshow.macroenabled.12/vnd.ms-powerpoint.slideshow.macroenabled.12.ppsm -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-powerpoint.template.macroenabled.12/vnd.ms-powerpoint.template.macroenabled.12.potm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-powerpoint.template.macroenabled.12/vnd.ms-powerpoint.template.macroenabled.12.potm -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-powerpoint/vnd.ms-powerpoint.ppt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-powerpoint/vnd.ms-powerpoint.ppt -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-word.document.macroenabled.12/vnd.ms-word.document.macroenabled.12.docm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-word.document.macroenabled.12/vnd.ms-word.document.macroenabled.12.docm -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.ms-word.template.macroenabled.12/vnd.ms-word.template.macroenabled.12.dotm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.ms-word.template.macroenabled.12/vnd.ms-word.template.macroenabled.12.dotm -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.openxmlformats-officedocument.presentationml.presentation/vnd.openxmlformats-officedocument.presentationml.presentation.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.openxmlformats-officedocument.presentationml.presentation/vnd.openxmlformats-officedocument.presentationml.presentation.pptx -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.openxmlformats-officedocument.presentationml.slideshow/vnd.openxmlformats-officedocument.presentationml.slideshow.ppsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.openxmlformats-officedocument.presentationml.slideshow/vnd.openxmlformats-officedocument.presentationml.slideshow.ppsx -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.openxmlformats-officedocument.presentationml.template/vnd.openxmlformats-officedocument.presentationml.template.potx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.openxmlformats-officedocument.presentationml.template/vnd.openxmlformats-officedocument.presentationml.template.potx -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/downloaded-from-google-docs.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/downloaded-from-google-docs.xlsx -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/vnd.openxmlformats-officedocument.spreadsheetml.sheet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet/vnd.openxmlformats-officedocument.spreadsheetml.sheet.xlsx -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.openxmlformats-officedocument.wordprocessingml.document/vnd.openxmlformats-officedocument.wordprocessingml.document.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.openxmlformats-officedocument.wordprocessingml.document/vnd.openxmlformats-officedocument.wordprocessingml.document.docx -------------------------------------------------------------------------------- /test/fixtures/name/application/vnd.openxmlformats-officedocument.wordprocessingml.template/vnd.openxmlformats-officedocument.wordprocessingml.template.dotx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/vnd.openxmlformats-officedocument.wordprocessingml.template/vnd.openxmlformats-officedocument.wordprocessingml.template.dotx -------------------------------------------------------------------------------- /test/fixtures/name/application/x-x509-ca-cert/x-x509-ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | -------------------------------------------------------------------------------- /test/fixtures/name/application/zip/zip.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/application/zip/zip.zip -------------------------------------------------------------------------------- /test/fixtures/name/audio/aac.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/audio/aac.aac -------------------------------------------------------------------------------- /test/fixtures/name/audio/flac.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/audio/flac.flac -------------------------------------------------------------------------------- /test/fixtures/name/audio/mp4/mp4.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/audio/mp4/mp4.m4a -------------------------------------------------------------------------------- /test/fixtures/name/audio/mpc/musepack.mpc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/audio/mpc/musepack.mpc -------------------------------------------------------------------------------- /test/fixtures/name/audio/mpeg/mpeg.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/audio/mpeg/mpeg.mp3 -------------------------------------------------------------------------------- /test/fixtures/name/audio/ogg.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/audio/ogg.ogg -------------------------------------------------------------------------------- /test/fixtures/name/audio/x-wav.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/audio/x-wav.wav -------------------------------------------------------------------------------- /test/fixtures/name/font/otf.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/font/otf.otf -------------------------------------------------------------------------------- /test/fixtures/name/font/ttf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/font/ttf.ttf -------------------------------------------------------------------------------- /test/fixtures/name/font/woff.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/font/woff.woff -------------------------------------------------------------------------------- /test/fixtures/name/font/woff2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/font/woff2.woff2 -------------------------------------------------------------------------------- /test/fixtures/name/image/bmp/bmp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/bmp/bmp.bmp -------------------------------------------------------------------------------- /test/fixtures/name/image/gif/gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/gif/gif.gif -------------------------------------------------------------------------------- /test/fixtures/name/image/heic.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/heic.heic -------------------------------------------------------------------------------- /test/fixtures/name/image/heif.heif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/heif.heif -------------------------------------------------------------------------------- /test/fixtures/name/image/jpeg/jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/jpeg/jpeg.jpg -------------------------------------------------------------------------------- /test/fixtures/name/image/png/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/png/png.png -------------------------------------------------------------------------------- /test/fixtures/name/image/svg+xml/svg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/name/image/tiff/tiff.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/tiff/tiff.tif -------------------------------------------------------------------------------- /test/fixtures/name/image/tiff/tiff.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/tiff/tiff.tiff -------------------------------------------------------------------------------- /test/fixtures/name/image/vnd.adobe.photoshop/saved-as-large-document-format.psb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/vnd.adobe.photoshop/saved-as-large-document-format.psb -------------------------------------------------------------------------------- /test/fixtures/name/image/vnd.adobe.photoshop/vnd.adobe.photoshop.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/image/vnd.adobe.photoshop/vnd.adobe.photoshop.psd -------------------------------------------------------------------------------- /test/fixtures/name/image/x-raw-canon/cr2.cr2: -------------------------------------------------------------------------------- 1 | MM*dd by alok -------------------------------------------------------------------------------- /test/fixtures/name/image/x-raw-sony/arw.arw: -------------------------------------------------------------------------------- 1 | MM*dd by alok -------------------------------------------------------------------------------- /test/fixtures/name/message/rfc822/rfc822.eml: -------------------------------------------------------------------------------- 1 | User-Agent: Microsoft-MacOutlook/0.0.0.151105 2 | Date: Wed, 12 Jul 2017 15:25:43 +0100 3 | Subject: Example email 4 | From: Tom Ward 5 | To: 6 | Message-ID: 7 | Thread-Topic: Example email 8 | Mime-version: 1.0 9 | Content-type: multipart/alternative; 10 | boundary="B_3582727856_2021010881" 11 | 12 | > This message is in MIME format. Since your mail reader does not understand 13 | this format, some or all of this message may not be legible. 14 | 15 | --B_3582727856_2021010881 16 | Content-type: text/plain; 17 | charset="UTF-8" 18 | Content-transfer-encoding: 7bit 19 | 20 | Hello 21 | 22 | 23 | --B_3582727856_2021010881 24 | Content-type: text/html; 25 | charset="UTF-8" 26 | Content-transfer-encoding: quoted-printable 27 | 28 |
Hello
32 | 33 | --B_3582727856_2021010881-- 34 | 35 | -------------------------------------------------------------------------------- /test/fixtures/name/text/css.css: -------------------------------------------------------------------------------- 1 | html { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/name/text/csv/csv.csv: -------------------------------------------------------------------------------- 1 | Name,Age,Occupation 2 | Marcel Marceau,94,Mime -------------------------------------------------------------------------------- /test/fixtures/name/text/html/html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/name/text/markdown/sample.markdown: -------------------------------------------------------------------------------- 1 | # Hello World 2 | -------------------------------------------------------------------------------- /test/fixtures/name/text/markdown/sample.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | -------------------------------------------------------------------------------- /test/fixtures/name/text/markdown/sample.mdtext: -------------------------------------------------------------------------------- 1 | # Hello World 2 | -------------------------------------------------------------------------------- /test/fixtures/name/text/markdown/sample.mkd: -------------------------------------------------------------------------------- 1 | # Hello World 2 | -------------------------------------------------------------------------------- /test/fixtures/name/text/plain/plain.asc: -------------------------------------------------------------------------------- 1 | Marcel 2 | -------------------------------------------------------------------------------- /test/fixtures/name/text/plain/plain.txt: -------------------------------------------------------------------------------- 1 | Marcel 2 | -------------------------------------------------------------------------------- /test/fixtures/name/text/vcard.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | VERSION:3.0 3 | END:VCARD 4 | -------------------------------------------------------------------------------- /test/fixtures/name/text/x-log/x-log.log: -------------------------------------------------------------------------------- 1 | Some logging 2 | -------------------------------------------------------------------------------- /test/fixtures/name/text/x-sass/sass.sass: -------------------------------------------------------------------------------- 1 | html 2 | color: red 3 | p 4 | color: blue 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/name/text/x-scss/scss.scss: -------------------------------------------------------------------------------- 1 | html { 2 | color: red; 3 | 4 | p { 5 | color: blue; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/name/video/mp4/mp4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/video/mp4/mp4.mp4 -------------------------------------------------------------------------------- /test/fixtures/name/video/quicktime/quicktime.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/marcel/170458c687ed22f07d8829043a04e008a2b1936b/test/fixtures/name/video/quicktime/quicktime.mov -------------------------------------------------------------------------------- /test/illustrator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeType::IllustratorTest < Marcel::TestCase 5 | test ".ai uploaded as application/postscript" do 6 | file = files("name/application/illustrator/illustrator.ai") 7 | assert_equal "application/illustrator", Marcel::MimeType.for(file, name: "illustrator.ai", declared_type: "application/postscript") 8 | end 9 | 10 | test ".ai uploaded as application/pdf" do 11 | file = files("name/application/illustrator/illustrator.ai") 12 | assert_equal "application/illustrator", Marcel::MimeType.for(file, name: "illustrator.ai", declared_type: "application/pdf") 13 | end 14 | 15 | test ".ai uploaded as binary" do 16 | file = files("name/application/illustrator/illustrator.ai") 17 | assert_equal "application/illustrator", Marcel::MimeType.for(file, name: "illustrator.ai", declared_type: "application/octet-stream") 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/magic_and_declared_type_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeType::MagicAndDeclaredTypeTest < Marcel::TestCase 5 | each_content_type_fixture('name') do |file, name, content_type| 6 | test "correctly returns #{content_type} for #{name} given both file and declared type" do 7 | assert_equal content_type, Marcel::MimeType.for(file, declared_type: content_type) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/magic_and_name_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeType::MagicAndNameTest < Marcel::TestCase 5 | # All fixtures that can be recognised by name should also be recognisable when given 6 | # the file contents and the name. In some cases, the file contents will point to a 7 | # generic type, while the name will choose a more specific subclass 8 | each_content_type_fixture('name') do |file, name, content_type| 9 | test "correctly returns #{content_type} for #{name} given both file and name" do 10 | assert_equal content_type, Marcel::MimeType.for(file, name: name) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/magic_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeType::MagicTest < Marcel::TestCase 5 | # These fixtures should be recognisable given only their contents. Where a generic type 6 | # has more specific subclasses (such as application/zip), these subclasses cannot usually 7 | # be recognised by magic alone; their name is also needed to correctly identify them. 8 | each_content_type_fixture('magic') do |file, name, content_type| 9 | test "gets type for #{content_type} by using only magic bytes #{name}" do 10 | assert_equal content_type, Marcel::MimeType.for(file) 11 | end 12 | end 13 | 14 | test "add and remove type" do 15 | Marcel::Magic.add('application/x-my-thing', extensions: 'mtg', parents: 'application/json') 16 | Marcel::Magic.remove('application/x-my-thing') 17 | end 18 | 19 | test "#extensions" do 20 | json = Marcel::Magic.by_extension('json') 21 | assert_equal ['json'], json.extensions 22 | end 23 | 24 | test ".child?" do 25 | assert Marcel::Magic.child?('text/csv', 'text/plain') 26 | refute Marcel::Magic.child?('text/plain', 'text/csv') 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/mime_type_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeTypeTest < Marcel::TestCase 5 | def setup 6 | @path = files("image.gif").to_s 7 | end 8 | 9 | test "gets content type from Files" do 10 | content_type = File.open @path do |file| 11 | Marcel::MimeType.for file 12 | end 13 | assert_equal "image/gif", content_type 14 | end 15 | 16 | test "gets content type from Pathnames" do 17 | content_type = Marcel::MimeType.for Pathname.new(@path) 18 | assert_equal "image/gif", content_type 19 | end 20 | 21 | test "closes Pathname files after use" do 22 | skip if RUBY_ENGINE == "jruby" 23 | open_files = ObjectSpace.each_object(File).reject(&:closed?) 24 | assert open_files.none? { |f| f.path == @path } 25 | end 26 | 27 | test "gets content type from Tempfiles" do 28 | Tempfile.open("Marcel") do |tempfile| 29 | tempfile.write(File.read(@path)) 30 | content_type = Marcel::MimeType.for tempfile 31 | assert_equal "image/gif", content_type 32 | end 33 | end 34 | 35 | test "gets content type from IOs" do 36 | io = StringIO.new(File.read(@path)) 37 | content_type = Marcel::MimeType.for io 38 | assert_equal "image/gif", content_type 39 | end 40 | 41 | test "gets content type from sources that conform to Rack RewindableInput" do 42 | io = StringIO.new(File.read(@path)) 43 | wrapper = Rack::RewindableInput.new(io) 44 | content_type = Marcel::MimeType.for wrapper 45 | assert_equal "image/gif", content_type 46 | end 47 | 48 | if Rack::Lint.const_defined?(:InputWrapper) 49 | test "gets content type from sources that conform to Rack 2's Rack::Lint::InputWrapper" do 50 | io = StringIO.new(File.read(@path)) 51 | wrapper = Rack::Lint::InputWrapper.new(io) 52 | content_type = Marcel::MimeType.for wrapper 53 | assert_equal "image/gif", content_type 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/name_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rack' 3 | 4 | class Marcel::MimeType::NameTest < Marcel::TestCase 5 | each_content_type_fixture('name') do |file, name, content_type| 6 | test "gets type for #{content_type} by filename from #{name}" do 7 | assert_equal content_type, Marcel::MimeType.for(name: name) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'marcel' 3 | require 'pathname' 4 | 5 | begin 6 | require "debug" 7 | rescue StandardError, LoadError 8 | end 9 | 10 | class Marcel::TestCase < Minitest::Test 11 | class << self 12 | def setup(&block) 13 | define_method(:setup, &block) 14 | end 15 | 16 | def teardown(&block) 17 | define_method(:teardown, &block) 18 | end 19 | 20 | def test(name, &block) 21 | test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym 22 | defined = instance_method(test_name) rescue false 23 | raise "#{test_name} is already defined in #{self}" if defined 24 | define_method(test_name, &block) 25 | end 26 | 27 | def fixture_path(name) 28 | File.expand_path("../fixtures/#{name}", __FILE__) 29 | end 30 | 31 | def files(name) 32 | Pathname.new fixture_path(name) 33 | end 34 | 35 | def each_content_type_fixture(folder) 36 | FileUtils.chdir fixture_path(folder) do 37 | Dir["**/*.*"].map do |name| 38 | if File.file?(name) 39 | _, content_type, extra, _ = *name.match(/\A([^\/]+\/[^\/]*)\/?(.*)\.(\w+)\Z/) 40 | extra = nil if content_type[-content_type.size..-1] == extra 41 | yield files("#{folder}/#{name}"), name, content_type 42 | end 43 | end 44 | end 45 | end 46 | end 47 | 48 | def files(name) 49 | Pathname.new fixture_path(name) 50 | end 51 | 52 | def fixture_path(name) 53 | self.class.fixture_path(name) 54 | end 55 | end 56 | --------------------------------------------------------------------------------