/src/mozilla-central/toolkit/components/mediasniffer/nsMediaSniffer.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | 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 | | static bool MatchesBrands(const uint8_t aData[4], nsACString& aSniffedType) |
57 | 0 | { |
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 | 0 | "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 | 0 | } |
68 | 0 | } |
69 | 0 | if (matched) { |
70 | 0 | aSniffedType.AssignASCII(currentEntry.mContentType); |
71 | 0 | return true; |
72 | 0 | } |
73 | 0 | } |
74 | 0 |
|
75 | 0 | return false; |
76 | 0 | } |
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 | | MatchesMP4(const uint8_t* aData, const uint32_t aLength, |
83 | | nsACString& aSniffedType) |
84 | 0 | { |
85 | 0 | if (aLength <= MP4_MIN_BYTES_COUNT) { |
86 | 0 | return false; |
87 | 0 | } |
88 | 0 | // Conversion from big endian to host byte order. |
89 | 0 | uint32_t boxSize = |
90 | 0 | (uint32_t)(aData[3] | aData[2] << 8 | aData[1] << 16 | aData[0] << 24); |
91 | 0 |
|
92 | 0 | // Boxsize should be evenly divisible by 4. |
93 | 0 | if (boxSize % 4 || aLength < boxSize) { |
94 | 0 | return false; |
95 | 0 | } |
96 | 0 | // 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 | 0 | } |
103 | 0 | if (MatchesBrands(&aData[8], aSniffedType)) { |
104 | 0 | return true; |
105 | 0 | } |
106 | 0 | // 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 | 0 | } |
112 | 0 | bytesRead += 4; |
113 | 0 | } |
114 | 0 |
|
115 | 0 | return false; |
116 | 0 | } |
117 | | |
118 | | static bool MatchesWebM(const uint8_t* aData, const uint32_t aLength) |
119 | 0 | { |
120 | 0 | return nestegg_sniff((uint8_t*)aData, aLength) ? true : false; |
121 | 0 | } |
122 | | |
123 | | // This function implements mp3 sniffing based on parsing |
124 | | // packet headers and looking for expected boundaries. |
125 | | static bool MatchesMP3(const uint8_t* aData, const uint32_t aLength) |
126 | 0 | { |
127 | 0 | return mp3_sniff(aData, (long)aLength); |
128 | 0 | } |
129 | | |
130 | | static bool MatchesFLAC(const uint8_t* aData, const uint32_t aLength) |
131 | 0 | { |
132 | 0 | return mozilla::FlacDemuxer::FlacSniffer(aData, aLength); |
133 | 0 | } |
134 | | |
135 | | static bool MatchesADTS(const uint8_t* aData, const uint32_t aLength) |
136 | 0 | { |
137 | 0 | return mozilla::ADTSDemuxer::ADTSSniffer(aData, aLength); |
138 | 0 | } |
139 | | |
140 | | NS_IMETHODIMP |
141 | | nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest, |
142 | | const uint8_t* aData, |
143 | | const uint32_t aLength, |
144 | | nsACString& aSniffedType) |
145 | 0 | { |
146 | 0 | nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); |
147 | 0 | if (channel) { |
148 | 0 | nsLoadFlags loadFlags = 0; |
149 | 0 | channel->GetLoadFlags(&loadFlags); |
150 | 0 | if (!(loadFlags & nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE)) { |
151 | 0 | // For media, we want to sniff only if the Content-Type is unknown, or if |
152 | 0 | // it is application/octet-stream. |
153 | 0 | nsAutoCString contentType; |
154 | 0 | nsresult rv = channel->GetContentType(contentType); |
155 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
156 | 0 | if (!contentType.IsEmpty() && |
157 | 0 | !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) && |
158 | 0 | !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { |
159 | 0 | return NS_ERROR_NOT_AVAILABLE; |
160 | 0 | } |
161 | 0 | } |
162 | 0 | } |
163 | 0 | |
164 | 0 | const uint32_t clampedLength = std::min(aLength, MAX_BYTES_SNIFFED); |
165 | 0 |
|
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 | 0 | } |
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 | 0 | } |
177 | 0 | } |
178 | 0 | if (matched) { |
179 | 0 | aSniffedType.AssignASCII(currentEntry.mContentType); |
180 | 0 | return NS_OK; |
181 | 0 | } |
182 | 0 | } |
183 | 0 |
|
184 | 0 | if (MatchesMP4(aData, clampedLength, aSniffedType)) { |
185 | 0 | return NS_OK; |
186 | 0 | } |
187 | 0 | |
188 | 0 | if (MatchesWebM(aData, clampedLength)) { |
189 | 0 | aSniffedType.AssignLiteral(VIDEO_WEBM); |
190 | 0 | return NS_OK; |
191 | 0 | } |
192 | 0 |
|
193 | 0 | // 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 | 0 | } |
198 | 0 |
|
199 | 0 | // Flac frames are generally big, often in excess of 24kB. |
200 | 0 | // Using a size of MAX_BYTES_SNIFFED effectively means that we will only |
201 | 0 | // 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 | 0 | } |
206 | 0 |
|
207 | 0 | if (MatchesADTS(aData, clampedLength)) { |
208 | 0 | aSniffedType.AssignLiteral(AUDIO_AAC); |
209 | 0 | return NS_OK; |
210 | 0 | } |
211 | 0 |
|
212 | 0 | // Could not sniff the media type, we are required to set it to |
213 | 0 | // application/octet-stream. |
214 | 0 | aSniffedType.AssignLiteral(APPLICATION_OCTET_STREAM); |
215 | 0 | return NS_ERROR_NOT_AVAILABLE; |
216 | 0 | } |