PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
getID3() Commercial License
===========================
getID3() is licensed under the "GNU Public License" (GPL) and/or the
"getID3() Commercial License" (gCL). This document describes the gCL.
---------------------------------------------------------------------
The license is non-exclusively granted to a single person or company,
per payment of the license fee, for the lifetime of that person or
company. The license is non-transferrable.
The gCL grants the licensee the right to use getID3() in commercial
closed-source projects. Modifications may be made to getID3() with no
obligation to release the modified source code. getID3() (or pieces
thereof) may be included in any number of projects authored (in whole
or in part) by the licensee.
The licensee may use any version of getID3(), past, present or future,
as is most convenient. This license does not entitle the licensee to
receive any technical support, updates or bugfixes, except as such are
made publicly available to all getID3() users.
The licensee may not sub-license getID3() itself, meaning that any
commercially released product containing all or parts of getID3() must
have added functionality beyond what is available in getID3();
getID3() itself may not be re-licensed by the licensee.

View File

@ -0,0 +1,29 @@
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
*****************************************************************
*****************************************************************
getID3() is released under multiple licenses. You may choose
from the following licenses, and use getID3 according to the
terms of the license most suitable to your project.
GNU GPL: https://gnu.org/licenses/gpl.html (v3)
https://gnu.org/licenses/old-licenses/gpl-2.0.html (v2)
https://gnu.org/licenses/old-licenses/gpl-1.0.html (v1)
GNU LGPL: https://gnu.org/licenses/lgpl.html (v3)
Mozilla MPL: http://www.mozilla.org/MPL/2.0/ (v2)
getID3 Commercial License: http://getid3.org/#gCL (payment required)
*****************************************************************
*****************************************************************
Copies of each of the above licenses are included in the 'licenses'
directory of the getID3 distribution.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,745 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
// //
// FLV module by Seth Kaufman <sethØwhirl-i-gig*com> //
// //
// * version 0.1 (26 June 2005) //
// //
// //
// * version 0.1.1 (15 July 2005) //
// minor modifications by James Heinrich <info@getid3.org> //
// //
// * version 0.2 (22 February 2006) //
// Support for On2 VP6 codec and meta information //
// by Steve Webster <steve.websterØfeaturecreep*com> //
// //
// * version 0.3 (15 June 2006) //
// Modified to not read entire file into memory //
// by James Heinrich <info@getid3.org> //
// //
// * version 0.4 (07 December 2007) //
// Bugfixes for incorrectly parsed FLV dimensions //
// and incorrect parsing of onMetaTag //
// by Evgeny Moysevich <moysevichØgmail*com> //
// //
// * version 0.5 (21 May 2009) //
// Fixed parsing of audio tags and added additional codec //
// details. The duration is now read from onMetaTag (if //
// exists), rather than parsing whole file //
// by Nigel Barnes <ngbarnesØhotmail*com> //
// //
// * version 0.6 (24 May 2009) //
// Better parsing of files with h264 video //
// by Evgeny Moysevich <moysevichØgmail*com> //
// //
// * version 0.6.1 (30 May 2011) //
// prevent infinite loops in expGolombUe() //
// //
// * version 0.7.0 (16 Jul 2013) //
// handle GETID3_FLV_VIDEO_VP6FLV_ALPHA //
// improved AVCSequenceParameterSetReader::readData() //
// by Xander Schouwerwou <schouwerwouØgmail*com> //
// //
/////////////////////////////////////////////////////////////////
// //
// module.audio-video.flv.php //
// module for analyzing Shockwave Flash Video files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
define('GETID3_FLV_TAG_AUDIO', 8);
define('GETID3_FLV_TAG_VIDEO', 9);
define('GETID3_FLV_TAG_META', 18);
define('GETID3_FLV_VIDEO_H263', 2);
define('GETID3_FLV_VIDEO_SCREEN', 3);
define('GETID3_FLV_VIDEO_VP6FLV', 4);
define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
define('GETID3_FLV_VIDEO_SCREENV2', 6);
define('GETID3_FLV_VIDEO_H264', 7);
define('H264_AVC_SEQUENCE_HEADER', 0);
define('H264_PROFILE_BASELINE', 66);
define('H264_PROFILE_MAIN', 77);
define('H264_PROFILE_EXTENDED', 88);
define('H264_PROFILE_HIGH', 100);
define('H264_PROFILE_HIGH10', 110);
define('H264_PROFILE_HIGH422', 122);
define('H264_PROFILE_HIGH444', 144);
define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
class getid3_flv extends getid3_handler {
const magic = 'FLV';
public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
$FLVheader = $this->fread(5);
$info['fileformat'] = 'flv';
$info['flv']['header']['signature'] = substr($FLVheader, 0, 3);
$info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
$TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
if ($info['flv']['header']['signature'] != self::magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"');
unset($info['flv'], $info['fileformat']);
return false;
}
$info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
$info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
$FrameSizeDataLength = getid3_lib::BigEndian2Int($this->fread(4));
$FLVheaderFrameLength = 9;
if ($FrameSizeDataLength > $FLVheaderFrameLength) {
$this->fseek($FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
}
$Duration = 0;
$found_video = false;
$found_audio = false;
$found_meta = false;
$found_valid_meta_playtime = false;
$tagParseCount = 0;
$info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
$flv_framecount = &$info['flv']['framecount'];
while ((($this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) {
$ThisTagHeader = $this->fread(16);
$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4));
$TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1));
$DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3));
$Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3));
$LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
$NextOffset = $this->ftell() - 1 + $DataLength;
if ($Timestamp > $Duration) {
$Duration = $Timestamp;
}
$flv_framecount['total']++;
switch ($TagType) {
case GETID3_FLV_TAG_AUDIO:
$flv_framecount['audio']++;
if (!$found_audio) {
$found_audio = true;
$info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F;
$info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03;
$info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
$info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01;
}
break;
case GETID3_FLV_TAG_VIDEO:
$flv_framecount['video']++;
if (!$found_video) {
$found_video = true;
$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
$FLVvideoHeader = $this->fread(11);
if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
// this code block contributed by: moysevichØgmail*com
$AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
// read AVCDecoderConfigurationRecord
$configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1));
$AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1));
$profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1));
$lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1));
$numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1));
if (($numOfSequenceParameterSets & 0x1F) != 0) {
// there is at least one SequenceParameterSet
// read size of the first SequenceParameterSet
//$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
$spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
// read the first SequenceParameterSet
$sps = $this->fread($spsSize);
if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red
$spsReader = new AVCSequenceParameterSetReader($sps);
$spsReader->readData();
$info['video']['resolution_x'] = $spsReader->getWidth();
$info['video']['resolution_y'] = $spsReader->getHeight();
}
}
}
// end: moysevichØgmail*com
} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
$PictureSizeType = $PictureSizeType & 0x0007;
$info['flv']['header']['videoSizeType'] = $PictureSizeType;
switch ($PictureSizeType) {
case 0:
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
//$PictureSizeEnc <<= 1;
//$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
//$PictureSizeEnc <<= 1;
//$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)) >> 7;
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)) >> 7;
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
break;
case 1:
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)) >> 7;
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)) >> 7;
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
break;
case 2:
$info['video']['resolution_x'] = 352;
$info['video']['resolution_y'] = 288;
break;
case 3:
$info['video']['resolution_x'] = 176;
$info['video']['resolution_y'] = 144;
break;
case 4:
$info['video']['resolution_x'] = 128;
$info['video']['resolution_y'] = 96;
break;
case 5:
$info['video']['resolution_x'] = 320;
$info['video']['resolution_y'] = 240;
break;
case 6:
$info['video']['resolution_x'] = 160;
$info['video']['resolution_y'] = 120;
break;
default:
$info['video']['resolution_x'] = 0;
$info['video']['resolution_y'] = 0;
break;
}
} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_VP6FLV_ALPHA) {
/* contributed by schouwerwouØgmail*com */
if (!isset($info['video']['resolution_x'])) { // only when meta data isn't set
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 2));
$info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3;
$info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3;
}
/* end schouwerwouØgmail*com */
}
if (!empty($info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) {
$info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
}
}
break;
// Meta tag
case GETID3_FLV_TAG_META:
if (!$found_meta) {
$found_meta = true;
$this->fseek(-1, SEEK_CUR);
$datachunk = $this->fread($DataLength);
$AMFstream = new AMFStream($datachunk);
$reader = new AMFReader($AMFstream);
$eventName = $reader->readData();
$info['flv']['meta'][$eventName] = $reader->readData();
unset($reader);
$copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
foreach ($copykeys as $sourcekey => $destkey) {
if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
switch ($sourcekey) {
case 'width':
case 'height':
$info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
break;
case 'audiodatarate':
$info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
break;
case 'videodatarate':
case 'frame_rate':
default:
$info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
break;
}
}
}
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
$found_valid_meta_playtime = true;
}
}
break;
default:
// noop
break;
}
$this->fseek($NextOffset);
}
$info['playtime_seconds'] = $Duration / 1000;
if ($info['playtime_seconds'] > 0) {
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
if ($info['flv']['header']['hasAudio']) {
$info['audio']['codec'] = self::audioFormatLookup($info['flv']['audio']['audioFormat']);
$info['audio']['sample_rate'] = self::audioRateLookup($info['flv']['audio']['audioRate']);
$info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']);
$info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
$info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
$info['audio']['dataformat'] = 'flv';
}
if (!empty($info['flv']['header']['hasVideo'])) {
$info['video']['codec'] = self::videoCodecLookup($info['flv']['video']['videoCodec']);
$info['video']['dataformat'] = 'flv';
$info['video']['lossless'] = false;
}
// Set information from meta
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
$info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
$info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']);
}
if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
$info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']);
}
return true;
}
public static function audioFormatLookup($id) {
static $lookup = array(
0 => 'Linear PCM, platform endian',
1 => 'ADPCM',
2 => 'mp3',
3 => 'Linear PCM, little endian',
4 => 'Nellymoser 16kHz mono',
5 => 'Nellymoser 8kHz mono',
6 => 'Nellymoser',
7 => 'G.711A-law logarithmic PCM',
8 => 'G.711 mu-law logarithmic PCM',
9 => 'reserved',
10 => 'AAC',
11 => 'Speex',
12 => false, // unknown?
13 => false, // unknown?
14 => 'mp3 8kHz',
15 => 'Device-specific sound',
);
return (isset($lookup[$id]) ? $lookup[$id] : false);
}
public static function audioRateLookup($id) {
static $lookup = array(
0 => 5500,
1 => 11025,
2 => 22050,
3 => 44100,
);
return (isset($lookup[$id]) ? $lookup[$id] : false);
}
public static function audioBitDepthLookup($id) {
static $lookup = array(
0 => 8,
1 => 16,
);
return (isset($lookup[$id]) ? $lookup[$id] : false);
}
public static function videoCodecLookup($id) {
static $lookup = array(
GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
GETID3_FLV_VIDEO_SCREEN => 'Screen video',
GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6',
GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2',
GETID3_FLV_VIDEO_H264 => 'Sorenson H.264',
);
return (isset($lookup[$id]) ? $lookup[$id] : false);
}
}
class AMFStream {
public $bytes;
public $pos;
public function __construct(&$bytes) {
$this->bytes =& $bytes;
$this->pos = 0;
}
public function readByte() {
return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
}
public function readInt() {
return ($this->readByte() << 8) + $this->readByte();
}
public function readLong() {
return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
}
public function readDouble() {
return getid3_lib::BigEndian2Float($this->read(8));
}
public function readUTF() {
$length = $this->readInt();
return $this->read($length);
}
public function readLongUTF() {
$length = $this->readLong();
return $this->read($length);
}
public function read($length) {
$val = substr($this->bytes, $this->pos, $length);
$this->pos += $length;
return $val;
}
public function peekByte() {
$pos = $this->pos;
$val = $this->readByte();
$this->pos = $pos;
return $val;
}
public function peekInt() {
$pos = $this->pos;
$val = $this->readInt();
$this->pos = $pos;
return $val;
}
public function peekLong() {
$pos = $this->pos;
$val = $this->readLong();
$this->pos = $pos;
return $val;
}
public function peekDouble() {
$pos = $this->pos;
$val = $this->readDouble();
$this->pos = $pos;
return $val;
}
public function peekUTF() {
$pos = $this->pos;
$val = $this->readUTF();
$this->pos = $pos;
return $val;
}
public function peekLongUTF() {
$pos = $this->pos;
$val = $this->readLongUTF();
$this->pos = $pos;
return $val;
}
}
class AMFReader {
public $stream;
public function __construct(&$stream) {
$this->stream =& $stream;
}
public function readData() {
$value = null;
$type = $this->stream->readByte();
switch ($type) {
// Double
case 0:
$value = $this->readDouble();
break;
// Boolean
case 1:
$value = $this->readBoolean();
break;
// String
case 2:
$value = $this->readString();
break;
// Object
case 3:
$value = $this->readObject();
break;
// null
case 6:
return null;
break;
// Mixed array
case 8:
$value = $this->readMixedArray();
break;
// Array
case 10:
$value = $this->readArray();
break;
// Date
case 11:
$value = $this->readDate();
break;
// Long string
case 13:
$value = $this->readLongString();
break;
// XML (handled as string)
case 15:
$value = $this->readXML();
break;
// Typed object (handled as object)
case 16:
$value = $this->readTypedObject();
break;
// Long string
default:
$value = '(unknown or unsupported data type)';
break;
}
return $value;
}
public function readDouble() {
return $this->stream->readDouble();
}
public function readBoolean() {
return $this->stream->readByte() == 1;
}
public function readString() {
return $this->stream->readUTF();
}
public function readObject() {
// Get highest numerical index - ignored
// $highestIndex = $this->stream->readLong();
$data = array();
while ($key = $this->stream->readUTF()) {
$data[$key] = $this->readData();
}
// Mixed array record ends with empty string (0x00 0x00) and 0x09
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
// Consume byte
$this->stream->readByte();
}
return $data;
}
public function readMixedArray() {
// Get highest numerical index - ignored
$highestIndex = $this->stream->readLong();
$data = array();
while ($key = $this->stream->readUTF()) {
if (is_numeric($key)) {
$key = (float) $key;
}
$data[$key] = $this->readData();
}
// Mixed array record ends with empty string (0x00 0x00) and 0x09
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
// Consume byte
$this->stream->readByte();
}
return $data;
}
public function readArray() {
$length = $this->stream->readLong();
$data = array();
for ($i = 0; $i < $length; $i++) {
$data[] = $this->readData();
}
return $data;
}
public function readDate() {
$timestamp = $this->stream->readDouble();
$timezone = $this->stream->readInt();
return $timestamp;
}
public function readLongString() {
return $this->stream->readLongUTF();
}
public function readXML() {
return $this->stream->readLongUTF();
}
public function readTypedObject() {
$className = $this->stream->readUTF();
return $this->readObject();
}
}
class AVCSequenceParameterSetReader {
public $sps;
public $start = 0;
public $currentBytes = 0;
public $currentBits = 0;
public $width;
public $height;
public function __construct($sps) {
$this->sps = $sps;
}
public function readData() {
$this->skipBits(8);
$this->skipBits(8);
$profile = $this->getBits(8); // read profile
if ($profile > 0) {
$this->skipBits(8);
$level_idc = $this->getBits(8); // level_idc
$this->expGolombUe(); // seq_parameter_set_id // sps
$this->expGolombUe(); // log2_max_frame_num_minus4
$picOrderType = $this->expGolombUe(); // pic_order_cnt_type
if ($picOrderType == 0) {
$this->expGolombUe(); // log2_max_pic_order_cnt_lsb_minus4
} elseif ($picOrderType == 1) {
$this->skipBits(1); // delta_pic_order_always_zero_flag
$this->expGolombSe(); // offset_for_non_ref_pic
$this->expGolombSe(); // offset_for_top_to_bottom_field
$num_ref_frames_in_pic_order_cnt_cycle = $this->expGolombUe(); // num_ref_frames_in_pic_order_cnt_cycle
for ($i = 0; $i < $num_ref_frames_in_pic_order_cnt_cycle; $i++) {
$this->expGolombSe(); // offset_for_ref_frame[ i ]
}
}
$this->expGolombUe(); // num_ref_frames
$this->skipBits(1); // gaps_in_frame_num_value_allowed_flag
$pic_width_in_mbs_minus1 = $this->expGolombUe(); // pic_width_in_mbs_minus1
$pic_height_in_map_units_minus1 = $this->expGolombUe(); // pic_height_in_map_units_minus1
$frame_mbs_only_flag = $this->getBits(1); // frame_mbs_only_flag
if ($frame_mbs_only_flag == 0) {
$this->skipBits(1); // mb_adaptive_frame_field_flag
}
$this->skipBits(1); // direct_8x8_inference_flag
$frame_cropping_flag = $this->getBits(1); // frame_cropping_flag
$frame_crop_left_offset = 0;
$frame_crop_right_offset = 0;
$frame_crop_top_offset = 0;
$frame_crop_bottom_offset = 0;
if ($frame_cropping_flag) {
$frame_crop_left_offset = $this->expGolombUe(); // frame_crop_left_offset
$frame_crop_right_offset = $this->expGolombUe(); // frame_crop_right_offset
$frame_crop_top_offset = $this->expGolombUe(); // frame_crop_top_offset
$frame_crop_bottom_offset = $this->expGolombUe(); // frame_crop_bottom_offset
}
$this->skipBits(1); // vui_parameters_present_flag
// etc
$this->width = (($pic_width_in_mbs_minus1 + 1) * 16) - ($frame_crop_left_offset * 2) - ($frame_crop_right_offset * 2);
$this->height = ((2 - $frame_mbs_only_flag) * ($pic_height_in_map_units_minus1 + 1) * 16) - ($frame_crop_top_offset * 2) - ($frame_crop_bottom_offset * 2);
}
}
public function skipBits($bits) {
$newBits = $this->currentBits + $bits;
$this->currentBytes += (int)floor($newBits / 8);
$this->currentBits = $newBits % 8;
}
public function getBit() {
$result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
$this->skipBits(1);
return $result;
}
public function getBits($bits) {
$result = 0;
for ($i = 0; $i < $bits; $i++) {
$result = ($result << 1) + $this->getBit();
}
return $result;
}
public function expGolombUe() {
$significantBits = 0;
$bit = $this->getBit();
while ($bit == 0) {
$significantBits++;
$bit = $this->getBit();
if ($significantBits > 31) {
// something is broken, this is an emergency escape to prevent infinite loops
return 0;
}
}
return (1 << $significantBits) + $this->getBits($significantBits) - 1;
}
public function expGolombSe() {
$result = $this->expGolombUe();
if (($result & 0x01) == 0) {
return -($result >> 1);
} else {
return ($result + 1) >> 1;
}
}
public function getWidth() {
return $this->width;
}
public function getHeight() {
return $this->height;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,733 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.ac3.php //
// module for analyzing AC-3 (aka Dolby Digital) audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
class getid3_ac3 extends getid3_handler
{
private $AC3header = array();
private $BSIoffset = 0;
const syncword = 0x0B77;
public function Analyze() {
$info = &$this->getid3->info;
///AH
$info['ac3']['raw']['bsi'] = array();
$thisfile_ac3 = &$info['ac3'];
$thisfile_ac3_raw = &$thisfile_ac3['raw'];
$thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi'];
// http://www.atsc.org/standards/a_52a.pdf
$info['fileformat'] = 'ac3';
// An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames
// Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256
// new audio samples per channel. A synchronization information (SI) header at the beginning
// of each frame contains information needed to acquire and maintain synchronization. A
// bit stream information (BSI) header follows SI, and contains parameters describing the coded
// audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the
// end of each frame is an error check field that includes a CRC word for error detection. An
// additional CRC word is located in the SI header, the use of which, by a decoder, is optional.
//
// syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC
// syncinfo() {
// syncword 16
// crc1 16
// fscod 2
// frmsizecod 6
// } /* end of syncinfo */
$this->fseek($info['avdataoffset']);
$tempAC3header = $this->fread(100); // should be enough to cover all data, there are some variable-length fields...?
$this->AC3header['syncinfo'] = getid3_lib::BigEndian2Int(substr($tempAC3header, 0, 2));
$this->AC3header['bsi'] = getid3_lib::BigEndian2Bin(substr($tempAC3header, 2));
$thisfile_ac3_raw_bsi['bsid'] = (getid3_lib::LittleEndian2Int(substr($tempAC3header, 5, 1)) & 0xF8) >> 3; // AC3 and E-AC3 put the "bsid" version identifier in the same place, but unfortnately the 4 bytes between the syncword and the version identifier are interpreted differently, so grab it here so the following code structure can make sense
unset($tempAC3header);
if ($this->AC3header['syncinfo'] !== self::syncword) {
if (!$this->isDependencyFor('matroska')) {
unset($info['fileformat'], $info['ac3']);
return $this->error('Expecting "'.dechex(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.dechex($this->AC3header['syncinfo']).'"');
}
}
$info['audio']['dataformat'] = 'ac3';
$info['audio']['bitrate_mode'] = 'cbr';
$info['audio']['lossless'] = false;
if ($thisfile_ac3_raw_bsi['bsid'] <= 8) {
$thisfile_ac3_raw_bsi['crc1'] = getid3_lib::Bin2Dec($this->readHeaderBSI(16));
$thisfile_ac3_raw_bsi['fscod'] = $this->readHeaderBSI(2); // 5.4.1.3
$thisfile_ac3_raw_bsi['frmsizecod'] = $this->readHeaderBSI(6); // 5.4.1.4
if ($thisfile_ac3_raw_bsi['frmsizecod'] > 37) { // binary: 100101 - see Table 5.18 Frame Size Code Table (1 word = 16 bits)
$this->warning('Unexpected ac3.bsi.frmsizecod value: '.$thisfile_ac3_raw_bsi['frmsizecod'].', bitrate not set correctly');
}
$thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
$thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
$thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
// If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
$thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
$thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
}
if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
// If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
$thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
$thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
}
if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
// When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
$thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
$thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
}
$thisfile_ac3_raw_bsi['flags']['lfeon'] = (bool) $this->readHeaderBSI(1);
// This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31.
// The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
$thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5); // 5.4.2.8 dialnorm: Dialogue Normalization, 5 Bits
$thisfile_ac3_raw_bsi['flags']['compr'] = (bool) $this->readHeaderBSI(1); // 5.4.2.9 compre: Compression Gain Word Exists, 1 Bit
if ($thisfile_ac3_raw_bsi['flags']['compr']) {
$thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8); // 5.4.2.10 compr: Compression Gain Word, 8 Bits
$thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']);
}
$thisfile_ac3_raw_bsi['flags']['langcod'] = (bool) $this->readHeaderBSI(1); // 5.4.2.11 langcode: Language Code Exists, 1 Bit
if ($thisfile_ac3_raw_bsi['flags']['langcod']) {
$thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8); // 5.4.2.12 langcod: Language Code, 8 Bits
}
$thisfile_ac3_raw_bsi['flags']['audprodinfo'] = (bool) $this->readHeaderBSI(1); // 5.4.2.13 audprodie: Audio Production Information Exists, 1 Bit
if ($thisfile_ac3_raw_bsi['flags']['audprodinfo']) {
$thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5); // 5.4.2.14 mixlevel: Mixing Level, 5 Bits
$thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2); // 5.4.2.15 roomtyp: Room Type, 2 Bits
$thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
$thisfile_ac3['room_type'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
}
$thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5); // 5.4.2.16 dialnorm2: Dialogue Normalization, ch2, 5 Bits
$thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB'; // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
$thisfile_ac3_raw_bsi['flags']['compr2'] = (bool) $this->readHeaderBSI(1); // 5.4.2.17 compr2e: Compression Gain Word Exists, ch2, 1 Bit
if ($thisfile_ac3_raw_bsi['flags']['compr2']) {
$thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8); // 5.4.2.18 compr2: Compression Gain Word, ch2, 8 Bits
$thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']);
}
$thisfile_ac3_raw_bsi['flags']['langcod2'] = (bool) $this->readHeaderBSI(1); // 5.4.2.19 langcod2e: Language Code Exists, ch2, 1 Bit
if ($thisfile_ac3_raw_bsi['flags']['langcod2']) {
$thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8); // 5.4.2.20 langcod2: Language Code, ch2, 8 Bits
}
$thisfile_ac3_raw_bsi['flags']['audprodinfo2'] = (bool) $this->readHeaderBSI(1); // 5.4.2.21 audprodi2e: Audio Production Information Exists, ch2, 1 Bit
if ($thisfile_ac3_raw_bsi['flags']['audprodinfo2']) {
$thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5); // 5.4.2.22 mixlevel2: Mixing Level, ch2, 5 Bits
$thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2); // 5.4.2.23 roomtyp2: Room Type, ch2, 2 Bits
$thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
$thisfile_ac3['room_type2'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
}
$thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1); // 5.4.2.24 copyrightb: Copyright Bit, 1 Bit
$thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1); // 5.4.2.25 origbs: Original Bit Stream, 1 Bit
$thisfile_ac3_raw_bsi['flags']['timecod1'] = $this->readHeaderBSI(2); // 5.4.2.26 timecod1e, timcode2e: Time Code (first and second) Halves Exist, 2 Bits
if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x01) {
$thisfile_ac3_raw_bsi['timecod1'] = $this->readHeaderBSI(14); // 5.4.2.27 timecod1: Time code first half, 14 bits
$thisfile_ac3['timecode1'] = 0;
$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x3E00) >> 9) * 3600; // The first 5 bits of this 14-bit field represent the time in hours, with valid values of 0<>23
$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x01F8) >> 3) * 60; // The next 6 bits represent the time in minutes, with valid values of 0<>59
$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x0003) >> 0) * 8; // The final 3 bits represents the time in 8 second increments, with valid values of 0<>7 (representing 0, 8, 16, ... 56 seconds)
}
if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x02) {
$thisfile_ac3_raw_bsi['timecod2'] = $this->readHeaderBSI(14); // 5.4.2.28 timecod2: Time code second half, 14 bits
$thisfile_ac3['timecode2'] = 0;
$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x3800) >> 11) * 1; // The first 3 bits of this 14-bit field represent the time in seconds, with valid values from 0<>7 (representing 0-7 seconds)
$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x07C0) >> 6) * (1 / 30); // The next 5 bits represents the time in frames, with valid values from 0<>29 (one frame = 1/30th of a second)
$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x003F) >> 0) * ((1 / 30) / 60); // The final 6 bits represents fractions of 1/64 of a frame, with valid values from 0<>63
}
$thisfile_ac3_raw_bsi['flags']['addbsi'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['addbsi']) {
$thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6) + 1; // This 6-bit code, which exists only if addbside is a 1, indicates the length in bytes of additional bit stream information. The valid range of addbsil is 0<>63, indicating 1<>64 additional bytes, respectively.
$this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length']));
$thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
$this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
}
} elseif ($thisfile_ac3_raw_bsi['bsid'] <= 16) { // E-AC3
$this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.');
$info['audio']['dataformat'] = 'eac3';
$thisfile_ac3_raw_bsi['strmtyp'] = $this->readHeaderBSI(2);
$thisfile_ac3_raw_bsi['substreamid'] = $this->readHeaderBSI(3);
$thisfile_ac3_raw_bsi['frmsiz'] = $this->readHeaderBSI(11);
$thisfile_ac3_raw_bsi['fscod'] = $this->readHeaderBSI(2);
if ($thisfile_ac3_raw_bsi['fscod'] == 3) {
$thisfile_ac3_raw_bsi['fscod2'] = $this->readHeaderBSI(2);
$thisfile_ac3_raw_bsi['numblkscod'] = 3; // six blocks per syncframe
} else {
$thisfile_ac3_raw_bsi['numblkscod'] = $this->readHeaderBSI(2);
}
$thisfile_ac3['bsi']['blocks_per_sync_frame'] = self::blocksPerSyncFrame($thisfile_ac3_raw_bsi['numblkscod']);
$thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
$thisfile_ac3_raw_bsi['flags']['lfeon'] = (bool) $this->readHeaderBSI(1);
$thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
$thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);
$thisfile_ac3_raw_bsi['flags']['compr'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['compr']) {
$thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);
}
if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
$thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);
$thisfile_ac3_raw_bsi['flags']['compr2'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['compr2']) {
$thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);
}
}
if ($thisfile_ac3_raw_bsi['strmtyp'] == 1) { // if dependent stream
$thisfile_ac3_raw_bsi['flags']['chanmap'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['chanmap']) {
$thisfile_ac3_raw_bsi['chanmap'] = $this->readHeaderBSI(8);
}
}
$thisfile_ac3_raw_bsi['flags']['mixmdat'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['mixmdat']) { // Mixing metadata
if ($thisfile_ac3_raw_bsi['acmod'] > 2) { // if more than 2 channels
$thisfile_ac3_raw_bsi['dmixmod'] = $this->readHeaderBSI(2);
}
if (($thisfile_ac3_raw_bsi['acmod'] & 0x01) && ($thisfile_ac3_raw_bsi['acmod'] > 2)) { // if three front channels exist
$thisfile_ac3_raw_bsi['ltrtcmixlev'] = $this->readHeaderBSI(3);
$thisfile_ac3_raw_bsi['lorocmixlev'] = $this->readHeaderBSI(3);
}
if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { // if a surround channel exists
$thisfile_ac3_raw_bsi['ltrtsurmixlev'] = $this->readHeaderBSI(3);
$thisfile_ac3_raw_bsi['lorosurmixlev'] = $this->readHeaderBSI(3);
}
if ($thisfile_ac3_raw_bsi['flags']['lfeon']) { // if the LFE channel exists
$thisfile_ac3_raw_bsi['flags']['lfemixlevcod'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['lfemixlevcod']) {
$thisfile_ac3_raw_bsi['lfemixlevcod'] = $this->readHeaderBSI(5);
}
}
if ($thisfile_ac3_raw_bsi['strmtyp'] == 0) { // if independent stream
$thisfile_ac3_raw_bsi['flags']['pgmscl'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['pgmscl']) {
$thisfile_ac3_raw_bsi['pgmscl'] = $this->readHeaderBSI(6);
}
if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
$thisfile_ac3_raw_bsi['flags']['pgmscl2'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['pgmscl2']) {
$thisfile_ac3_raw_bsi['pgmscl2'] = $this->readHeaderBSI(6);
}
}
$thisfile_ac3_raw_bsi['flags']['extpgmscl'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['extpgmscl']) {
$thisfile_ac3_raw_bsi['extpgmscl'] = $this->readHeaderBSI(6);
}
$thisfile_ac3_raw_bsi['mixdef'] = $this->readHeaderBSI(2);
if ($thisfile_ac3_raw_bsi['mixdef'] == 1) { // mixing option 2
$thisfile_ac3_raw_bsi['premixcmpsel'] = (bool) $this->readHeaderBSI(1);
$thisfile_ac3_raw_bsi['drcsrc'] = (bool) $this->readHeaderBSI(1);
$thisfile_ac3_raw_bsi['premixcmpscl'] = $this->readHeaderBSI(3);
} elseif ($thisfile_ac3_raw_bsi['mixdef'] == 2) { // mixing option 3
$thisfile_ac3_raw_bsi['mixdata'] = $this->readHeaderBSI(12);
} elseif ($thisfile_ac3_raw_bsi['mixdef'] == 3) { // mixing option 4
$mixdefbitsread = 0;
$thisfile_ac3_raw_bsi['mixdeflen'] = $this->readHeaderBSI(5); $mixdefbitsread += 5;
$thisfile_ac3_raw_bsi['flags']['mixdata2'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['mixdata2']) {
$thisfile_ac3_raw_bsi['premixcmpsel'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
$thisfile_ac3_raw_bsi['drcsrc'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
$thisfile_ac3_raw_bsi['premixcmpscl'] = $this->readHeaderBSI(3); $mixdefbitsread += 3;
$thisfile_ac3_raw_bsi['flags']['extpgmlscl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmlscl']) {
$thisfile_ac3_raw_bsi['extpgmlscl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
$thisfile_ac3_raw_bsi['flags']['extpgmcscl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmcscl']) {
$thisfile_ac3_raw_bsi['extpgmcscl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
$thisfile_ac3_raw_bsi['flags']['extpgmrscl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmrscl']) {
$thisfile_ac3_raw_bsi['extpgmrscl'] = $this->readHeaderBSI(4);
}
$thisfile_ac3_raw_bsi['flags']['extpgmlsscl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmlsscl']) {
$thisfile_ac3_raw_bsi['extpgmlsscl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
$thisfile_ac3_raw_bsi['flags']['extpgmrsscl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmrsscl']) {
$thisfile_ac3_raw_bsi['extpgmrsscl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
$thisfile_ac3_raw_bsi['flags']['extpgmlfescl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmlfescl']) {
$thisfile_ac3_raw_bsi['extpgmlfescl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
$thisfile_ac3_raw_bsi['flags']['dmixscl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['dmixscl']) {
$thisfile_ac3_raw_bsi['dmixscl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
$thisfile_ac3_raw_bsi['flags']['addch'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['addch']) {
$thisfile_ac3_raw_bsi['flags']['extpgmaux1scl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmaux1scl']) {
$thisfile_ac3_raw_bsi['extpgmaux1scl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
$thisfile_ac3_raw_bsi['flags']['extpgmaux2scl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['extpgmaux2scl']) {
$thisfile_ac3_raw_bsi['extpgmaux2scl'] = $this->readHeaderBSI(4); $mixdefbitsread += 4;
}
}
}
$thisfile_ac3_raw_bsi['flags']['mixdata3'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['mixdata3']) {
$thisfile_ac3_raw_bsi['spchdat'] = $this->readHeaderBSI(5); $mixdefbitsread += 5;
$thisfile_ac3_raw_bsi['flags']['addspchdat'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['addspchdat']) {
$thisfile_ac3_raw_bsi['spchdat1'] = $this->readHeaderBSI(5); $mixdefbitsread += 5;
$thisfile_ac3_raw_bsi['spchan1att'] = $this->readHeaderBSI(2); $mixdefbitsread += 2;
$thisfile_ac3_raw_bsi['flags']['addspchdat1'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
if ($thisfile_ac3_raw_bsi['flags']['addspchdat1']) {
$thisfile_ac3_raw_bsi['spchdat2'] = $this->readHeaderBSI(5); $mixdefbitsread += 5;
$thisfile_ac3_raw_bsi['spchan2att'] = $this->readHeaderBSI(3); $mixdefbitsread += 3;
}
}
}
$mixdata_bits = (8 * ($thisfile_ac3_raw_bsi['mixdeflen'] + 2)) - $mixdefbitsread;
$mixdata_fill = (($mixdata_bits % 8) ? 8 - ($mixdata_bits % 8) : 0);
$thisfile_ac3_raw_bsi['mixdata'] = $this->readHeaderBSI($mixdata_bits);
$thisfile_ac3_raw_bsi['mixdatafill'] = $this->readHeaderBSI($mixdata_fill);
unset($mixdefbitsread, $mixdata_bits, $mixdata_fill);
}
if ($thisfile_ac3_raw_bsi['acmod'] < 2) { // if mono or dual mono source
$thisfile_ac3_raw_bsi['flags']['paninfo'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['paninfo']) {
$thisfile_ac3_raw_bsi['panmean'] = $this->readHeaderBSI(8);
$thisfile_ac3_raw_bsi['paninfo'] = $this->readHeaderBSI(6);
}
if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
$thisfile_ac3_raw_bsi['flags']['paninfo2'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['paninfo2']) {
$thisfile_ac3_raw_bsi['panmean2'] = $this->readHeaderBSI(8);
$thisfile_ac3_raw_bsi['paninfo2'] = $this->readHeaderBSI(6);
}
}
}
$thisfile_ac3_raw_bsi['flags']['frmmixcfginfo'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['frmmixcfginfo']) { // mixing configuration information
if ($thisfile_ac3_raw_bsi['numblkscod'] == 0) {
$thisfile_ac3_raw_bsi['blkmixcfginfo'][0] = $this->readHeaderBSI(5);
} else {
for ($blk = 0; $blk < $thisfile_ac3_raw_bsi['numblkscod']; $blk++) {
$thisfile_ac3_raw_bsi['flags']['blkmixcfginfo'.$blk] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['blkmixcfginfo'.$blk]) { // mixing configuration information
$thisfile_ac3_raw_bsi['blkmixcfginfo'][$blk] = $this->readHeaderBSI(5);
}
}
}
}
}
}
$thisfile_ac3_raw_bsi['flags']['infomdat'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['infomdat']) { // Informational metadata
$thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
$thisfile_ac3_raw_bsi['flags']['copyrightb'] = (bool) $this->readHeaderBSI(1);
$thisfile_ac3_raw_bsi['flags']['origbs'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['acmod'] == 2) { // if in 2/0 mode
$thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
$thisfile_ac3_raw_bsi['dheadphonmod'] = $this->readHeaderBSI(2);
}
if ($thisfile_ac3_raw_bsi['acmod'] >= 6) { // if both surround channels exist
$thisfile_ac3_raw_bsi['dsurexmod'] = $this->readHeaderBSI(2);
}
$thisfile_ac3_raw_bsi['flags']['audprodi'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['audprodi']) {
$thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);
$thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2);
$thisfile_ac3_raw_bsi['flags']['adconvtyp'] = (bool) $this->readHeaderBSI(1);
}
if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
$thisfile_ac3_raw_bsi['flags']['audprodi2'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['audprodi2']) {
$thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);
$thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2);
$thisfile_ac3_raw_bsi['flags']['adconvtyp2'] = (bool) $this->readHeaderBSI(1);
}
}
if ($thisfile_ac3_raw_bsi['fscod'] < 3) { // if not half sample rate
$thisfile_ac3_raw_bsi['flags']['sourcefscod'] = (bool) $this->readHeaderBSI(1);
}
}
if (($thisfile_ac3_raw_bsi['strmtyp'] == 0) && ($thisfile_ac3_raw_bsi['numblkscod'] != 3)) { // if both surround channels exist
$thisfile_ac3_raw_bsi['flags']['convsync'] = (bool) $this->readHeaderBSI(1);
}
if ($thisfile_ac3_raw_bsi['strmtyp'] == 2) { // if bit stream converted from AC-3
if ($thisfile_ac3_raw_bsi['numblkscod'] != 3) { // 6 blocks per syncframe
$thisfile_ac3_raw_bsi['flags']['blkid'] = 1;
} else {
$thisfile_ac3_raw_bsi['flags']['blkid'] = (bool) $this->readHeaderBSI(1);
}
if ($thisfile_ac3_raw_bsi['flags']['blkid']) {
$thisfile_ac3_raw_bsi['frmsizecod'] = $this->readHeaderBSI(6);
}
}
$thisfile_ac3_raw_bsi['flags']['addbsi'] = (bool) $this->readHeaderBSI(1);
if ($thisfile_ac3_raw_bsi['flags']['addbsi']) {
$thisfile_ac3_raw_bsi['addbsil'] = $this->readHeaderBSI(6);
$thisfile_ac3_raw_bsi['addbsi'] = $this->readHeaderBSI(($thisfile_ac3_raw_bsi['addbsil'] + 1) * 8);
}
} else {
$this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 16. Please submit a support ticket with a sample file.');
unset($info['ac3']);
return false;
}
if (isset($thisfile_ac3_raw_bsi['fscod2'])) {
$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup2($thisfile_ac3_raw_bsi['fscod2']);
} else {
$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw_bsi['fscod']);
}
if ($thisfile_ac3_raw_bsi['fscod'] <= 3) {
$info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
} else {
$this->warning('Unexpected ac3.bsi.fscod value: '.$thisfile_ac3_raw_bsi['fscod']);
}
if (isset($thisfile_ac3_raw_bsi['frmsizecod'])) {
$thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw_bsi['frmsizecod'], $thisfile_ac3_raw_bsi['fscod']);
$thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw_bsi['frmsizecod']);
} elseif (!empty($thisfile_ac3_raw_bsi['frmsiz'])) {
// this isn't right, but it's (usually) close, roughly 5% less than it should be.
// but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know!
$thisfile_ac3['bitrate'] = ($thisfile_ac3_raw_bsi['frmsiz'] + 1) * 16 * 30; // The frmsiz field shall contain a value one less than the overall size of the coded syncframe in 16-bit words. That is, this field may assume a value ranging from 0 to 2047, and these values correspond to syncframe sizes ranging from 1 to 2048.
// kludge-fix to make it approximately the expected value, still not "right":
$thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16000;
}
$info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
$thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
$ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
foreach($ac3_coding_mode as $key => $value) {
$thisfile_ac3[$key] = $value;
}
switch ($thisfile_ac3_raw_bsi['acmod']) {
case 0:
case 1:
$info['audio']['channelmode'] = 'mono';
break;
case 3:
case 4:
$info['audio']['channelmode'] = 'stereo';
break;
default:
$info['audio']['channelmode'] = 'surround';
break;
}
$info['audio']['channels'] = $thisfile_ac3['num_channels'];
$thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['flags']['lfeon'];
if ($thisfile_ac3_raw_bsi['flags']['lfeon']) {
$info['audio']['channels'] .= '.1';
}
$thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['flags']['lfeon']);
$thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';
return true;
}
private function readHeaderBSI($length) {
$data = substr($this->AC3header['bsi'], $this->BSIoffset, $length);
$this->BSIoffset += $length;
return bindec($data);
}
public static function sampleRateCodeLookup($fscod) {
static $sampleRateCodeLookup = array(
0 => 48000,
1 => 44100,
2 => 32000,
3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
);
return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false);
}
public static function sampleRateCodeLookup2($fscod2) {
static $sampleRateCodeLookup2 = array(
0 => 24000,
1 => 22050,
2 => 16000,
3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
);
return (isset($sampleRateCodeLookup2[$fscod2]) ? $sampleRateCodeLookup2[$fscod2] : false);
}
public static function serviceTypeLookup($bsmod, $acmod) {
static $serviceTypeLookup = array();
if (empty($serviceTypeLookup)) {
for ($i = 0; $i <= 7; $i++) {
$serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)';
$serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)';
$serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)';
$serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)';
$serviceTypeLookup[4][$i] = 'associated service: dialogue (D)';
$serviceTypeLookup[5][$i] = 'associated service: commentary (C)';
$serviceTypeLookup[6][$i] = 'associated service: emergency (E)';
}
$serviceTypeLookup[7][1] = 'associated service: voice over (VO)';
for ($i = 2; $i <= 7; $i++) {
$serviceTypeLookup[7][$i] = 'main audio service: karaoke';
}
}
return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false);
}
public static function audioCodingModeLookup($acmod) {
// array(channel configuration, # channels (not incl LFE), channel order)
static $audioCodingModeLookup = array (
0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'),
1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'),
2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'),
3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'),
4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'),
5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'),
6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'),
7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'),
);
return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false);
}
public static function centerMixLevelLookup($cmixlev) {
static $centerMixLevelLookup;
if (empty($centerMixLevelLookup)) {
$centerMixLevelLookup = array(
0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB)
1 => pow(2, -4.5 / 6), // 0.595 (-4.5 dB)
2 => pow(2, -6.0 / 6), // 0.500 (-6.0 dB)
3 => 'reserved'
);
}
return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false);
}
public static function surroundMixLevelLookup($surmixlev) {
static $surroundMixLevelLookup;
if (empty($surroundMixLevelLookup)) {
$surroundMixLevelLookup = array(
0 => pow(2, -3.0 / 6),
1 => pow(2, -6.0 / 6),
2 => 0,
3 => 'reserved'
);
}
return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false);
}
public static function dolbySurroundModeLookup($dsurmod) {
static $dolbySurroundModeLookup = array(
0 => 'not indicated',
1 => 'Not Dolby Surround encoded',
2 => 'Dolby Surround encoded',
3 => 'reserved'
);
return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false);
}
public static function channelsEnabledLookup($acmod, $lfeon) {
$lookup = array(
'ch1'=>(bool) ($acmod == 0),
'ch2'=>(bool) ($acmod == 0),
'left'=>(bool) ($acmod > 1),
'right'=>(bool) ($acmod > 1),
'center'=>(bool) ($acmod & 0x01),
'surround_mono'=>false,
'surround_left'=>false,
'surround_right'=>false,
'lfe'=>$lfeon);
switch ($acmod) {
case 4:
case 5:
$lookup['surround_mono'] = true;
break;
case 6:
case 7:
$lookup['surround_left'] = true;
$lookup['surround_right'] = true;
break;
}
return $lookup;
}
public static function heavyCompression($compre) {
// The first four bits indicate gain changes in 6.02dB increments which can be
// implemented with an arithmetic shift operation. The following four bits
// indicate linear gain changes, and require a 5-bit multiply.
// We will represent the two 4-bit fields of compr as follows:
// X0 X1 X2 X3 . Y4 Y5 Y6 Y7
// The meaning of the X values is most simply described by considering X to represent a 4-bit
// signed integer with values from -8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The
// following table shows this in detail.
// Meaning of 4 msb of compr
// 7 +48.16 dB
// 6 +42.14 dB
// 5 +36.12 dB
// 4 +30.10 dB
// 3 +24.08 dB
// 2 +18.06 dB
// 1 +12.04 dB
// 0 +6.02 dB
// -1 0 dB
// -2 -6.02 dB
// -3 -12.04 dB
// -4 -18.06 dB
// -5 -24.08 dB
// -6 -30.10 dB
// -7 -36.12 dB
// -8 -42.14 dB
$fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT);
if ($fourbit{0} == '1') {
$log_gain = -8 + bindec(substr($fourbit, 1));
} else {
$log_gain = bindec(substr($fourbit, 1));
}
$log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2);
// The value of Y is a linear representation of a gain change of up to -6 dB. Y is considered to
// be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can
// represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain
// changes from -0.28 dB to -6.02 dB.
$lin_gain = (16 + ($compre & 0x0F)) / 32;
// The combination of X and Y values allows compr to indicate gain changes from
// 48.16 - 0.28 = +47.89 dB, to
// -42.14 - 6.02 = -48.16 dB.
return $log_gain - $lin_gain;
}
public static function roomTypeLookup($roomtyp) {
static $roomTypeLookup = array(
0 => 'not indicated',
1 => 'large room, X curve monitor',
2 => 'small room, flat monitor',
3 => 'reserved'
);
return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false);
}
public static function frameSizeLookup($frmsizecod, $fscod) {
// LSB is whether padding is used or not
$padding = (bool) ($frmsizecod & 0x01);
$framesizeid = ($frmsizecod & 0x3E) >> 1;
static $frameSizeLookup = array();
if (empty($frameSizeLookup)) {
$frameSizeLookup = array (
0 => array( 128, 138, 192), // 32 kbps
1 => array( 160, 174, 240), // 40 kbps
2 => array( 192, 208, 288), // 48 kbps
3 => array( 224, 242, 336), // 56 kbps
4 => array( 256, 278, 384), // 64 kbps
5 => array( 320, 348, 480), // 80 kbps
6 => array( 384, 416, 576), // 96 kbps
7 => array( 448, 486, 672), // 112 kbps
8 => array( 512, 556, 768), // 128 kbps
9 => array( 640, 696, 960), // 160 kbps
10 => array( 768, 834, 1152), // 192 kbps
11 => array( 896, 974, 1344), // 224 kbps
12 => array(1024, 1114, 1536), // 256 kbps
13 => array(1280, 1392, 1920), // 320 kbps
14 => array(1536, 1670, 2304), // 384 kbps
15 => array(1792, 1950, 2688), // 448 kbps
16 => array(2048, 2228, 3072), // 512 kbps
17 => array(2304, 2506, 3456), // 576 kbps
18 => array(2560, 2786, 3840) // 640 kbps
);
}
if (($fscod == 1) && $padding) {
// frame lengths are padded by 1 word (16 bits) at 44100
$frameSizeLookup[$frmsizecod] += 2;
}
return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false);
}
public static function bitrateLookup($frmsizecod) {
// LSB is whether padding is used or not
$padding = (bool) ($frmsizecod & 0x01);
$framesizeid = ($frmsizecod & 0x3E) >> 1;
static $bitrateLookup = array(
0 => 32000,
1 => 40000,
2 => 48000,
3 => 56000,
4 => 64000,
5 => 80000,
6 => 96000,
7 => 112000,
8 => 128000,
9 => 160000,
10 => 192000,
11 => 224000,
12 => 256000,
13 => 320000,
14 => 384000,
15 => 448000,
16 => 512000,
17 => 576000,
18 => 640000,
);
return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false);
}
public static function blocksPerSyncFrame($numblkscod) {
static $blocksPerSyncFrameLookup = array(
0 => 1,
1 => 2,
2 => 3,
3 => 6,
);
return (isset($blocksPerSyncFrameLookup[$numblkscod]) ? $blocksPerSyncFrameLookup[$numblkscod] : false);
}
}

