|
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 "nsMediaSniffer.h" |
|
8 #include "nsIHttpChannel.h" |
|
9 #include "nsString.h" |
|
10 #include "nsMimeTypes.h" |
|
11 #include "mozilla/ArrayUtils.h" |
|
12 #include "mozilla/ModuleUtils.h" |
|
13 #include "mp3sniff.h" |
|
14 #ifdef MOZ_WEBM |
|
15 #include "nestegg/nestegg.h" |
|
16 #endif |
|
17 |
|
18 #include "nsIClassInfoImpl.h" |
|
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 nsMediaSniffer::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 }; |
|
40 |
|
41 // This function implements mp4 sniffing algorithm, described at |
|
42 // http://mimesniff.spec.whatwg.org/#signature-for-mp4 |
|
43 static bool MatchesMP4(const uint8_t* aData, const uint32_t aLength) |
|
44 { |
|
45 if (aLength <= MP4_MIN_BYTES_COUNT) { |
|
46 return false; |
|
47 } |
|
48 // Conversion from big endian to host byte order. |
|
49 uint32_t boxSize = (uint32_t)(aData[3] | aData[2] << 8 | aData[1] << 16 | aData[0] << 24); |
|
50 |
|
51 // Boxsize should be evenly divisible by 4. |
|
52 if (boxSize % 4 || aLength < boxSize) { |
|
53 return false; |
|
54 } |
|
55 // The string "ftyp". |
|
56 if (aData[4] != 0x66 || |
|
57 aData[5] != 0x74 || |
|
58 aData[6] != 0x79 || |
|
59 aData[7] != 0x70) { |
|
60 return false; |
|
61 } |
|
62 for (uint32_t i = 2; i <= boxSize / 4 - 1 ; i++) { |
|
63 if (i == 3) { |
|
64 continue; |
|
65 } |
|
66 // The string "mp42" or "mp41". |
|
67 if (aData[4*i] == 0x6D && |
|
68 aData[4*i+1] == 0x70 && |
|
69 aData[4*i+2] == 0x34) { |
|
70 return true; |
|
71 } |
|
72 // The string "isom" or "iso2". |
|
73 if (aData[4*i] == 0x69 && |
|
74 aData[4*i+1] == 0x73 && |
|
75 aData[4*i+2] == 0x6F && |
|
76 (aData[4*i+3] == 0x6D || aData[4*i+3] == 0x32)) { |
|
77 return true; |
|
78 } |
|
79 } |
|
80 return false; |
|
81 } |
|
82 |
|
83 static bool MatchesWebM(const uint8_t* aData, const uint32_t aLength) |
|
84 { |
|
85 #ifdef MOZ_WEBM |
|
86 return nestegg_sniff((uint8_t*)aData, aLength) ? true : false; |
|
87 #else |
|
88 return false; |
|
89 #endif |
|
90 } |
|
91 |
|
92 // This function implements mp3 sniffing based on parsing |
|
93 // packet headers and looking for expected boundaries. |
|
94 static bool MatchesMP3(const uint8_t* aData, const uint32_t aLength) |
|
95 { |
|
96 return mp3_sniff(aData, (long)aLength); |
|
97 } |
|
98 |
|
99 NS_IMETHODIMP |
|
100 nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest, |
|
101 const uint8_t* aData, |
|
102 const uint32_t aLength, |
|
103 nsACString& aSniffedType) |
|
104 { |
|
105 // For media, we want to sniff only if the Content-Type is unknown, or if it |
|
106 // is application/octet-stream. |
|
107 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); |
|
108 if (channel) { |
|
109 nsAutoCString contentType; |
|
110 nsresult rv = channel->GetContentType(contentType); |
|
111 NS_ENSURE_SUCCESS(rv, rv); |
|
112 if (!contentType.IsEmpty() && |
|
113 !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) && |
|
114 !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { |
|
115 return NS_ERROR_NOT_AVAILABLE; |
|
116 } |
|
117 } |
|
118 |
|
119 const uint32_t clampedLength = std::min(aLength, MAX_BYTES_SNIFFED); |
|
120 |
|
121 for (uint32_t i = 0; i < mozilla::ArrayLength(sSnifferEntries); ++i) { |
|
122 const nsMediaSnifferEntry& currentEntry = sSnifferEntries[i]; |
|
123 if (clampedLength < currentEntry.mLength || currentEntry.mLength == 0) { |
|
124 continue; |
|
125 } |
|
126 bool matched = true; |
|
127 for (uint32_t j = 0; j < currentEntry.mLength; ++j) { |
|
128 if ((currentEntry.mMask[j] & aData[j]) != currentEntry.mPattern[j]) { |
|
129 matched = false; |
|
130 break; |
|
131 } |
|
132 } |
|
133 if (matched) { |
|
134 aSniffedType.AssignASCII(currentEntry.mContentType); |
|
135 return NS_OK; |
|
136 } |
|
137 } |
|
138 |
|
139 if (MatchesMP4(aData, clampedLength)) { |
|
140 aSniffedType.AssignLiteral(VIDEO_MP4); |
|
141 return NS_OK; |
|
142 } |
|
143 |
|
144 if (MatchesWebM(aData, clampedLength)) { |
|
145 aSniffedType.AssignLiteral(VIDEO_WEBM); |
|
146 return NS_OK; |
|
147 } |
|
148 |
|
149 // Bug 950023: 512 bytes are often not enough to sniff for mp3. |
|
150 if (MatchesMP3(aData, std::min(aLength, MAX_BYTES_SNIFFED_MP3))) { |
|
151 aSniffedType.AssignLiteral(AUDIO_MP3); |
|
152 return NS_OK; |
|
153 } |
|
154 |
|
155 // Could not sniff the media type, we are required to set it to |
|
156 // application/octet-stream. |
|
157 aSniffedType.AssignLiteral(APPLICATION_OCTET_STREAM); |
|
158 return NS_ERROR_NOT_AVAILABLE; |
|
159 } |