Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 tw=80 et cindent: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "ADTSDemuxer.h"
8 : #include "FlacDemuxer.h"
9 : #include "mozilla/ArrayUtils.h"
10 : #include "mozilla/ModuleUtils.h"
11 : #include "mp3sniff.h"
12 : #include "nestegg/nestegg.h"
13 : #include "nsIClassInfoImpl.h"
14 : #include "nsIHttpChannel.h"
15 : #include "nsMediaSniffer.h"
16 : #include "nsMimeTypes.h"
17 : #include "nsString.h"
18 :
19 : #include <algorithm>
20 :
21 : // The minimum number of bytes that are needed to attempt to sniff an mp4 file.
22 : static const unsigned MP4_MIN_BYTES_COUNT = 12;
23 : // The maximum number of bytes to consider when attempting to sniff a file.
24 : static const uint32_t MAX_BYTES_SNIFFED = 512;
25 : // The maximum number of bytes to consider when attempting to sniff for a mp3
26 : // bitstream.
27 : // This is 320kbps * 144 / 32kHz + 1 padding byte + 4 bytes of capture pattern.
28 : static const uint32_t MAX_BYTES_SNIFFED_MP3 = 320 * 144 / 32 + 1 + 4;
29 :
30 26 : NS_IMPL_ISUPPORTS(nsMediaSniffer, nsIContentSniffer)
31 :
32 : nsMediaSnifferEntry nsMediaSniffer::sSnifferEntries[] = {
33 : // The string OggS, followed by the null byte.
34 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF\xFF", "OggS", APPLICATION_OGG),
35 : // The string RIFF, followed by four bytes, followed by the string WAVE
36 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", "RIFF\x00\x00\x00\x00WAVE", AUDIO_WAV),
37 : // mp3 with ID3 tags, the string "ID3".
38 : PATTERN_ENTRY("\xFF\xFF\xFF", "ID3", AUDIO_MP3),
39 : // FLAC with standard header
40 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "fLaC", AUDIO_FLAC)
41 : };
42 :
43 : // For a complete list of file types, see http://www.ftyps.com/index.html
44 : nsMediaSnifferEntry sFtypEntries[] = {
45 : PATTERN_ENTRY("\xFF\xFF\xFF", "mp4", VIDEO_MP4), // Could be mp41 or mp42.
46 : PATTERN_ENTRY("\xFF\xFF\xFF", "avc", VIDEO_MP4), // Could be avc1, avc2, ...
47 : PATTERN_ENTRY("\xFF\xFF\xFF", "3gp", VIDEO_3GPP), // Could be 3gp4, 3gp5, ...
48 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "M4V ", VIDEO_MP4),
49 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "M4A ", AUDIO_MP4),
50 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "M4P ", AUDIO_MP4),
51 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "qt ", VIDEO_QUICKTIME),
52 : PATTERN_ENTRY("\xFF\xFF\xFF", "iso", VIDEO_MP4), // Could be isom or iso2.
53 : PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "mmp4", VIDEO_MP4),
54 : };
55 :
56 0 : static bool MatchesBrands(const uint8_t aData[4], nsACString& aSniffedType)
57 : {
58 0 : for (size_t i = 0; i < mozilla::ArrayLength(sFtypEntries); ++i) {
59 0 : const auto& currentEntry = sFtypEntries[i];
60 0 : bool matched = true;
61 0 : MOZ_ASSERT(currentEntry.mLength <= 4,
62 : "Pattern is too large to match brand strings.");
63 0 : for (uint32_t j = 0; j < currentEntry.mLength; ++j) {
64 0 : if ((currentEntry.mMask[j] & aData[j]) != currentEntry.mPattern[j]) {
65 0 : matched = false;
66 0 : break;
67 : }
68 : }
69 0 : if (matched) {
70 0 : aSniffedType.AssignASCII(currentEntry.mContentType);
71 0 : return true;
72 : }
73 : }
74 :
75 0 : return false;
76 : }
77 :
78 : // This function implements sniffing algorithm for MP4 family file types,
79 : // including MP4 (described at http://mimesniff.spec.whatwg.org/#signature-for-mp4),
80 : // M4A (Apple iTunes audio), and 3GPP.
81 : static bool
82 0 : MatchesMP4(const uint8_t* aData, const uint32_t aLength,
83 : nsACString& aSniffedType)
84 : {
85 0 : if (aLength <= MP4_MIN_BYTES_COUNT) {
86 0 : return false;
87 : }
88 : // Conversion from big endian to host byte order.
89 : uint32_t boxSize =
90 0 : (uint32_t)(aData[3] | aData[2] << 8 | aData[1] << 16 | aData[0] << 24);
91 :
92 : // Boxsize should be evenly divisible by 4.
93 0 : if (boxSize % 4 || aLength < boxSize) {
94 0 : return false;
95 : }
96 : // The string "ftyp".
97 0 : if (aData[4] != 0x66 ||
98 0 : aData[5] != 0x74 ||
99 0 : aData[6] != 0x79 ||
100 0 : aData[7] != 0x70) {
101 0 : return false;
102 : }
103 0 : if (MatchesBrands(&aData[8], aSniffedType)) {
104 0 : return true;
105 : }
106 : // Skip minor_version (bytes 12-15).
107 0 : uint32_t bytesRead = 16;
108 0 : while (bytesRead < boxSize) {
109 0 : if (MatchesBrands(&aData[bytesRead], aSniffedType)) {
110 0 : return true;
111 : }
112 0 : bytesRead += 4;
113 : }
114 :
115 0 : return false;
116 : }
117 :
118 0 : static bool MatchesWebM(const uint8_t* aData, const uint32_t aLength)
119 : {
120 0 : return nestegg_sniff((uint8_t*)aData, aLength) ? true : false;
121 : }
122 :
123 : // This function implements mp3 sniffing based on parsing
124 : // packet headers and looking for expected boundaries.
125 0 : static bool MatchesMP3(const uint8_t* aData, const uint32_t aLength)
126 : {
127 0 : return mp3_sniff(aData, (long)aLength);
128 : }
129 :
130 0 : static bool MatchesFLAC(const uint8_t* aData, const uint32_t aLength)
131 : {
132 0 : return mozilla::FlacDemuxer::FlacSniffer(aData, aLength);
133 : }
134 :
135 0 : static bool MatchesADTS(const uint8_t* aData, const uint32_t aLength)
136 : {
137 0 : return mozilla::ADTSDemuxer::ADTSSniffer(aData, aLength);
138 : }
139 :
140 : NS_IMETHODIMP
141 4 : nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest,
142 : const uint8_t* aData,
143 : const uint32_t aLength,
144 : nsACString& aSniffedType)
145 : {
146 8 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
147 4 : if (channel) {
148 4 : nsLoadFlags loadFlags = 0;
149 4 : channel->GetLoadFlags(&loadFlags);
150 4 : if (!(loadFlags & nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE)) {
151 : // For media, we want to sniff only if the Content-Type is unknown, or if
152 : // it is application/octet-stream.
153 4 : nsAutoCString contentType;
154 4 : nsresult rv = channel->GetContentType(contentType);
155 4 : NS_ENSURE_SUCCESS(rv, rv);
156 12 : if (!contentType.IsEmpty() &&
157 8 : !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) &&
158 4 : !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
159 4 : return NS_ERROR_NOT_AVAILABLE;
160 : }
161 : }
162 : }
163 :
164 0 : const uint32_t clampedLength = std::min(aLength, MAX_BYTES_SNIFFED);
165 :
166 0 : for (size_t i = 0; i < mozilla::ArrayLength(sSnifferEntries); ++i) {
167 0 : const nsMediaSnifferEntry& currentEntry = sSnifferEntries[i];
168 0 : if (clampedLength < currentEntry.mLength || currentEntry.mLength == 0) {
169 0 : continue;
170 : }
171 0 : bool matched = true;
172 0 : for (uint32_t j = 0; j < currentEntry.mLength; ++j) {
173 0 : if ((currentEntry.mMask[j] & aData[j]) != currentEntry.mPattern[j]) {
174 0 : matched = false;
175 0 : break;
176 : }
177 : }
178 0 : if (matched) {
179 0 : aSniffedType.AssignASCII(currentEntry.mContentType);
180 0 : return NS_OK;
181 : }
182 : }
183 :
184 0 : if (MatchesMP4(aData, clampedLength, aSniffedType)) {
185 0 : return NS_OK;
186 : }
187 :
188 0 : if (MatchesWebM(aData, clampedLength)) {
189 0 : aSniffedType.AssignLiteral(VIDEO_WEBM);
190 0 : return NS_OK;
191 : }
192 :
193 : // Bug 950023: 512 bytes are often not enough to sniff for mp3.
194 0 : if (MatchesMP3(aData, std::min(aLength, MAX_BYTES_SNIFFED_MP3))) {
195 0 : aSniffedType.AssignLiteral(AUDIO_MP3);
196 0 : return NS_OK;
197 : }
198 :
199 : // Flac frames are generally big, often in excess of 24kB.
200 : // Using a size of MAX_BYTES_SNIFFED effectively means that we will only
201 : // recognize flac content if it starts with a frame.
202 0 : if (MatchesFLAC(aData, clampedLength)) {
203 0 : aSniffedType.AssignLiteral(AUDIO_FLAC);
204 0 : return NS_OK;
205 : }
206 :
207 0 : if (MatchesADTS(aData, clampedLength)) {
208 0 : aSniffedType.AssignLiteral(AUDIO_AAC);
209 0 : return NS_OK;
210 : }
211 :
212 : // Could not sniff the media type, we are required to set it to
213 : // application/octet-stream.
214 0 : aSniffedType.AssignLiteral(APPLICATION_OCTET_STREAM);
215 0 : return NS_ERROR_NOT_AVAILABLE;
216 : }
|