View File

@ -0,0 +1,291 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.dts.php //
// module for analyzing DTS Audio files //
// dependencies: NONE //
// //
/////////////////////////////////////////////////////////////////
/**
* @tutorial http://wiki.multimedia.cx/index.php?title=DTS
*/
class getid3_dts extends getid3_handler
{
/**
* Default DTS syncword used in native .cpt or .dts formats
*/
const syncword = "\x7F\xFE\x80\x01";
private $readBinDataOffset = 0;
/**
* Possible syncwords indicating bitstream encoding
*/
public static $syncwords = array(
0 => "\x7F\xFE\x80\x01", // raw big-endian
1 => "\xFE\x7F\x01\x80", // raw little-endian
2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian
3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'dts';
$this->fseek($info['avdataoffset']);
$DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes
// check syncword
$sync = substr($DTSheader, 0, 4);
if (($encoding = array_search($sync, self::$syncwords)) !== false) {
$info['dts']['raw']['magic'] = $sync;
$this->readBinDataOffset = 32;
} elseif ($this->isDependencyFor('matroska')) {
// Matroska contains DTS without syncword encoded as raw big-endian format
$encoding = 0;
$this->readBinDataOffset = 0;
} else {
unset($info['fileformat']);
return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"');
}
// decode header
$fhBS = '';
for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) {
switch ($encoding) {
case 0: // raw big-endian
$fhBS .= getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) );
break;
case 1: // raw little-endian
$fhBS .= getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2)));
break;
case 2: // 14-bit big-endian
$fhBS .= substr(getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ), 2, 14);
break;
case 3: // 14-bit little-endian
$fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14);
break;
}
}
$info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, 1);
$info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, 5);
$info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, 7);
$info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, 14);
$info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, 6);
$info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, 4);
$info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, 5);
$info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, 3);
$info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, 2);
$info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, 1);
if ($info['dts']['flags']['crc_present']) {
$info['dts']['raw']['crc16'] = $this->readBinData($fhBS, 16);
}
$info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, 4);
$info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, 2);
$info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, 2);
$info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, 4);
$info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']);
$info['dts']['bits_per_sample'] = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']);
$info['dts']['sample_rate'] = self::sampleRateLookup($info['dts']['raw']['sample_frequency']);
$info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']);
$info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false);
$info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr');
$info['dts']['channels'] = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']);
$info['dts']['channel_arrangement'] = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']);
$info['audio']['dataformat'] = 'dts';
$info['audio']['lossless'] = $info['dts']['flags']['lossless'];
$info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode'];
$info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample'];
$info['audio']['sample_rate'] = $info['dts']['sample_rate'];
$info['audio']['channels'] = $info['dts']['channels'];
$info['audio']['bitrate'] = $info['dts']['bitrate'];
if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) {
$info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8);
if (($encoding == 2) || ($encoding == 3)) {
// 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate
$info['playtime_seconds'] *= (14 / 16);
}
}
return true;
}
private function readBinData($bin, $length) {
$data = substr($bin, $this->readBinDataOffset, $length);
$this->readBinDataOffset += $length;
return bindec($data);
}
public static function bitrateLookup($index) {
static $lookup = array(
0 => 32000,
1 => 56000,
2 => 64000,
3 => 96000,
4 => 112000,
5 => 128000,
6 => 192000,
7 => 224000,
8 => 256000,
9 => 320000,
10 => 384000,
11 => 448000,
12 => 512000,
13 => 576000,
14 => 640000,
15 => 768000,
16 => 960000,
17 => 1024000,
18 => 1152000,
19 => 1280000,
20 => 1344000,
21 => 1408000,
22 => 1411200,
23 => 1472000,
24 => 1536000,
25 => 1920000,
26 => 2048000,
27 => 3072000,
28 => 3840000,
29 => 'open',
30 => 'variable',
31 => 'lossless',
);
return (isset($lookup[$index]) ? $lookup[$index] : false);
}
public static function sampleRateLookup($index) {
static $lookup = array(
0 => 'invalid',
1 => 8000,
2 => 16000,
3 => 32000,
4 => 'invalid',
5 => 'invalid',
6 => 11025,
7 => 22050,
8 => 44100,
9 => 'invalid',
10 => 'invalid',
11 => 12000,
12 => 24000,
13 => 48000,
14 => 'invalid',
15 => 'invalid',
);
return (isset($lookup[$index]) ? $lookup[$index] : false);
}
public static function bitPerSampleLookup($index) {
static $lookup = array(
0 => 16,
1 => 20,
2 => 24,
3 => 24,
);
return (isset($lookup[$index]) ? $lookup[$index] : false);
}
public static function numChannelsLookup($index) {
switch ($index) {
case 0:
return 1;
break;
case 1:
case 2:
case 3:
case 4:
return 2;
break;
case 5:
case 6:
return 3;
break;
case 7:
case 8:
return 4;
break;
case 9:
return 5;
break;
case 10:
case 11:
case 12:
return 6;
break;
case 13:
return 7;
break;
case 14:
case 15:
return 8;
break;
}
return false;
}
public static function channelArrangementLookup($index) {
static $lookup = array(
0 => 'A',
1 => 'A + B (dual mono)',
2 => 'L + R (stereo)',
3 => '(L+R) + (L-R) (sum-difference)',
4 => 'LT + RT (left and right total)',
5 => 'C + L + R',
6 => 'L + R + S',
7 => 'C + L + R + S',
8 => 'L + R + SL + SR',
9 => 'C + L + R + SL + SR',
10 => 'CL + CR + L + R + SL + SR',
11 => 'C + L + R+ LR + RR + OV',
12 => 'CF + CR + LF + RF + LR + RR',
13 => 'CL + C + CR + L + R + SL + SR',
14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2',
15 => 'CL + C+ CR + L + R + SL + S + SR',
);
return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined');
}
public static function dialogNormalization($index, $version) {
switch ($version) {
case 7:
return 0 - $index;
break;
case 6:
return 0 - 16 - $index;
break;
}
return false;
}
}

View File

@ -0,0 +1,453 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.flac.php //
// module for analyzing FLAC and OggFLAC audio files //
// dependencies: module.audio.ogg.php //
// ///
/////////////////////////////////////////////////////////////////
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
/**
* @tutorial http://flac.sourceforge.net/format.html
*/
class getid3_flac extends getid3_handler
{
const syncword = 'fLaC';
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$StreamMarker = $this->fread(4);
if ($StreamMarker != self::syncword) {
return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
}
$info['fileformat'] = 'flac';
$info['audio']['dataformat'] = 'flac';
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['lossless'] = true;
// parse flac container
return $this->parseMETAdata();
}
public function parseMETAdata() {
$info = &$this->getid3->info;
do {
$BlockOffset = $this->ftell();
$BlockHeader = $this->fread(4);
$LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1));
$LastBlockFlag = (bool) ($LBFBT & 0x80);
$BlockType = ($LBFBT & 0x7F);
$BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
$BlockTypeText = self::metaBlockTypeLookup($BlockType);
if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
$this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
break;
}
if ($BlockLength < 1) {
$this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
break;
}
$info['flac'][$BlockTypeText]['raw'] = array();
$BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
$BlockTypeText_raw['offset'] = $BlockOffset;
$BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
$BlockTypeText_raw['block_type'] = $BlockType;
$BlockTypeText_raw['block_type_text'] = $BlockTypeText;
$BlockTypeText_raw['block_length'] = $BlockLength;
if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
$BlockTypeText_raw['block_data'] = $this->fread($BlockLength);
}
switch ($BlockTypeText) {
case 'STREAMINFO': // 0x00
if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'PADDING': // 0x01
unset($info['flac']['PADDING']); // ignore
break;
case 'APPLICATION': // 0x02
if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'SEEKTABLE': // 0x03
if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'VORBIS_COMMENT': // 0x04
if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'CUESHEET': // 0x05
if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'PICTURE': // 0x06
if (!$this->parsePICTURE()) {
return false;
}
break;
default:
$this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
}
unset($info['flac'][$BlockTypeText]['raw']);
$info['avdataoffset'] = $this->ftell();
}
while ($LastBlockFlag === false);
// handle tags
if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
$info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
}
if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
$info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
}
// copy attachments to 'comments' array if nesesary
if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
foreach ($info['flac']['PICTURE'] as $entry) {
if (!empty($entry['data'])) {
if (!isset($info['flac']['comments']['picture'])) {
$info['flac']['comments']['picture'] = array();
}
$comments_picture_data = array();
foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
if (isset($entry[$picture_key])) {
$comments_picture_data[$picture_key] = $entry[$picture_key];
}
}
$info['flac']['comments']['picture'][] = $comments_picture_data;
unset($comments_picture_data);
}
}
}
if (isset($info['flac']['STREAMINFO'])) {
if (!$this->isDependencyFor('matroska')) {
$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
}
$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
if ($info['flac']['uncompressed_audio_bytes'] == 0) {
return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
}
if (!empty($info['flac']['compressed_audio_bytes'])) {
$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
}
}
// set md5_data_source - built into flac 0.5+
if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
$this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
}
else {
$info['md5_data_source'] = '';
$md5 = $info['flac']['STREAMINFO']['audio_signature'];
for ($i = 0; $i < strlen($md5); $i++) {
$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
}
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
unset($info['md5_data_source']);
}
}
}
if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
if ($info['audio']['bits_per_sample'] == 8) {
// special case
// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
$this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
}
}
return true;
}
private function parseSTREAMINFO($BlockData) {
$info = &$this->getid3->info;
$info['flac']['STREAMINFO'] = array();
$streaminfo = &$info['flac']['STREAMINFO'];
$streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
$streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
$streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
$streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));
$SRCSBSS = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
$streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20));
$streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1;
$streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1;
$streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));
$streaminfo['audio_signature'] = substr($BlockData, 18, 16);
if (!empty($streaminfo['sample_rate'])) {
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['sample_rate'] = $streaminfo['sample_rate'];
$info['audio']['channels'] = $streaminfo['channels'];
$info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample'];
$info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate'];
if ($info['playtime_seconds'] > 0) {
if (!$this->isDependencyFor('matroska')) {
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
else {
$this->warning('Cannot determine audio bitrate because total stream size is unknown');
}
}
} else {
return $this->error('Corrupt METAdata block: STREAMINFO');
}
return true;
}
private function parseAPPLICATION($BlockData) {
$info = &$this->getid3->info;
$ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
$info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
return true;
}
private function parseSEEKTABLE($BlockData) {
$info = &$this->getid3->info;
$offset = 0;
$BlockLength = strlen($BlockData);
$placeholderpattern = str_repeat("\xFF", 8);
while ($offset < $BlockLength) {
$SampleNumberString = substr($BlockData, $offset, 8);
$offset += 8;
if ($SampleNumberString == $placeholderpattern) {
// placeholder point
getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
$offset += 10;
} else {
$SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString);
$info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
$offset += 2;
}
}
return true;
}
private function parseVORBIS_COMMENT($BlockData) {
$info = &$this->getid3->info;
$getid3_ogg = new getid3_ogg($this->getid3);
if ($this->isDependencyFor('matroska')) {
$getid3_ogg->setStringMode($this->data_string);
}
$getid3_ogg->ParseVorbisComments();
if (isset($info['ogg'])) {
unset($info['ogg']['comments_raw']);
$info['flac']['VORBIS_COMMENT'] = $info['ogg'];
unset($info['ogg']);
}
unset($getid3_ogg);
return true;
}
private function parseCUESHEET($BlockData) {
$info = &$this->getid3->info;
$offset = 0;
$info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0");
$offset += 128;
$info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
$offset += 1;
$offset += 258; // reserved
$info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$TrackNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset;
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12);
$offset += 12;
$TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80);
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
$offset += 13; // reserved
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$IndexNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
$offset += 3; // reserved
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
}
}
return true;
}
/**
* Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
* External usage: audio.ogg
*/
public function parsePICTURE() {
$info = &$this->getid3->info;
$picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['picturetype'] = self::pictureTypeLookup($picture['typeid']);
$picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
$descr_length = getid3_lib::BigEndian2Int($this->fread(4));
if ($descr_length) {
$picture['description'] = $this->fread($descr_length);
}
$picture['image_width'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['image_height'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['datalength'] = getid3_lib::BigEndian2Int($this->fread(4));
if ($picture['image_mime'] == '-->') {
$picture['data'] = $this->fread($picture['datalength']);
} else {
$picture['data'] = $this->saveAttachment(
str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(),
$this->ftell(),
$picture['datalength'],
$picture['image_mime']);
}
$info['flac']['PICTURE'][] = $picture;
return true;
}
public static function metaBlockTypeLookup($blocktype) {
static $lookup = array(
0 => 'STREAMINFO',
1 => 'PADDING',
2 => 'APPLICATION',
3 => 'SEEKTABLE',
4 => 'VORBIS_COMMENT',
5 => 'CUESHEET',
6 => 'PICTURE',
);
return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
}
public static function applicationIDLookup($applicationid) {
// http://flac.sourceforge.net/id.html
static $lookup = array(
0x41544348 => 'FlacFile', // "ATCH"
0x42534F4C => 'beSolo', // "BSOL"
0x42554753 => 'Bugs Player', // "BUGS"
0x43756573 => 'GoldWave cue points (specification)', // "Cues"
0x46696361 => 'CUE Splitter', // "Fica"
0x46746F6C => 'flac-tools', // "Ftol"
0x4D4F5442 => 'MOTB MetaCzar', // "MOTB"
0x4D505345 => 'MP3 Stream Editor', // "MPSE"
0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML"
0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF"
0x5346464C => 'Sound Font FLAC', // "SFFL"
0x534F4E59 => 'Sony Creative Software', // "SONY"
0x5351455A => 'flacsqueeze', // "SQEZ"
0x54745776 => 'TwistedWave', // "TtWv"
0x55495453 => 'UITS Embedding tools', // "UITS"
0x61696666 => 'FLAC AIFF chunk storage', // "aiff"
0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag"
0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem"
0x71667374 => 'QFLAC Studio', // "qfst"
0x72696666 => 'FLAC RIFF chunk storage', // "riff"
0x74756E65 => 'TagTuner', // "tune"
0x78626174 => 'XBAT', // "xbat"
0x786D6364 => 'xmcd', // "xmcd"
);
return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
}
public static function pictureTypeLookup($type_id) {
static $lookup = array (
0 => 'Other',
1 => '32x32 pixels \'file icon\' (PNG only)',
2 => 'Other file icon',
3 => 'Cover (front)',
4 => 'Cover (back)',
5 => 'Leaflet page',
6 => 'Media (e.g. label side of CD)',
7 => 'Lead artist/lead performer/soloist',
8 => 'Artist/performer',
9 => 'Conductor',
10 => 'Band/Orchestra',
11 => 'Composer',
12 => 'Lyricist/text writer',
13 => 'Recording Location',
14 => 'During recording',
15 => 'During performance',
16 => 'Movie/video screen capture',
17 => 'A bright coloured fish',
18 => 'Illustration',
19 => 'Band/artist logotype',
20 => 'Publisher/Studio logotype',
);
return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,840 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.ogg.php //
// module for analyzing Ogg Vorbis, OggFLAC and Speex files //
// dependencies: module.audio.flac.php //
// ///
/////////////////////////////////////////////////////////////////
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
class getid3_ogg extends getid3_handler
{
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'ogg';
// Warn about illegal tags - only vorbiscomments are allowed
if (isset($info['id3v2'])) {
$this->warning('Illegal ID3v2 tag present.');
}
if (isset($info['id3v1'])) {
$this->warning('Illegal ID3v1 tag present.');
}
if (isset($info['ape'])) {
$this->warning('Illegal APE tag present.');
}
// Page 1 - Stream Header
$this->fseek($info['avdataoffset']);
$oggpageinfo = $this->ParseOggPageHeader();
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
$this->error('Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)');
unset($info['fileformat']);
unset($info['ogg']);
return false;
}
$filedata = $this->fread($oggpageinfo['page_length']);
$filedataoffset = 0;
if (substr($filedata, 0, 4) == 'fLaC') {
$info['audio']['dataformat'] = 'flac';
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['lossless'] = true;
} elseif (substr($filedata, 1, 6) == 'vorbis') {
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
} elseif (substr($filedata, 0, 8) == 'OpusHead') {
if( $this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) == false ) {
return false;
}
} elseif (substr($filedata, 0, 8) == 'Speex ') {
// http://www.speex.org/manual/node10.html
$info['audio']['dataformat'] = 'speex';
$info['mime_type'] = 'audio/speex';
$info['audio']['bitrate_mode'] = 'abr';
$info['audio']['lossless'] = false;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex '
$filedataoffset += 8;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20);
$filedataoffset += 20;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
$info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
$info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
$info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
$info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
$info['audio']['sample_rate'] = $info['speex']['sample_rate'];
$info['audio']['channels'] = $info['speex']['channels'];
if ($info['speex']['vbr']) {
$info['audio']['bitrate_mode'] = 'vbr';
}
} elseif (substr($filedata, 0, 7) == "\x80".'theora') {
// http://www.theora.org/doc/Theora.pdf (section 6.2)
$info['ogg']['pageheader']['theora']['theora_magic'] = substr($filedata, $filedataoffset, 7); // hard-coded to "\x80.'theora'
$filedataoffset += 7;
$info['ogg']['pageheader']['theora']['version_major'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['pageheader']['theora']['version_minor'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['pageheader']['theora']['version_revision'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['pageheader']['theora']['frame_width_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2));
$filedataoffset += 2;
$info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2));
$filedataoffset += 2;
$info['ogg']['pageheader']['theora']['resolution_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
$filedataoffset += 3;
$info['ogg']['pageheader']['theora']['resolution_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
$filedataoffset += 3;
$info['ogg']['pageheader']['theora']['picture_offset_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['pageheader']['theora']['picture_offset_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['pageheader']['theora']['frame_rate_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader']['theora']['frame_rate_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
$filedataoffset += 3;
$info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
$filedataoffset += 3;
$info['ogg']['pageheader']['theora']['color_space_id'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['pageheader']['theora']['nominal_bitrate'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
$filedataoffset += 3;
$info['ogg']['pageheader']['theora']['flags'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2));
$filedataoffset += 2;
$info['ogg']['pageheader']['theora']['quality'] = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10;
$info['ogg']['pageheader']['theora']['kfg_shift'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >> 5;
$info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >> 3;
$info['ogg']['pageheader']['theora']['reserved'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >> 0; // should be 0
$info['ogg']['pageheader']['theora']['color_space'] = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']);
$info['ogg']['pageheader']['theora']['pixel_format'] = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']);
$info['video']['dataformat'] = 'theora';
$info['mime_type'] = 'video/ogg';
//$info['audio']['bitrate_mode'] = 'abr';
//$info['audio']['lossless'] = false;
$info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x'];
$info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y'];
if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) {
$info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator'];
}
if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) {
$info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'];
}
$this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable');
} elseif (substr($filedata, 0, 8) == "fishead\x00") {
// Ogg Skeleton version 3.0 Format Specification
// http://xiph.org/ogg/doc/skeleton.html
$filedataoffset += 8;
$info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
$filedataoffset += 2;
$info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
$filedataoffset += 2;
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
$filedataoffset += 20;
$info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
$info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
$info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
$counter = 0;
do {
$oggpageinfo = $this->ParseOggPageHeader();
$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
$filedata = $this->fread($oggpageinfo['page_length']);
$this->fseek($oggpageinfo['page_end_offset']);
if (substr($filedata, 0, 8) == "fisbone\x00") {
$filedataoffset = 8;
$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3);
$filedataoffset += 3;
} elseif (substr($filedata, 1, 6) == 'theora') {
$info['video']['dataformat'] = 'theora1';
$this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']');
//break;
} elseif (substr($filedata, 1, 6) == 'vorbis') {
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
} else {
$this->error('unexpected');
//break;
}
//} while ($oggpageinfo['page_seqno'] == 0);
} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
$this->fseek($oggpageinfo['page_start_offset']);
$this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']');
//return false;
} else {
$this->error('Expecting either "Speex ", "OpusHead" or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"');
unset($info['ogg']);
unset($info['mime_type']);
return false;
}
// Page 2 - Comment Header
$oggpageinfo = $this->ParseOggPageHeader();
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
switch ($info['audio']['dataformat']) {
case 'vorbis':
$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis'
$this->ParseVorbisComments();
break;
case 'flac':
$flac = new getid3_flac($this->getid3);
if (!$flac->parseMETAdata()) {
$this->error('Failed to parse FLAC headers');
return false;
}
unset($flac);
break;
case 'speex':
$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
$this->ParseVorbisComments();
break;
case 'opus':
$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags'
if(substr($filedata, 0, 8) != 'OpusTags') {
$this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"');
return false;
}
$this->ParseVorbisComments();
break;
}
// Last Page - Number of Samples
if (!getid3_lib::intValueSupported($info['avdataend'])) {
$this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)');
} else {
$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
$info['avdataend'] = $this->ftell();
$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
$info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
if ($info['ogg']['samples'] == 0) {
$this->error('Corrupt Ogg file: eos.number of samples == zero');
return false;
}
if (!empty($info['audio']['sample_rate'])) {
$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
}
}
}
if (!empty($info['ogg']['bitrate_average'])) {
$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
} elseif (!empty($info['ogg']['bitrate_nominal'])) {
$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
}
if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
if ($info['audio']['bitrate'] == 0) {
$this->error('Corrupt Ogg file: bitrate_audio == zero');
return false;
}
$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
}
if (isset($info['ogg']['vendor'])) {
$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
// Vorbis only
if ($info['audio']['dataformat'] == 'vorbis') {
// Vorbis 1.0 starts with Xiph.Org
if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
if ($info['audio']['bitrate_mode'] == 'abr') {
// Set -b 128 on abr files
$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
// Set -q N on vbr files
$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
}
}
if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
}
}
}
return true;
}
public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
$info = &$this->getid3->info;
$info['audio']['dataformat'] = 'vorbis';
$info['audio']['lossless'] = false;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
$filedataoffset += 6;
$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$info['audio']['channels'] = $info['ogg']['numberofchannels'];
$info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
if ($info['ogg']['samplerate'] == 0) {
$this->error('Corrupt Ogg file: sample rate == zero');
return false;
}
$info['audio']['sample_rate'] = $info['ogg']['samplerate'];
$info['ogg']['samples'] = 0; // filled in later
$info['ogg']['bitrate_average'] = 0; // filled in later
$info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
$info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
$info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
unset($info['ogg']['bitrate_max']);
$info['audio']['bitrate_mode'] = 'abr';
}
if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
unset($info['ogg']['bitrate_nominal']);
}
if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
unset($info['ogg']['bitrate_min']);
$info['audio']['bitrate_mode'] = 'abr';
}
return true;
}
// http://tools.ietf.org/html/draft-ietf-codec-oggopus-03
public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
$info = &$this->getid3->info;
$info['audio']['dataformat'] = 'opus';
$info['mime_type'] = 'audio/ogg; codecs=opus';
/** @todo find a usable way to detect abr (vbr that is padded to be abr) */
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['lossless'] = false;
$info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead'
$filedataoffset += 8;
$info['ogg']['pageheader']['opus']['version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) {
$this->error('Unknown opus version number (only accepting 1-15)');
return false;
}
$info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) {
$this->error('Invalid channel count in opus header (must not be zero)');
return false;
}
$info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
$filedataoffset += 2;
$info['ogg']['pageheader']['opus']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
//$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
//$filedataoffset += 2;
//$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
//$filedataoffset += 1;
$info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version'];
$info['opus']['sample_rate'] = $info['ogg']['pageheader']['opus']['sample_rate'];
$info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count'];
$info['audio']['channels'] = $info['opus']['out_channel_count'];
$info['audio']['sample_rate'] = $info['opus']['sample_rate'];
return true;
}
public function ParseOggPageHeader() {
// http://xiph.org/ogg/vorbis/doc/framing.html
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
$filedata = $this->fread($this->getid3->fread_buffer_size());
$filedataoffset = 0;
while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
// should be found before here
return false;
}
if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
// get some more data, unless eof, in which case fail
return false;
}
}
}
$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
$oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
$oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
$oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
$oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
$filedataoffset += 8;
$oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$filedataoffset += 4;
$oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$oggheader['page_length'] = 0;
for ($i = 0; $i < $oggheader['page_segments']; $i++) {
$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
$filedataoffset += 1;
$oggheader['page_length'] += $oggheader['segment_table'][$i];
}
$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
$oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
$this->fseek($oggheader['header_end_offset']);
return $oggheader;
}
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
public function ParseVorbisComments() {
$info = &$this->getid3->info;
$OriginalOffset = $this->ftell();
$commentdataoffset = 0;
$VorbisCommentPage = 1;
switch ($info['audio']['dataformat']) {
case 'vorbis':
case 'speex':
case 'opus':
$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
$this->fseek($CommentStartOffset);
$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
if ($info['audio']['dataformat'] == 'vorbis') {
$commentdataoffset += (strlen('vorbis') + 1);
}
else if ($info['audio']['dataformat'] == 'opus') {
$commentdataoffset += strlen('OpusTags');
}
break;
case 'flac':
$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
$this->fseek($CommentStartOffset);
$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
break;
default:
return false;
break;
}
$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
$commentdataoffset += 4;
$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
$commentdataoffset += $VendorSize;
$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
$commentdataoffset += 4;
$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
for ($i = 0; $i < $CommentsCount; $i++) {
if ($i >= 10000) {
// https://github.com/owncloud/music/issues/212#issuecomment-43082336
$this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments');
break;
}
$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
if ($oggpageinfo = $this->ParseOggPageHeader()) {
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
$VorbisCommentPage++;
// First, save what we haven't read yet
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
// Then take that data off the end
$commentdata = substr($commentdata, 0, $commentdataoffset);
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
// Finally, stick the unused data back on the end
$commentdata .= $AsYetUnusedData;
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
}
}
$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
// replace avdataoffset with position just after the last vorbiscomment
$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
$commentdataoffset += 4;
while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
$this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments');
break 2;
}
$VorbisCommentPage++;
$oggpageinfo = $this->ParseOggPageHeader();
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
// First, save what we haven't read yet
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
// Then take that data off the end
$commentdata = substr($commentdata, 0, $commentdataoffset);
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
// Finally, stick the unused data back on the end
$commentdata .= $AsYetUnusedData;
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
$this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
break;
}
$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
if ($readlength <= 0) {
$this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
break;
}
$commentdata .= $this->fread($readlength);
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
}
$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
if (!$commentstring) {
// no comment?
$this->warning('Blank Ogg comment ['.$i.']');
} elseif (strstr($commentstring, '=')) {
$commentexploded = explode('=', $commentstring, 2);
$ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
// The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.
// http://flac.sourceforge.net/format.html#metadata_block_picture
$flac = new getid3_flac($this->getid3);
$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
$flac->parsePICTURE();
$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
unset($flac);
} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
/** @todo use 'coverartmime' where available */
$imageinfo = getid3_lib::GetDataImageSize($data);
if ($imageinfo === false || !isset($imageinfo['mime'])) {
$this->warning('COVERART vorbiscomment tag contains invalid image');
continue;
}
$ogg = new self($this->getid3);
$ogg->setStringMode($data);
$info['ogg']['comments']['picture'][] = array(
'image_mime' => $imageinfo['mime'],
'datalength' => strlen($data),
'picturetype' => 'cover art',
'image_height' => $imageinfo['height'],
'image_width' => $imageinfo['width'],
'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
);
unset($ogg);
} else {
$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
}
} else {
$this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring);
}
unset($ThisFileInfo_ogg_comments_raw[$i]);
}
unset($ThisFileInfo_ogg_comments_raw);
// Replay Gain Adjustment
// http://privatewww.essex.ac.uk/~djmrob/replaygain/
if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
foreach ($info['ogg']['comments'] as $index => $commentvalue) {
switch ($index) {
case 'rg_audiophile':
case 'replaygain_album_gain':
$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
unset($info['ogg']['comments'][$index]);
break;
case 'rg_radio':
case 'replaygain_track_gain':
$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
unset($info['ogg']['comments'][$index]);
break;
case 'replaygain_album_peak':
$info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
unset($info['ogg']['comments'][$index]);
break;
case 'rg_peak':
case 'replaygain_track_peak':
$info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
unset($info['ogg']['comments'][$index]);
break;
case 'replaygain_reference_loudness':
$info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
unset($info['ogg']['comments'][$index]);
break;
default:
// do nothing
break;
}
}
}
$this->fseek($OriginalOffset);
return true;
}
public static function SpeexBandModeLookup($mode) {
static $SpeexBandModeLookup = array();
if (empty($SpeexBandModeLookup)) {
$SpeexBandModeLookup[0] = 'narrow';
$SpeexBandModeLookup[1] = 'wide';
$SpeexBandModeLookup[2] = 'ultra-wide';
}
return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
}
public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
for ($i = 0; $i < $SegmentNumber; $i++) {
$segmentlength = 0;
foreach ($OggInfoArray['segment_table'] as $key => $value) {
$segmentlength += $value;
if ($value < 255) {
break;
}
}
}
return $segmentlength;
}
public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
// decrease precision
$nominal_bitrate = $nominal_bitrate / 1000;
if ($nominal_bitrate < 128) {
// q-1 to q4
$qval = ($nominal_bitrate - 64) / 16;
} elseif ($nominal_bitrate < 256) {
// q4 to q8
$qval = $nominal_bitrate / 32;
} elseif ($nominal_bitrate < 320) {
// q8 to q9
$qval = ($nominal_bitrate + 256) / 64;
} else {
// q9 to q10
$qval = ($nominal_bitrate + 1300) / 180;
}
//return $qval; // 5.031324
//return intval($qval); // 5
return round($qval, 1); // 5 or 4.9
}
public static function TheoraColorSpace($colorspace_id) {
// http://www.theora.org/doc/Theora.pdf (table 6.3)
static $TheoraColorSpaceLookup = array();
if (empty($TheoraColorSpaceLookup)) {
$TheoraColorSpaceLookup[0] = 'Undefined';
$TheoraColorSpaceLookup[1] = 'Rec. 470M';
$TheoraColorSpaceLookup[2] = 'Rec. 470BG';
$TheoraColorSpaceLookup[3] = 'Reserved';
}
return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null);
}
public static function TheoraPixelFormat($pixelformat_id) {
// http://www.theora.org/doc/Theora.pdf (table 6.4)
static $TheoraPixelFormatLookup = array();
if (empty($TheoraPixelFormatLookup)) {
$TheoraPixelFormatLookup[0] = '4:2:0';
$TheoraPixelFormatLookup[1] = 'Reserved';
$TheoraPixelFormatLookup[2] = '4:2:2';
$TheoraPixelFormatLookup[3] = '4:4:4';
}
return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null);
}
}

