diff options
Diffstat (limited to 'toolkit/components/mediasniffer')
21 files changed, 730 insertions, 0 deletions
diff --git a/toolkit/components/mediasniffer/moz.build b/toolkit/components/mediasniffer/moz.build new file mode 100644 index 000000000..d4f935f56 --- /dev/null +++ b/toolkit/components/mediasniffer/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini'] + +EXPORTS += [ + 'nsMediaSniffer.h', +] + +UNIFIED_SOURCES += [ + 'mp3sniff.c', + 'nsMediaSniffer.cpp', + 'nsMediaSnifferModule.cpp', +] + +FINAL_LIBRARY = 'xul' + +with Files('**'): + BUG_COMPONENT = ('Core', 'Video/Audio') diff --git a/toolkit/components/mediasniffer/mp3sniff.c b/toolkit/components/mediasniffer/mp3sniff.c new file mode 100644 index 000000000..a515d9c58 --- /dev/null +++ b/toolkit/components/mediasniffer/mp3sniff.c @@ -0,0 +1,156 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* MPEG format parsing */ + +#include "mp3sniff.h" + +/* Maximum packet size is 320 kbits/s * 144 / 32 kHz + 1 padding byte */ +#define MP3_MAX_SIZE 1441 + +typedef struct { + int version; + int layer; + int errp; + int bitrate; + int freq; + int pad; + int priv; + int mode; + int modex; + int copyright; + int original; + int emphasis; +} mp3_header; + +/* Parse the 4-byte header in p and fill in the header struct. */ +static void mp3_parse(const uint8_t *p, mp3_header *header) +{ + const int bitrates[2][16] = { + /* MPEG version 1 layer 3 bitrates. */ + {0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, + 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0}, + /* MPEG Version 2 and 2.5 layer 3 bitrates */ + {0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, + 80000, 96000, 112000, 128000, 144000, 160000, 0} }; + const int samplerates[4] = {44100, 48000, 32000, 0}; + + header->version = (p[1] & 0x18) >> 3; + header->layer = 4 - ((p[1] & 0x06) >> 1); + header->errp = (p[1] & 0x01); + + header->bitrate = bitrates[(header->version & 1) ? 0 : 1][(p[2] & 0xf0) >> 4]; + header->freq = samplerates[(p[2] & 0x0c) >> 2]; + if (header->version == 2) header->freq >>= 1; + else if (header->version == 0) header->freq >>= 2; + header->pad = (p[2] & 0x02) >> 1; + header->priv = (p[2] & 0x01); + + header->mode = (p[3] & 0xc0) >> 6; + header->modex = (p[3] & 0x30) >> 4; + header->copyright = (p[3] & 0x08) >> 3; + header->original = (p[3] & 0x04) >> 2; + header->emphasis = (p[3] & 0x03); +} + +/* calculate the size of an mp3 frame from its header */ +static int mp3_framesize(mp3_header *header) +{ + int size; + int scale; + + if ((header->version & 1) == 0) scale = 72; + else scale = 144; + size = header->bitrate * scale / header->freq; + if (header->pad) size += 1; + + return size; +} + +static int is_mp3(const uint8_t *p, long length) { + /* Do we have enough room to see a 4 byte header? */ + if (length < 4) return 0; + /* Do we have a sync pattern? */ + if (p[0] == 0xff && (p[1] & 0xe0) == 0xe0) { + /* Do we have any illegal field values? */ + if (((p[1] & 0x06) >> 1) == 0) return 0; /* No layer 4 */ + if (((p[2] & 0xf0) >> 4) == 15) return 0; /* Bitrate can't be 1111 */ + if (((p[2] & 0x0c) >> 2) == 3) return 0; /* Samplerate can't be 11 */ + /* Looks like a header. */ + if ((4 - ((p[1] & 0x06) >> 1)) != 3) return 0; /* Only want level 3 */ + return 1; + } + return 0; +} + +/* Identify an ID3 tag based on its header. */ +/* http://id3.org/id3v2.4.0-structure */ +static int is_id3(const uint8_t *p, long length) { + /* Do we have enough room to see the header? */ + if (length < 10) return 0; + /* Do we have a sync pattern? */ + if (p[0] == 'I' && p[1] == 'D' && p[2] == '3') { + if (p[3] == 0xff || p[4] == 0xff) return 0; /* Illegal version. */ + if (p[6] & 0x80 || p[7] & 0x80 || + p[8] & 0x80) return 0; /* Bad length encoding. */ + /* Looks like an id3 header. */ + return 1; + } + return 0; +} + +/* Calculate the size of an id3 tag structure from its header. */ +static int id3_framesize(const uint8_t *p, long length) +{ + int size; + + /* Header is 10 bytes. */ + if (length < 10) { + return 0; + } + /* Frame is header plus declared size. */ + size = 10 + (p[9] | (p[8] << 7) | (p[7] << 14) | (p[6] << 21)); + + return size; +} + +int mp3_sniff(const uint8_t *buf, long length) +{ + mp3_header header; + const uint8_t *p; + long skip; + long avail; + + p = buf; + avail = length; + while (avail >= 4) { + if (is_id3(p, avail)) { + /* Skip over any id3 tags */ + skip = id3_framesize(p, avail); + p += skip; + avail -= skip; + } else if (is_mp3(p, avail)) { + mp3_parse(p, &header); + skip = mp3_framesize(&header); + if (skip < 4 || skip + 4 >= avail) { + return 0; + } + p += skip; + avail -= skip; + /* Check for a second header at the expected offset. */ + if (is_mp3(p, avail)) { + /* Looks like mp3. */ + return 1; + } else { + /* No second header. Not mp3. */ + return 0; + } + } else { + /* No id3 tag or mp3 header. Not mp3. */ + return 0; + } + } + + return 0; +} diff --git a/toolkit/components/mediasniffer/mp3sniff.h b/toolkit/components/mediasniffer/mp3sniff.h new file mode 100644 index 000000000..5b041a0a2 --- /dev/null +++ b/toolkit/components/mediasniffer/mp3sniff.h @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +int mp3_sniff(const uint8_t *buf, long length); + +#ifdef __cplusplus +} +#endif diff --git a/toolkit/components/mediasniffer/nsMediaSniffer.cpp b/toolkit/components/mediasniffer/nsMediaSniffer.cpp new file mode 100644 index 000000000..29ba311e6 --- /dev/null +++ b/toolkit/components/mediasniffer/nsMediaSniffer.cpp @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 tw=80 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMediaSniffer.h" +#include "nsIHttpChannel.h" +#include "nsString.h" +#include "nsMimeTypes.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ModuleUtils.h" +#include "mp3sniff.h" +#include "nestegg/nestegg.h" +#include "FlacDemuxer.h" + +#include "nsIClassInfoImpl.h" +#include <algorithm> + +// The minimum number of bytes that are needed to attempt to sniff an mp4 file. +static const unsigned MP4_MIN_BYTES_COUNT = 12; +// The maximum number of bytes to consider when attempting to sniff a file. +static const uint32_t MAX_BYTES_SNIFFED = 512; +// The maximum number of bytes to consider when attempting to sniff for a mp3 +// bitstream. +// This is 320kbps * 144 / 32kHz + 1 padding byte + 4 bytes of capture pattern. +static const uint32_t MAX_BYTES_SNIFFED_MP3 = 320 * 144 / 32 + 1 + 4; + +NS_IMPL_ISUPPORTS(nsMediaSniffer, nsIContentSniffer) + +nsMediaSnifferEntry nsMediaSniffer::sSnifferEntries[] = { + // The string OggS, followed by the null byte. + PATTERN_ENTRY("\xFF\xFF\xFF\xFF\xFF", "OggS", APPLICATION_OGG), + // The string RIFF, followed by four bytes, followed by the string WAVE + PATTERN_ENTRY("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", "RIFF\x00\x00\x00\x00WAVE", AUDIO_WAV), + // mp3 with ID3 tags, the string "ID3". + PATTERN_ENTRY("\xFF\xFF\xFF", "ID3", AUDIO_MP3), + // FLAC with standard header + PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "fLaC", AUDIO_FLAC) +}; + +// For a complete list of file types, see http://www.ftyps.com/index.html +nsMediaSnifferEntry sFtypEntries[] = { + PATTERN_ENTRY("\xFF\xFF\xFF", "mp4", VIDEO_MP4), // Could be mp41 or mp42. + PATTERN_ENTRY("\xFF\xFF\xFF", "avc", VIDEO_MP4), // Could be avc1, avc2, ... + PATTERN_ENTRY("\xFF\xFF\xFF", "3gp", VIDEO_3GPP), // Could be 3gp4, 3gp5, ... + PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "M4A ", AUDIO_MP4), + PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "M4P ", AUDIO_MP4), + PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "qt ", VIDEO_QUICKTIME), + PATTERN_ENTRY("\xFF\xFF\xFF", "iso", VIDEO_MP4), // Could be isom or iso2. + PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "mmp4", VIDEO_MP4), +}; + +static bool MatchesBrands(const uint8_t aData[4], nsACString& aSniffedType) +{ + for (size_t i = 0; i < mozilla::ArrayLength(sFtypEntries); ++i) { + const auto& currentEntry = sFtypEntries[i]; + bool matched = true; + MOZ_ASSERT(currentEntry.mLength <= 4, "Pattern is too large to match brand strings."); + for (uint32_t j = 0; j < currentEntry.mLength; ++j) { + if ((currentEntry.mMask[j] & aData[j]) != currentEntry.mPattern[j]) { + matched = false; + break; + } + } + if (matched) { + aSniffedType.AssignASCII(currentEntry.mContentType); + return true; + } + } + + return false; +} + +// This function implements sniffing algorithm for MP4 family file types, +// including MP4 (described at http://mimesniff.spec.whatwg.org/#signature-for-mp4), +// M4A (Apple iTunes audio), and 3GPP. +static bool MatchesMP4(const uint8_t* aData, const uint32_t aLength, nsACString& aSniffedType) +{ + if (aLength <= MP4_MIN_BYTES_COUNT) { + return false; + } + // Conversion from big endian to host byte order. + uint32_t boxSize = (uint32_t)(aData[3] | aData[2] << 8 | aData[1] << 16 | aData[0] << 24); + + // Boxsize should be evenly divisible by 4. + if (boxSize % 4 || aLength < boxSize) { + return false; + } + // The string "ftyp". + if (aData[4] != 0x66 || + aData[5] != 0x74 || + aData[6] != 0x79 || + aData[7] != 0x70) { + return false; + } + if (MatchesBrands(&aData[8], aSniffedType)) { + return true; + } + // Skip minor_version (bytes 12-15). + uint32_t bytesRead = 16; + while (bytesRead < boxSize) { + if (MatchesBrands(&aData[bytesRead], aSniffedType)) { + return true; + } + bytesRead += 4; + } + + return false; +} + +static bool MatchesWebM(const uint8_t* aData, const uint32_t aLength) +{ + return nestegg_sniff((uint8_t*)aData, aLength) ? true : false; +} + +// This function implements mp3 sniffing based on parsing +// packet headers and looking for expected boundaries. +static bool MatchesMP3(const uint8_t* aData, const uint32_t aLength) +{ + return mp3_sniff(aData, (long)aLength); +} + +static bool MatchesFLAC(const uint8_t* aData, const uint32_t aLength) +{ + return mozilla::FlacDemuxer::FlacSniffer(aData, aLength); +} + +NS_IMETHODIMP +nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest, + const uint8_t* aData, + const uint32_t aLength, + nsACString& aSniffedType) +{ + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if (channel) { + nsLoadFlags loadFlags = 0; + channel->GetLoadFlags(&loadFlags); + if (!(loadFlags & nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE)) { + // For media, we want to sniff only if the Content-Type is unknown, or if it + // is application/octet-stream. + nsAutoCString contentType; + nsresult rv = channel->GetContentType(contentType); + NS_ENSURE_SUCCESS(rv, rv); + if (!contentType.IsEmpty() && + !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) && + !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + } + + const uint32_t clampedLength = std::min(aLength, MAX_BYTES_SNIFFED); + + for (size_t i = 0; i < mozilla::ArrayLength(sSnifferEntries); ++i) { + const nsMediaSnifferEntry& currentEntry = sSnifferEntries[i]; + if (clampedLength < currentEntry.mLength || currentEntry.mLength == 0) { + continue; + } + bool matched = true; + for (uint32_t j = 0; j < currentEntry.mLength; ++j) { + if ((currentEntry.mMask[j] & aData[j]) != currentEntry.mPattern[j]) { + matched = false; + break; + } + } + if (matched) { + aSniffedType.AssignASCII(currentEntry.mContentType); + return NS_OK; + } + } + + if (MatchesMP4(aData, clampedLength, aSniffedType)) { + return NS_OK; + } + + if (MatchesWebM(aData, clampedLength)) { + aSniffedType.AssignLiteral(VIDEO_WEBM); + return NS_OK; + } + + // Bug 950023: 512 bytes are often not enough to sniff for mp3. + if (MatchesMP3(aData, std::min(aLength, MAX_BYTES_SNIFFED_MP3))) { + aSniffedType.AssignLiteral(AUDIO_MP3); + return NS_OK; + } + + // Flac frames are generally big, often in excess of 24kB. + // Using a size of MAX_BYTES_SNIFFED effectively means that we will only + // recognize flac content if it starts with a frame. + if (MatchesFLAC(aData, clampedLength)) { + aSniffedType.AssignLiteral(AUDIO_FLAC); + return NS_OK; + } + + // Could not sniff the media type, we are required to set it to + // application/octet-stream. + aSniffedType.AssignLiteral(APPLICATION_OCTET_STREAM); + return NS_ERROR_NOT_AVAILABLE; +} diff --git a/toolkit/components/mediasniffer/nsMediaSniffer.h b/toolkit/components/mediasniffer/nsMediaSniffer.h new file mode 100644 index 000000000..45f6ac854 --- /dev/null +++ b/toolkit/components/mediasniffer/nsMediaSniffer.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 tw=80 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMediaSniffer_h +#define nsMediaSniffer_h + +#include "nsIModule.h" +#include "nsIFactory.h" + +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsIContentSniffer.h" +#include "mozilla/Attributes.h" + +// ed905ba3-c656-480e-934e-6bc35bd36aff +#define NS_MEDIA_SNIFFER_CID \ +{0x3fdd6c28, 0x5b87, 0x4e3e, \ +{0x8b, 0x57, 0x8e, 0x83, 0xc2, 0x3c, 0x1a, 0x6d}} + +#define NS_MEDIA_SNIFFER_CONTRACTID "@mozilla.org/media/sniffer;1" + +#define PATTERN_ENTRY(mask, pattern, contentType) \ + {(const uint8_t*)mask, (const uint8_t*)pattern, sizeof(mask) - 1, contentType} + +struct nsMediaSnifferEntry { + const uint8_t* mMask; + const uint8_t* mPattern; + const uint32_t mLength; + const char* mContentType; +}; + +class nsMediaSniffer final : public nsIContentSniffer +{ + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTSNIFFER + + private: + ~nsMediaSniffer() {} + + static nsMediaSnifferEntry sSnifferEntries[]; +}; + +#endif diff --git a/toolkit/components/mediasniffer/nsMediaSnifferModule.cpp b/toolkit/components/mediasniffer/nsMediaSnifferModule.cpp new file mode 100644 index 000000000..f95e61084 --- /dev/null +++ b/toolkit/components/mediasniffer/nsMediaSnifferModule.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" + +#include "nsMediaSniffer.h" + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMediaSniffer) + +NS_DEFINE_NAMED_CID(NS_MEDIA_SNIFFER_CID); + +static const mozilla::Module::CIDEntry kMediaSnifferCIDs[] = { + { &kNS_MEDIA_SNIFFER_CID, false, nullptr, nsMediaSnifferConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kMediaSnifferContracts[] = { + { NS_MEDIA_SNIFFER_CONTRACTID, &kNS_MEDIA_SNIFFER_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kMediaSnifferCategories[] = { + { "content-sniffing-services", NS_MEDIA_SNIFFER_CONTRACTID, NS_MEDIA_SNIFFER_CONTRACTID}, + { "net-content-sniffers", NS_MEDIA_SNIFFER_CONTRACTID, NS_MEDIA_SNIFFER_CONTRACTID}, + { nullptr } +}; + +static const mozilla::Module kMediaSnifferModule = { + mozilla::Module::kVersion, + kMediaSnifferCIDs, + kMediaSnifferContracts, + kMediaSnifferCategories +}; + +NSMODULE_DEFN(nsMediaSnifferModule) = &kMediaSnifferModule; diff --git a/toolkit/components/mediasniffer/test/unit/.eslintrc.js b/toolkit/components/mediasniffer/test/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/components/mediasniffer/test/unit/data/bug1079747.mp4 b/toolkit/components/mediasniffer/test/unit/data/bug1079747.mp4 Binary files differnew file mode 100644 index 000000000..f00731d7e --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/bug1079747.mp4 diff --git a/toolkit/components/mediasniffer/test/unit/data/detodos.mp3 b/toolkit/components/mediasniffer/test/unit/data/detodos.mp3 Binary files differnew file mode 100644 index 000000000..12e3f89c2 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/detodos.mp3 diff --git a/toolkit/components/mediasniffer/test/unit/data/ff-inst.exe b/toolkit/components/mediasniffer/test/unit/data/ff-inst.exe Binary files differnew file mode 100644 index 000000000..0f02f36e1 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/ff-inst.exe diff --git a/toolkit/components/mediasniffer/test/unit/data/file.mkv b/toolkit/components/mediasniffer/test/unit/data/file.mkv Binary files differnew file mode 100644 index 000000000..4618cda03 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/file.mkv diff --git a/toolkit/components/mediasniffer/test/unit/data/file.webm b/toolkit/components/mediasniffer/test/unit/data/file.webm Binary files differnew file mode 100644 index 000000000..7bc738b8b --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/file.webm diff --git a/toolkit/components/mediasniffer/test/unit/data/fl10.mp2 b/toolkit/components/mediasniffer/test/unit/data/fl10.mp2 Binary files differnew file mode 100644 index 000000000..bf84d7367 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/fl10.mp2 diff --git a/toolkit/components/mediasniffer/test/unit/data/he_free.mp3 b/toolkit/components/mediasniffer/test/unit/data/he_free.mp3 Binary files differnew file mode 100644 index 000000000..e3da8e6a7 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/he_free.mp3 diff --git a/toolkit/components/mediasniffer/test/unit/data/id3tags.mp3 b/toolkit/components/mediasniffer/test/unit/data/id3tags.mp3 Binary files differnew file mode 100644 index 000000000..23091e666 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/id3tags.mp3 diff --git a/toolkit/components/mediasniffer/test/unit/data/notags-bad.mp3 b/toolkit/components/mediasniffer/test/unit/data/notags-bad.mp3 Binary files differnew file mode 100644 index 000000000..5ad89786f --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/notags-bad.mp3 diff --git a/toolkit/components/mediasniffer/test/unit/data/notags-scan.mp3 b/toolkit/components/mediasniffer/test/unit/data/notags-scan.mp3 Binary files differnew file mode 100644 index 000000000..949b7c468 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/notags-scan.mp3 diff --git a/toolkit/components/mediasniffer/test/unit/data/notags.mp3 b/toolkit/components/mediasniffer/test/unit/data/notags.mp3 Binary files differnew file mode 100644 index 000000000..c7db94361 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/data/notags.mp3 diff --git a/toolkit/components/mediasniffer/test/unit/test_mediasniffer.js b/toolkit/components/mediasniffer/test/unit/test_mediasniffer.js new file mode 100644 index 000000000..b26d554a8 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/test_mediasniffer.js @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Ci = Components.interfaces; +var Cu = Components.utils; + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +const PATH = "/file.meh"; +var httpserver = new HttpServer(); + +// Each time, the data consist in a string that should be sniffed as Ogg. +const data = "OggS\0meeeh."; +var testRan = 0; + +// If the content-type is not present, or if it's application/octet-stream, it +// should be sniffed to application/ogg by the media sniffer. Otherwise, it +// should not be changed. +const tests = [ + // Those three first case are the case of a media loaded in a media element. + // All three should be sniffed. + { contentType: "", + expectedContentType: "application/ogg", + flags: Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS | Ci.nsIChannel.LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE }, + { contentType: "application/octet-stream", + expectedContentType: "application/ogg", + flags: Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS | Ci.nsIChannel.LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE }, + { contentType: "application/something", + expectedContentType: "application/ogg", + flags: Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS | Ci.nsIChannel.LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE }, + // This last cases test the case of a channel opened while allowing content + // sniffers to override the content-type, like in the docshell. + { contentType: "application/octet-stream", + expectedContentType: "application/ogg", + flags: Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS }, + { contentType: "", + expectedContentType: "application/ogg", + flags: Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS }, + { contentType: "application/something", + expectedContentType: "application/something", + flags: Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS }, +]; + +// A basic listener that reads checks the if we sniffed properly. +var listener = { + onStartRequest: function(request, context) { + do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType, + tests[testRan].expectedContentType); + }, + + onDataAvailable: function(request, context, stream, offset, count) { + try { + var bis = Components.classes["@mozilla.org/binaryinputstream;1"] + .createInstance(Components.interfaces.nsIBinaryInputStream); + bis.setInputStream(stream); + bis.readByteArray(bis.available()); + } catch (ex) { + do_throw("Error in onDataAvailable: " + ex); + } + }, + + onStopRequest: function(request, context, status) { + testRan++; + runNext(); + } +}; + +function setupChannel(url, flags) +{ + let uri = "http://localhost:" + + httpserver.identity.primaryPort + url; + var chan = NetUtil.newChannel({ + uri: uri, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_MEDIA + }); + chan.loadFlags |= flags; + var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel); + return httpChan; +} + +function runNext() { + if (testRan == tests.length) { + do_test_finished(); + return; + } + var channel = setupChannel(PATH, tests[testRan].flags); + httpserver.registerPathHandler(PATH, function(request, response) { + response.setHeader("Content-Type", tests[testRan].contentType, false); + response.bodyOutputStream.write(data, data.length); + }); + channel.asyncOpen2(listener); +} + +function run_test() { + httpserver.start(-1); + do_test_pending(); + try { + runNext(); + } catch (e) { + print("ERROR - " + e + "\n"); + } +} diff --git a/toolkit/components/mediasniffer/test/unit/test_mediasniffer_ext.js b/toolkit/components/mediasniffer/test/unit/test_mediasniffer_ext.js new file mode 100644 index 000000000..ce30a5c1b --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/test_mediasniffer_ext.js @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cc = Components.classes; +var CC = Components.Constructor; + +var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream"); + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://gre/modules/NetUtil.jsm"); + +var httpserver = new HttpServer(); + +var testRan = 0; + +// The tests files we want to test, and the type we should have after sniffing. +const tests = [ + // Real webm and mkv files truncated to 512 bytes. + { path: "data/file.webm", expected: "video/webm" }, + { path: "data/file.mkv", expected: "application/octet-stream" }, + // MP3 files with and without id3 headers truncated to 512 bytes. + // NB these have 208/209 byte frames, but mp3 can require up to + // 1445 bytes to detect with our method. + { path: "data/id3tags.mp3", expected: "audio/mpeg" }, + { path: "data/notags.mp3", expected: "audio/mpeg" }, + // MPEG-2 mp3 files. + { path: "data/detodos.mp3", expected: "audio/mpeg" }, + // Padding bit flipped in the first header: sniffing should fail. + { path: "data/notags-bad.mp3", expected: "application/octet-stream" }, + // Garbage before header: sniffing should fail. + { path: "data/notags-scan.mp3", expected: "application/octet-stream" }, + // VBR from the layer III test patterns. We can't sniff this. + { path: "data/he_free.mp3", expected: "application/octet-stream" }, + // Make sure we reject mp2, which has a similar header. + { path: "data/fl10.mp2", expected: "application/octet-stream" }, + // Truncated ff installer regression test for bug 875769. + { path: "data/ff-inst.exe", expected: "application/octet-stream" }, + // MP4 with invalid box size (0) for "ftyp". + { path: "data/bug1079747.mp4", expected: "application/octet-stream" }, +]; + +// A basic listener that reads checks the if we sniffed properly. +var listener = { + onStartRequest: function(request, context) { + do_print("Sniffing " + tests[testRan].path); + do_check_eq(request.QueryInterface(Ci.nsIChannel).contentType, tests[testRan].expected); + }, + + onDataAvailable: function(request, context, stream, offset, count) { + try { + var bis = Components.classes["@mozilla.org/binaryinputstream;1"] + .createInstance(Components.interfaces.nsIBinaryInputStream); + bis.setInputStream(stream); + bis.readByteArray(bis.available()); + } catch (ex) { + do_throw("Error in onDataAvailable: " + ex); + } + }, + + onStopRequest: function(request, context, status) { + testRan++; + runNext(); + } +}; + +function setupChannel(url) { + var chan = NetUtil.newChannel({ + uri: "http://localhost:" + httpserver.identity.primaryPort + url, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_MEDIA + }); + var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel); + return httpChan; +} + +function runNext() { + if (testRan == tests.length) { + do_test_finished(); + return; + } + var channel = setupChannel("/"); + channel.asyncOpen2(listener); +} + +function getFileContents(aFile) { + var fileStream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + fileStream.init(aFile, 1, -1, null); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"] + .createInstance(Components.interfaces.nsIBinaryInputStream); + bis.setInputStream(fileStream); + + var data = bis.readByteArray(bis.available()); + + return data; +} + +function handler(metadata, response) { + response.setStatusLine(metadata.httpVersion, 200, "OK"); + // Send an empty Content-Type, so we are guaranteed to sniff. + response.setHeader("Content-Type", "", false); + var body = getFileContents(do_get_file(tests[testRan].path)); + var bos = new BinaryOutputStream(response.bodyOutputStream); + bos.writeByteArray(body, body.length); +} + +function run_test() { + // We use a custom handler so we can change the header to force sniffing. + httpserver.registerPathHandler("/", handler); + httpserver.start(-1); + do_test_pending(); + try { + runNext(); + } catch (e) { + print("ERROR - " + e + "\n"); + } +} diff --git a/toolkit/components/mediasniffer/test/unit/xpcshell.ini b/toolkit/components/mediasniffer/test/unit/xpcshell.ini new file mode 100644 index 000000000..5ab4763f9 --- /dev/null +++ b/toolkit/components/mediasniffer/test/unit/xpcshell.ini @@ -0,0 +1,19 @@ +[DEFAULT] +head = +tail = +skip-if = toolkit == 'android' +support-files = + data/bug1079747.mp4 + data/detodos.mp3 + data/ff-inst.exe + data/file.mkv + data/file.webm + data/fl10.mp2 + data/he_free.mp3 + data/id3tags.mp3 + data/notags-bad.mp3 + data/notags-scan.mp3 + data/notags.mp3 + +[test_mediasniffer.js] +[test_mediasniffer_ext.js] |