View File

@ -0,0 +1,416 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.tag.apetag.php //
// module for analyzing APE tags //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
class getid3_apetag extends getid3_handler
{
public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
public $overrideendoffset = 0;
public function Analyze() {
$info = &$this->getid3->info;
if (!getid3_lib::intValueSupported($info['filesize'])) {
$this->warning('Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$id3v1tagsize = 128;
$apetagheadersize = 32;
$lyrics3tagsize = 10;
if ($this->overrideendoffset == 0) {
$this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
$APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
//if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
// APE tag found before ID3v1
$info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
//} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
} elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
// APE tag found, no ID3v1
$info['ape']['tag_offset_end'] = $info['filesize'];
}
} else {
$this->fseek($this->overrideendoffset - $apetagheadersize);
if ($this->fread(8) == 'APETAGEX') {
$info['ape']['tag_offset_end'] = $this->overrideendoffset;
}
}
if (!isset($info['ape']['tag_offset_end'])) {
// APE tag not found
unset($info['ape']);
return false;
}
// shortcut
$thisfile_ape = &$info['ape'];
$this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
$APEfooterData = $this->fread(32);
if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
$this->error('Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']);
return false;
}
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
$this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
$thisfile_ape['tag_offset_start'] = $this->ftell();
$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
} else {
$thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
$this->fseek($thisfile_ape['tag_offset_start']);
$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
}
$info['avdataend'] = $thisfile_ape['tag_offset_start'];
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
$this->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data');
unset($info['id3v1']);
foreach ($info['warning'] as $key => $value) {
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
unset($info['warning'][$key]);
sort($info['warning']);
break;
}
}
}
$offset = 0;
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
$offset += $apetagheadersize;
} else {
$this->error('Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']);
return false;
}
}
// shortcut
$info['replay_gain'] = array();
$thisfile_replaygain = &$info['replay_gain'];
for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
$offset += 4;
$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
$offset += 4;
if (strstr(substr($APEtagData, $offset), "\x00") === false) {
$this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset));
return false;
}
$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
$item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
// shortcut
$thisfile_ape['items'][$item_key] = array();
$thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
$thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
$offset += ($ItemKeyLength + 1); // skip 0x00 terminator
$thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
$offset += $value_size;
$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
case 0: // UTF-8
case 2: // Locator (URL, filename, etc), UTF-8 encoded
$thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']);
break;
case 1: // binary data
default:
break;
}
switch (strtolower($item_key)) {
// http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain
case 'replaygain_track_gain':
if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
$thisfile_replaygain['track']['originator'] = 'unspecified';
} else {
$this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'replaygain_track_peak':
if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
$thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
$thisfile_replaygain['track']['originator'] = 'unspecified';
if ($thisfile_replaygain['track']['peak'] <= 0) {
$this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
}
} else {
$this->warning('MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'replaygain_album_gain':
if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
$thisfile_replaygain['album']['originator'] = 'unspecified';
} else {
$this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'replaygain_album_peak':
if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) {
$thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
$thisfile_replaygain['album']['originator'] = 'unspecified';
if ($thisfile_replaygain['album']['peak'] <= 0) {
$this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
}
} else {
$this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'mp3gain_undo':
if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) {
list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
$thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
$thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
} else {
$this->warning('MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'mp3gain_minmax':
if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
} else {
$this->warning('MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'mp3gain_album_minmax':
if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
} else {
$this->warning('MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'tracknumber':
if (is_array($thisfile_ape_items_current['data'])) {
foreach ($thisfile_ape_items_current['data'] as $comment) {
$thisfile_ape['comments']['track'][] = $comment;
}
}
break;
case 'cover art (artist)':
case 'cover art (back)':
case 'cover art (band logo)':
case 'cover art (band)':
case 'cover art (colored fish)':
case 'cover art (composer)':
case 'cover art (conductor)':
case 'cover art (front)':
case 'cover art (icon)':
case 'cover art (illustration)':
case 'cover art (lead)':
case 'cover art (leaflet)':
case 'cover art (lyricist)':
case 'cover art (media)':
case 'cover art (movie scene)':
case 'cover art (other icon)':
case 'cover art (other)':
case 'cover art (performance)':
case 'cover art (publisher logo)':
case 'cover art (recording)':
case 'cover art (studio)':
// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
if (is_array($thisfile_ape_items_current['data'])) {
$this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
$thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
}
list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
do {
$thisfile_ape_items_current['image_mime'] = '';
$imageinfo = array();
$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) {
$this->warning('APEtag "'.$item_key.'" contains invalid image data');
break;
}
$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
if ($this->inline_attachments === false) {
// skip entirely
unset($thisfile_ape_items_current['data']);
break;
}
if ($this->inline_attachments === true) {
// great
} elseif (is_int($this->inline_attachments)) {
if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
// too big, skip
$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)');
unset($thisfile_ape_items_current['data']);
break;
}
} elseif (is_string($this->inline_attachments)) {
$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
if (!is_dir($this->inline_attachments) || !getID3::is_writable($this->inline_attachments)) {
// cannot write, skip
$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)');
unset($thisfile_ape_items_current['data']);
break;
}
}
// if we get this far, must be OK
if (is_string($this->inline_attachments)) {
$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
} else {
$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)');
}
$thisfile_ape_items_current['data_filename'] = $destination_filename;
unset($thisfile_ape_items_current['data']);
} else {
if (!isset($info['ape']['comments']['picture'])) {
$info['ape']['comments']['picture'] = array();
}
$comments_picture_data = array();
foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
if (isset($thisfile_ape_items_current[$picture_key])) {
$comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key];
}
}
$info['ape']['comments']['picture'][] = $comments_picture_data;
unset($comments_picture_data);
}
} while (false);
break;
default:
if (is_array($thisfile_ape_items_current['data'])) {
foreach ($thisfile_ape_items_current['data'] as $comment) {
$thisfile_ape['comments'][strtolower($item_key)][] = $comment;
}
}
break;
}
}
if (empty($thisfile_replaygain)) {
unset($info['replay_gain']);
}
return true;
}
public function parseAPEheaderFooter($APEheaderFooterData) {
// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
// shortcut
$headerfooterinfo['raw'] = array();
$headerfooterinfo_raw = &$headerfooterinfo['raw'];
$headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8);
if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
return false;
}
$headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4));
$headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
$headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
$headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
$headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8);
$headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000;
if ($headerfooterinfo['tag_version'] >= 2) {
$headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
}
return $headerfooterinfo;
}
public function parseAPEtagFlags($rawflagint) {
// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
// All are set to zero on creation and ignored on reading."
// http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
$flags['header'] = (bool) ($rawflagint & 0x80000000);
$flags['footer'] = (bool) ($rawflagint & 0x40000000);
$flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
$flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1;
$flags['read_only'] = (bool) ($rawflagint & 0x00000001);
$flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
return $flags;
}
public function APEcontentTypeFlagLookup($contenttypeid) {
static $APEcontentTypeFlagLookup = array(
0 => 'utf-8',
1 => 'binary',
2 => 'external',
3 => 'reserved'
);
return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
}
public function APEtagItemIsUTF8Lookup($itemkey) {
static $APEtagItemIsUTF8Lookup = array(
'title',
'subtitle',
'artist',
'album',
'debut album',
'publisher',
'conductor',
'track',
'composer',
'comment',
'copyright',
'publicationright',
'file',
'year',
'record date',
'record location',
'genre',
'media',
'related',
'isrc',
'abstract',
'language',
'bibliography'
);
return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
}
}

View File

@ -0,0 +1,381 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.tag.id3v1.php //
// module for analyzing ID3v1 tags //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
class getid3_id3v1 extends getid3_handler
{
public function Analyze() {
$info = &$this->getid3->info;
if (!getid3_lib::intValueSupported($info['filesize'])) {
$this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$this->fseek(-256, SEEK_END);
$preid3v1 = $this->fread(128);
$id3v1tag = $this->fread(128);
if (substr($id3v1tag, 0, 3) == 'TAG') {
$info['avdataend'] = $info['filesize'] - 128;
$ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
$ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
$ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
$ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
$ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them
$ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
// If second-last byte of comment field is null and last byte of comment field is non-null
// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
$ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1));
$ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
}
$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
if (!empty($ParsedID3v1['genre'])) {
unset($ParsedID3v1['genreid']);
}
if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
unset($ParsedID3v1['genre']);
}
foreach ($ParsedID3v1 as $key => $value) {
$ParsedID3v1['comments'][$key][0] = $value;
}
// ID3v1 encoding detection hack START
// ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
// Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
$ID3v1encoding = 'ISO-8859-1';
foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
foreach ($valuearray as $key => $value) {
if (preg_match('#^[\\x00-\\x40\\xA8\\B8\\x80-\\xFF]+$#', $value)) {
foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
$ID3v1encoding = $id3v1_bad_encoding;
break 3;
} elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
$ID3v1encoding = $id3v1_bad_encoding;
break 3;
}
}
}
}
}
// ID3v1 encoding detection hack END
// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
$ParsedID3v1['title'],
$ParsedID3v1['artist'],
$ParsedID3v1['album'],
$ParsedID3v1['year'],
(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
$ParsedID3v1['comment'],
(!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
$ParsedID3v1['padding_valid'] = true;
if ($id3v1tag !== $GoodFormatID3v1tag) {
$ParsedID3v1['padding_valid'] = false;
$this->warning('Some ID3v1 fields do not use NULL characters for padding');
}
$ParsedID3v1['tag_offset_end'] = $info['filesize'];
$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
$info['id3v1'] = $ParsedID3v1;
$info['id3v1']['encoding'] = $ID3v1encoding;
}
if (substr($preid3v1, 0, 3) == 'TAG') {
// The way iTunes handles tags is, well, brain-damaged.
// It completely ignores v1 if ID3v2 is present.
// This goes as far as adding a new v1 tag *even if there already is one*
// A suspected double-ID3v1 tag has been detected, but it could be that
// the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
if (substr($preid3v1, 96, 8) == 'APETAGEX') {
// an APE tag footer was found before the last ID3v1, assume false "TAG" synch
} elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
// a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
} else {
// APE and Lyrics3 footers not found - assume double ID3v1
$this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes');
$info['avdataend'] -= 128;
}
}
return true;
}
public static function cutfield($str) {
return trim(substr($str, 0, strcspn($str, "\x00")));
}
public static function ArrayOfGenres($allowSCMPXextended=false) {
static $GenreLookup = array(
0 => 'Blues',
1 => 'Classic Rock',
2 => 'Country',
3 => 'Dance',
4 => 'Disco',
5 => 'Funk',
6 => 'Grunge',
7 => 'Hip-Hop',
8 => 'Jazz',
9 => 'Metal',
10 => 'New Age',
11 => 'Oldies',
12 => 'Other',
13 => 'Pop',
14 => 'R&B',
15 => 'Rap',
16 => 'Reggae',
17 => 'Rock',
18 => 'Techno',
19 => 'Industrial',
20 => 'Alternative',
21 => 'Ska',
22 => 'Death Metal',
23 => 'Pranks',
24 => 'Soundtrack',
25 => 'Euro-Techno',
26 => 'Ambient',
27 => 'Trip-Hop',
28 => 'Vocal',
29 => 'Jazz+Funk',
30 => 'Fusion',
31 => 'Trance',
32 => 'Classical',
33 => 'Instrumental',
34 => 'Acid',
35 => 'House',
36 => 'Game',
37 => 'Sound Clip',
38 => 'Gospel',
39 => 'Noise',
40 => 'Alt. Rock',
41 => 'Bass',
42 => 'Soul',
43 => 'Punk',
44 => 'Space',
45 => 'Meditative',
46 => 'Instrumental Pop',
47 => 'Instrumental Rock',
48 => 'Ethnic',
49 => 'Gothic',
50 => 'Darkwave',
51 => 'Techno-Industrial',
52 => 'Electronic',
53 => 'Pop-Folk',
54 => 'Eurodance',
55 => 'Dream',
56 => 'Southern Rock',
57 => 'Comedy',
58 => 'Cult',
59 => 'Gangsta Rap',
60 => 'Top 40',
61 => 'Christian Rap',
62 => 'Pop/Funk',
63 => 'Jungle',
64 => 'Native American',
65 => 'Cabaret',
66 => 'New Wave',
67 => 'Psychedelic',
68 => 'Rave',
69 => 'Showtunes',
70 => 'Trailer',
71 => 'Lo-Fi',
72 => 'Tribal',
73 => 'Acid Punk',
74 => 'Acid Jazz',
75 => 'Polka',
76 => 'Retro',
77 => 'Musical',
78 => 'Rock & Roll',
79 => 'Hard Rock',
80 => 'Folk',
81 => 'Folk/Rock',
82 => 'National Folk',
83 => 'Swing',
84 => 'Fast-Fusion',
85 => 'Bebob',
86 => 'Latin',
87 => 'Revival',
88 => 'Celtic',
89 => 'Bluegrass',
90 => 'Avantgarde',
91 => 'Gothic Rock',
92 => 'Progressive Rock',
93 => 'Psychedelic Rock',
94 => 'Symphonic Rock',
95 => 'Slow Rock',
96 => 'Big Band',
97 => 'Chorus',
98 => 'Easy Listening',
99 => 'Acoustic',
100 => 'Humour',
101 => 'Speech',
102 => 'Chanson',
103 => 'Opera',
104 => 'Chamber Music',
105 => 'Sonata',
106 => 'Symphony',
107 => 'Booty Bass',
108 => 'Primus',
109 => 'Porn Groove',
110 => 'Satire',
111 => 'Slow Jam',
112 => 'Club',
113 => 'Tango',
114 => 'Samba',
115 => 'Folklore',
116 => 'Ballad',
117 => 'Power Ballad',
118 => 'Rhythmic Soul',
119 => 'Freestyle',
120 => 'Duet',
121 => 'Punk Rock',
122 => 'Drum Solo',
123 => 'A Cappella',
124 => 'Euro-House',
125 => 'Dance Hall',
126 => 'Goa',
127 => 'Drum & Bass',
128 => 'Club-House',
129 => 'Hardcore',
130 => 'Terror',
131 => 'Indie',
132 => 'BritPop',
133 => 'Negerpunk',
134 => 'Polsk Punk',
135 => 'Beat',
136 => 'Christian Gangsta Rap',
137 => 'Heavy Metal',
138 => 'Black Metal',
139 => 'Crossover',
140 => 'Contemporary Christian',
141 => 'Christian Rock',
142 => 'Merengue',
143 => 'Salsa',
144 => 'Thrash Metal',
145 => 'Anime',
146 => 'JPop',
147 => 'Synthpop',
255 => 'Unknown',
'CR' => 'Cover',
'RX' => 'Remix'
);
static $GenreLookupSCMPX = array();
if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
$GenreLookupSCMPX = $GenreLookup;
// http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
// Extended ID3v1 genres invented by SCMPX
// Note that 255 "Japanese Anime" conflicts with standard "Unknown"
$GenreLookupSCMPX[240] = 'Sacred';
$GenreLookupSCMPX[241] = 'Northern Europe';
$GenreLookupSCMPX[242] = 'Irish & Scottish';
$GenreLookupSCMPX[243] = 'Scotland';
$GenreLookupSCMPX[244] = 'Ethnic Europe';
$GenreLookupSCMPX[245] = 'Enka';
$GenreLookupSCMPX[246] = 'Children\'s Song';
$GenreLookupSCMPX[247] = 'Japanese Sky';
$GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
$GenreLookupSCMPX[249] = 'Japanese Doom Rock';
$GenreLookupSCMPX[250] = 'Japanese J-POP';
$GenreLookupSCMPX[251] = 'Japanese Seiyu';
$GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
$GenreLookupSCMPX[253] = 'Japanese Moemoe';
$GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
//$GenreLookupSCMPX[255] = 'Japanese Anime';
}
return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
}
public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
switch ($genreid) {
case 'RX':
case 'CR':
break;
default:
if (!is_numeric($genreid)) {
return false;
}
$genreid = intval($genreid); // to handle 3 or '3' or '03'
break;
}
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
}
public static function LookupGenreID($genre, $allowSCMPXextended=false) {
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
foreach ($GenreLookup as $key => $value) {
if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
return $key;
}
}
return false;
}
public static function StandardiseID3v1GenreName($OriginalGenre) {
if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
return self::LookupGenreName($GenreID);
}
return $OriginalGenre;
}
public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
$ID3v1Tag = 'TAG';
$ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
if (!empty($track) && ($track > 0) && ($track <= 255)) {
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= "\x00";
if (gettype($track) == 'string') {
$track = (int) $track;
}
$ID3v1Tag .= chr($track);
} else {
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
}
if (($genreid < 0) || ($genreid > 147)) {
$genreid = 255; // 'unknown' genre
}
switch (gettype($genreid)) {
case 'string':
case 'integer':
$ID3v1Tag .= chr(intval($genreid));
break;
default:
$ID3v1Tag .= chr(255); // 'unknown' genre
break;
}
return $ID3v1Tag;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,298 @@
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
/// //
// module.tag.lyrics3.php //
// module for analyzing Lyrics3 tags //
// dependencies: module.tag.apetag.php (optional) //
// ///
/////////////////////////////////////////////////////////////////
class getid3_lyrics3 extends getid3_handler
{
public function Analyze() {
$info = &$this->getid3->info;
// http://www.volweb.cz/str/tags.htm
if (!getid3_lib::intValueSupported($info['filesize'])) {
$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
$lyrics3_id3v1 = $this->fread(128 + 9 + 6);
$lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size
$lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200
$id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1
if ($lyrics3end == 'LYRICSEND') {
// Lyrics3v1, ID3v1, no APE
$lyrics3size = 5100;
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
$lyrics3version = 1;
} elseif ($lyrics3end == 'LYRICS200') {
// Lyrics3v2, ID3v1, no APE
// LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200');
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
$lyrics3version = 2;
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) {
// Lyrics3v1, no ID3v1, no APE
$lyrics3size = 5100;
$lyrics3offset = $info['filesize'] - $lyrics3size;
$lyrics3version = 1;
$lyrics3offset = $info['filesize'] - $lyrics3size;
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) {
// Lyrics3v2, no ID3v1, no APE
$lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
$lyrics3offset = $info['filesize'] - $lyrics3size;
$lyrics3version = 2;
} else {
if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) {
$this->fseek($info['ape']['tag_offset_start'] - 15);
$lyrics3lsz = $this->fread(6);
$lyrics3end = $this->fread(9);
if ($lyrics3end == 'LYRICSEND') {
// Lyrics3v1, APE, maybe ID3v1
$lyrics3size = 5100;
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
$info['avdataend'] = $lyrics3offset;
$lyrics3version = 1;
$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');
} elseif ($lyrics3end == 'LYRICS200') {
// Lyrics3v2, APE, maybe ID3v1
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
$lyrics3version = 2;
$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');
}
}
}
if (isset($lyrics3offset)) {
$info['avdataend'] = $lyrics3offset;
$this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size);
if (!isset($info['ape'])) {
if (isset($info['lyrics3']['tag_offset_start'])) {
$GETID3_ERRORARRAY = &$info['warning'];
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true);
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename);
$getid3_apetag = new getid3_apetag($getid3_temp);
$getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start'];
$getid3_apetag->Analyze();
if (!empty($getid3_temp->info['ape'])) {
$info['ape'] = $getid3_temp->info['ape'];
}
if (!empty($getid3_temp->info['replay_gain'])) {
$info['replay_gain'] = $getid3_temp->info['replay_gain'];
}
unset($getid3_temp, $getid3_apetag);
} else {
$this->warning('Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)');
}
}
}
return true;
}
public function getLyrics3Data($endoffset, $version, $length) {
// http://www.volweb.cz/str/tags.htm
$info = &$this->getid3->info;
if (!getid3_lib::intValueSupported($endoffset)) {
$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$this->fseek($endoffset);
if ($length <= 0) {
return false;
}
$rawdata = $this->fread($length);
$ParsedLyrics3['raw']['lyrics3version'] = $version;
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
$ParsedLyrics3['tag_offset_start'] = $endoffset;
$ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1;
if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
$this->warning('"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version);
$info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
$rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
$length = strlen($rawdata);
$ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
} else {
$this->error('"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead');
return false;
}
}
switch ($version) {
case 1:
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
$ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
} else {
$this->error('"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
return false;
}
break;
case 2:
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
$ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ
$rawdata = $ParsedLyrics3['raw']['unparsed'];
while (strlen($rawdata) > 0) {
$fieldname = substr($rawdata, 0, 3);
$fieldsize = (int) substr($rawdata, 3, 5);
$ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
$rawdata = substr($rawdata, 3 + 5 + $fieldsize);
}
if (isset($ParsedLyrics3['raw']['IND'])) {
$i = 0;
$flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
foreach ($flagnames as $flagname) {
if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
$ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
}
}
}
$fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author');
foreach ($fieldnametranslation as $key => $value) {
if (isset($ParsedLyrics3['raw'][$key])) {
$ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
}
}
if (isset($ParsedLyrics3['raw']['IMG'])) {
$imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
foreach ($imagestrings as $key => $imagestring) {
if (strpos($imagestring, '||') !== false) {
$imagearray = explode('||', $imagestring);
$ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : '');
$ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : '');
$ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
}
}
}
if (isset($ParsedLyrics3['raw']['LYR'])) {
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
}
} else {
$this->error('"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
return false;
}
break;
default:
$this->error('Cannot process Lyrics3 version '.$version.' (only v1 and v2)');
return false;
break;
}
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
$this->warning('ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data');
unset($info['id3v1']);
foreach ($info['warning'] as $key => $value) {
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
unset($info['warning'][$key]);
sort($info['warning']);
break;
}
}
}
$info['lyrics3'] = $ParsedLyrics3;
return true;
}
public function Lyrics3Timestamp2Seconds($rawtimestamp) {
if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) {
return (int) (($regs[1] * 60) + $regs[2]);
}
return false;
}
public function Lyrics3LyricsTimestampParse(&$Lyrics3data) {
$lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']);
foreach ($lyricsarray as $key => $lyricline) {
$regs = array();
unset($thislinetimestamps);
while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) {
$thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]);
$lyricline = str_replace($regs[0], '', $lyricline);
}
$notimestamplyricsarray[$key] = $lyricline;
if (isset($thislinetimestamps) && is_array($thislinetimestamps)) {
sort($thislinetimestamps);
foreach ($thislinetimestamps as $timestampkey => $timestamp) {
if (isset($Lyrics3data['synchedlyrics'][$timestamp])) {
// timestamps only have a 1-second resolution, it's possible that multiple lines
// could have the same timestamp, if so, append
$Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline;
} else {
$Lyrics3data['synchedlyrics'][$timestamp] = $lyricline;
}
}
}
}
$Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray);
if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) {
ksort($Lyrics3data['synchedlyrics']);
}
return true;
}
public function IntString2Bool($char) {
if ($char == '1') {
return true;
} elseif ($char == '0') {
return false;
}
return null;
}
}

View File

@ -0,0 +1,604 @@
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or http://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
*****************************************************************
*****************************************************************
getID3() is released under multiple licenses. You may choose
from the following licenses, and use getID3 according to the
terms of the license most suitable to your project.
GNU GPL: https://gnu.org/licenses/gpl.html (v3)
https://gnu.org/licenses/old-licenses/gpl-2.0.html (v2)
https://gnu.org/licenses/old-licenses/gpl-1.0.html (v1)
GNU LGPL: https://gnu.org/licenses/lgpl.html (v3)
Mozilla MPL: http://www.mozilla.org/MPL/2.0/ (v2)
getID3 Commercial License: http://getid3.org/#gCL (payment required)
*****************************************************************
*****************************************************************
Copies of each of the above licenses are included in the 'licenses'
directory of the getID3 distribution.
+---------------------------------------------+
| If you want to donate, there is a link on |
| http://www.getid3.org for PayPal donations. |
+---------------------------------------------+
Quick Start
===========================================================================
Q: How can I check that getID3() works on my server/files?
A: Unzip getID3() to a directory, then access /demos/demo.browse.php
Support
===========================================================================
Q: I have a question, or I found a bug. What do I do?
A: The preferred method of support requests and/or bug reports is the
forum at http://support.getid3.org/
Sourceforge Notification
===========================================================================
It's highly recommended that you sign up for notification from
Sourceforge for when new versions are released. Please visit:
http://sourceforge.net/project/showfiles.php?group_id=55859
and click the little "monitor package" icon/link. If you're
previously signed up for the mailing list, be aware that it has
been discontinued, only the automated Sourceforge notification
will be used from now on.
What does getID3() do?
===========================================================================
Reads & parses (to varying degrees):
¤ tags:
* APE (v1 and v2)
* ID3v1 (& ID3v1.1)
* ID3v2 (v2.4, v2.3, v2.2)
* Lyrics3 (v1 & v2)
¤ audio-lossy:
* MP3/MP2/MP1
* MPC / Musepack
* Ogg (Vorbis, OggFLAC, Speex)
* AAC / MP4
* AC3
* DTS
* RealAudio
* Speex
* DSS
* VQF
¤ audio-lossless:
* AIFF
* AU
* Bonk
* CD-audio (*.cda)
* FLAC
* LA (Lossless Audio)
* LiteWave
* LPAC
* MIDI
* Monkey's Audio
* OptimFROG
* RKAU
* Shorten
* TTA
* VOC
* WAV (RIFF)
* WavPack
¤ audio-video:
* ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV)
* AVI (RIFF)
* Flash
* Matroska (MKV)
* MPEG-1 / MPEG-2
* NSV (Nullsoft Streaming Video)
* Quicktime (including MP4)
* RealVideo
¤ still image:
* BMP
* GIF
* JPEG
* PNG
* TIFF
* SWF (Flash)
* PhotoCD
¤ data:
* ISO-9660 CD-ROM image (directory structure)
* SZIP (limited support)
* ZIP (directory structure)
* TAR
* CUE
Writes:
* ID3v1 (& ID3v1.1)
* ID3v2 (v2.3 & v2.4)
* VorbisComment on OggVorbis
* VorbisComment on FLAC (not OggFLAC)
* APE v2
* Lyrics3 (delete only)
Requirements
===========================================================================
* PHP 4.2.0 up to 5.2.x for getID3() 1.7.x (and earlier)
* PHP 5.0.5 (or higher) for getID3() 1.8.x (and up)
* PHP 5.0.5 (or higher) for getID3() 2.0.x (and up)
* at least 4MB memory for PHP. 8MB or more is highly recommended.
12MB is required with all modules loaded.
Usage
===========================================================================
See /demos/demo.basic.php for a very basic use of getID3() with no
fancy output, just scanning one file.
See structure.txt for the returned data structure.
*> For an example of a complete directory-browsing, <*
*> file-scanning implementation of getID3(), please run <*
*> /demos/demo.browse.php <*
See /demos/demo.mysql.php for a sample recursive scanning code that
scans every file in a given directory, and all sub-directories, stores
the results in a database and allows various analysis / maintenance
operations
To analyze remote files over HTTP or FTP you need to copy the file
locally first before running getID3(). Your code would look something
like this:
// Copy remote file locally to scan with getID3()
$remotefilename = 'http://www.example.com/filename.mp3';
if ($fp_remote = fopen($remotefilename, 'rb')) {
$localtempfilename = tempnam('/tmp', 'getID3');
if ($fp_local = fopen($localtempfilename, 'wb')) {
while ($buffer = fread($fp_remote, 8192)) {
fwrite($fp_local, $buffer);
}
fclose($fp_local);
// Initialize getID3 engine
$getID3 = new getID3;
$ThisFileInfo = $getID3->analyze($filename);
// Delete temporary file
unlink($localtempfilename);
}
fclose($fp_remote);
}
See /demos/demo.write.php for how to write tags.
What does the returned data structure look like?
===========================================================================
See structure.txt
It is recommended that you look at the output of
/demos/demo.browse.php scanning the file(s) you're interested in to
confirm what data is actually returned for any particular filetype in
general, and your files in particular, as the actual data returned
may vary considerably depending on what information is available in
the file itself.
Notes
===========================================================================
getID3() 1.x:
If the format parser encounters a critical problem, it will return
something in $fileinfo['error'], describing the encountered error. If
a less critical error or notice is generated it will appear in
$fileinfo['warning']. Both keys may contain more than one warning or
error. If something is returned in ['error'] then the file was not
correctly parsed and returned data may or may not be correct and/or
complete. If something is returned in ['warning'] (and not ['error'])
then the data that is returned is OK - usually getID3() is reporting
errors in the file that have been worked around due to known bugs in
other programs. Some warnings may indicate that the data that is
returned is OK but that some data could not be extracted due to
errors in the file.
getID3() 2.x:
See above except errors are thrown (so you will only get one error).
Disclaimer
===========================================================================
getID3() has been tested on many systems, on many types of files,
under many operating systems, and is generally believe to be stable
and safe. That being said, there is still the chance there is an
undiscovered and/or unfixed bug that may potentially corrupt your
file, especially within the writing functions. By using getID3() you
agree that it's not my fault if any of your files are corrupted.
In fact, I'm not liable for anything :)
License
===========================================================================
GNU General Public License - see license.txt
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA.
FAQ:
Q: Can I use getID3() in my program? Do I need a commercial license?
A: You're generally free to use getID3 however you see fit. The only
case in which you would require a commercial license is if you're
selling your closed-source program that integrates getID3. If you
sell your program including a copy of getID3, that's fine as long
as you include a copy of the sourcecode when you sell it. Or you
can distribute your code without getID3 and say "download it from
getid3.sourceforge.net"
Why is it called "getID3()" if it does so much more than just that?
===========================================================================
v0.1 did in fact just do that. I don't have a copy of code that old, but I
could essentially write it today with a one-line function:
function getID3($filename) { return unpack('a3TAG/a30title/a30artist/a30album/a4year/a28comment/c1track/c1genreid', substr(file_get_contents($filename), -128)); }
Future Plans
===========================================================================
http://www.getid3.org/phpBB3/viewforum.php?f=7
* Better support for MP4 container format
* Scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0)
* Support for JPEG-2000 (http://www.morgan-multimedia.com/jpeg2000_overview.htm)
* Support for MOD (mod/stm/s3m/it/xm/mtm/ult/669)
* Support for ACE (thanks Vince)
* Support for Ogg other than Vorbis, Speex and OggFlac (ie. Ogg+Xvid)
* Ability to create Xing/LAME VBR header for VBR MP3s that are missing VBR header
* Ability to "clean" ID3v2 padding (replace invalid padding with valid padding)
* Warn if MP3s change version mid-stream (in full-scan mode)
* check for corrupt/broken mid-file MP3 streams in histogram scan
* Support for lossless-compression formats
(http://www.firstpr.com.au/audiocomp/lossless/#Links)
(http://compression.ca/act-sound.html)
(http://web.inter.nl.net/users/hvdh/lossless/lossless.htm)
* Support for RIFF-INFO chunks
* http://lotto.st-andrews.ac.uk/~njh/tag_interchange.html
(thanks Nick Humfrey <njhØsurgeradio*co*uk>)
* http://abcavi.narod.ru/sof/abcavi/infotags.htm
(thanks Kibi)
* Better support for Bink video
* http://www.hr/josip/DSP/AudioFile2.html
* http://www.pcisys.net/~melanson/codecs/
* Detect mp3PRO
* Support for PSD
* Support for JPC
* Support for JP2
* Support for JPX
* Support for JB2
* Support for IFF
* Support for ICO
* Support for ANI
* Support for EXE (comments, author, etc) (thanks p*quaedackersØplanet*nl)
* Support for DVD-IFO (region, subtitles, aspect ratio, etc)
(thanks p*quaedackersØplanet*nl)
* More complete support for SWF - parsing encapsulated MP3 and/or JPEG content
(thanks n8n8Øyahoo*com)
* Support for a2b
* Optional scan-through-frames for AVI verification
(thanks rockcohenØmassive-interactive*nl)
* Support for TTF (thanks infoØbutterflyx*com)
* Support for DSS (http://www.getid3.org/phpBB3/viewtopic.php?t=171)
* Support for SMAF (http://smaf-yamaha.com/what/demo.html)
http://www.getid3.org/phpBB3/viewtopic.php?t=182
* Support for AMR (http://www.getid3.org/phpBB3/viewtopic.php?t=195)
* Support for 3gpp (http://www.getid3.org/phpBB3/viewtopic.php?t=195)
* Support for ID4 (http://www.wackysoft.cjb.net grizlyY2KØhotmail*com)
* Parse XML data returned in Ogg comments
* Parse XML data from Quicktime SMIL metafiles (klausrathØmac*com)
* ID3v2 genre string creator function
* More complete parsing of JPG
* Support for all old-style ASF packets
* ASF/WMA/WMV tag writing
* Parse declared T??? ID3v2 text information frames, where appropriate
(thanks Christian Fritz for the idea)
* Recognize encoder:
http://www.guerillasoft.com/EncSpot2/index.html
http://ff123.net/identify.html
http://www.hydrogenaudio.org/?act=ST&f=16&t=9414
http://www.hydrogenaudio.org/?showtopic=11785
* Support for other OS/2 bitmap structures: Bitmap Array('BA'),
Color Icon('CI'), Color Pointer('CP'), Icon('IC'), Pointer ('PT')
http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
* Support for WavPack RAW mode
* ASF/WMA/WMV data packet parsing
* ID3v2FrameFlagsLookupTagAlter()
* ID3v2FrameFlagsLookupFileAlter()
* obey ID3v2 tag alter/preserve/discard rules
* http://www.geocities.com/SiliconValley/Sector/9654/Softdoc/Illyrium/Aolyr.htm
* proper checking for LINK/LNK frame validity in ID3v2 writing
* proper checking for ASPI-TLEN frame validity in ID3v2 writing
* proper checking for COMR frame validity in ID3v2 writing
* http://www.geocities.co.jp/SiliconValley-Oakland/3664/index.html
* decode GEOB ID3v2 structure as encoded by RealJukebox,
decode NCON ID3v2 structure as encoded by MusicMatch
(probably won't happen - the formats are proprietary)
Known Bugs/Issues in getID3() that may be fixed eventually
===========================================================================
http://www.getid3.org/phpBB3/viewtopic.php?t=25
* Cannot determine bitrate for MPEG video with VBR video data
(need documentation)
* Interlace/progressive cannot be determined for MPEG video
(need documentation)
* MIDI playtime is sometimes inaccurate
* AAC-RAW mode files cannot be identified
* WavPack-RAW mode files cannot be identified
* mp4 files report lots of "Unknown QuickTime atom type"
(need documentation)
* Encrypted ASF/WMA/WMV files warn about "unhandled GUID
ASF_Content_Encryption_Object"
* Bitrate split between audio and video cannot be calculated for
NSV, only the total bitrate. (need documentation)
* All Ogg formats (Vorbis, OggFLAC, Speex) are affected by the
problem of large VorbisComments spanning multiple Ogg pages, but
but only OggVorbis files can be processed with vorbiscomment.
* The version of "head" supplied with Mac OS 10.2.8 (maybe other
versions too) does only understands a single option (-n) and
therefore fails. getID3 ignores this and returns wrong md5_data.
Known Bugs/Issues in getID3() that cannot be fixed
--------------------------------------------------
http://www.getid3.org/phpBB3/viewtopic.php?t=25
* 32-bit PHP installations only:
Files larger than 2GB cannot always be parsed fully by getID3()
due to limitations in the 32-bit PHP filesystem functions.
NOTE: Since v1.7.8b3 there is partial support for larger-than-
2GB files, most of which will parse OK, as long as no critical
data is located beyond the 2GB offset.
Known will-work:
* all file formats on 64-bit PHP
* ZIP (format doesn't support files >2GB)
* FLAC (current encoders don't support files >2GB)
Known will-not-work:
* ID3v1 tags (always located at end-of-file)
* Lyrics3 tags (always located at end-of-file)
* APE tags (always located at end-of-file)
Maybe-will-work:
* Quicktime (will work if needed metadata is before 2GB offset,
that is if the file has been hinted/optimized for streaming)
* RIFF.WAV (should work fine, but gives warnings about not being
able to parse all chunks)
* RIFF.AVI (playtime will probably be wrong, is only based on
"movi" chunk that fits in the first 2GB, should issue error
to show that playtime is incorrect. Other data should be mostly
correct, assuming that data is constant throughout the file)
* PHP <= v5 on Windows cannot read UTF-8 filenames
Known Bugs/Issues in other programs
-----------------------------------
http://www.getid3.org/phpBB3/viewtopic.php?t=25
* Windows Media Player (up to v11) and iTunes (up to v10+) do
not correctly handle ID3v2.3 tags with UTF-16BE+BOM
encoding (they assume the data is UTF-16LE+BOM and either
crash (WMP) or output Asian character set (iTunes)
* Winamp (up to v2.80 at least) does not support ID3v2.4 tags,
only ID3v2.3
see: http://forums.winamp.com/showthread.php?postid=387524
* Some versions of Helium2 (www.helium2.com) do not write
ID3v2.4-compliant Frame Sizes, even though the tag is marked
as ID3v2.4) (detected by getID3())
* MP3ext V3.3.17 places a non-compliant padding string at the end
of the ID3v2 header. This is supposedly fixed in v3.4b21 but
only if you manually add a registry key. This fix is not yet
confirmed. (detected by getID3())
* CDex v1.40 (fixed by v1.50b7) writes non-compliant Ogg comment
strings, supposed to be in the format "NAME=value" but actually
written just "value" (detected by getID3())
* Oggenc 0.9-rc3 flags the encoded file as ABR whether it's
actually ABR or VBR.
* iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably
other versions are too) writes ID3v2.3 comment tags using a
frame name 'COM ' which is not valid for ID3v2.3+ (it's an
ID3v2.2-style frame name) (detected by getID3())
* MP2enc does not encode mono CBR MP2 files properly (half speed
sound and double playtime)
* MP2enc does not encode mono VBR MP2 files properly (actually
encoded as stereo)
* tooLAME does not encode mono VBR MP2 files properly (actually
encoded as stereo)
* AACenc encodes files in VBR mode (actually ABR) even if CBR is
specified
* AAC/ADIF - bitrate_mode = cbr for vbr files
* LAME 3.90-3.92 prepends one frame of null data (space for the
LAME/VBR header, but it never gets written) when encoding in CBR
mode with the DLL
* Ahead Nero encodes TwinVQF with a DSIZ value (which is supposed
to be the filesize in bytes) of "0" for TwinVQF v1.0 and "1" for
TwinVQF v2.0 (detected by getID3())
* Ahead Nero encodes TwinVQF files 1 second shorter than they
should be
* AAC-ADTS files are always actually encoded VBR, even if CBR mode
is specified (the CBR-mode switches on the encoder enable ABR
mode, not CBR as such, but it's not possible to tell the
difference between such ABR files and true VBR)
* STREAMINFO.audio_signature in OggFLAC is always null. "The reason
it's like that is because there is no seeking support in
libOggFLAC yet, so it has no way to go back and write the
computed sum after encoding. Seeking support in Ogg FLAC is the
#1 item for the next release." - Josh Coalson (FLAC developer)
NOTE: getID3() will calculate md5_data in a method similar to
other file formats, but that value cannot be compared to the
md5_data value from FLAC data in a FLAC file format.
* STREAMINFO.audio_signature is not calculated in FLAC v0.3.0 &
v0.4.0 - getID3() will calculate md5_data in a method similar to
other file formats, but that value cannot be compared to the
md5_data value from FLAC v0.5.0+
* RioPort (various versions including 2.0 and 3.11) tags ID3v2 with
a WCOM frame that has no data portion
* Earlier versions of Coolplayer adds illegal ID3 tags to Ogg Vorbis
files, thus making them corrupt.
* Meracl ID3 Tag Writer v1.3.4 (and older) incorrectly truncates the
last byte of data from an MP3 file when appending a new ID3v1 tag.
(detected by getID3())
* Lossless-Audio files encoded with and without the -noseek switch
do actually differ internally and therefore cannot match md5_data
* iTunes has been known to append a new ID3v1 tag on the end of an
existing ID3v1 tag when ID3v2 tag is also present
(detected by getID3())
* MediaMonkey may write a blank RGAD ID3v2 frame but put actual
replay gain adjustments in a series of user-defined TXXX frames
(detected and handled by getID3() since v1.9.2)
Reference material:
===========================================================================
[www.id3.org material now mirrored at http://id3lib.sourceforge.net/id3/]
* http://www.id3.org/id3v2.4.0-structure.txt
* http://www.id3.org/id3v2.4.0-frames.txt
* http://www.id3.org/id3v2.4.0-changes.txt
* http://www.id3.org/id3v2.3.0.txt
* http://www.id3.org/id3v2-00.txt
* http://www.id3.org/mp3frame.html
* http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html <mathewhendry@hotmail.com>
* http://www.dv.co.yu/mpgscript/mpeghdr.htm
* http://www.mp3-tech.org/programmer/frame_header.html
* http://users.belgacom.net/gc247244/extra/tag.html
* http://gabriel.mp3-tech.org/mp3infotag.html
* http://www.id3.org/iso4217.html
* http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT
* http://www.xiph.org/ogg/vorbis/doc/framing.html
* http://www.xiph.org/ogg/vorbis/doc/v-comment.html
* http://leknor.com/code/php/class.ogg.php.txt
* http://www.id3.org/iso639-2.html
* http://www.id3.org/lyrics3.html
* http://www.id3.org/lyrics3200.html
* http://www.psc.edu/general/software/packages/ieee/ieee.html
* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
* http://www.jmcgowan.com/avi.html
* http://www.wotsit.org/
* http://www.herdsoft.com/ti/davincie/davp3xo2.htm
* http://www.mathdogs.com/vorbis-illuminated/bitstream-appendix.html
* "Standard MIDI File Format" by Dustin Caldwell (from www.wotsit.org)
* http://midistudio.com/Help/GMSpecs_Patches.htm
* http://www.xiph.org/archives/vorbis/200109/0459.html
* http://www.replaygain.org/
* http://www.lossless-audio.com/
* http://download.microsoft.com/download/winmediatech40/Doc/1.0/WIN98MeXP/EN-US/ASF_Specification_v.1.0.exe
* http://mediaxw.sourceforge.net/files/doc/Active%20Streaming%20Format%20(ASF)%201.0%20Specification.pdf
* http://www.uni-jena.de/~pfk/mpp/sv8/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/sv8/)
* http://jfaul.de/atl/
* http://www.uni-jena.de/~pfk/mpp/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/)
* http://www.libpng.org/pub/png/spec/png-1.2-pdg.html
* http://www.real.com/devzone/library/creating/rmsdk/doc/rmff.htm
* http://www.fastgraph.com/help/bmp_os2_header_format.html
* http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
* http://flac.sourceforge.net/format.html
* http://www.research.att.com/projects/mpegaudio/mpeg2.html
* http://www.audiocoding.com/wiki/index.php?page=AAC
* http://libmpeg.org/mpeg4/doc/w2203tfs.pdf
* http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
* http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm
* http://www.nullsoft.com/nsv/
* http://www.wotsit.org/download.asp?f=iso9660
* http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html
* http://www.cdroller.com/htm/readdata.html
* http://www.speex.org/manual/node10.html
* http://www.harmony-central.com/Computer/Programming/aiff-file-format.doc
* http://www.faqs.org/rfcs/rfc2361.html
* http://ghido.shelter.ro/
* http://www.ebu.ch/tech_t3285.pdf
* http://www.sr.se/utveckling/tu/bwf
* http://ftp.aessc.org/pub/aes46-2002.pdf
* http://cartchunk.org:8080/
* http://www.broadcastpapers.com/radio/cartchunk01.htm
* http://www.hr/josip/DSP/AudioFile2.html
* http://home.attbi.com/~chris.bagwell/AudioFormats-11.html
* http://www.pure-mac.com/extkey.html
* http://cesnet.dl.sourceforge.net/sourceforge/bonkenc/bonk-binary-format-0.9.txt
* http://www.headbands.com/gspot/
* http://www.openswf.org/spec/SWFfileformat.html
* http://j-faul.virtualave.net/
* http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html
* http://cui.unige.ch/OSG/info/AudioFormats/ap11.html
* http://sswf.sourceforge.net/SWFalexref.html
* http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
* http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm
* http://developer.apple.com/quicktime/icefloe/dispatch012.html
* http://www.csdn.net/Dev/Format/graphics/PCD.htm
* http://tta.iszf.irk.ru/
* http://www.atsc.org/standards/a_52a.pdf
* http://www.alanwood.net/unicode/
* http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html
* http://www.its.msstate.edu/net/real/reports/config/tags.stats
* http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt
* http://brennan.young.net/Comp/LiveStage/things.html
* http://www.multiweb.cz/twoinches/MP3inside.htm
* http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
* http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
* http://www.unicode.org/unicode/faq/utf_bom.html
* http://tta.corecodec.org/?menu=format
* http://www.scvi.net/nsvformat.htm
* http://pda.etsi.org/pda/queryform.asp
* http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm
* http://trac.musepack.net/trac/wiki/SV8Specification
* http://wyday.com/cuesharp/specification.php
* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html

View File

@ -0,0 +1,32 @@
<?php
/**
* IXR_Base64
*
* @package IXR
* @since 1.5.0
*/
class IXR_Base64
{
var $data;
/**
* PHP5 constructor.
*/
function __construct( $data )
{
$this->data = $data;
}
/**
* PHP4 constructor.
*/
public function IXR_Base64( $data ) {
self::__construct( $data );
}
function getXml()
{
return '<base64>'.base64_encode($this->data).'</base64>';
}
}

View File

@ -0,0 +1,166 @@
<?php
/**
* IXR_Client
*
* @package IXR
* @since 1.5.0
*
*/
class IXR_Client
{
var $server;
var $port;
var $path;
var $useragent;
var $response;
var $message = false;
var $debug = false;
var $timeout;
var $headers = array();
// Storage place for an error message
var $error = false;
/**
* PHP5 constructor.
*/
function __construct( $server, $path = false, $port = 80, $timeout = 15 )
{
if (!$path) {
// Assume we have been given a URL instead
$bits = parse_url($server);
$this->server = $bits['host'];
$this->port = isset($bits['port']) ? $bits['port'] : 80;
$this->path = isset($bits['path']) ? $bits['path'] : '/';
// Make absolutely sure we have a path
if (!$this->path) {
$this->path = '/';
}
if ( ! empty( $bits['query'] ) ) {
$this->path .= '?' . $bits['query'];
}
} else {
$this->server = $server;
$this->path = $path;
$this->port = $port;
}
$this->useragent = 'The Incutio XML-RPC PHP Library';
$this->timeout = $timeout;
}
/**
* PHP4 constructor.
*/
public function IXR_Client( $server, $path = false, $port = 80, $timeout = 15 ) {
self::__construct( $server, $path, $port, $timeout );
}
function query()
{
$args = func_get_args();
$method = array_shift($args);
$request = new IXR_Request($method, $args);
$length = $request->getLength();
$xml = $request->getXml();
$r = "\r\n";
$request = "POST {$this->path} HTTP/1.0$r";
// Merged from WP #8145 - allow custom headers
$this->headers['Host'] = $this->server;
$this->headers['Content-Type'] = 'text/xml';
$this->headers['User-Agent'] = $this->useragent;
$this->headers['Content-Length']= $length;
foreach( $this->headers as $header => $value ) {
$request .= "{$header}: {$value}{$r}";
}
$request .= $r;
$request .= $xml;
// Now send the request
if ($this->debug) {
echo '<pre class="ixr_request">'.htmlspecialchars($request)."\n</pre>\n\n";
}
if ($this->timeout) {
$fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
} else {
$fp = @fsockopen($this->server, $this->port, $errno, $errstr);
}
if (!$fp) {
$this->error = new IXR_Error(-32300, 'transport error - could not open socket');
return false;
}
fputs($fp, $request);
$contents = '';
$debugContents = '';
$gotFirstLine = false;
$gettingHeaders = true;
while (!feof($fp)) {
$line = fgets($fp, 4096);
if (!$gotFirstLine) {
// Check line for '200'
if (strstr($line, '200') === false) {
$this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');
return false;
}
$gotFirstLine = true;
}
if (trim($line) == '') {
$gettingHeaders = false;
}
if (!$gettingHeaders) {
// merged from WP #12559 - remove trim
$contents .= $line;
}
if ($this->debug) {
$debugContents .= $line;
}
}
if ($this->debug) {
echo '<pre class="ixr_response">'.htmlspecialchars($debugContents)."\n</pre>\n\n";
}
// Now parse what we've got back
$this->message = new IXR_Message($contents);
if (!$this->message->parse()) {
// XML error
$this->error = new IXR_Error(-32700, 'parse error. not well formed');
return false;
}
// Is the message a fault?
if ($this->message->messageType == 'fault') {
$this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
return false;
}
// Message must be OK
return true;
}
function getResponse()
{
// methodResponses can only have one param - return that
return $this->message->params[0];
}
function isError()
{
return (is_object($this->error));
}
function getErrorCode()
{
return $this->error->code;
}
function getErrorMessage()
{
return $this->error->message;
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* IXR_ClientMulticall
*
* @package IXR
* @since 1.5.0
*/
class IXR_ClientMulticall extends IXR_Client
{
var $calls = array();
/**
* PHP5 constructor.
*/
function __construct( $server, $path = false, $port = 80 )
{
parent::IXR_Client($server, $path, $port);
$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
}
/**
* PHP4 constructor.
*/
public function IXR_ClientMulticall( $server, $path = false, $port = 80 ) {
self::__construct( $server, $path, $port );
}
function addCall()
{
$args = func_get_args();
$methodName = array_shift($args);
$struct = array(
'methodName' => $methodName,
'params' => $args
);
$this->calls[] = $struct;
}
function query()
{
// Prepare multicall, then call the parent::query() method
return parent::query('system.multicall', $this->calls);
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* IXR_Date
*
* @package IXR
* @since 1.5.0
*/
class IXR_Date {
var $year;
var $month;
var $day;
var $hour;
var $minute;
var $second;
var $timezone;
/**
* PHP5 constructor.
*/
function __construct( $time )
{
// $time can be a PHP timestamp or an ISO one
if (is_numeric($time)) {
$this->parseTimestamp($time);
} else {
$this->parseIso($time);
}
}
/**
* PHP4 constructor.
*/
public function IXR_Date( $time ) {
self::__construct( $time );
}
function parseTimestamp($timestamp)
{
$this->year = date('Y', $timestamp);
$this->month = date('m', $timestamp);
$this->day = date('d', $timestamp);
$this->hour = date('H', $timestamp);
$this->minute = date('i', $timestamp);
$this->second = date('s', $timestamp);
$this->timezone = '';
}
function parseIso($iso)
{
$this->year = substr($iso, 0, 4);
$this->month = substr($iso, 4, 2);
$this->day = substr($iso, 6, 2);
$this->hour = substr($iso, 9, 2);
$this->minute = substr($iso, 12, 2);
$this->second = substr($iso, 15, 2);
$this->timezone = substr($iso, 17);
}
function getIso()
{
return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;
}
function getXml()
{
return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
}
function getTimestamp()
{
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* IXR_Error
*
* @package IXR
* @since 1.5.0
*/
class IXR_Error
{
var $code;
var $message;
/**
* PHP5 constructor.
*/
function __construct( $code, $message )
{
$this->code = $code;
$this->message = htmlspecialchars($message);
}
/**
* PHP4 constructor.
*/
public function IXR_Error( $code, $message ) {
self::__construct( $code, $message );
}
function getXml()
{
$xml = <<<EOD
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>{$this->code}</int></value>
</member>
<member>
<name>faultString</name>
<value><string>{$this->message}</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
EOD;
return $xml;
}
}

View File

@ -0,0 +1,174 @@
<?php
/**
* IXR_IntrospectionServer
*
* @package IXR
* @since 1.5.0
*/
class IXR_IntrospectionServer extends IXR_Server
{
var $signatures;
var $help;
/**
* PHP5 constructor.
*/
function __construct()
{
$this->setCallbacks();
$this->setCapabilities();
$this->capabilities['introspection'] = array(
'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
'specVersion' => 1
);
$this->addCallback(
'system.methodSignature',
'this:methodSignature',
array('array', 'string'),
'Returns an array describing the return type and required parameters of a method'
);
$this->addCallback(
'system.getCapabilities',
'this:getCapabilities',
array('struct'),
'Returns a struct describing the XML-RPC specifications supported by this server'
);
$this->addCallback(
'system.listMethods',
'this:listMethods',
array('array'),
'Returns an array of available methods on this server'
);
$this->addCallback(
'system.methodHelp',
'this:methodHelp',
array('string', 'string'),
'Returns a documentation string for the specified method'
);
}
/**
* PHP4 constructor.
*/
public function IXR_IntrospectionServer() {
self::__construct();
}
function addCallback($method, $callback, $args, $help)
{
$this->callbacks[$method] = $callback;
$this->signatures[$method] = $args;
$this->help[$method] = $help;
}
function call($methodname, $args)
{
// Make sure it's in an array
if ($args && !is_array($args)) {
$args = array($args);
}
// Over-rides default call method, adds signature check
if (!$this->hasMethod($methodname)) {
return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');
}
$method = $this->callbacks[$methodname];
$signature = $this->signatures[$methodname];
$returnType = array_shift($signature);
// Check the number of arguments
if (count($args) != count($signature)) {
return new IXR_Error(-32602, 'server error. wrong number of method parameters');
}
// Check the argument types
$ok = true;
$argsbackup = $args;
for ($i = 0, $j = count($args); $i < $j; $i++) {
$arg = array_shift($args);
$type = array_shift($signature);
switch ($type) {
case 'int':
case 'i4':
if (is_array($arg) || !is_int($arg)) {
$ok = false;
}
break;
case 'base64':
case 'string':
if (!is_string($arg)) {
$ok = false;
}
break;
case 'boolean':
if ($arg !== false && $arg !== true) {
$ok = false;
}
break;
case 'float':
case 'double':
if (!is_float($arg)) {
$ok = false;
}
break;
case 'date':
case 'dateTime.iso8601':
if (!is_a($arg, 'IXR_Date')) {
$ok = false;
}
break;
}
if (!$ok) {
return new IXR_Error(-32602, 'server error. invalid method parameters');
}
}
// It passed the test - run the "real" method call
return parent::call($methodname, $argsbackup);
}
function methodSignature($method)
{
if (!$this->hasMethod($method)) {
return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');
}
// We should be returning an array of types
$types = $this->signatures[$method];
$return = array();
foreach ($types as $type) {
switch ($type) {
case 'string':
$return[] = 'string';
break;
case 'int':
case 'i4':
$return[] = 42;
break;
case 'double':
$return[] = 3.1415;
break;
case 'dateTime.iso8601':
$return[] = new IXR_Date(time());
break;
case 'boolean':
$return[] = true;
break;
case 'base64':
$return[] = new IXR_Base64('base64');
break;
case 'array':
$return[] = array('array');
break;
case 'struct':
$return[] = array('struct' => 'struct');
break;
}
}
return $return;
}
function methodHelp($method)
{
return $this->help[$method];
}
}

View File

@ -0,0 +1,234 @@
<?php
/**
* IXR_MESSAGE
*
* @package IXR
* @since 1.5.0
*
*/
class IXR_Message
{
var $message = false;
var $messageType = false; // methodCall / methodResponse / fault
var $faultCode = false;
var $faultString = false;
var $methodName = '';
var $params = array();
// Current variable stacks
var $_arraystructs = array(); // The stack used to keep track of the current array/struct
var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
var $_currentStructName = array(); // A stack as well
var $_param;
var $_value;
var $_currentTag;
var $_currentTagContents;
// The XML parser
var $_parser;
/**
* PHP5 constructor.
*/
function __construct( $message )
{
$this->message =& $message;
}
/**
* PHP4 constructor.
*/
public function IXR_Message( $message ) {
self::__construct( $message );
}
function parse()
{
if ( ! function_exists( 'xml_parser_create' ) ) {
trigger_error( __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) );
return false;
}
// first remove the XML declaration
// merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
$header = preg_replace( '/<\?xml.*?\?'.'>/s', '', substr( $this->message, 0, 100 ), 1 );
$this->message = trim( substr_replace( $this->message, $header, 0, 100 ) );
if ( '' == $this->message ) {
return false;
}
// Then remove the DOCTYPE
$header = preg_replace( '/^<!DOCTYPE[^>]*+>/i', '', substr( $this->message, 0, 200 ), 1 );
$this->message = trim( substr_replace( $this->message, $header, 0, 200 ) );
if ( '' == $this->message ) {
return false;
}
// Check that the root tag is valid
$root_tag = substr( $this->message, 0, strcspn( substr( $this->message, 0, 20 ), "> \t\r\n" ) );
if ( '<!DOCTYPE' === strtoupper( $root_tag ) ) {
return false;
}
if ( ! in_array( $root_tag, array( '<methodCall', '<methodResponse', '<fault' ) ) ) {
return false;
}
// Bail if there are too many elements to parse
$element_limit = 30000;
if ( function_exists( 'apply_filters' ) ) {
/**
* Filters the number of elements to parse in an XML-RPC response.
*
* @since 4.0.0
*
* @param int $element_limit Default elements limit.
*/
$element_limit = apply_filters( 'xmlrpc_element_limit', $element_limit );
}
if ( $element_limit && 2 * $element_limit < substr_count( $this->message, '<' ) ) {
return false;
}
$this->_parser = xml_parser_create();
// Set XML parser to take the case of tags in to account
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
// Set XML parser callback functions
xml_set_object($this->_parser, $this);
xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
xml_set_character_data_handler($this->_parser, 'cdata');
// 256Kb, parse in chunks to avoid the RAM usage on very large messages
$chunk_size = 262144;
/**
* Filters the chunk size that can be used to parse an XML-RPC reponse message.
*
* @since 4.4.0
*
* @param int $chunk_size Chunk size to parse in bytes.
*/
$chunk_size = apply_filters( 'xmlrpc_chunk_parsing_size', $chunk_size );
$final = false;
do {
if (strlen($this->message) <= $chunk_size) {
$final = true;
}
$part = substr($this->message, 0, $chunk_size);
$this->message = substr($this->message, $chunk_size);
if (!xml_parse($this->_parser, $part, $final)) {
return false;
}
if ($final) {
break;
}
} while (true);
xml_parser_free($this->_parser);
// Grab the error messages, if any
if ($this->messageType == 'fault') {
$this->faultCode = $this->params[0]['faultCode'];
$this->faultString = $this->params[0]['faultString'];
}
return true;
}
function tag_open($parser, $tag, $attr)
{
$this->_currentTagContents = '';
$this->currentTag = $tag;
switch($tag) {
case 'methodCall':
case 'methodResponse':
case 'fault':
$this->messageType = $tag;
break;
/* Deal with stacks of arrays and structs */
case 'data': // data is to all intents and puposes more interesting than array
$this->_arraystructstypes[] = 'array';
$this->_arraystructs[] = array();
break;
case 'struct':
$this->_arraystructstypes[] = 'struct';
$this->_arraystructs[] = array();
break;
}
}
function cdata($parser, $cdata)
{
$this->_currentTagContents .= $cdata;
}
function tag_close($parser, $tag)
{
$valueFlag = false;
switch($tag) {
case 'int':
case 'i4':
$value = (int)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'double':
$value = (double)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'string':
$value = (string)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'dateTime.iso8601':
$value = new IXR_Date(trim($this->_currentTagContents));
$valueFlag = true;
break;
case 'value':
// "If no type is indicated, the type is string."
if (trim($this->_currentTagContents) != '') {
$value = (string)$this->_currentTagContents;
$valueFlag = true;
}
break;
case 'boolean':
$value = (boolean)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'base64':
$value = base64_decode($this->_currentTagContents);
$valueFlag = true;
break;
/* Deal with stacks of arrays and structs */
case 'data':
case 'struct':
$value = array_pop($this->_arraystructs);
array_pop($this->_arraystructstypes);
$valueFlag = true;
break;
case 'member':
array_pop($this->_currentStructName);
break;
case 'name':
$this->_currentStructName[] = trim($this->_currentTagContents);
break;
case 'methodName':
$this->methodName = trim($this->_currentTagContents);
break;
}
if ($valueFlag) {
if (count($this->_arraystructs) > 0) {
// Add value to struct or array
if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
// Add to struct
$this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
} else {
// Add to array
$this->_arraystructs[count($this->_arraystructs)-1][] = $value;
}
} else {
// Just add as a parameter
$this->params[] = $value;
}
}
$this->_currentTagContents = '';
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* IXR_Request
*
* @package IXR
* @since 1.5.0
*/
class IXR_Request
{
var $method;
var $args;
var $xml;
/**
* PHP5 constructor.
*/
function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
$this->xml = <<<EOD
<?xml version="1.0"?>
<methodCall>
<methodName>{$this->method}</methodName>
<params>
EOD;
foreach ($this->args as $arg) {
$this->xml .= '<param><value>';
$v = new IXR_Value($arg);
$this->xml .= $v->getXml();
$this->xml .= "</value></param>\n";
}
$this->xml .= '</params></methodCall>';
}
/**
* PHP4 constructor.
*/
public function IXR_Request( $method, $args ) {
self::__construct( $method, $args );
}
function getLength()
{
return strlen($this->xml);
}
function getXml()
{
return $this->xml;
}
}

View File

@ -0,0 +1,225 @@
<?php
/**
* IXR_Server
*
* @package IXR
* @since 1.5.0
*/
class IXR_Server
{
var $data;
var $callbacks = array();
var $message;
var $capabilities;
/**
* PHP5 constructor.
*/
function __construct( $callbacks = false, $data = false, $wait = false )
{
$this->setCapabilities();
if ($callbacks) {
$this->callbacks = $callbacks;
}
$this->setCallbacks();
if (!$wait) {
$this->serve($data);
}
}
/**
* PHP4 constructor.
*/
public function IXR_Server( $callbacks = false, $data = false, $wait = false ) {
self::__construct( $callbacks, $data, $wait );
}
function serve($data = false)
{
if (!$data) {
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
if ( function_exists( 'status_header' ) ) {
status_header( 405 ); // WP #20986
header( 'Allow: POST' );
}
header('Content-Type: text/plain'); // merged from WP #9093
die('XML-RPC server accepts POST requests only.');
}
global $HTTP_RAW_POST_DATA;
if (empty($HTTP_RAW_POST_DATA)) {
// workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293
$data = file_get_contents('php://input');
} else {
$data =& $HTTP_RAW_POST_DATA;
}
}
$this->message = new IXR_Message($data);
if (!$this->message->parse()) {
$this->error(-32700, 'parse error. not well formed');
}
if ($this->message->messageType != 'methodCall') {
$this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
}
$result = $this->call($this->message->methodName, $this->message->params);
// Is the result an error?
if (is_a($result, 'IXR_Error')) {
$this->error($result);
}
// Encode the result
$r = new IXR_Value($result);
$resultxml = $r->getXml();
// Create the XML
$xml = <<<EOD
<methodResponse>
<params>
<param>
<value>
$resultxml
</value>
</param>
</params>
</methodResponse>
EOD;
// Send it
$this->output($xml);
}
function call($methodname, $args)
{
if (!$this->hasMethod($methodname)) {
return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
}
$method = $this->callbacks[$methodname];
// Perform the callback and send the response
if (count($args) == 1) {
// If only one parameter just send that instead of the whole array
$args = $args[0];
}
// Are we dealing with a function or a method?
if (is_string($method) && substr($method, 0, 5) == 'this:') {
// It's a class method - check it exists
$method = substr($method, 5);
if (!method_exists($this, $method)) {
return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
}
//Call the method
$result = $this->$method($args);
} else {
// It's a function - does it exist?
if (is_array($method)) {
if (!is_callable(array($method[0], $method[1]))) {
return new IXR_Error(-32601, 'server error. requested object method "'.$method[1].'" does not exist.');
}
} else if (!function_exists($method)) {
return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
}
// Call the function
$result = call_user_func($method, $args);
}
return $result;
}
function error($error, $message = false)
{
// Accepts either an error object or an error code and message
if ($message && !is_object($error)) {
$error = new IXR_Error($error, $message);
}
$this->output($error->getXml());
}
function output($xml)
{
$charset = function_exists('get_option') ? get_option('blog_charset') : '';
if ($charset)
$xml = '<?xml version="1.0" encoding="'.$charset.'"?>'."\n".$xml;
else
$xml = '<?xml version="1.0"?>'."\n".$xml;
$length = strlen($xml);
header('Connection: close');
if ($charset)
header('Content-Type: text/xml; charset='.$charset);
else
header('Content-Type: text/xml');
header('Date: '.date('r'));
echo $xml;
exit;
}
function hasMethod($method)
{
return in_array($method, array_keys($this->callbacks));
}
function setCapabilities()
{
// Initialises capabilities array
$this->capabilities = array(
'xmlrpc' => array(
'specUrl' => 'http://www.xmlrpc.com/spec',
'specVersion' => 1
),
'faults_interop' => array(
'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
'specVersion' => 20010516
),
'system.multicall' => array(
'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
'specVersion' => 1
),
);
}
function getCapabilities($args)
{
return $this->capabilities;
}
function setCallbacks()
{
$this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
$this->callbacks['system.listMethods'] = 'this:listMethods';
$this->callbacks['system.multicall'] = 'this:multiCall';
}
function listMethods($args)
{
// Returns a list of methods - uses array_reverse to ensure user defined
// methods are listed before server defined methods
return array_reverse(array_keys($this->callbacks));
}
function multiCall($methodcalls)
{
// See http://www.xmlrpc.com/discuss/msgReader$1208
$return = array();
foreach ($methodcalls as $call) {
$method = $call['methodName'];
$params = $call['params'];
if ($method == 'system.multicall') {
$result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
} else {
$result = $this->call($method, $params);
}
if (is_a($result, 'IXR_Error')) {
$return[] = array(
'faultCode' => $result->code,
'faultString' => $result->message
);
} else {
$return[] = array($result);
}
}
return $return;
}
}

View File

@ -0,0 +1,138 @@
<?php
/**
* IXR_Value
*
* @package IXR
* @since 1.5.0
*/
class IXR_Value {
var $data;
var $type;
/**
* PHP5 constructor.
*/
function __construct( $data, $type = false )
{
$this->data = $data;
if (!$type) {
$type = $this->calculateType();
}
$this->type = $type;
if ($type == 'struct') {
// Turn all the values in the array in to new IXR_Value objects
foreach ($this->data as $key => $value) {
$this->data[$key] = new IXR_Value($value);
}
}
if ($type == 'array') {
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
$this->data[$i] = new IXR_Value($this->data[$i]);
}
}
}
/**
* PHP4 constructor.
*/
public function IXR_Value( $data, $type = false ) {
self::__construct( $data, $type );
}
function calculateType()
{
if ($this->data === true || $this->data === false) {
return 'boolean';
}
if (is_integer($this->data)) {
return 'int';
}
if (is_double($this->data)) {
return 'double';
}
// Deal with IXR object types base64 and date
if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
return 'date';
}
if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
return 'base64';
}
// If it is a normal PHP object convert it in to a struct
if (is_object($this->data)) {
$this->data = get_object_vars($this->data);
return 'struct';
}
if (!is_array($this->data)) {
return 'string';
}
// We have an array - is it an array or a struct?
if ($this->isStruct($this->data)) {
return 'struct';
} else {
return 'array';
}
}
function getXml()
{
// Return XML for this value
switch ($this->type) {
case 'boolean':
return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';
break;
case 'int':
return '<int>'.$this->data.'</int>';
break;
case 'double':
return '<double>'.$this->data.'</double>';
break;
case 'string':
return '<string>'.htmlspecialchars($this->data).'</string>';
break;
case 'array':
$return = '<array><data>'."\n";
foreach ($this->data as $item) {
$return .= ' <value>'.$item->getXml()."</value>\n";
}
$return .= '</data></array>';
return $return;
break;
case 'struct':
$return = '<struct>'."\n";
foreach ($this->data as $name => $value) {
$name = htmlspecialchars($name);
$return .= " <member><name>$name</name><value>";
$return .= $value->getXml()."</value></member>\n";
}
$return .= '</struct>';
return $return;
break;
case 'date':
case 'base64':
return $this->data->getXml();
break;
}
return false;
}
/**
* Checks whether or not the supplied array is a struct or not
*
* @param array $array
* @return bool
*/
function isStruct($array)
{
$expected = 0;
foreach ($array as $key => $value) {
if ((string)$key !== (string)$expected) {
return true;
}
$expected++;
}
return false;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Authentication provider interface
*
* @package Requests
* @subpackage Authentication
*/
/**
* Authentication provider interface
*
* Implement this interface to act as an authentication provider.
*
* Parameters should be passed via the constructor where possible, as this
* makes it much easier for users to use your provider.
*
* @see Requests_Hooks
* @package Requests
* @subpackage Authentication
*/
interface Requests_Auth {
/**
* Register hooks as needed
*
* This method is called in {@see Requests::request} when the user has set
* an instance as the 'auth' option. Use this callback to register all the
* hooks you'll need.
*
* @see Requests_Hooks::register
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks);
}

View File

@ -0,0 +1,88 @@
<?php
/**
* Basic Authentication provider
*
* @package Requests
* @subpackage Authentication
*/
/**
* Basic Authentication provider
*
* Provides a handler for Basic HTTP authentication via the Authorization
* header.
*
* @package Requests
* @subpackage Authentication
*/
class Requests_Auth_Basic implements Requests_Auth {
/**
* Username
*
* @var string
*/
public $user;
/**
* Password
*
* @var string
*/
public $pass;
/**
* Constructor
*
* @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
* @param array|null $args Array of user and password. Must have exactly two elements
*/
public function __construct($args = null) {
if (is_array($args)) {
if (count($args) !== 2) {
throw new Requests_Exception('Invalid number of arguments', 'authbasicbadargs');
}
list($this->user, $this->pass) = $args;
}
}
/**
* Register the necessary callbacks
*
* @see curl_before_send
* @see fsockopen_header
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks) {
$hooks->register('curl.before_send', array(&$this, 'curl_before_send'));
$hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header'));
}
/**
* Set cURL parameters before the data is sent
*
* @param resource $handle cURL resource
*/
public function curl_before_send(&$handle) {
curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString());
}
/**
* Add extra headers to the request before sending
*
* @param string $out HTTP header string
*/
public function fsockopen_header(&$out) {
$out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString()));
}
/**
* Get the authentication string (user:pass)
*
* @return string
*/
public function getAuthString() {
return $this->user . ':' . $this->pass;
}
}

View File

@ -0,0 +1,500 @@
<?php
/**
* Cookie storage object
*
* @package Requests
* @subpackage Cookies
*/
/**
* Cookie storage object
*
* @package Requests
* @subpackage Cookies
*/
class Requests_Cookie {
/**
* Cookie name.
*
* @var string
*/
public $name;
/**
* Cookie value.
*
* @var string
*/
public $value;
/**
* Cookie attributes
*
* Valid keys are (currently) path, domain, expires, max-age, secure and
* httponly.
*
* @var Requests_Utility_CaseInsensitiveDictionary|array Array-like object
*/
public $attributes = array();
/**
* Cookie flags
*
* Valid keys are (currently) creation, last-access, persistent and
* host-only.
*
* @var array
*/
public $flags = array();
/**
* Reference time for relative calculations
*
* This is used in place of `time()` when calculating Max-Age expiration and
* checking time validity.
*
* @var int
*/
public $reference_time = 0;
/**
* Create a new cookie object
*
* @param string $name
* @param string $value
* @param array|Requests_Utility_CaseInsensitiveDictionary $attributes Associative array of attribute data
*/
public function __construct($name, $value, $attributes = array(), $flags = array(), $reference_time = null) {
$this->name = $name;
$this->value = $value;
$this->attributes = $attributes;
$default_flags = array(
'creation' => time(),
'last-access' => time(),
'persistent' => false,
'host-only' => true,
);
$this->flags = array_merge($default_flags, $flags);
$this->reference_time = time();
if ($reference_time !== null) {
$this->reference_time = $reference_time;
}
$this->normalize();
}
/**
* Check if a cookie is expired.
*
* Checks the age against $this->reference_time to determine if the cookie
* is expired.
*
* @return boolean True if expired, false if time is valid.
*/
public function is_expired() {
// RFC6265, s. 4.1.2.2:
// If a cookie has both the Max-Age and the Expires attribute, the Max-
// Age attribute has precedence and controls the expiration date of the
// cookie.
if (isset($this->attributes['max-age'])) {
$max_age = $this->attributes['max-age'];
return $max_age < $this->reference_time;
}
if (isset($this->attributes['expires'])) {
$expires = $this->attributes['expires'];
return $expires < $this->reference_time;
}
return false;
}
/**
* Check if a cookie is valid for a given URI
*
* @param Requests_IRI $uri URI to check
* @return boolean Whether the cookie is valid for the given URI
*/
public function uri_matches(Requests_IRI $uri) {
if (!$this->domain_matches($uri->host)) {
return false;
}
if (!$this->path_matches($uri->path)) {
return false;
}
return empty($this->attributes['secure']) || $uri->scheme === 'https';
}
/**
* Check if a cookie is valid for a given domain
*
* @param string $string Domain to check
* @return boolean Whether the cookie is valid for the given domain
*/
public function domain_matches($string) {
if (!isset($this->attributes['domain'])) {
// Cookies created manually; cookies created by Requests will set
// the domain to the requested domain
return true;
}
$domain_string = $this->attributes['domain'];
if ($domain_string === $string) {
// The domain string and the string are identical.
return true;
}
// If the cookie is marked as host-only and we don't have an exact
// match, reject the cookie
if ($this->flags['host-only'] === true) {
return false;
}
if (strlen($string) <= strlen($domain_string)) {
// For obvious reasons, the string cannot be a suffix if the domain
// is shorter than the domain string
return false;
}
if (substr($string, -1 * strlen($domain_string)) !== $domain_string) {
// The domain string should be a suffix of the string.
return false;
}
$prefix = substr($string, 0, strlen($string) - strlen($domain_string));
if (substr($prefix, -1) !== '.') {
// The last character of the string that is not included in the
// domain string should be a %x2E (".") character.
return false;
}
// The string should be a host name (i.e., not an IP address).
return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $string);
}
/**
* Check if a cookie is valid for a given path
*
* From the path-match check in RFC 6265 section 5.1.4
*
* @param string $request_path Path to check
* @return boolean Whether the cookie is valid for the given path
*/
public function path_matches($request_path) {
if (empty($request_path)) {
// Normalize empty path to root
$request_path = '/';
}
if (!isset($this->attributes['path'])) {
// Cookies created manually; cookies created by Requests will set
// the path to the requested path
return true;
}
$cookie_path = $this->attributes['path'];
if ($cookie_path === $request_path) {
// The cookie-path and the request-path are identical.
return true;
}
if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) {
if (substr($cookie_path, -1) === '/') {
// The cookie-path is a prefix of the request-path, and the last
// character of the cookie-path is %x2F ("/").
return true;
}
if (substr($request_path, strlen($cookie_path), 1) === '/') {
// The cookie-path is a prefix of the request-path, and the
// first character of the request-path that is not included in
// the cookie-path is a %x2F ("/") character.
return true;
}
}
return false;
}
/**
* Normalize cookie and attributes
*
* @return boolean Whether the cookie was successfully normalized
*/
public function normalize() {
foreach ($this->attributes as $key => $value) {
$orig_value = $value;
$value = $this->normalize_attribute($key, $value);
if ($value === null) {
unset($this->attributes[$key]);
continue;
}
if ($value !== $orig_value) {
$this->attributes[$key] = $value;
}
}
return true;
}
/**
* Parse an individual cookie attribute
*
* Handles parsing individual attributes from the cookie values.
*
* @param string $name Attribute name
* @param string|boolean $value Attribute value (string value, or true if empty/flag)
* @return mixed Value if available, or null if the attribute value is invalid (and should be skipped)
*/
protected function normalize_attribute($name, $value) {
switch (strtolower($name)) {
case 'expires':
// Expiration parsing, as per RFC 6265 section 5.2.1
if (is_int($value)) {
return $value;
}
$expiry_time = strtotime($value);
if ($expiry_time === false) {
return null;
}
return $expiry_time;
case 'max-age':
// Expiration parsing, as per RFC 6265 section 5.2.2
if (is_int($value)) {
return $value;
}
// Check that we have a valid age
if (!preg_match('/^-?\d+$/', $value)) {
return null;
}
$delta_seconds = (int) $value;
if ($delta_seconds <= 0) {
$expiry_time = 0;
}
else {
$expiry_time = $this->reference_time + $delta_seconds;
}
return $expiry_time;
case 'domain':
// Domain normalization, as per RFC 6265 section 5.2.3
if ($value[0] === '.') {
$value = substr($value, 1);
}
return $value;
default:
return $value;
}
}
/**
* Format a cookie for a Cookie header
*
* This is used when sending cookies to a server.
*
* @return string Cookie formatted for Cookie header
*/
public function format_for_header() {
return sprintf('%s=%s', $this->name, $this->value);
}
/**
* Format a cookie for a Cookie header
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie::format_for_header}
* @return string
*/
public function formatForHeader() {
return $this->format_for_header();
}
/**
* Format a cookie for a Set-Cookie header
*
* This is used when sending cookies to clients. This isn't really
* applicable to client-side usage, but might be handy for debugging.
*
* @return string Cookie formatted for Set-Cookie header
*/
public function format_for_set_cookie() {
$header_value = $this->format_for_header();
if (!empty($this->attributes)) {
$parts = array();
foreach ($this->attributes as $key => $value) {
// Ignore non-associative attributes
if (is_numeric($key)) {
$parts[] = $value;
}
else {
$parts[] = sprintf('%s=%s', $key, $value);
}
}
$header_value .= '; ' . implode('; ', $parts);
}
return $header_value;
}
/**
* Format a cookie for a Set-Cookie header
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie::format_for_set_cookie}
* @return string
*/
public function formatForSetCookie() {
return $this->format_for_set_cookie();
}
/**
* Get the cookie value
*
* Attributes and other data can be accessed via methods.
*/
public function __toString() {
return $this->value;
}
/**
* Parse a cookie string into a cookie object
*
* Based on Mozilla's parsing code in Firefox and related projects, which
* is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
* specifies some of this handling, but not in a thorough manner.
*
* @param string Cookie header value (from a Set-Cookie header)
* @return Requests_Cookie Parsed cookie object
*/
public static function parse($string, $name = '', $reference_time = null) {
$parts = explode(';', $string);
$kvparts = array_shift($parts);
if (!empty($name)) {
$value = $string;
}
elseif (strpos($kvparts, '=') === false) {
// Some sites might only have a value without the equals separator.
// Deviate from RFC 6265 and pretend it was actually a blank name
// (`=foo`)
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=169091
$name = '';
$value = $kvparts;
}
else {
list($name, $value) = explode('=', $kvparts, 2);
}
$name = trim($name);
$value = trim($value);
// Attribute key are handled case-insensitively
$attributes = new Requests_Utility_CaseInsensitiveDictionary();
if (!empty($parts)) {
foreach ($parts as $part) {
if (strpos($part, '=') === false) {
$part_key = $part;
$part_value = true;
}
else {
list($part_key, $part_value) = explode('=', $part, 2);
$part_value = trim($part_value);
}
$part_key = trim($part_key);
$attributes[$part_key] = $part_value;
}
}
return new Requests_Cookie($name, $value, $attributes, array(), $reference_time);
}
/**
* Parse all Set-Cookie headers from request headers
*
* @param Requests_Response_Headers $headers Headers to parse from
* @param Requests_IRI|null $origin URI for comparing cookie origins
* @param int|null $time Reference time for expiration calculation
* @return array
*/
public static function parse_from_headers(Requests_Response_Headers $headers, Requests_IRI $origin = null, $time = null) {
$cookie_headers = $headers->getValues('Set-Cookie');
if (empty($cookie_headers)) {
return array();
}
$cookies = array();
foreach ($cookie_headers as $header) {
$parsed = self::parse($header, '', $time);
// Default domain/path attributes
if (empty($parsed->attributes['domain']) && !empty($origin)) {
$parsed->attributes['domain'] = $origin->host;
$parsed->flags['host-only'] = true;
}
else {
$parsed->flags['host-only'] = false;
}
$path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/');
if (!$path_is_valid && !empty($origin)) {
$path = $origin->path;
// Default path normalization as per RFC 6265 section 5.1.4
if (substr($path, 0, 1) !== '/') {
// If the uri-path is empty or if the first character of
// the uri-path is not a %x2F ("/") character, output
// %x2F ("/") and skip the remaining steps.
$path = '/';
}
elseif (substr_count($path, '/') === 1) {
// If the uri-path contains no more than one %x2F ("/")
// character, output %x2F ("/") and skip the remaining
// step.
$path = '/';
}
else {
// Output the characters of the uri-path from the first
// character up to, but not including, the right-most
// %x2F ("/").
$path = substr($path, 0, strrpos($path, '/'));
}
$parsed->attributes['path'] = $path;
}
// Reject invalid cookie domains
if (!empty($origin) && !$parsed->domain_matches($origin->host)) {
continue;
}
$cookies[$parsed->name] = $parsed;
}
return $cookies;
}
/**
* Parse all Set-Cookie headers from request headers
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie::parse_from_headers}
* @return string
*/
public static function parseFromHeaders(Requests_Response_Headers $headers) {
return self::parse_from_headers($headers);
}
}

View File

@ -0,0 +1,175 @@
<?php
/**
* Cookie holder object
*
* @package Requests
* @subpackage Cookies
*/
/**
* Cookie holder object
*
* @package Requests
* @subpackage Cookies
*/
class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
/**
* Actual item data
*
* @var array
*/
protected $cookies = array();
/**
* Create a new jar
*
* @param array $cookies Existing cookie values
*/
public function __construct($cookies = array()) {
$this->cookies = $cookies;
}
/**
* Normalise cookie data into a Requests_Cookie
*
* @param string|Requests_Cookie $cookie
* @return Requests_Cookie
*/
public function normalize_cookie($cookie, $key = null) {
if ($cookie instanceof Requests_Cookie) {
return $cookie;
}
return Requests_Cookie::parse($cookie, $key);
}
/**
* Normalise cookie data into a Requests_Cookie
*
* @codeCoverageIgnore
* @deprecated Use {@see Requests_Cookie_Jar::normalize_cookie}
* @return Requests_Cookie
*/
public function normalizeCookie($cookie, $key = null) {
return $this->normalize_cookie($cookie, $key);
}
/**
* Check if the given item exists
*
* @param string $key Item key
* @return boolean Does the item exist?
*/
public function offsetExists($key) {
return isset($this->cookies[$key]);
}
/**
* Get the value for the item
*
* @param string $key Item key
* @return string Item value
*/
public function offsetGet($key) {
if (!isset($this->cookies[$key])) {
return null;
}
return $this->cookies[$key];
}
/**
* Set the given item
*
* @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
*
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
$this->cookies[$key] = $value;
}
/**
* Unset the given header
*
* @param string $key
*/
public function offsetUnset($key) {
unset($this->cookies[$key]);
}
/**
* Get an iterator for the data
*
* @return ArrayIterator
*/
public function getIterator() {
return new ArrayIterator($this->cookies);
}
/**
* Register the cookie handler with the request's hooking system
*
* @param Requests_Hooker $hooks Hooking system
*/
public function register(Requests_Hooker $hooks) {
$hooks->register('requests.before_request', array($this, 'before_request'));
$hooks->register('requests.before_redirect_check', array($this, 'before_redirect_check'));
}
/**
* Add Cookie header to a request if we have any
*
* As per RFC 6265, cookies are separated by '; '
*
* @param string $url
* @param array $headers
* @param array $data
* @param string $type
* @param array $options
*/
public function before_request($url, &$headers, &$data, &$type, &$options) {
if (!$url instanceof Requests_IRI) {
$url = new Requests_IRI($url);
}
if (!empty($this->cookies)) {
$cookies = array();
foreach ($this->cookies as $key => $cookie) {
$cookie = $this->normalize_cookie($cookie, $key);
// Skip expired cookies
if ($cookie->is_expired()) {
continue;
}
if ($cookie->domain_matches($url->host)) {
$cookies[] = $cookie->format_for_header();
}
}
$headers['Cookie'] = implode('; ', $cookies);
}
}
/**
* Parse all cookies from a response and attach them to the response
*
* @var Requests_Response $response
*/
public function before_redirect_check(Requests_Response &$return) {
$url = $return->url;
if (!$url instanceof Requests_IRI) {
$url = new Requests_IRI($url);
}
$cookies = Requests_Cookie::parse_from_headers($return->headers, $url);
$this->cookies = array_merge($this->cookies, $cookies);
$return->cookies = $this;
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Exception for HTTP requests
*
* @package Requests
*/
/**
* Exception for HTTP requests
*
* @package Requests
*/
class Requests_Exception extends Exception {
/**
* Type of exception
*
* @var string
*/
protected $type;
/**
* Data associated with the exception
*
* @var mixed
*/
protected $data;
/**
* Create a new exception
*
* @param string $message Exception message
* @param string $type Exception type
* @param mixed $data Associated data
* @param integer $code Exception numerical code, if applicable
*/
public function __construct($message, $type, $data = null, $code = 0) {
parent::__construct($message, $code);
$this->type = $type;
$this->data = $data;
}
/**
* Like {@see getCode()}, but a string code.
*
* @codeCoverageIgnore
* @return string
*/
public function getType() {
return $this->type;
}
/**
* Gives any relevant data
*
* @codeCoverageIgnore
* @return mixed
*/
public function getData() {
return $this->data;
}
}

View File

@ -0,0 +1,71 @@
<?php
/**
* Exception based on HTTP response
*
* @package Requests
*/
/**
* Exception based on HTTP response
*
* @package Requests
*/
class Requests_Exception_HTTP extends Requests_Exception {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 0;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unknown';
/**
* Create a new exception
*
* There is no mechanism to pass in the status code, as this is set by the
* subclass used. Reason phrases can vary, however.
*
* @param string|null $reason Reason phrase
* @param mixed $data Associated data
*/
public function __construct($reason = null, $data = null) {
if ($reason !== null) {
$this->reason = $reason;
}
$message = sprintf('%d %s', $this->code, $this->reason);
parent::__construct($message, 'httpresponse', $data, $this->code);
}
/**
* Get the status message
*/
public function getReason() {
return $this->reason;
}
/**
* Get the correct exception class for a given error code
*
* @param int|bool $code HTTP status code, or false if unavailable
* @return string Exception class name to use
*/
public static function get_class($code) {
if (!$code) {
return 'Requests_Exception_HTTP_Unknown';
}
$class = sprintf('Requests_Exception_HTTP_%d', $code);
if (class_exists($class)) {
return $class;
}
return 'Requests_Exception_HTTP_Unknown';
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 304 Not Modified responses
*
* @package Requests
*/
/**
* Exception for 304 Not Modified responses
*
* @package Requests
*/
class Requests_Exception_HTTP_304 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 304;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Not Modified';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 305 Use Proxy responses
*
* @package Requests
*/
/**
* Exception for 305 Use Proxy responses
*
* @package Requests
*/
class Requests_Exception_HTTP_305 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 305;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Use Proxy';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 306 Switch Proxy responses
*
* @package Requests
*/
/**
* Exception for 306 Switch Proxy responses
*
* @package Requests
*/
class Requests_Exception_HTTP_306 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 306;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Switch Proxy';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 400 Bad Request responses
*
* @package Requests
*/
/**
* Exception for 400 Bad Request responses
*
* @package Requests
*/
class Requests_Exception_HTTP_400 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 400;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Bad Request';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 401 Unauthorized responses
*
* @package Requests
*/
/**
* Exception for 401 Unauthorized responses
*
* @package Requests
*/
class Requests_Exception_HTTP_401 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 401;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unauthorized';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 402 Payment Required responses
*
* @package Requests
*/
/**
* Exception for 402 Payment Required responses
*
* @package Requests
*/
class Requests_Exception_HTTP_402 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 402;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Payment Required';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 403 Forbidden responses
*
* @package Requests
*/
/**
* Exception for 403 Forbidden responses
*
* @package Requests
*/
class Requests_Exception_HTTP_403 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 403;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Forbidden';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 404 Not Found responses
*
* @package Requests
*/
/**
* Exception for 404 Not Found responses
*
* @package Requests
*/
class Requests_Exception_HTTP_404 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 404;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Not Found';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 405 Method Not Allowed responses
*
* @package Requests
*/
/**
* Exception for 405 Method Not Allowed responses
*
* @package Requests
*/
class Requests_Exception_HTTP_405 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 405;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Method Not Allowed';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 406 Not Acceptable responses
*
* @package Requests
*/
/**
* Exception for 406 Not Acceptable responses
*
* @package Requests
*/
class Requests_Exception_HTTP_406 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 406;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Not Acceptable';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 407 Proxy Authentication Required responses
*
* @package Requests
*/
/**
* Exception for 407 Proxy Authentication Required responses
*
* @package Requests
*/
class Requests_Exception_HTTP_407 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 407;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Proxy Authentication Required';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 408 Request Timeout responses
*
* @package Requests
*/
/**
* Exception for 408 Request Timeout responses
*
* @package Requests
*/
class Requests_Exception_HTTP_408 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 408;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request Timeout';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 409 Conflict responses
*
* @package Requests
*/
/**
* Exception for 409 Conflict responses
*
* @package Requests
*/
class Requests_Exception_HTTP_409 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 409;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Conflict';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 410 Gone responses
*
* @package Requests
*/
/**
* Exception for 410 Gone responses
*
* @package Requests
*/
class Requests_Exception_HTTP_410 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 410;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Gone';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 411 Length Required responses
*
* @package Requests
*/
/**
* Exception for 411 Length Required responses
*
* @package Requests
*/
class Requests_Exception_HTTP_411 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 411;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Length Required';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 412 Precondition Failed responses
*
* @package Requests
*/
/**
* Exception for 412 Precondition Failed responses
*
* @package Requests
*/
class Requests_Exception_HTTP_412 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 412;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Precondition Failed';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 413 Request Entity Too Large responses
*
* @package Requests
*/
/**
* Exception for 413 Request Entity Too Large responses
*
* @package Requests
*/
class Requests_Exception_HTTP_413 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 413;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request Entity Too Large';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 414 Request-URI Too Large responses
*
* @package Requests
*/
/**
* Exception for 414 Request-URI Too Large responses
*
* @package Requests
*/
class Requests_Exception_HTTP_414 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 414;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request-URI Too Large';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 415 Unsupported Media Type responses
*
* @package Requests
*/
/**
* Exception for 415 Unsupported Media Type responses
*
* @package Requests
*/
class Requests_Exception_HTTP_415 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 415;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unsupported Media Type';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 416 Requested Range Not Satisfiable responses
*
* @package Requests
*/
/**
* Exception for 416 Requested Range Not Satisfiable responses
*
* @package Requests
*/
class Requests_Exception_HTTP_416 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 416;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Requested Range Not Satisfiable';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 417 Expectation Failed responses
*
* @package Requests
*/
/**
* Exception for 417 Expectation Failed responses
*
* @package Requests
*/
class Requests_Exception_HTTP_417 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 417;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Expectation Failed';
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Exception for 418 I'm A Teapot responses
*
* @see https://tools.ietf.org/html/rfc2324
* @package Requests
*/
/**
* Exception for 418 I'm A Teapot responses
*
* @see https://tools.ietf.org/html/rfc2324
* @package Requests
*/
class Requests_Exception_HTTP_418 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 418;
/**
* Reason phrase
*
* @var string
*/
protected $reason = "I'm A Teapot";
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Exception for 428 Precondition Required responses
*
* @see https://tools.ietf.org/html/rfc6585
* @package Requests
*/
/**
* Exception for 428 Precondition Required responses
*
* @see https://tools.ietf.org/html/rfc6585
* @package Requests
*/
class Requests_Exception_HTTP_428 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 428;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Precondition Required';
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Exception for 429 Too Many Requests responses
*
* @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04
* @package Requests
*/
/**
* Exception for 429 Too Many Requests responses
*
* @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04
* @package Requests
*/
class Requests_Exception_HTTP_429 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 429;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Too Many Requests';
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Exception for 431 Request Header Fields Too Large responses
*
* @see https://tools.ietf.org/html/rfc6585
* @package Requests
*/
/**
* Exception for 431 Request Header Fields Too Large responses
*
* @see https://tools.ietf.org/html/rfc6585
* @package Requests
*/
class Requests_Exception_HTTP_431 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 431;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request Header Fields Too Large';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 500 Internal Server Error responses
*
* @package Requests
*/
/**
* Exception for 500 Internal Server Error responses
*
* @package Requests
*/
class Requests_Exception_HTTP_500 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 500;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Internal Server Error';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 501 Not Implemented responses
*
* @package Requests
*/
/**
* Exception for 501 Not Implemented responses
*
* @package Requests
*/
class Requests_Exception_HTTP_501 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 501;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Not Implemented';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 502 Bad Gateway responses
*
* @package Requests
*/
/**
* Exception for 502 Bad Gateway responses
*
* @package Requests
*/
class Requests_Exception_HTTP_502 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 502;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Bad Gateway';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 503 Service Unavailable responses
*
* @package Requests
*/
/**
* Exception for 503 Service Unavailable responses
*
* @package Requests
*/
class Requests_Exception_HTTP_503 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 503;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Service Unavailable';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 504 Gateway Timeout responses
*
* @package Requests
*/
/**
* Exception for 504 Gateway Timeout responses
*
* @package Requests
*/
class Requests_Exception_HTTP_504 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 504;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Gateway Timeout';
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Exception for 505 HTTP Version Not Supported responses
*
* @package Requests
*/
/**
* Exception for 505 HTTP Version Not Supported responses
*
* @package Requests
*/
class Requests_Exception_HTTP_505 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 505;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'HTTP Version Not Supported';
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Exception for 511 Network Authentication Required responses
*
* @see https://tools.ietf.org/html/rfc6585
* @package Requests
*/
/**
* Exception for 511 Network Authentication Required responses
*
* @see https://tools.ietf.org/html/rfc6585
* @package Requests
*/
class Requests_Exception_HTTP_511 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 511;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Network Authentication Required';
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Exception for unknown status responses
*
* @package Requests
*/
/**
* Exception for unknown status responses
*
* @package Requests
*/
class Requests_Exception_HTTP_Unknown extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer|bool Code if available, false if an error occurred
*/
protected $code = 0;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unknown';
/**
* Create a new exception
*
* If `$data` is an instance of {@see Requests_Response}, uses the status
* code from it. Otherwise, sets as 0
*
* @param string|null $reason Reason phrase
* @param mixed $data Associated data
*/
public function __construct($reason = null, $data = null) {
if ($data instanceof Requests_Response) {
$this->code = $data->status_code;
}
parent::__construct($reason, $data);
}
}

View File

@ -0,0 +1,5 @@
<?php
class Requests_Exception_Transport extends Requests_Exception {
}

View File

@ -0,0 +1,56 @@
<?php
class Requests_Exception_Transport_cURL extends Requests_Exception_Transport {
const EASY = 'cURLEasy';
const MULTI = 'cURLMulti';
const SHARE = 'cURLShare';
/**
* cURL error code
*
* @var integer
*/
protected $code = -1;
/**
* Which type of cURL error
*
* EASY|MULTI|SHARE
*
* @var string
*/
protected $type = 'Unknown';
/**
* Clear text error message
*
* @var string
*/
protected $reason = 'Unknown';
public function __construct($message, $type, $data = null, $code = 0) {
if ($type !== null) {
$this->type = $type;
}
if ($code !== null) {
$this->code = $code;
}
if ($message !== null) {
$this->reason = $message;
}
$message = sprintf('%d %s', $this->code, $this->reason);
parent::__construct($message, $this->type, $data, $this->code);
}
/**
* Get the error message
*/
public function getReason() {
return $this->reason;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Event dispatcher
*
* @package Requests
* @subpackage Utilities
*/
/**
* Event dispatcher
*
* @package Requests
* @subpackage Utilities
*/
interface Requests_Hooker {
/**
* Register a callback for a hook
*
* @param string $hook Hook name
* @param callback $callback Function/method to call on event
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later
*/
public function register($hook, $callback, $priority = 0);
/**
* Dispatch a message
*
* @param string $hook Hook name
* @param array $parameters Parameters to pass to callbacks
* @return boolean Successfulness
*/
public function dispatch($hook, $parameters = array());
}

View File

@ -0,0 +1,68 @@
<?php
/**
* Handles adding and dispatching events
*
* @package Requests
* @subpackage Utilities
*/
/**
* Handles adding and dispatching events
*
* @package Requests
* @subpackage Utilities
*/
class Requests_Hooks implements Requests_Hooker {
/**
* Registered callbacks for each hook
*
* @var array
*/
protected $hooks = array();
/**
* Constructor
*/
public function __construct() {
// pass
}
/**
* Register a callback for a hook
*
* @param string $hook Hook name
* @param callback $callback Function/method to call on event
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later
*/
public function register($hook, $callback, $priority = 0) {
if (!isset($this->hooks[$hook])) {
$this->hooks[$hook] = array();
}
if (!isset($this->hooks[$hook][$priority])) {
$this->hooks[$hook][$priority] = array();
}
$this->hooks[$hook][$priority][] = $callback;
}
/**
* Dispatch a message
*
* @param string $hook Hook name
* @param array $parameters Parameters to pass to callbacks
* @return boolean Successfulness
*/
public function dispatch($hook, $parameters = array()) {
if (empty($this->hooks[$hook])) {
return false;
}
foreach ($this->hooks[$hook] as $priority => $hooked) {
foreach ($hooked as $callback) {
call_user_func_array($callback, $parameters);
}
}
return true;
}
}

View File

@ -0,0 +1,388 @@
<?php
/**
* IDNA URL encoder
*
* Note: Not fully compliant, as nameprep does nothing yet.
*
* @package Requests
* @subpackage Utilities
* @see https://tools.ietf.org/html/rfc3490 IDNA specification
* @see https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
*/
class Requests_IDNAEncoder {
/**
* ACE prefix used for IDNA
*
* @see https://tools.ietf.org/html/rfc3490#section-5
* @var string
*/
const ACE_PREFIX = 'xn--';
/**#@+
* Bootstrap constant for Punycode
*
* @see https://tools.ietf.org/html/rfc3492#section-5
* @var int
*/
const BOOTSTRAP_BASE = 36;
const BOOTSTRAP_TMIN = 1;
const BOOTSTRAP_TMAX = 26;
const BOOTSTRAP_SKEW = 38;
const BOOTSTRAP_DAMP = 700;
const BOOTSTRAP_INITIAL_BIAS = 72;
const BOOTSTRAP_INITIAL_N = 128;
/**#@-*/
/**
* Encode a hostname using Punycode
*
* @param string $string Hostname
* @return string Punycode-encoded hostname
*/
public static function encode($string) {
$parts = explode('.', $string);
foreach ($parts as &$part) {
$part = self::to_ascii($part);
}
return implode('.', $parts);
}
/**
* Convert a UTF-8 string to an ASCII string using Punycode
*
* @throws Requests_Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
* @throws Requests_Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
* @throws Requests_Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
* @throws Requests_Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
*
* @param string $string ASCII or UTF-8 string (max length 64 characters)
* @return string ASCII string
*/
public static function to_ascii($string) {
// Step 1: Check if the string is already ASCII
if (self::is_ascii($string)) {
// Skip to step 7
if (strlen($string) < 64) {
return $string;
}
throw new Requests_Exception('Provided string is too long', 'idna.provided_too_long', $string);
}
// Step 2: nameprep
$string = self::nameprep($string);
// Step 3: UseSTD3ASCIIRules is false, continue
// Step 4: Check if it's ASCII now
if (self::is_ascii($string)) {
// Skip to step 7
if (strlen($string) < 64) {
return $string;
}
throw new Requests_Exception('Prepared string is too long', 'idna.prepared_too_long', $string);
}
// Step 5: Check ACE prefix
if (strpos($string, self::ACE_PREFIX) === 0) {
throw new Requests_Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $string);
}
// Step 6: Encode with Punycode
$string = self::punycode_encode($string);
// Step 7: Prepend ACE prefix
$string = self::ACE_PREFIX . $string;
// Step 8: Check size
if (strlen($string) < 64) {
return $string;
}
throw new Requests_Exception('Encoded string is too long', 'idna.encoded_too_long', $string);
}
/**
* Check whether a given string contains only ASCII characters
*
* @internal (Testing found regex was the fastest implementation)
*
* @param string $string
* @return bool Is the string ASCII-only?
*/
protected static function is_ascii($string) {
return (preg_match('/(?:[^\x00-\x7F])/', $string) !== 1);
}
/**
* Prepare a string for use as an IDNA name
*
* @todo Implement this based on RFC 3491 and the newer 5891
* @param string $string
* @return string Prepared string
*/
protected static function nameprep($string) {
return $string;
}
/**
* Convert a UTF-8 string to a UCS-4 codepoint array
*
* Based on Requests_IRI::replace_invalid_with_pct_encoding()
*
* @throws Requests_Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
* @param string $input
* @return array Unicode code points
*/
protected static function utf8_to_codepoints($input) {
$codepoints = array();
// Get number of bytes
$strlen = strlen($input);
for ($position = 0; $position < $strlen; $position++) {
$value = ord($input[$position]);
// One byte sequence:
if ((~$value & 0x80) === 0x80) {
$character = $value;
$length = 1;
$remaining = 0;
}
// Two byte sequence:
elseif (($value & 0xE0) === 0xC0) {
$character = ($value & 0x1F) << 6;
$length = 2;
$remaining = 1;
}
// Three byte sequence:
elseif (($value & 0xF0) === 0xE0) {
$character = ($value & 0x0F) << 12;
$length = 3;
$remaining = 2;
}
// Four byte sequence:
elseif (($value & 0xF8) === 0xF0) {
$character = ($value & 0x07) << 18;
$length = 4;
$remaining = 3;
}
// Invalid byte:
else {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
}
if ($remaining > 0) {
if ($position + $length > $strlen) {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
}
for ($position++; $remaining > 0; $position++) {
$value = ord($input[$position]);
// If it is invalid, count the sequence as invalid and reprocess the current byte:
if (($value & 0xC0) !== 0x80) {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
}
$character |= ($value & 0x3F) << (--$remaining * 6);
}
$position--;
}
if (
// Non-shortest form sequences are invalid
$length > 1 && $character <= 0x7F
|| $length > 2 && $character <= 0x7FF
|| $length > 3 && $character <= 0xFFFF
// Outside of range of ucschar codepoints
// Noncharacters
|| ($character & 0xFFFE) === 0xFFFE
|| $character >= 0xFDD0 && $character <= 0xFDEF
|| (
// Everything else not in ucschar
$character > 0xD7FF && $character < 0xF900
|| $character < 0x20
|| $character > 0x7E && $character < 0xA0
|| $character > 0xEFFFD
)
) {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
}
$codepoints[] = $character;
}
return $codepoints;
}
/**
* RFC3492-compliant encoder
*
* @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
* @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
*
* @param string $input UTF-8 encoded string to encode
* @return string Punycode-encoded string
*/
public static function punycode_encode($input) {
$output = '';
# let n = initial_n
$n = self::BOOTSTRAP_INITIAL_N;
# let delta = 0
$delta = 0;
# let bias = initial_bias
$bias = self::BOOTSTRAP_INITIAL_BIAS;
# let h = b = the number of basic code points in the input
$h = $b = 0; // see loop
# copy them to the output in order
$codepoints = self::utf8_to_codepoints($input);
$extended = array();
foreach ($codepoints as $char) {
if ($char < 128) {
// Character is valid ASCII
// TODO: this should also check if it's valid for a URL
$output .= chr($char);
$h++;
}
// Check if the character is non-ASCII, but below initial n
// This never occurs for Punycode, so ignore in coverage
// @codeCoverageIgnoreStart
elseif ($char < $n) {
throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char);
}
// @codeCoverageIgnoreEnd
else {
$extended[$char] = true;
}
}
$extended = array_keys($extended);
sort($extended);
$b = $h;
# [copy them] followed by a delimiter if b > 0
if (strlen($output) > 0) {
$output .= '-';
}
# {if the input contains a non-basic code point < n then fail}
# while h < length(input) do begin
while ($h < count($codepoints)) {
# let m = the minimum code point >= n in the input
$m = array_shift($extended);
//printf('next code point to insert is %s' . PHP_EOL, dechex($m));
# let delta = delta + (m - n) * (h + 1), fail on overflow
$delta += ($m - $n) * ($h + 1);
# let n = m
$n = $m;
# for each code point c in the input (in order) do begin
for ($num = 0; $num < count($codepoints); $num++) {
$c = $codepoints[$num];
# if c < n then increment delta, fail on overflow
if ($c < $n) {
$delta++;
}
# if c == n then begin
elseif ($c === $n) {
# let q = delta
$q = $delta;
# for k = base to infinity in steps of base do begin
for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) {
# let t = tmin if k <= bias {+ tmin}, or
# tmax if k >= bias + tmax, or k - bias otherwise
if ($k <= ($bias + self::BOOTSTRAP_TMIN)) {
$t = self::BOOTSTRAP_TMIN;
}
elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) {
$t = self::BOOTSTRAP_TMAX;
}
else {
$t = $k - $bias;
}
# if q < t then break
if ($q < $t) {
break;
}
# output the code point for digit t + ((q - t) mod (base - t))
$digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t));
$output .= self::digit_to_char($digit);
# let q = (q - t) div (base - t)
$q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
# end
}
# output the code point for digit q
$output .= self::digit_to_char($q);
# let bias = adapt(delta, h + 1, test h equals b?)
$bias = self::adapt($delta, $h + 1, $h === $b);
# let delta = 0
$delta = 0;
# increment h
$h++;
# end
}
# end
}
# increment delta and n
$delta++;
$n++;
# end
}
return $output;
}
/**
* Convert a digit to its respective character
*
* @see https://tools.ietf.org/html/rfc3492#section-5
* @throws Requests_Exception On invalid digit (`idna.invalid_digit`)
*
* @param int $digit Digit in the range 0-35
* @return string Single character corresponding to digit
*/
protected static function digit_to_char($digit) {
// @codeCoverageIgnoreStart
// As far as I know, this never happens, but still good to be sure.
if ($digit < 0 || $digit > 35) {
throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
}
// @codeCoverageIgnoreEnd
$digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
return substr($digits, $digit, 1);
}
/**
* Adapt the bias
*
* @see https://tools.ietf.org/html/rfc3492#section-6.1
* @param int $delta
* @param int $numpoints
* @param bool $firsttime
* @return int New bias
*/
protected static function adapt($delta, $numpoints, $firsttime) {
# function adapt(delta,numpoints,firsttime):
# if firsttime then let delta = delta div damp
if ($firsttime) {
$delta = floor($delta / self::BOOTSTRAP_DAMP);
}
# else let delta = delta div 2
else {
$delta = floor($delta / 2);
}
# let delta = delta + (delta div numpoints)
$delta += floor($delta / $numpoints);
# let k = 0
$k = 0;
# while delta > ((base - tmin) * tmax) div 2 do begin
$max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2);
while ($delta > $max) {
# let delta = delta div (base - tmin)
$delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN));
# let k = k + base
$k += self::BOOTSTRAP_BASE;
# end
}
# return k + (((base - tmin + 1) * delta) div (delta + skew))
return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW));
}
}

View File

@ -0,0 +1,190 @@
<?php
/**
* Class to validate and to work with IPv6 addresses
*
* @package Requests
* @subpackage Utilities
*/
/**
* Class to validate and to work with IPv6 addresses
*
* This was originally based on the PEAR class of the same name, but has been
* entirely rewritten.
*
* @package Requests
* @subpackage Utilities
*/
class Requests_IPv6 {
/**
* Uncompresses an IPv6 address
*
* RFC 4291 allows you to compress consecutive zero pieces in an address to
* '::'. This method expects a valid IPv6 address and expands the '::' to
* the required number of zero pieces.
*
* Example: FF01::101 -> FF01:0:0:0:0:0:0:101
* ::1 -> 0:0:0:0:0:0:0:1
*
* @author Alexander Merz <alexander.merz@web.de>
* @author elfrink at introweb dot nl
* @author Josh Peck <jmp at joshpeck dot org>
* @copyright 2003-2005 The PHP Group
* @license http://www.opensource.org/licenses/bsd-license.php
* @param string $ip An IPv6 address
* @return string The uncompressed IPv6 address
*/
public static function uncompress($ip) {
if (substr_count($ip, '::') !== 1) {
return $ip;
}
list($ip1, $ip2) = explode('::', $ip);
$c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':');
$c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':');
if (strpos($ip2, '.') !== false) {
$c2++;
}
// ::
if ($c1 === -1 && $c2 === -1) {
$ip = '0:0:0:0:0:0:0:0';
}
// ::xxx
else if ($c1 === -1) {
$fill = str_repeat('0:', 7 - $c2);
$ip = str_replace('::', $fill, $ip);
}
// xxx::
else if ($c2 === -1) {
$fill = str_repeat(':0', 7 - $c1);
$ip = str_replace('::', $fill, $ip);
}
// xxx::xxx
else {
$fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
$ip = str_replace('::', $fill, $ip);
}
return $ip;
}
/**
* Compresses an IPv6 address
*
* RFC 4291 allows you to compress consecutive zero pieces in an address to
* '::'. This method expects a valid IPv6 address and compresses consecutive
* zero pieces to '::'.
*
* Example: FF01:0:0:0:0:0:0:101 -> FF01::101
* 0:0:0:0:0:0:0:1 -> ::1
*
* @see uncompress()
* @param string $ip An IPv6 address
* @return string The compressed IPv6 address
*/
public static function compress($ip) {
// Prepare the IP to be compressed
$ip = self::uncompress($ip);
$ip_parts = self::split_v6_v4($ip);
// Replace all leading zeros
$ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
// Find bunches of zeros
if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
$max = 0;
$pos = null;
foreach ($matches[0] as $match) {
if (strlen($match[0]) > $max) {
$max = strlen($match[0]);
$pos = $match[1];
}
}
$ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
}
if ($ip_parts[1] !== '') {
return implode(':', $ip_parts);
}
else {
return $ip_parts[0];
}
}
/**
* Splits an IPv6 address into the IPv6 and IPv4 representation parts
*
* RFC 4291 allows you to represent the last two parts of an IPv6 address
* using the standard IPv4 representation
*
* Example: 0:0:0:0:0:0:13.1.68.3
* 0:0:0:0:0:FFFF:129.144.52.38
*
* @param string $ip An IPv6 address
* @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
*/
protected static function split_v6_v4($ip) {
if (strpos($ip, '.') !== false) {
$pos = strrpos($ip, ':');
$ipv6_part = substr($ip, 0, $pos);
$ipv4_part = substr($ip, $pos + 1);
return array($ipv6_part, $ipv4_part);
}
else {
return array($ip, '');
}
}
/**
* Checks an IPv6 address
*
* Checks if the given IP is a valid IPv6 address
*
* @param string $ip An IPv6 address
* @return bool true if $ip is a valid IPv6 address
*/
public static function check_ipv6($ip) {
$ip = self::uncompress($ip);
list($ipv6, $ipv4) = self::split_v6_v4($ip);
$ipv6 = explode(':', $ipv6);
$ipv4 = explode('.', $ipv4);
if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) {
foreach ($ipv6 as $ipv6_part) {
// The section can't be empty
if ($ipv6_part === '') {
return false;
}
// Nor can it be over four characters
if (strlen($ipv6_part) > 4) {
return false;
}
// Remove leading zeros (this is safe because of the above)
$ipv6_part = ltrim($ipv6_part, '0');
if ($ipv6_part === '') {
$ipv6_part = '0';
}
// Check the value is valid
$value = hexdec($ipv6_part);
if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) {
return false;
}
}
if (count($ipv4) === 4) {
foreach ($ipv4 as $ipv4_part) {
$value = (int) $ipv4_part;
if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
return false;
}
}
}
return true;
}
else {
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
<?php
/**
* Proxy connection interface
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
/**
* Proxy connection interface
*
* Implement this interface to handle proxy settings and authentication
*
* Parameters should be passed via the constructor where possible, as this
* makes it much easier for users to use your provider.
*
* @see Requests_Hooks
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
interface Requests_Proxy {
/**
* Register hooks as needed
*
* This method is called in {@see Requests::request} when the user has set
* an instance as the 'auth' option. Use this callback to register all the
* hooks you'll need.
*
* @see Requests_Hooks::register
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks);
}

View File

@ -0,0 +1,151 @@
<?php
/**
* HTTP Proxy connection interface
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
/**
* HTTP Proxy connection interface
*
* Provides a handler for connection via an HTTP proxy
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
class Requests_Proxy_HTTP implements Requests_Proxy {
/**
* Proxy host and port
*
* Notation: "host:port" (eg 127.0.0.1:8080 or someproxy.com:3128)
*
* @var string
*/
public $proxy;
/**
* Username
*
* @var string
*/
public $user;
/**
* Password
*
* @var string
*/
public $pass;
/**
* Do we need to authenticate? (ie username & password have been provided)
*
* @var boolean
*/
public $use_authentication;
/**
* Constructor
*
* @since 1.6
* @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
* @param array|null $args Array of user and password. Must have exactly two elements
*/
public function __construct($args = null) {
if (is_string($args)) {
$this->proxy = $args;
}
elseif (is_array($args)) {
if (count($args) == 1) {
list($this->proxy) = $args;
}
elseif (count($args) == 3) {
list($this->proxy, $this->user, $this->pass) = $args;
$this->use_authentication = true;
}
else {
throw new Requests_Exception('Invalid number of arguments', 'proxyhttpbadargs');
}
}
}
/**
* Register the necessary callbacks
*
* @since 1.6
* @see curl_before_send
* @see fsockopen_remote_socket
* @see fsockopen_remote_host_path
* @see fsockopen_header
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks) {
$hooks->register('curl.before_send', array(&$this, 'curl_before_send'));
$hooks->register('fsockopen.remote_socket', array(&$this, 'fsockopen_remote_socket'));
$hooks->register('fsockopen.remote_host_path', array(&$this, 'fsockopen_remote_host_path'));
if ($this->use_authentication) {
$hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header'));
}
}
/**
* Set cURL parameters before the data is sent
*
* @since 1.6
* @param resource $handle cURL resource
*/
public function curl_before_send(&$handle) {
curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
curl_setopt($handle, CURLOPT_PROXY, $this->proxy);
if ($this->use_authentication) {
curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string());
}
}
/**
* Alter remote socket information before opening socket connection
*
* @since 1.6
* @param string $remote_socket Socket connection string
*/
public function fsockopen_remote_socket(&$remote_socket) {
$remote_socket = $this->proxy;
}
/**
* Alter remote path before getting stream data
*
* @since 1.6
* @param string $path Path to send in HTTP request string ("GET ...")
* @param string $url Full URL we're requesting
*/
public function fsockopen_remote_host_path(&$path, $url) {
$path = $url;
}
/**
* Add extra headers to the request before sending
*
* @since 1.6
* @param string $out HTTP header string
*/
public function fsockopen_header(&$out) {
$out .= sprintf("Proxy-Authorization: Basic %s\r\n", base64_encode($this->get_auth_string()));
}
/**
* Get the authentication string (user:pass)
*
* @since 1.6
* @return string
*/
public function get_auth_string() {
return $this->user . ':' . $this->pass;
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* HTTP response class
*
* Contains a response from Requests::request()
* @package Requests
*/
/**
* HTTP response class
*
* Contains a response from Requests::request()
* @package Requests
*/
class Requests_Response {
/**
* Constructor
*/
public function __construct() {
$this->headers = new Requests_Response_Headers();
$this->cookies = new Requests_Cookie_Jar();
}
/**
* Response body
*
* @var string
*/
public $body = '';
/**
* Raw HTTP data from the transport
*
* @var string
*/
public $raw = '';
/**
* Headers, as an associative array
*
* @var Requests_Response_Headers Array-like object representing headers
*/
public $headers = array();
/**
* Status code, false if non-blocking
*
* @var integer|boolean
*/
public $status_code = false;
/**
* Protocol version, false if non-blocking
* @var float|boolean
*/
public $protocol_version = false;
/**
* Whether the request succeeded or not
*
* @var boolean
*/
public $success = false;
/**
* Number of redirects the request used
*
* @var integer
*/
public $redirects = 0;
/**
* URL requested
*
* @var string
*/
public $url = '';
/**
* Previous requests (from redirects)
*
* @var array Array of Requests_Response objects
*/
public $history = array();
/**
* Cookies from the request
*
* @var Requests_Cookie_Jar Array-like object representing a cookie jar
*/
public $cookies = array();
/**
* Is the response a redirect?
*
* @return boolean True if redirect (3xx status), false if not.
*/
public function is_redirect() {
$code = $this->status_code;
return in_array($code, array(300, 301, 302, 303, 307)) || $code > 307 && $code < 400;
}
/**
* Throws an exception if the request was not successful
*
* @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
* @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404})
* @param boolean $allow_redirects Set to false to throw on a 3xx as well
*/
public function throw_for_status($allow_redirects = true) {
if ($this->is_redirect()) {
if (!$allow_redirects) {
throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this);
}
}
elseif (!$this->success) {
$exception = Requests_Exception_HTTP::get_class($this->status_code);
throw new $exception(null, $this);
}
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* Case-insensitive dictionary, suitable for HTTP headers
*
* @package Requests
*/
/**
* Case-insensitive dictionary, suitable for HTTP headers
*
* @package Requests
*/
class Requests_Response_Headers extends Requests_Utility_CaseInsensitiveDictionary {
/**
* Get the given header
*
* Unlike {@see self::getValues()}, this returns a string. If there are
* multiple values, it concatenates them with a comma as per RFC2616.
*
* Avoid using this where commas may be used unquoted in values, such as
* Set-Cookie headers.
*
* @param string $key
* @return string Header value
*/
public function offsetGet($key) {
$key = strtolower($key);
if (!isset($this->data[$key])) {
return null;
}
return $this->flatten($this->data[$key]);
}
/**
* Set the given item
*
* @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
*
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
$key = strtolower($key);
if (!isset($this->data[$key])) {
$this->data[$key] = array();
}
$this->data[$key][] = $value;
}
/**
* Get all values for a given header
*
* @param string $key
* @return array Header values
*/
public function getValues($key) {
$key = strtolower($key);
if (!isset($this->data[$key])) {
return null;
}
return $this->data[$key];
}
/**
* Flattens a value into a string
*
* Converts an array into a string by imploding values with a comma, as per
* RFC2616's rules for folding headers.
*
* @param string|array $value Value to flatten
* @return string Flattened value
*/
public function flatten($value) {
if (is_array($value)) {
$value = implode(',', $value);
}
return $value;
}
/**
* Get an iterator for the data
*
* Converts the internal
* @return ArrayIterator
*/
public function getIterator() {
return new Requests_Utility_FilteredIterator($this->data, array($this, 'flatten'));
}
}

View File

@ -0,0 +1,152 @@
<?php
/**
* SSL utilities for Requests
*
* @package Requests
* @subpackage Utilities
*/
/**
* SSL utilities for Requests
*
* Collection of utilities for working with and verifying SSL certificates.
*
* @package Requests
* @subpackage Utilities
*/
class Requests_SSL {
/**
* Verify the certificate against common name and subject alternative names
*
* Unfortunately, PHP doesn't check the certificate against the alternative
* names, leading things like 'https://www.github.com/' to be invalid.
* Instead
*
* @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
*
* @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
* @param string $host Host name to verify against
* @param array $cert Certificate data from openssl_x509_parse()
* @return bool
*/
public static function verify_certificate($host, $cert) {
// Calculate the valid wildcard match if the host is not an IP address
$parts = explode('.', $host);
if (ip2long($host) === false) {
$parts[0] = '*';
}
$wildcard = implode('.', $parts);
$has_dns_alt = false;
// Check the subjectAltName
if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
$altnames = explode(',', $cert['extensions']['subjectAltName']);
foreach ($altnames as $altname) {
$altname = trim($altname);
if (strpos($altname, 'DNS:') !== 0) {
continue;
}
$has_dns_alt = true;
// Strip the 'DNS:' prefix and trim whitespace
$altname = trim(substr($altname, 4));
// Check for a match
if (self::match_domain($host, $altname) === true) {
return true;
}
}
}
// Fall back to checking the common name if we didn't get any dNSName
// alt names, as per RFC2818
if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
// Check for a match
if (self::match_domain($host, $cert['subject']['CN']) === true) {
return true;
}
}
return false;
}
/**
* Verify that a reference name is valid
*
* Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
* - Wildcards can only occur in a name with more than 3 components
* - Wildcards can only occur as the last character in the first
* component
* - Wildcards may be preceded by additional characters
*
* We modify these rules to be a bit stricter and only allow the wildcard
* character to be the full first component; that is, with the exclusion of
* the third rule.
*
* @param string $reference Reference dNSName
* @return boolean Is the name valid?
*/
public static function verify_reference_name($reference) {
$parts = explode('.', $reference);
// Check the first part of the name
$first = array_shift($parts);
if (strpos($first, '*') !== false) {
// Check that the wildcard is the full part
if ($first !== '*') {
return false;
}
// Check that we have at least 3 components (including first)
if (count($parts) < 2) {
return false;
}
}
// Check the remaining parts
foreach ($parts as $part) {
if (strpos($part, '*') !== false) {
return false;
}
}
// Nothing found, verified!
return true;
}
/**
* Match a hostname against a dNSName reference
*
* @param string $host Requested host
* @param string $reference dNSName to match against
* @return boolean Does the domain match?
*/
public static function match_domain($host, $reference) {
// Check if the reference is blacklisted first
if (self::verify_reference_name($reference) !== true) {
return false;
}
// Check for a direct match
if ($host === $reference) {
return true;
}
// Calculate the valid wildcard match if the host is not an IP address
// Also validates that the host has 3 parts or more, as per Firefox's
// ruleset.
if (ip2long($host) === false) {
$parts = explode('.', $host);
$parts[0] = '*';
$wildcard = implode('.', $parts);
if ($wildcard === $reference) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,266 @@
<?php
/**
* Session handler for persistent requests and default parameters
*
* @package Requests
* @subpackage Session Handler
*/
/**
* Session handler for persistent requests and default parameters
*
* Allows various options to be set as default values, and merges both the
* options and URL properties together. A base URL can be set for all requests,
* with all subrequests resolved from this. Base options can be set (including
* a shared cookie jar), then overridden for individual requests.
*
* @package Requests
* @subpackage Session Handler
*/
class Requests_Session {
/**
* Base URL for requests
*
* URLs will be made absolute using this as the base
* @var string|null
*/
public $url = null;
/**
* Base headers for requests
* @var array
*/
public $headers = array();
/**
* Base data for requests
*
* If both the base data and the per-request data are arrays, the data will
* be merged before sending the request.
*
* @var array
*/
public $data = array();
/**
* Base options for requests
*
* The base options are merged with the per-request data for each request.
* The only default option is a shared cookie jar between requests.
*
* Values here can also be set directly via properties on the Session
* object, e.g. `$session->useragent = 'X';`
*
* @var array
*/
public $options = array();
/**
* Create a new session
*
* @param string|null $url Base URL for requests
* @param array $headers Default headers for requests
* @param array $data Default data for requests
* @param array $options Default options for requests
*/
public function __construct($url = null, $headers = array(), $data = array(), $options = array()) {
$this->url = $url;
$this->headers = $headers;
$this->data = $data;
$this->options = $options;
if (empty($this->options['cookies'])) {
$this->options['cookies'] = new Requests_Cookie_Jar();
}
}
/**
* Get a property's value
*
* @param string $key Property key
* @return mixed|null Property value, null if none found
*/
public function __get($key) {
if (isset($this->options[$key])) {
return $this->options[$key];
}
return null;
}
/**
* Set a property's value
*
* @param string $key Property key
* @param mixed $value Property value
*/
public function __set($key, $value) {
$this->options[$key] = $value;
}
/**
* Remove a property's value
*
* @param string $key Property key
*/
public function __isset($key) {
return isset($this->options[$key]);
}
/**
* Remove a property's value
*
* @param string $key Property key
*/
public function __unset($key) {
if (isset($this->options[$key])) {
unset($this->options[$key]);
}
}
/**#@+
* @see request()
* @param string $url
* @param array $headers
* @param array $options
* @return Requests_Response
*/
/**
* Send a GET request
*/
public function get($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::GET, $options);
}
/**
* Send a HEAD request
*/
public function head($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::HEAD, $options);
}
/**
* Send a DELETE request
*/
public function delete($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::DELETE, $options);
}
/**#@-*/
/**#@+
* @see request()
* @param string $url
* @param array $headers
* @param array $data
* @param array $options
* @return Requests_Response
*/
/**
* Send a POST request
*/
public function post($url, $headers = array(), $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::POST, $options);
}
/**
* Send a PUT request
*/
public function put($url, $headers = array(), $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::PUT, $options);
}
/**
* Send a PATCH request
*
* Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
* specification recommends that should send an ETag
*
* @link https://tools.ietf.org/html/rfc5789
*/
public function patch($url, $headers, $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::PATCH, $options);
}
/**#@-*/
/**
* Main interface for HTTP requests
*
* This method initiates a request and sends it via a transport before
* parsing.
*
* @see Requests::request()
*
* @throws Requests_Exception On invalid URLs (`nonhttp`)
*
* @param string $url URL to request
* @param array $headers Extra headers to send with the request
* @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type (use Requests constants)
* @param array $options Options for the request (see {@see Requests::request})
* @return Requests_Response
*/
public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) {
$request = $this->merge_request(compact('url', 'headers', 'data', 'options'));
return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
}
/**
* Send multiple HTTP requests simultaneously
*
* @see Requests::request_multiple()
*
* @param array $requests Requests data (see {@see Requests::request_multiple})
* @param array $options Global and default options (see {@see Requests::request})
* @return array Responses (either Requests_Response or a Requests_Exception object)
*/
public function request_multiple($requests, $options = array()) {
foreach ($requests as $key => $request) {
$requests[$key] = $this->merge_request($request, false);
}
$options = array_merge($this->options, $options);
// Disallow forcing the type, as that's a per request setting
unset($options['type']);
return Requests::request_multiple($requests, $options);
}
/**
* Merge a request's data with the default data
*
* @param array $request Request data (same form as {@see request_multiple})
* @param boolean $merge_options Should we merge options as well?
* @return array Request data
*/
protected function merge_request($request, $merge_options = true) {
if ($this->url !== null) {
$request['url'] = Requests_IRI::absolutize($this->url, $request['url']);
$request['url'] = $request['url']->uri;
}
if (empty($request['headers'])) {
$request['headers'] = array();
}
$request['headers'] = array_merge($this->headers, $request['headers']);
if (empty($request['data'])) {
if (is_array($this->data)) {
$request['data'] = $this->data;
}
}
elseif (is_array($request['data']) && is_array($this->data)) {
$request['data'] = array_merge($this->data, $request['data']);
}
if ($merge_options !== false) {
$request['options'] = array_merge($this->options, $request['options']);
// Disallow forcing the type, as that's a per request setting
unset($request['options']['type']);
}
return $request;
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* Base HTTP transport
*
* @package Requests
* @subpackage Transport
*/
/**
* Base HTTP transport
*
* @package Requests
* @subpackage Transport
*/
interface Requests_Transport {
/**
* Perform a request
*
* @param string $url URL to request
* @param array $headers Associative array of request headers
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
* @param array $options Request options, see {@see Requests::response()} for documentation
* @return string Raw HTTP result
*/
public function request($url, $headers = array(), $data = array(), $options = array());
/**
* Send multiple requests simultaneously
*
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
* @param array $options Global options, see {@see Requests::response()} for documentation
* @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
*/
public function request_multiple($requests, $options);
/**
* Self-test whether the transport can be used
* @return bool
*/
public static function test();
}

View File

@ -0,0 +1,542 @@
<?php
/**
* cURL HTTP transport
*
* @package Requests
* @subpackage Transport
*/
/**
* cURL HTTP transport
*
* @package Requests
* @subpackage Transport
*/
class Requests_Transport_cURL implements Requests_Transport {
const CURL_7_10_5 = 0x070A05;
const CURL_7_16_2 = 0x071002;
/**
* Raw HTTP data
*
* @var string
*/
public $headers = '';
/**
* Raw body data
*
* @var string
*/
public $response_data = '';
/**
* Information on the current request
*
* @var array cURL information array, see {@see https://secure.php.net/curl_getinfo}
*/
public $info;
/**
* Version string
*
* @var long
*/
public $version;
/**
* cURL handle
*
* @var resource
*/
protected $handle;
/**
* Hook dispatcher instance
*
* @var Requests_Hooks
*/
protected $hooks;
/**
* Have we finished the headers yet?
*
* @var boolean
*/
protected $done_headers = false;
/**
* If streaming to a file, keep the file pointer
*
* @var resource
*/
protected $stream_handle;
/**
* How many bytes are in the response body?
*
* @var int
*/
protected $response_bytes;
/**
* What's the maximum number of bytes we should keep?
*
* @var int|bool Byte count, or false if no limit.
*/
protected $response_byte_limit;
/**
* Constructor
*/
public function __construct() {
$curl = curl_version();
$this->version = $curl['version_number'];
$this->handle = curl_init();
curl_setopt($this->handle, CURLOPT_HEADER, false);
curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
if ($this->version >= self::CURL_7_10_5) {
curl_setopt($this->handle, CURLOPT_ENCODING, '');
}
if (defined('CURLOPT_PROTOCOLS')) {
curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
}
if (defined('CURLOPT_REDIR_PROTOCOLS')) {
curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
}
}
/**
* Destructor
*/
public function __destruct() {
if (is_resource($this->handle)) {
curl_close($this->handle);
}
}
/**
* Perform a request
*
* @throws Requests_Exception On a cURL error (`curlerror`)
*
* @param string $url URL to request
* @param array $headers Associative array of request headers
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
* @param array $options Request options, see {@see Requests::response()} for documentation
* @return string Raw HTTP result
*/
public function request($url, $headers = array(), $data = array(), $options = array()) {
$this->hooks = $options['hooks'];
$this->setup_handle($url, $headers, $data, $options);
$options['hooks']->dispatch('curl.before_send', array(&$this->handle));
if ($options['filename'] !== false) {
$this->stream_handle = fopen($options['filename'], 'wb');
}
$this->response_data = '';
$this->response_bytes = 0;
$this->response_byte_limit = false;
if ($options['max_bytes'] !== false) {
$this->response_byte_limit = $options['max_bytes'];
}
if (isset($options['verify'])) {
if ($options['verify'] === false) {
curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
}
elseif (is_string($options['verify'])) {
curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
}
}
if (isset($options['verifyname']) && $options['verifyname'] === false) {
curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
}
curl_exec($this->handle);
$response = $this->response_data;
$options['hooks']->dispatch('curl.after_send', array());
if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) {
// Reset encoding and try again
curl_setopt($this->handle, CURLOPT_ENCODING, 'none');
$this->response_data = '';
$this->response_bytes = 0;
curl_exec($this->handle);
$response = $this->response_data;
}
$this->process_response($response, $options);
// Need to remove the $this reference from the curl handle.
// Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called.
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
return $this->headers;
}
/**
* Send multiple requests simultaneously
*
* @param array $requests Request data
* @param array $options Global options
* @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
*/
public function request_multiple($requests, $options) {
// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
if (empty($requests)) {
return array();
}
$multihandle = curl_multi_init();
$subrequests = array();
$subhandles = array();
$class = get_class($this);
foreach ($requests as $id => $request) {
$subrequests[$id] = new $class();
$subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
$request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id]));
curl_multi_add_handle($multihandle, $subhandles[$id]);
}
$completed = 0;
$responses = array();
$request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));
do {
$active = false;
do {
$status = curl_multi_exec($multihandle, $active);
}
while ($status === CURLM_CALL_MULTI_PERFORM);
$to_process = array();
// Read the information as needed
while ($done = curl_multi_info_read($multihandle)) {
$key = array_search($done['handle'], $subhandles, true);
if (!isset($to_process[$key])) {
$to_process[$key] = $done;
}
}
// Parse the finished requests before we start getting the new ones
foreach ($to_process as $key => $done) {
$options = $requests[$key]['options'];
if (CURLE_OK !== $done['result']) {
//get error string for handle.
$reason = curl_error($done['handle']);
$exception = new Requests_Exception_Transport_cURL(
$reason,
Requests_Exception_Transport_cURL::EASY,
$done['handle'],
$done['result']
);
$responses[$key] = $exception;
$options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key]));
}
else {
$responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
$options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key]));
}
curl_multi_remove_handle($multihandle, $done['handle']);
curl_close($done['handle']);
if (!is_string($responses[$key])) {
$options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key));
}
$completed++;
}
}
while ($active || $completed < count($subrequests));
$request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle));
curl_multi_close($multihandle);
return $responses;
}
/**
* Get the cURL handle for use in a multi-request
*
* @param string $url URL to request
* @param array $headers Associative array of request headers
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
* @param array $options Request options, see {@see Requests::response()} for documentation
* @return resource Subrequest's cURL handle
*/
public function &get_subrequest_handle($url, $headers, $data, $options) {
$this->setup_handle($url, $headers, $data, $options);
if ($options['filename'] !== false) {
$this->stream_handle = fopen($options['filename'], 'wb');
}
$this->response_data = '';
$this->response_bytes = 0;
$this->response_byte_limit = false;
if ($options['max_bytes'] !== false) {
$this->response_byte_limit = $options['max_bytes'];
}
$this->hooks = $options['hooks'];
return $this->handle;
}
/**
* Setup the cURL handle for the given data
*
* @param string $url URL to request
* @param array $headers Associative array of request headers
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
* @param array $options Request options, see {@see Requests::response()} for documentation
*/
protected function setup_handle($url, $headers, $data, $options) {
$options['hooks']->dispatch('curl.before_request', array(&$this->handle));
// Force closing the connection for old versions of cURL (<7.22).
if ( ! isset( $headers['Connection'] ) ) {
$headers['Connection'] = 'close';
}
$headers = Requests::flatten($headers);
if (!empty($data)) {
$data_format = $options['data_format'];
if ($data_format === 'query') {
$url = self::format_get($url, $data);
$data = '';
}
elseif (!is_string($data)) {
$data = http_build_query($data, null, '&');
}
}
switch ($options['type']) {
case Requests::POST:
curl_setopt($this->handle, CURLOPT_POST, true);
curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
break;
case Requests::HEAD:
curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
curl_setopt($this->handle, CURLOPT_NOBODY, true);
break;
case Requests::TRACE:
curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
break;
case Requests::PATCH:
case Requests::PUT:
case Requests::DELETE:
case Requests::OPTIONS:
default:
curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
if (!empty($data)) {
curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
}
}
// cURL requires a minimum timeout of 1 second when using the system
// DNS resolver, as it uses `alarm()`, which is second resolution only.
// There's no way to detect which DNS resolver is being used from our
// end, so we need to round up regardless of the supplied timeout.
//
// https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
$timeout = max($options['timeout'], 1);
if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
}
else {
curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
}
if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
}
else {
curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
}
curl_setopt($this->handle, CURLOPT_URL, $url);
curl_setopt($this->handle, CURLOPT_REFERER, $url);
curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
if (!empty($headers)) {
curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
}
if ($options['protocol_version'] === 1.1) {
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
}
else {
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
}
if (true === $options['blocking']) {
curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers'));
curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body'));
curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
}
}
/**
* Process a response
*
* @param string $response Response data from the body
* @param array $options Request options
* @return string HTTP response data including headers
*/
public function process_response($response, $options) {
if ($options['blocking'] === false) {
$fake_headers = '';
$options['hooks']->dispatch('curl.after_request', array(&$fake_headers));
return false;
}
if ($options['filename'] !== false) {
fclose($this->stream_handle);
$this->headers = trim($this->headers);
}
else {
$this->headers .= $response;
}
if (curl_errno($this->handle)) {
$error = sprintf(
'cURL error %s: %s',
curl_errno($this->handle),
curl_error($this->handle)
);
throw new Requests_Exception($error, 'curlerror', $this->handle);
}
$this->info = curl_getinfo($this->handle);
$options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info));
return $this->headers;
}
/**
* Collect the headers as they are received
*
* @param resource $handle cURL resource
* @param string $headers Header string
* @return integer Length of provided header
*/
public function stream_headers($handle, $headers) {
// Why do we do this? cURL will send both the final response and any
// interim responses, such as a 100 Continue. We don't need that.
// (We may want to keep this somewhere just in case)
if ($this->done_headers) {
$this->headers = '';
$this->done_headers = false;
}
$this->headers .= $headers;
if ($headers === "\r\n") {
$this->done_headers = true;
}
return strlen($headers);
}
/**
* Collect data as it's received
*
* @since 1.6.1
*
* @param resource $handle cURL resource
* @param string $data Body data
* @return integer Length of provided data
*/
public function stream_body($handle, $data) {
$this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit));
$data_length = strlen($data);
// Are we limiting the response size?
if ($this->response_byte_limit) {
if ($this->response_bytes === $this->response_byte_limit) {
// Already at maximum, move on
return $data_length;
}
if (($this->response_bytes + $data_length) > $this->response_byte_limit) {
// Limit the length
$limited_length = ($this->response_byte_limit - $this->response_bytes);
$data = substr($data, 0, $limited_length);
}
}
if ($this->stream_handle) {
fwrite($this->stream_handle, $data);
}
else {
$this->response_data .= $data;
}
$this->response_bytes += strlen($data);
return $data_length;
}
/**
* Format a URL given GET data
*
* @param string $url
* @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
* @return string URL with data
*/
protected static function format_get($url, $data) {
if (!empty($data)) {
$url_parts = parse_url($url);
if (empty($url_parts['query'])) {
$query = $url_parts['query'] = '';
}
else {
$query = $url_parts['query'];
}
$query .= '&' . http_build_query($data, null, '&');
$query = trim($query, '&');
if (empty($url_parts['query'])) {
$url .= '?' . $query;
}
else {
$url = str_replace($url_parts['query'], $query, $url);
}
}
return $url;
}
/**
* Whether this transport is valid
*
* @codeCoverageIgnore
* @return boolean True if the transport is valid, false otherwise.
*/
public static function test($capabilities = array()) {
if (!function_exists('curl_init') || !function_exists('curl_exec')) {
return false;
}
// If needed, check that our installed curl version supports SSL
if (isset($capabilities['ssl']) && $capabilities['ssl']) {
$curl_version = curl_version();
if (!(CURL_VERSION_SSL & $curl_version['features'])) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,444 @@
<?php
/**
* fsockopen HTTP transport
*
* @package Requests
* @subpackage Transport
*/
/**
* fsockopen HTTP transport
*
* @package Requests
* @subpackage Transport
*/
class Requests_Transport_fsockopen implements Requests_Transport {
/**
* Second to microsecond conversion
*
* @var integer
*/
const SECOND_IN_MICROSECONDS = 1000000;
/**
* Raw HTTP data
*
* @var string
*/
public $headers = '';
/**
* Stream metadata
*
* @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data}
*/
public $info;
/**
* What's the maximum number of bytes we should keep?
*
* @var int|bool Byte count, or false if no limit.
*/
protected $max_bytes = false;
protected $connect_error = '';
/**
* Perform a request
*
* @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
* @throws Requests_Exception On socket timeout (`timeout`)
*
* @param string $url URL to request
* @param array $headers Associative array of request headers
* @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
* @param array $options Request options, see {@see Requests::response()} for documentation
* @return string Raw HTTP result
*/
public function request($url, $headers = array(), $data = array(), $options = array()) {
$options['hooks']->dispatch('fsockopen.before_request');
$url_parts = parse_url($url);
if (empty($url_parts)) {
throw new Requests_Exception('Invalid URL.', 'invalidurl', $url);
}
$host = $url_parts['host'];
$context = stream_context_create();
$verifyname = false;
$case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers);
// HTTPS support
if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
$remote_socket = 'ssl://' . $host;
if (!isset($url_parts['port'])) {
$url_parts['port'] = 443;
}
$context_options = array(
'verify_peer' => true,
// 'CN_match' => $host,
'capture_peer_cert' => true
);
$verifyname = true;
// SNI, if enabled (OpenSSL >=0.9.8j)
if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
$context_options['SNI_enabled'] = true;
if (isset($options['verifyname']) && $options['verifyname'] === false) {
$context_options['SNI_enabled'] = false;
}
}
if (isset($options['verify'])) {
if ($options['verify'] === false) {
$context_options['verify_peer'] = false;
}
elseif (is_string($options['verify'])) {
$context_options['cafile'] = $options['verify'];
}
}
if (isset($options['verifyname']) && $options['verifyname'] === false) {
$context_options['verify_peer_name'] = false;
$verifyname = false;
}
stream_context_set_option($context, array('ssl' => $context_options));
}
else {
$remote_socket = 'tcp://' . $host;
}
$this->max_bytes = $options['max_bytes'];
if (!isset($url_parts['port'])) {
$url_parts['port'] = 80;
}
$remote_socket .= ':' . $url_parts['port'];
set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);
$options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));
$socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context);
restore_error_handler();
if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
}
if (!$socket) {
if ($errno === 0) {
// Connection issue
throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
}
throw new Requests_Exception($errstr, 'fsockopenerror', null, $errno);
}
$data_format = $options['data_format'];
if ($data_format === 'query') {
$path = self::format_get($url_parts, $data);
$data = '';
}
else {
$path = self::format_get($url_parts, array());
}
$options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
$request_body = '';
$out = sprintf("%s %s HTTP/%.1f\r\n", $options['type'], $path, $options['protocol_version']);
if ($options['type'] !== Requests::TRACE) {
if (is_array($data)) {
$request_body = http_build_query($data, null, '&');
}
else {
$request_body = $data;
}
if (!empty($data)) {
if (!isset($case_insensitive_headers['Content-Length'])) {
$headers['Content-Length'] = strlen($request_body);
}
if (!isset($case_insensitive_headers['Content-Type'])) {
$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
}
}
}
if (!isset($case_insensitive_headers['Host'])) {
$out .= sprintf('Host: %s', $url_parts['host']);
if (( 'http' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 80 ) || ( 'https' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 443 )) {
$out .= ':' . $url_parts['port'];
}
$out .= "\r\n";
}
if (!isset($case_insensitive_headers['User-Agent'])) {
$out .= sprintf("User-Agent: %s\r\n", $options['useragent']);
}
$accept_encoding = $this->accept_encoding();
if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) {
$out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding);
}
$headers = Requests::flatten($headers);
if (!empty($headers)) {
$out .= implode($headers, "\r\n") . "\r\n";
}
$options['hooks']->dispatch('fsockopen.after_headers', array(&$out));
if (substr($out, -2) !== "\r\n") {
$out .= "\r\n";
}
if (!isset($case_insensitive_headers['Connection'])) {
$out .= "Connection: Close\r\n";
}
$out .= "\r\n" . $request_body;
$options['hooks']->dispatch('fsockopen.before_send', array(&$out));
fwrite($socket, $out);
$options['hooks']->dispatch('fsockopen.after_send', array($out));
if (!$options['blocking']) {
fclose($socket);
$fake_headers = '';
$options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
return '';
}
$timeout_sec = (int) floor($options['timeout']);
if ($timeout_sec == $options['timeout']) {
$timeout_msec = 0;
}
else {
$timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS;
}
stream_set_timeout($socket, $timeout_sec, $timeout_msec);
$response = $body = $headers = '';
$this->info = stream_get_meta_data($socket);
$size = 0;
$doingbody = false;
$download = false;
if ($options['filename']) {
$download = fopen($options['filename'], 'wb');
}
while (!feof($socket)) {
$this->info = stream_get_meta_data($socket);
if ($this->info['timed_out']) {
throw new Requests_Exception('fsocket timed out', 'timeout');
}
$block = fread($socket, Requests::BUFFER_SIZE);
if (!$doingbody) {
$response .= $block;
if (strpos($response, "\r\n\r\n")) {
list($headers, $block) = explode("\r\n\r\n", $response, 2);
$doingbody = true;
}
}
// Are we in body mode now?
if ($doingbody) {
$options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes));
$data_length = strlen($block);
if ($this->max_bytes) {
// Have we already hit a limit?
if ($size === $this->max_bytes) {
continue;
}
if (($size + $data_length) > $this->max_bytes) {
// Limit the length
$limited_length = ($this->max_bytes - $size);
$block = substr($block, 0, $limited_length);
}
}
$size += strlen($block);
if ($download) {
fwrite($download, $block);
}
else {
$body .= $block;
}
}
}
$this->headers = $headers;
if ($download) {
fclose($download);
}
else {
$this->headers .= "\r\n\r\n" . $body;
}
fclose($socket);
$options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info));
return $this->headers;
}
/**
* Send multiple requests simultaneously
*
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
* @param array $options Global options, see {@see Requests::response()} for documentation
* @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
*/
public function request_multiple($requests, $options) {
$responses = array();
$class = get_class($this);
foreach ($requests as $id => $request) {
try {
$handler = new $class();
$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
$request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
}
catch (Requests_Exception $e) {
$responses[$id] = $e;
}
if (!is_string($responses[$id])) {
$request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
}
}
return $responses;
}
/**
* Retrieve the encodings we can accept
*
* @return string Accept-Encoding header value
*/
protected static function accept_encoding() {
$type = array();
if (function_exists('gzinflate')) {
$type[] = 'deflate;q=1.0';
}
if (function_exists('gzuncompress')) {
$type[] = 'compress;q=0.5';
}
$type[] = 'gzip;q=0.5';
return implode(', ', $type);
}
/**
* Format a URL given GET data
*
* @param array $url_parts
* @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
* @return string URL with data
*/
protected static function format_get($url_parts, $data) {
if (!empty($data)) {
if (empty($url_parts['query'])) {
$url_parts['query'] = '';
}
$url_parts['query'] .= '&' . http_build_query($data, null, '&');
$url_parts['query'] = trim($url_parts['query'], '&');
}
if (isset($url_parts['path'])) {
if (isset($url_parts['query'])) {
$get = $url_parts['path'] . '?' . $url_parts['query'];
}
else {
$get = $url_parts['path'];
}
}
else {
$get = '/';
}
return $get;
}
/**
* Error handler for stream_socket_client()
*
* @param int $errno Error number (e.g. E_WARNING)
* @param string $errstr Error message
*/
public function connect_error_handler($errno, $errstr) {
// Double-check we can handle it
if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) {
// Return false to indicate the default error handler should engage
return false;
}
$this->connect_error .= $errstr . "\n";
return true;
}
/**
* Verify the certificate against common name and subject alternative names
*
* Unfortunately, PHP doesn't check the certificate against the alternative
* names, leading things like 'https://www.github.com/' to be invalid.
* Instead
*
* @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
*
* @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
* @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
* @param string $host Host name to verify against
* @param resource $context Stream context
* @return bool
*/
public function verify_certificate_from_context($host, $context) {
$meta = stream_context_get_options($context);
// If we don't have SSL options, then we couldn't make the connection at
// all
if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
}
$cert = openssl_x509_parse($meta['ssl']['peer_certificate']);
return Requests_SSL::verify_certificate($host, $cert);
}
/**
* Whether this transport is valid
*
* @codeCoverageIgnore
* @return boolean True if the transport is valid, false otherwise.
*/
public static function test($capabilities = array()) {
if (!function_exists('fsockopen')) {
return false;
}
// If needed, check that streams support SSL
if (isset($capabilities['ssl']) && $capabilities['ssl']) {
if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
return false;
}
// Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156
if (defined('HHVM_VERSION')) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,103 @@
<?php
/**
* Case-insensitive dictionary, suitable for HTTP headers
*
* @package Requests
* @subpackage Utilities
*/
/**
* Case-insensitive dictionary, suitable for HTTP headers
*
* @package Requests
* @subpackage Utilities
*/
class Requests_Utility_CaseInsensitiveDictionary implements ArrayAccess, IteratorAggregate {
/**
* Actual item data
*
* @var array
*/
protected $data = array();
/**
* Creates a case insensitive dictionary.
*
* @param array $data Dictionary/map to convert to case-insensitive
*/
public function __construct(array $data = array()) {
foreach ($data as $key => $value) {
$this->offsetSet($key, $value);
}
}
/**
* Check if the given item exists
*
* @param string $key Item key
* @return boolean Does the item exist?
*/
public function offsetExists($key) {
$key = strtolower($key);
return isset($this->data[$key]);
}
/**
* Get the value for the item
*
* @param string $key Item key
* @return string Item value
*/
public function offsetGet($key) {
$key = strtolower($key);
if (!isset($this->data[$key])) {
return null;
}
return $this->data[$key];
}
/**
* Set the given item
*
* @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
*
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
$key = strtolower($key);
$this->data[$key] = $value;
}
/**
* Unset the given header
*
* @param string $key
*/
public function offsetUnset($key) {
unset($this->data[strtolower($key)]);
}
/**
* Get an iterator for the data
*
* @return ArrayIterator
*/
public function getIterator() {
return new ArrayIterator($this->data);
}
/**
* Get the headers as an array
*
* @return array Header data
*/
public function getAll() {
return $this->data;
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Iterator for arrays requiring filtered values
*
* @package Requests
* @subpackage Utilities
*/
/**
* Iterator for arrays requiring filtered values
*
* @package Requests
* @subpackage Utilities
*/
class Requests_Utility_FilteredIterator extends ArrayIterator {
/**
* Callback to run as a filter
*
* @var callable
*/
protected $callback;
/**
* Create a new iterator
*
* @param array $data
* @param callable $callback Callback to be called on each value
*/
public function __construct($data, $callback) {
parent::__construct($data);
$this->callback = $callback;
}
/**
* Get the current item's value after filtering
*
* @return string
*/
public function current() {
$value = parent::current();
$value = call_user_func($this->callback, $value);
return $value;
}
}

View File

@ -0,0 +1,157 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Manages all author-related data
*
* Used by {@see SimplePie_Item::get_author()} and {@see SimplePie::get_authors()}
*
* This class can be overloaded with {@see SimplePie::set_author_class()}
*
* @package SimplePie
* @subpackage API
*/
class SimplePie_Author
{
/**
* Author's name
*
* @var string
* @see get_name()
*/
var $name;
/**
* Author's link
*
* @var string
* @see get_link()
*/
var $link;
/**
* Author's email address
*
* @var string
* @see get_email()
*/
var $email;
/**
* Constructor, used to input the data
*
* @param string $name
* @param string $link
* @param string $email
*/
public function __construct($name = null, $link = null, $email = null)
{
$this->name = $name;
$this->link = $link;
$this->email = $email;
}
/**
* String-ified version
*
* @return string
*/
public function __toString()
{
// There is no $this->data here
return md5(serialize($this));
}
/**
* Author's name
*
* @return string|null
*/
public function get_name()
{
if ($this->name !== null)
{
return $this->name;
}
else
{
return null;
}
}
/**
* Author's link
*
* @return string|null
*/
public function get_link()
{
if ($this->link !== null)
{
return $this->link;
}
else
{
return null;
}
}
/**
* Author's email address
*
* @return string|null
*/
public function get_email()
{
if ($this->email !== null)
{
return $this->email;
}
else
{
return null;
}
}
}

View File

@ -0,0 +1,133 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Used to create cache objects
*
* This class can be overloaded with {@see SimplePie::set_cache_class()},
* although the preferred way is to create your own handler
* via {@see register()}
*
* @package SimplePie
* @subpackage Caching
*/
class SimplePie_Cache
{
/**
* Cache handler classes
*
* These receive 3 parameters to their constructor, as documented in
* {@see register()}
* @var array
*/
protected static $handlers = array(
'mysql' => 'SimplePie_Cache_MySQL',
'memcache' => 'SimplePie_Cache_Memcache',
);
/**
* Don't call the constructor. Please.
*/
private function __construct() { }
/**
* Create a new SimplePie_Cache object
*
* @param string $location URL location (scheme is used to determine handler)
* @param string $filename Unique identifier for cache object
* @param string $extension 'spi' or 'spc'
* @return SimplePie_Cache_Base Type of object depends on scheme of `$location`
*/
public static function get_handler($location, $filename, $extension)
{
$type = explode(':', $location, 2);
$type = $type[0];
if (!empty(self::$handlers[$type]))
{
$class = self::$handlers[$type];
return new $class($location, $filename, $extension);
}
return new SimplePie_Cache_File($location, $filename, $extension);
}
/**
* Create a new SimplePie_Cache object
*
* @deprecated Use {@see get_handler} instead
*/
public function create($location, $filename, $extension)
{
trigger_error('Cache::create() has been replaced with Cache::get_handler(). Switch to the registry system to use this.', E_USER_DEPRECATED);
return self::get_handler($location, $filename, $extension);
}
/**
* Register a handler
*
* @param string $type DSN type to register for
* @param string $class Name of handler class. Must implement SimplePie_Cache_Base
*/
public static function register($type, $class)
{
self::$handlers[$type] = $class;
}
/**
* Parse a URL into an array
*
* @param string $url
* @return array
*/
public static function parse_URL($url)
{
$params = parse_url($url);
$params['extras'] = array();
if (isset($params['query']))
{
parse_str($params['query'], $params['extras']);
}
return $params;
}
}

View File

@ -0,0 +1,114 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Base for cache objects
*
* Classes to be used with {@see SimplePie_Cache::register()} are expected
* to implement this interface.
*
* @package SimplePie
* @subpackage Caching
*/
interface SimplePie_Cache_Base
{
/**
* Feed cache type
*
* @var string
*/
const TYPE_FEED = 'spc';
/**
* Image cache type
*
* @var string
*/
const TYPE_IMAGE = 'spi';
/**
* Create a new cache object
*
* @param string $location Location string (from SimplePie::$cache_location)
* @param string $name Unique ID for the cache
* @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
*/
public function __construct($location, $name, $type);
/**
* Save data to the cache
*
* @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
* @return bool Successfulness
*/
public function save($data);
/**
* Retrieve the data saved to the cache
*
* @return array Data for SimplePie::$data
*/
public function load();
/**
* Retrieve the last modified time for the cache
*
* @return int Timestamp
*/
public function mtime();
/**
* Set the last modified time to the current time
*
* @return bool Success status
*/
public function touch();
/**
* Remove the cache
*
* @return bool Success status
*/
public function unlink();
}

View File

@ -0,0 +1,137 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Base class for database-based caches
*
* @package SimplePie
* @subpackage Caching
*/
abstract class SimplePie_Cache_DB implements SimplePie_Cache_Base
{
/**
* Helper for database conversion
*
* Converts a given {@see SimplePie} object into data to be stored
*
* @param SimplePie $data
* @return array First item is the serialized data for storage, second item is the unique ID for this item
*/
protected static function prepare_simplepie_object_for_cache($data)
{
$items = $data->get_items();
$items_by_id = array();
if (!empty($items))
{
foreach ($items as $item)
{
$items_by_id[$item->get_id()] = $item;
}
if (count($items_by_id) !== count($items))
{
$items_by_id = array();
foreach ($items as $item)
{
$items_by_id[$item->get_id(true)] = $item;
}
}
if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
{
$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
}
elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
{
$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
}
elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
{
$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
}
elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]))
{
$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0];
}
else
{
$channel = null;
}
if ($channel !== null)
{
if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']))
{
unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']);
}
if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']))
{
unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']);
}
if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']))
{
unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']);
}
if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']))
{
unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']);
}
if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']))
{
unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']);
}
}
if (isset($data->data['items']))
{
unset($data->data['items']);
}
if (isset($data->data['ordered_items']))
{
unset($data->data['ordered_items']);
}
}
return array(serialize($data->data), $items_by_id);
}
}

View File

@ -0,0 +1,173 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Caches data to the filesystem
*
* @package SimplePie
* @subpackage Caching
*/
class SimplePie_Cache_File implements SimplePie_Cache_Base
{
/**
* Location string
*
* @see SimplePie::$cache_location
* @var string
*/
protected $location;
/**
* Filename
*
* @var string
*/
protected $filename;
/**
* File extension
*
* @var string
*/
protected $extension;
/**
* File path
*
* @var string
*/
protected $name;
/**
* Create a new cache object
*
* @param string $location Location string (from SimplePie::$cache_location)
* @param string $name Unique ID for the cache
* @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
*/
public function __construct($location, $name, $type)
{
$this->location = $location;
$this->filename = $name;
$this->extension = $type;
$this->name = "$this->location/$this->filename.$this->extension";
}
/**
* Save data to the cache
*
* @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
* @return bool Successfulness
*/
public function save($data)
{
if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location))
{
if ($data instanceof SimplePie)
{
$data = $data->data;
}
$data = serialize($data);
return (bool) file_put_contents($this->name, $data);
}
return false;
}
/**
* Retrieve the data saved to the cache
*
* @return array Data for SimplePie::$data
*/
public function load()
{
if (file_exists($this->name) && is_readable($this->name))
{
return unserialize(file_get_contents($this->name));
}
return false;
}
/**
* Retrieve the last modified time for the cache
*
* @return int Timestamp
*/
public function mtime()
{
if (file_exists($this->name))
{
return filemtime($this->name);
}
return false;
}
/**
* Set the last modified time to the current time
*
* @return bool Success status
*/
public function touch()
{
if (file_exists($this->name))
{
return touch($this->name);
}
return false;
}
/**
* Remove the cache
*
* @return bool Success status
*/
public function unlink()
{
if (file_exists($this->name))
{
return unlink($this->name);
}
return false;
}
}

View File

@ -0,0 +1,183 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Caches data to memcache
*
* Registered for URLs with the "memcache" protocol
*
* For example, `memcache://localhost:11211/?timeout=3600&prefix=sp_` will
* connect to memcache on `localhost` on port 11211. All tables will be
* prefixed with `sp_` and data will expire after 3600 seconds
*
* @package SimplePie
* @subpackage Caching
* @uses Memcache
*/
class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
{
/**
* Memcache instance
*
* @var Memcache
*/
protected $cache;
/**
* Options
*
* @var array
*/
protected $options;
/**
* Cache name
*
* @var string
*/
protected $name;
/**
* Create a new cache object
*
* @param string $location Location string (from SimplePie::$cache_location)
* @param string $name Unique ID for the cache
* @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
*/
public function __construct($location, $name, $type)
{
$this->options = array(
'host' => '127.0.0.1',
'port' => 11211,
'extras' => array(
'timeout' => 3600, // one hour
'prefix' => 'simplepie_',
),
);
$parsed = SimplePie_Cache::parse_URL($location);
$this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host'];
$this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port'];
$this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']);
$this->name = $this->options['extras']['prefix'] . md5("$name:$type");
$this->cache = new Memcache();
$this->cache->addServer($this->options['host'], (int) $this->options['port']);
}
/**
* Save data to the cache
*
* @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
* @return bool Successfulness
*/
public function save($data)
{
if ($data instanceof SimplePie)
{
$data = $data->data;
}
return $this->cache->set($this->name, serialize($data), MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']);
}
/**
* Retrieve the data saved to the cache
*
* @return array Data for SimplePie::$data
*/
public function load()
{
$data = $this->cache->get($this->name);
if ($data !== false)
{
return unserialize($data);
}
return false;
}
/**
* Retrieve the last modified time for the cache
*
* @return int Timestamp
*/
public function mtime()
{
$data = $this->cache->get($this->name);
if ($data !== false)
{
// essentially ignore the mtime because Memcache expires on it's own
return time();
}
return false;
}
/**
* Set the last modified time to the current time
*
* @return bool Success status
*/
public function touch()
{
$data = $this->cache->get($this->name);
if ($data !== false)
{
return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration);
}
return false;
}
/**
* Remove the cache
*
* @return bool Success status
*/
public function unlink()
{
return $this->cache->delete($this->name, 0);
}
}

View File

@ -0,0 +1,438 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Caches data to a MySQL database
*
* Registered for URLs with the "mysql" protocol
*
* For example, `mysql://root:password@localhost:3306/mydb?prefix=sp_` will
* connect to the `mydb` database on `localhost` on port 3306, with the user
* `root` and the password `password`. All tables will be prefixed with `sp_`
*
* @package SimplePie
* @subpackage Caching
*/
class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
{
/**
* PDO instance
*
* @var PDO
*/
protected $mysql;
/**
* Options
*
* @var array
*/
protected $options;
/**
* Cache ID
*
* @var string
*/
protected $id;
/**
* Create a new cache object
*
* @param string $location Location string (from SimplePie::$cache_location)
* @param string $name Unique ID for the cache
* @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
*/
public function __construct($location, $name, $type)
{
$this->options = array(
'user' => null,
'pass' => null,
'host' => '127.0.0.1',
'port' => '3306',
'path' => '',
'extras' => array(
'prefix' => '',
),
);
$this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
// Path is prefixed with a "/"
$this->options['dbname'] = substr($this->options['path'], 1);
try
{
$this->mysql = new PDO("mysql:dbname={$this->options['dbname']};host={$this->options['host']};port={$this->options['port']}", $this->options['user'], $this->options['pass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
}
catch (PDOException $e)
{
$this->mysql = null;
return;
}
$this->id = $name . $type;
if (!$query = $this->mysql->query('SHOW TABLES'))
{
$this->mysql = null;
return;
}
$db = array();
while ($row = $query->fetchColumn())
{
$db[] = $row;
}
if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db))
{
$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))');
if ($query === false)
{
$this->mysql = null;
}
}
if (!in_array($this->options['extras']['prefix'] . 'items', $db))
{
$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))');
if ($query === false)
{
$this->mysql = null;
}
}
}
/**
* Save data to the cache
*
* @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
* @return bool Successfulness
*/
public function save($data)
{
if ($this->mysql === null)
{
return false;
}
if ($data instanceof SimplePie)
{
$data = clone $data;
$prepared = self::prepare_simplepie_object_for_cache($data);
$query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
$query->bindValue(':feed', $this->id);
if ($query->execute())
{
if ($query->fetchColumn() > 0)
{
$items = count($prepared[1]);
if ($items)
{
$sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed';
$query = $this->mysql->prepare($sql);
$query->bindValue(':items', $items);
}
else
{
$sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed';
$query = $this->mysql->prepare($sql);
}
$query->bindValue(':data', $prepared[0]);
$query->bindValue(':time', time());
$query->bindValue(':feed', $this->id);
if (!$query->execute())
{
return false;
}
}
else
{
$query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)');
$query->bindValue(':feed', $this->id);
$query->bindValue(':count', count($prepared[1]));
$query->bindValue(':data', $prepared[0]);
$query->bindValue(':time', time());
if (!$query->execute())
{
return false;
}
}
$ids = array_keys($prepared[1]);
if (!empty($ids))
{
foreach ($ids as $id)
{
$database_ids[] = $this->mysql->quote($id);
}
$query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed');
$query->bindValue(':feed', $this->id);
if ($query->execute())
{
$existing_ids = array();
while ($row = $query->fetchColumn())
{
$existing_ids[] = $row;
}
$new_ids = array_diff($ids, $existing_ids);
foreach ($new_ids as $new_id)
{
if (!($date = $prepared[1][$new_id]->get_date('U')))
{
$date = time();
}
$query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)');
$query->bindValue(':feed', $this->id);
$query->bindValue(':id', $new_id);
$query->bindValue(':data', serialize($prepared[1][$new_id]->data));
$query->bindValue(':date', $date);
if (!$query->execute())
{
return false;
}
}
return true;
}
}
else
{
return true;
}
}
}
else
{
$query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
$query->bindValue(':feed', $this->id);
if ($query->execute())
{
if ($query->rowCount() > 0)
{
$query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed');
$query->bindValue(':data', serialize($data));
$query->bindValue(':time', time());
$query->bindValue(':feed', $this->id);
if ($this->execute())
{
return true;
}
}
else
{
$query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)');
$query->bindValue(':id', $this->id);
$query->bindValue(':data', serialize($data));
$query->bindValue(':time', time());
if ($query->execute())
{
return true;
}
}
}
}
return false;
}
/**
* Retrieve the data saved to the cache
*
* @return array Data for SimplePie::$data
*/
public function load()
{
if ($this->mysql === null)
{
return false;
}
$query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
$query->bindValue(':id', $this->id);
if ($query->execute() && ($row = $query->fetch()))
{
$data = unserialize($row[1]);
if (isset($this->options['items'][0]))
{
$items = (int) $this->options['items'][0];
}
else
{
$items = (int) $row[0];
}
if ($items !== 0)
{
if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
{
$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
}
elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
{
$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
}
elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
{
$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
}
elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]))
{
$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0];
}
else
{
$feed = null;
}
if ($feed !== null)
{
$sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC';
if ($items > 0)
{
$sql .= ' LIMIT ' . $items;
}
$query = $this->mysql->prepare($sql);
$query->bindValue(':feed', $this->id);
if ($query->execute())
{
while ($row = $query->fetchColumn())
{
$feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row);
}
}
else
{
return false;
}
}
}
return $data;
}
return false;
}
/**
* Retrieve the last modified time for the cache
*
* @return int Timestamp
*/
public function mtime()
{
if ($this->mysql === null)
{
return false;
}
$query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
$query->bindValue(':id', $this->id);
if ($query->execute() && ($time = $query->fetchColumn()))
{
return $time;
}
else
{
return false;
}
}
/**
* Set the last modified time to the current time
*
* @return bool Success status
*/
public function touch()
{
if ($this->mysql === null)
{
return false;
}
$query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id');
$query->bindValue(':time', time());
$query->bindValue(':id', $this->id);
if ($query->execute() && $query->rowCount() > 0)
{
return true;
}
else
{
return false;
}
}
/**
* Remove the cache
*
* @return bool Success status
*/
public function unlink()
{
if ($this->mysql === null)
{
return false;
}
$query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
$query->bindValue(':id', $this->id);
$query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id');
$query2->bindValue(':id', $this->id);
if ($query->execute() && $query2->execute())
{
return true;
}
else
{
return false;
}
}
}

View File

@ -0,0 +1,210 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Handles `<media:text>` captions as defined in Media RSS.
*
* Used by {@see SimplePie_Enclosure::get_caption()} and {@see SimplePie_Enclosure::get_captions()}
*
* This class can be overloaded with {@see SimplePie::set_caption_class()}
*
* @package SimplePie
* @subpackage API
*/
class SimplePie_Caption
{
/**
* Content type
*
* @var string
* @see get_type()
*/
var $type;
/**
* Language
*
* @var string
* @see get_language()
*/
var $lang;
/**
* Start time
*
* @var string
* @see get_starttime()
*/
var $startTime;
/**
* End time
*
* @var string
* @see get_endtime()
*/
var $endTime;
/**
* Caption text
*
* @var string
* @see get_text()
*/
var $text;
/**
* Constructor, used to input the data
*
* For documentation on all the parameters, see the corresponding
* properties and their accessors
*/
public function __construct($type = null, $lang = null, $startTime = null, $endTime = null, $text = null)
{
$this->type = $type;
$this->lang = $lang;
$this->startTime = $startTime;
$this->endTime = $endTime;
$this->text = $text;
}
/**
* String-ified version
*
* @return string
*/
public function __toString()
{
// There is no $this->data here
return md5(serialize($this));
}
/**
* Get the end time
*
* @return string|null Time in the format 'hh:mm:ss.SSS'
*/
public function get_endtime()
{
if ($this->endTime !== null)
{
return $this->endTime;
}
else
{
return null;
}
}
/**
* Get the language
*
* @link http://tools.ietf.org/html/rfc3066
* @return string|null Language code as per RFC 3066
*/
public function get_language()
{
if ($this->lang !== null)
{
return $this->lang;
}
else
{
return null;
}
}
/**
* Get the start time
*
* @return string|null Time in the format 'hh:mm:ss.SSS'
*/
public function get_starttime()
{
if ($this->startTime !== null)
{
return $this->startTime;
}
else
{
return null;
}
}
/**
* Get the text of the caption
*
* @return string|null
*/
public function get_text()
{
if ($this->text !== null)
{
return $this->text;
}
else
{
return null;
}
}
/**
* Get the content type (not MIME type)
*
* @return string|null Either 'text' or 'html'
*/
public function get_type()
{
if ($this->type !== null)
{
return $this->type;
}
else
{
return null;
}
}
}

View File

@ -0,0 +1,157 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Manages all category-related data
*
* Used by {@see SimplePie_Item::get_category()} and {@see SimplePie_Item::get_categories()}
*
* This class can be overloaded with {@see SimplePie::set_category_class()}
*
* @package SimplePie
* @subpackage API
*/
class SimplePie_Category
{
/**
* Category identifier
*
* @var string
* @see get_term
*/
var $term;
/**
* Categorization scheme identifier
*
* @var string
* @see get_scheme()
*/
var $scheme;
/**
* Human readable label
*
* @var string
* @see get_label()
*/
var $label;
/**
* Constructor, used to input the data
*
* @param string $term
* @param string $scheme
* @param string $label
*/
public function __construct($term = null, $scheme = null, $label = null)
{
$this->term = $term;
$this->scheme = $scheme;
$this->label = $label;
}
/**
* String-ified version
*
* @return string
*/
public function __toString()
{
// There is no $this->data here
return md5(serialize($this));
}
/**
* Get the category identifier
*
* @return string|null
*/
public function get_term()
{
if ($this->term !== null)
{
return $this->term;
}
else
{
return null;
}
}
/**
* Get the categorization scheme identifier
*
* @return string|null
*/
public function get_scheme()
{
if ($this->scheme !== null)
{
return $this->scheme;
}
else
{
return null;
}
}
/**
* Get the human readable label
*
* @return string|null
*/
public function get_label()
{
if ($this->label !== null)
{
return $this->label;
}
else
{
return $this->get_term();
}
}
}

View File

@ -0,0 +1,332 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Content-type sniffing
*
* Based on the rules in http://tools.ietf.org/html/draft-abarth-mime-sniff-06
*
* This is used since we can't always trust Content-Type headers, and is based
* upon the HTML5 parsing rules.
*
*
* This class can be overloaded with {@see SimplePie::set_content_type_sniffer_class()}
*
* @package SimplePie
* @subpackage HTTP
*/
class SimplePie_Content_Type_Sniffer
{
/**
* File object
*
* @var SimplePie_File
*/
var $file;
/**
* Create an instance of the class with the input file
*
* @param SimplePie_Content_Type_Sniffer $file Input file
*/
public function __construct($file)
{
$this->file = $file;
}
/**
* Get the Content-Type of the specified file
*
* @return string Actual Content-Type
*/
public function get_type()
{
if (isset($this->file->headers['content-type']))
{
if (!isset($this->file->headers['content-encoding'])
&& ($this->file->headers['content-type'] === 'text/plain'
|| $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1'
|| $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1'
|| $this->file->headers['content-type'] === 'text/plain; charset=UTF-8'))
{
return $this->text_or_binary();
}
if (($pos = strpos($this->file->headers['content-type'], ';')) !== false)
{
$official = substr($this->file->headers['content-type'], 0, $pos);
}
else
{
$official = $this->file->headers['content-type'];
}
$official = trim(strtolower($official));
if ($official === 'unknown/unknown'
|| $official === 'application/unknown')
{
return $this->unknown();
}
elseif (substr($official, -4) === '+xml'
|| $official === 'text/xml'
|| $official === 'application/xml')
{
return $official;
}
elseif (substr($official, 0, 6) === 'image/')
{
if ($return = $this->image())
{
return $return;
}
else
{
return $official;
}
}
elseif ($official === 'text/html')
{
return $this->feed_or_html();
}
else
{
return $official;
}
}
else
{
return $this->unknown();
}
}
/**
* Sniff text or binary
*
* @return string Actual Content-Type
*/
public function text_or_binary()
{
if (substr($this->file->body, 0, 2) === "\xFE\xFF"
|| substr($this->file->body, 0, 2) === "\xFF\xFE"
|| substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF"
|| substr($this->file->body, 0, 3) === "\xEF\xBB\xBF")
{
return 'text/plain';
}
elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body))
{
return 'application/octect-stream';
}
else
{
return 'text/plain';
}
}
/**
* Sniff unknown
*
* @return string Actual Content-Type
*/
public function unknown()
{
$ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20");
if (strtolower(substr($this->file->body, $ws, 14)) === '<!doctype html'
|| strtolower(substr($this->file->body, $ws, 5)) === '<html'
|| strtolower(substr($this->file->body, $ws, 7)) === '<script')
{
return 'text/html';
}
elseif (substr($this->file->body, 0, 5) === '%PDF-')
{
return 'application/pdf';
}
elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-')
{
return 'application/postscript';
}
elseif (substr($this->file->body, 0, 6) === 'GIF87a'
|| substr($this->file->body, 0, 6) === 'GIF89a')
{
return 'image/gif';
}
elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
{
return 'image/png';
}
elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
{
return 'image/jpeg';
}
elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
{
return 'image/bmp';
}
elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00")
{
return 'image/vnd.microsoft.icon';
}
else
{
return $this->text_or_binary();
}
}
/**
* Sniff images
*
* @return string Actual Content-Type
*/
public function image()
{
if (substr($this->file->body, 0, 6) === 'GIF87a'
|| substr($this->file->body, 0, 6) === 'GIF89a')
{
return 'image/gif';
}
elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
{
return 'image/png';
}
elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
{
return 'image/jpeg';
}
elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
{
return 'image/bmp';
}
elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00")
{
return 'image/vnd.microsoft.icon';
}
else
{
return false;
}
}
/**
* Sniff HTML
*
* @return string Actual Content-Type
*/
public function feed_or_html()
{
$len = strlen($this->file->body);
$pos = strspn($this->file->body, "\x09\x0A\x0D\x20");
while ($pos < $len)
{
switch ($this->file->body[$pos])
{
case "\x09":
case "\x0A":
case "\x0D":
case "\x20":
$pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos);
continue 2;
case '<':
$pos++;
break;
default:
return 'text/html';
}
if (substr($this->file->body, $pos, 3) === '!--')
{
$pos += 3;
if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false)
{
$pos += 3;
}
else
{
return 'text/html';
}
}
elseif (substr($this->file->body, $pos, 1) === '!')
{
if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false)
{
$pos++;
}
else
{
return 'text/html';
}
}
elseif (substr($this->file->body, $pos, 1) === '?')
{
if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false)
{
$pos += 2;
}
else
{
return 'text/html';
}
}
elseif (substr($this->file->body, $pos, 3) === 'rss'
|| substr($this->file->body, $pos, 7) === 'rdf:RDF')
{
return 'application/rss+xml';
}
elseif (substr($this->file->body, $pos, 4) === 'feed')
{
return 'application/atom+xml';
}
else
{
return 'text/html';
}
}
return 'text/html';
}
}

View File

@ -0,0 +1,130 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Manages `<media:copyright>` copyright tags as defined in Media RSS
*
* Used by {@see SimplePie_Enclosure::get_copyright()}
*
* This class can be overloaded with {@see SimplePie::set_copyright_class()}
*
* @package SimplePie
* @subpackage API
*/
class SimplePie_Copyright
{
/**
* Copyright URL
*
* @var string
* @see get_url()
*/
var $url;
/**
* Attribution
*
* @var string
* @see get_attribution()
*/
var $label;
/**
* Constructor, used to input the data
*
* For documentation on all the parameters, see the corresponding
* properties and their accessors
*/
public function __construct($url = null, $label = null)
{
$this->url = $url;
$this->label = $label;
}
/**
* String-ified version
*
* @return string
*/
public function __toString()
{
// There is no $this->data here
return md5(serialize($this));
}
/**
* Get the copyright URL
*
* @return string|null URL to copyright information
*/
public function get_url()
{
if ($this->url !== null)
{
return $this->url;
}
else
{
return null;
}
}
/**
* Get the attribution text
*
* @return string|null
*/
public function get_attribution()
{
if ($this->label !== null)
{
return $this->label;
}
else
{
return null;
}
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2009, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* SimplePie class.
*
* Class for backward compatibility.
*
* @deprecated Use {@see SimplePie} directly
* @package SimplePie
* @subpackage API
*/
class SimplePie_Core extends SimplePie
{
}

View File

@ -0,0 +1,156 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Handles `<media:credit>` as defined in Media RSS
*
* Used by {@see SimplePie_Enclosure::get_credit()} and {@see SimplePie_Enclosure::get_credits()}
*
* This class can be overloaded with {@see SimplePie::set_credit_class()}
*
* @package SimplePie
* @subpackage API
*/
class SimplePie_Credit
{
/**
* Credited role
*
* @var string
* @see get_role()
*/
var $role;
/**
* Organizational scheme
*
* @var string
* @see get_scheme()
*/
var $scheme;
/**
* Credited name
*
* @var string
* @see get_name()
*/
var $name;
/**
* Constructor, used to input the data
*
* For documentation on all the parameters, see the corresponding
* properties and their accessors
*/
public function __construct($role = null, $scheme = null, $name = null)
{
$this->role = $role;
$this->scheme = $scheme;
$this->name = $name;
}
/**
* String-ified version
*
* @return string
*/
public function __toString()
{
// There is no $this->data here
return md5(serialize($this));
}
/**
* Get the role of the person receiving credit
*
* @return string|null
*/
public function get_role()
{
if ($this->role !== null)
{
return $this->role;
}
else
{
return null;
}
}
/**
* Get the organizational scheme
*
* @return string|null
*/
public function get_scheme()
{
if ($this->scheme !== null)
{
return $this->scheme;
}
else
{
return null;
}
}
/**
* Get the credited person/entity's name
*
* @return string|null
*/
public function get_name()
{
if ($this->name !== null)
{
return $this->name;
}
else
{
return null;
}
}
}

View File

@ -0,0 +1,617 @@
<?php
/**
* SimplePie
*
* A PHP-Based RSS and Atom Feed Framework.
* Takes the hard work out of managing a complete RSS/Atom solution.
*
* Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* * Neither the name of the SimplePie Team nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
* @author Ryan McCue
* @link http://simplepie.org/ SimplePie
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
/**
* Decode HTML Entities
*
* This implements HTML5 as of revision 967 (2007-06-28)
*
* @deprecated Use DOMDocument instead!
* @package SimplePie
*/
class SimplePie_Decode_HTML_Entities
{
/**
* Data to be parsed
*
* @access private
* @var string
*/
var $data = '';
/**
* Currently consumed bytes
*
* @access private
* @var string
*/
var $consumed = '';
/**
* Position of the current byte being parsed
*
* @access private
* @var int
*/
var $position = 0;
/**
* Create an instance of the class with the input data
*
* @access public
* @param string $data Input data
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Parse the input data
*
* @access public
* @return string Output data
*/
public function parse()
{
while (($this->position = strpos($this->data, '&', $this->position)) !== false)
{
$this->consume();
$this->entity();
$this->consumed = '';
}
return $this->data;
}
/**
* Consume the next byte
*
* @access private
* @return mixed The next byte, or false, if there is no more data
*/
public function consume()
{
if (isset($this->data[$this->position]))
{
$this->consumed .= $this->data[$this->position];
return $this->data[$this->position++];
}
else
{
return false;
}
}
/**
* Consume a range of characters
*
* @access private
* @param string $chars Characters to consume
* @return mixed A series of characters that match the range, or false
*/
public function consume_range($chars)
{
if ($len = strspn($this->data, $chars, $this->position))
{
$data = substr($this->data, $this->position, $len);
$this->consumed .= $data;
$this->position += $len;
return $data;
}
else
{
return false;
}
}
/**
* Unconsume one byte
*
* @access private
*/
public function unconsume()
{
$this->consumed = substr($this->consumed, 0, -1);
$this->position--;
}
/**
* Decode an entity
*
* @access private
*/
public function entity()
{
switch ($this->consume())
{
case "\x09":
case "\x0A":
case "\x0B":
case "\x0B":
case "\x0C":
case "\x20":
case "\x3C":
case "\x26":
case false:
break;
case "\x23":
switch ($this->consume())
{
case "\x78":
case "\x58":
$range = '0123456789ABCDEFabcdef';
$hex = true;
break;
default:
$range = '0123456789';
$hex = false;
$this->unconsume();
break;
}
if ($codepoint = $this->consume_range($range))
{
static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8");
if ($hex)
{
$codepoint = hexdec($codepoint);
}
else
{
$codepoint = intval($codepoint);
}
if (isset($windows_1252_specials[$codepoint]))
{
$replacement = $windows_1252_specials[$codepoint];
}
else
{
$replacement = SimplePie_Misc::codepoint_to_utf8($codepoint);
}
if (!in_array($this->consume(), array(';', false), true))
{
$this->unconsume();
}
$consumed_length = strlen($this->consumed);
$this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length);
$this->position += strlen($replacement) - $consumed_length;
}
break;
default:
static $entities = array(
'Aacute' => "\xC3\x81",
'aacute' => "\xC3\xA1",
'Aacute;' => "\xC3\x81",
'aacute;' => "\xC3\xA1",
'Acirc' => "\xC3\x82",
'acirc' => "\xC3\xA2",
'Acirc;' => "\xC3\x82",
'acirc;' => "\xC3\xA2",
'acute' => "\xC2\xB4",
'acute;' => "\xC2\xB4",
'AElig' => "\xC3\x86",
'aelig' => "\xC3\xA6",
'AElig;' => "\xC3\x86",
'aelig;' => "\xC3\xA6",
'Agrave' => "\xC3\x80",
'agrave' => "\xC3\xA0",
'Agrave;' => "\xC3\x80",
'agrave;' => "\xC3\xA0",
'alefsym;' => "\xE2\x84\xB5",
'Alpha;' => "\xCE\x91",
'alpha;' => "\xCE\xB1",
'AMP' => "\x26",
'amp' => "\x26",
'AMP;' => "\x26",
'amp;' => "\x26",
'and;' => "\xE2\x88\xA7",
'ang;' => "\xE2\x88\xA0",
'apos;' => "\x27",
'Aring' => "\xC3\x85",
'aring' => "\xC3\xA5",
'Aring;' => "\xC3\x85",
'aring;' => "\xC3\xA5",
'asymp;' => "\xE2\x89\x88",
'Atilde' => "\xC3\x83",
'atilde' => "\xC3\xA3",
'Atilde;' => "\xC3\x83",
'atilde;' => "\xC3\xA3",
'Auml' => "\xC3\x84",
'auml' => "\xC3\xA4",
'Auml;' => "\xC3\x84",
'auml;' => "\xC3\xA4",
'bdquo;' => "\xE2\x80\x9E",
'Beta;' => "\xCE\x92",
'beta;' => "\xCE\xB2",
'brvbar' => "\xC2\xA6",
'brvbar;' => "\xC2\xA6",
'bull;' => "\xE2\x80\xA2",
'cap;' => "\xE2\x88\xA9",
'Ccedil' => "\xC3\x87",
'ccedil' => "\xC3\xA7",
'Ccedil;' => "\xC3\x87",
'ccedil;' => "\xC3\xA7",
'cedil' => "\xC2\xB8",
'cedil;' => "\xC2\xB8",
'cent' => "\xC2\xA2",
'cent;' => "\xC2\xA2",
'Chi;' => "\xCE\xA7",
'chi;' => "\xCF\x87",
'circ;' => "\xCB\x86",
'clubs;' => "\xE2\x99\xA3",
'cong;' => "\xE2\x89\x85",
'COPY' => "\xC2\xA9",
'copy' => "\xC2\xA9",
'COPY;' => "\xC2\xA9",
'copy;' => "\xC2\xA9",
'crarr;' => "\xE2\x86\xB5",
'cup;' => "\xE2\x88\xAA",
'curren' => "\xC2\xA4",
'curren;' => "\xC2\xA4",
'Dagger;' => "\xE2\x80\xA1",
'dagger;' => "\xE2\x80\xA0",
'dArr;' => "\xE2\x87\x93",
'darr;' => "\xE2\x86\x93",
'deg' => "\xC2\xB0",
'deg;' => "\xC2\xB0",
'Delta;' => "\xCE\x94",
'delta;' => "\xCE\xB4",
'diams;' => "\xE2\x99\xA6",
'divide' => "\xC3\xB7",
'divide;' => "\xC3\xB7",
'Eacute' => "\xC3\x89",
'eacute' => "\xC3\xA9",
'Eacute;' => "\xC3\x89",
'eacute;' => "\xC3\xA9",
'Ecirc' => "\xC3\x8A",
'ecirc' => "\xC3\xAA",
'Ecirc;' => "\xC3\x8A",
'ecirc;' => "\xC3\xAA",
'Egrave' => "\xC3\x88",
'egrave' => "\xC3\xA8",
'Egrave;' => "\xC3\x88",
'egrave;' => "\xC3\xA8",
'empty;' => "\xE2\x88\x85",
'emsp;' => "\xE2\x80\x83",
'ensp;' => "\xE2\x80\x82",
'Epsilon;' => "\xCE\x95",
'epsilon;' => "\xCE\xB5",
'equiv;' => "\xE2\x89\xA1",
'Eta;' => "\xCE\x97",
'eta;' => "\xCE\xB7",
'ETH' => "\xC3\x90",
'eth' => "\xC3\xB0",
'ETH;' => "\xC3\x90",
'eth;' => "\xC3\xB0",
'Euml' => "\xC3\x8B",
'euml' => "\xC3\xAB",
'Euml;' => "\xC3\x8B",
'euml;' => "\xC3\xAB",
'euro;' => "\xE2\x82\xAC",
'exist;' => "\xE2\x88\x83",
'fnof;' => "\xC6\x92",
'forall;' => "\xE2\x88\x80",
'frac12' => "\xC2\xBD",
'frac12;' => "\xC2\xBD",
'frac14' => "\xC2\xBC",
'frac14;' => "\xC2\xBC",
'frac34' => "\xC2\xBE",
'frac34;' => "\xC2\xBE",
'frasl;' => "\xE2\x81\x84",
'Gamma;' => "\xCE\x93",
'gamma;' => "\xCE\xB3",
'ge;' => "\xE2\x89\xA5",
'GT' => "\x3E",
'gt' => "\x3E",
'GT;' => "\x3E",
'gt;' => "\x3E",
'hArr;' => "\xE2\x87\x94",
'harr;' => "\xE2\x86\x94",
'hearts;' => "\xE2\x99\xA5",
'hellip;' => "\xE2\x80\xA6",
'Iacute' => "\xC3\x8D",
'iacute' => "\xC3\xAD",
'Iacute;' => "\xC3\x8D",
'iacute;' => "\xC3\xAD",
'Icirc' => "\xC3\x8E",
'icirc' => "\xC3\xAE",
'Icirc;' => "\xC3\x8E",
'icirc;' => "\xC3\xAE",
'iexcl' => "\xC2\xA1",
'iexcl;' => "\xC2\xA1",
'Igrave' => "\xC3\x8C",
'igrave' => "\xC3\xAC",
'Igrave;' => "\xC3\x8C",
'igrave;' => "\xC3\xAC",
'image;' => "\xE2\x84\x91",
'infin;' => "\xE2\x88\x9E",
'int;' => "\xE2\x88\xAB",
'Iota;' => "\xCE\x99",
'iota;' => "\xCE\xB9",
'iquest' => "\xC2\xBF",
'iquest;' => "\xC2\xBF",
'isin;' => "\xE2\x88\x88",
'Iuml' => "\xC3\x8F",
'iuml' => "\xC3\xAF",
'Iuml;' => "\xC3\x8F",
'iuml;' => "\xC3\xAF",
'Kappa;' => "\xCE\x9A",
'kappa;' => "\xCE\xBA",
'Lambda;' => "\xCE\x9B",
'lambda;' => "\xCE\xBB",
'lang;' => "\xE3\x80\x88",
'laquo' => "\xC2\xAB",
'laquo;' => "\xC2\xAB",
'lArr;' => "\xE2\x87\x90",
'larr;' => "\xE2\x86\x90",
'lceil;' => "\xE2\x8C\x88",
'ldquo;' => "\xE2\x80\x9C",
'le;' => "\xE2\x89\xA4",
'lfloor;' => "\xE2\x8C\x8A",
'lowast;' => "\xE2\x88\x97",
'loz;' => "\xE2\x97\x8A",
'lrm;' => "\xE2\x80\x8E",
'lsaquo;' => "\xE2\x80\xB9",
'lsquo;' => "\xE2\x80\x98",
'LT' => "\x3C",
'lt' => "\x3C",
'LT;' => "\x3C",
'lt;' => "\x3C",
'macr' => "\xC2\xAF",
'macr;' => "\xC2\xAF",
'mdash;' => "\xE2\x80\x94",
'micro' => "\xC2\xB5",
'micro;' => "\xC2\xB5",
'middot' => "\xC2\xB7",
'middot;' => "\xC2\xB7",
'minus;' => "\xE2\x88\x92",
'Mu;' => "\xCE\x9C",
'mu;' => "\xCE\xBC",
'nabla;' => "\xE2\x88\x87",
'nbsp' => "\xC2\xA0",
'nbsp;' => "\xC2\xA0",
'ndash;' => "\xE2\x80\x93",
'ne;' => "\xE2\x89\xA0",
'ni;' => "\xE2\x88\x8B",
'not' => "\xC2\xAC",
'not;' => "\xC2\xAC",
'notin;' => "\xE2\x88\x89",
'nsub;' => "\xE2\x8A\x84",
'Ntilde' => "\xC3\x91",
'ntilde' => "\xC3\xB1",
'Ntilde;' => "\xC3\x91",
'ntilde;' => "\xC3\xB1",
'Nu;' => "\xCE\x9D",
'nu;' => "\xCE\xBD",
'Oacute' => "\xC3\x93",
'oacute' => "\xC3\xB3",
'Oacute;' => "\xC3\x93",
'oacute;' => "\xC3\xB3",
'Ocirc' => "\xC3\x94",
'ocirc' => "\xC3\xB4",
'Ocirc;' => "\xC3\x94",
'ocirc;' => "\xC3\xB4",
'OElig;' => "\xC5\x92",
'oelig;' => "\xC5\x93",
'Ograve' => "\xC3\x92",
'ograve' => "\xC3\xB2",
'Ograve;' => "\xC3\x92",
'ograve;' => "\xC3\xB2",
'oline;' => "\xE2\x80\xBE",
'Omega;' => "\xCE\xA9",
'omega;' => "\xCF\x89",
'Omicron;' => "\xCE\x9F",
'omicron;' => "\xCE\xBF",
'oplus;' => "\xE2\x8A\x95",
'or;' => "\xE2\x88\xA8",
'ordf' => "\xC2\xAA",
'ordf;' => "\xC2\xAA",
'ordm' => "\xC2\xBA",
'ordm;' => "\xC2\xBA",
'Oslash' => "\xC3\x98",
'oslash' => "\xC3\xB8",
'Oslash;' => "\xC3\x98",
'oslash;' => "\xC3\xB8",
'Otilde' => "\xC3\x95",
'otilde' => "\xC3\xB5",
'Otilde;' => "\xC3\x95",
'otilde;' => "\xC3\xB5",
'otimes;' => "\xE2\x8A\x97",
'Ouml' => "\xC3\x96",
'ouml' => "\xC3\xB6",
'Ouml;' => "\xC3\x96",
'ouml;' => "\xC3\xB6",
'para' => "\xC2\xB6",
'para;' => "\xC2\xB6",
'part;' => "\xE2\x88\x82",
'permil;' => "\xE2\x80\xB0",
'perp;' => "\xE2\x8A\xA5",
'Phi;' => "\xCE\xA6",
'phi;' => "\xCF\x86",
'Pi;' => "\xCE\xA0",
'pi;' => "\xCF\x80",
'piv;' => "\xCF\x96",
'plusmn' => "\xC2\xB1",
'plusmn;' => "\xC2\xB1",
'pound' => "\xC2\xA3",
'pound;' => "\xC2\xA3",
'Prime;' => "\xE2\x80\xB3",
'prime;' => "\xE2\x80\xB2",
'prod;' => "\xE2\x88\x8F",
'prop;' => "\xE2\x88\x9D",
'Psi;' => "\xCE\xA8",
'psi;' => "\xCF\x88",
'QUOT' => "\x22",
'quot' => "\x22",
'QUOT;' => "\x22",
'quot;' => "\x22",
'radic;' => "\xE2\x88\x9A",
'rang;' => "\xE3\x80\x89",
'raquo' => "\xC2\xBB",
'raquo;' => "\xC2\xBB",
'rArr;' => "\xE2\x87\x92",
'rarr;' => "\xE2\x86\x92",
'rceil;' => "\xE2\x8C\x89",
'rdquo;' => "\xE2\x80\x9D",
'real;' => "\xE2\x84\x9C",
'REG' => "\xC2\xAE",
'reg' => "\xC2\xAE",
'REG;' => "\xC2\xAE",
'reg;' => "\xC2\xAE",
'rfloor;' => "\xE2\x8C\x8B",
'Rho;' => "\xCE\xA1",
'rho;' => "\xCF\x81",
'rlm;' => "\xE2\x80\x8F",
'rsaquo;' => "\xE2\x80\xBA",
'rsquo;' => "\xE2\x80\x99",
'sbquo;' => "\xE2\x80\x9A",
'Scaron;' => "\xC5\xA0",
'scaron;' => "\xC5\xA1",
'sdot;' => "\xE2\x8B\x85",
'sect' => "\xC2\xA7",
'sect;' => "\xC2\xA7",
'shy' => "\xC2\xAD",
'shy;' => "\xC2\xAD",
'Sigma;' => "\xCE\xA3",
'sigma;' => "\xCF\x83",
'sigmaf;' => "\xCF\x82",
'sim;' => "\xE2\x88\xBC",
'spades;' => "\xE2\x99\xA0",
'sub;' => "\xE2\x8A\x82",
'sube;' => "\xE2\x8A\x86",
'sum;' => "\xE2\x88\x91",
'sup;' => "\xE2\x8A\x83",
'sup1' => "\xC2\xB9",
'sup1;' => "\xC2\xB9",
'sup2' => "\xC2\xB2",
'sup2;' => "\xC2\xB2",
'sup3' => "\xC2\xB3",
'sup3;' => "\xC2\xB3",
'supe;' => "\xE2\x8A\x87",
'szlig' => "\xC3\x9F",
'szlig;' => "\xC3\x9F",
'Tau;' => "\xCE\xA4",
'tau;' => "\xCF\x84",
'there4;' => "\xE2\x88\xB4",
'Theta;' => "\xCE\x98",
'theta;' => "\xCE\xB8",
'thetasym;' => "\xCF\x91",
'thinsp;' => "\xE2\x80\x89",
'THORN' => "\xC3\x9E",
'thorn' => "\xC3\xBE",
'THORN;' => "\xC3\x9E",
'thorn;' => "\xC3\xBE",
'tilde;' => "\xCB\x9C",
'times' => "\xC3\x97",
'times;' => "\xC3\x97",
'TRADE;' => "\xE2\x84\xA2",
'trade;' => "\xE2\x84\xA2",
'Uacute' => "\xC3\x9A",
'uacute' => "\xC3\xBA",
'Uacute;' => "\xC3\x9A",
'uacute;' => "\xC3\xBA",
'uArr;' => "\xE2\x87\x91",
'uarr;' => "\xE2\x86\x91",
'Ucirc' => "\xC3\x9B",
'ucirc' => "\xC3\xBB",
'Ucirc;' => "\xC3\x9B",
'ucirc;' => "\xC3\xBB",
'Ugrave' => "\xC3\x99",
'ugrave' => "\xC3\xB9",
'Ugrave;' => "\xC3\x99",
'ugrave;' => "\xC3\xB9",
'uml' => "\xC2\xA8",
'uml;' => "\xC2\xA8",
'upsih;' => "\xCF\x92",
'Upsilon;' => "\xCE\xA5",
'upsilon;' => "\xCF\x85",
'Uuml' => "\xC3\x9C",
'uuml' => "\xC3\xBC",
'Uuml;' => "\xC3\x9C",
'uuml;' => "\xC3\xBC",
'weierp;' => "\xE2\x84\x98",
'Xi;' => "\xCE\x9E",
'xi;' => "\xCE\xBE",
'Yacute' => "\xC3\x9D",
'yacute' => "\xC3\xBD",
'Yacute;' => "\xC3\x9D",
'yacute;' => "\xC3\xBD",
'yen' => "\xC2\xA5",
'yen;' => "\xC2\xA5",
'yuml' => "\xC3\xBF",
'Yuml;' => "\xC5\xB8",
'yuml;' => "\xC3\xBF",
'Zeta;' => "\xCE\x96",
'zeta;' => "\xCE\xB6",
'zwj;' => "\xE2\x80\x8D",
'zwnj;' => "\xE2\x80\x8C"
);
for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++)
{
$consumed = substr($this->consumed, 1);
if (isset($entities[$consumed]))
{
$match = $consumed;
}
}
if ($match !== null)
{
$this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1);
$this->position += strlen($entities[$match]) - strlen($consumed) - 1;
}
break;
}
}
}

Some files were not shown because too many files have changed in this diff Show More