PageRenderTime 49ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/files/stream.php

https://github.com/jmarc06/yacs
PHP | 431 lines | 203 code | 91 blank | 137 comment | 38 complexity | 0eb1b5dcad58de41550ca776c6485307 MD5 | raw file
  1. <?php
  2. /**
  3. * stream a file
  4. *
  5. * This script turns a YACS server into a pseudo-streaming server.
  6. * On intranets or at home, with VLC or Winamp or Windows Media Player installed at workstations,
  7. * it allows people to view films on-demand.
  8. *
  9. * @link http://www.videolan.org/vlc/ VLC media player
  10. *
  11. * This script acts as a redirector for well-known types:
  12. * - [code].3gp[/code] (load a flash player in full screen)
  13. * - [code].aif[/code] (through a [code].m3u[/code] redirector)
  14. * - [code].aiff[/code] (through a [code].m3u[/code] redirector)
  15. * - [code].au[/code] (through a [code].m3u[/code] redirector)
  16. * - [code].flv[/code] (load a flash player in full screen)
  17. * - [code].gan[/code] (loaded through Javascript)
  18. * - [code].m4v[/code] (load a flash player in full screen)
  19. * - [code].mka[/code] (through a [code].m3u[/code] redirector)
  20. * - [code].mm[/code] (load a flash player)
  21. * - [code].mov[/code] (load a flash player in full screen)
  22. * - [code].mp3[/code] (through a [code].m3u[/code] redirector)
  23. * - [code].mp4[/code] (load a flash player in full screen)
  24. * - [code].snd[/code] (through a [code].m3u[/code] redirector)
  25. * - [code].swf[/code] (loaded through Javascript)
  26. * - [code].wav[/code] (through a [code].m3u[/code] redirector)
  27. * - [code].wma[/code] (through a [code].wax[/code] redirector)
  28. * - [code].wmv[/code] (through a [code].wvx[/code] redirector)
  29. * - [code].ra[/code] (through a [code].ram[/code] redirector)
  30. *
  31. * @link http://www.spartanicus.utvinternet.ie/streaming.htm Streaming audio/video from a web server
  32. * @link http://forums.winamp.com/showthread.php?s=dbec47f3a05d10a3a77959f17926d39c&threadid=65772 The Unofficial M3U and PLS Specification
  33. *
  34. * The downloaded object is always cacheable, to avoid IE to remove it too early from temporary directory.
  35. *
  36. * @link http://www.webmasterworld.com/forum88/5891.htm Internet Explorer download problem
  37. *
  38. * For authentication on protected page this script use basic HTTP authentication.
  39. * This means that the anonymous surfer will have either to use the regular login page, or to provide name and password on a per-request basis.
  40. *
  41. * Per-request authentication is based on HTTP basic authentication mechanism, as explained in
  42. * [link=RFC2617]http://www.faqs.org/rfcs/rfc2617.html[/link].
  43. *
  44. * @link http://www.faqs.org/rfcs/rfc2617.html HTTP Authentication: Basic and Digest Access Authentication
  45. *
  46. * Accept following invocations:
  47. * - stream.php/12
  48. * - stream.php?id=12
  49. *
  50. * If the anchor for this item specifies a specific skin (option keyword '[code]skin_xyz[/code]'),
  51. * or a specific variant (option keyword '[code]variant_xyz[/code]'), they are used instead default values.
  52. *
  53. * @author Bernard Paques
  54. * @author GnapZ
  55. * @reference
  56. * @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License
  57. */
  58. // common definitions and initial processing
  59. include_once '../shared/global.php';
  60. include_once 'files.php';
  61. include_once '../users/activities.php'; // record file fetch
  62. // check network credentials, if any -- used by winamp and other media players
  63. if($user = Users::authenticate())
  64. Surfer::empower($user['capability']);
  65. // look for the id
  66. $id = NULL;
  67. if(isset($_REQUEST['id']))
  68. $id = $_REQUEST['id'];
  69. elseif(isset($context['arguments'][0]))
  70. $id = $context['arguments'][0];
  71. $id = strip_tags($id);
  72. // get the item from the database
  73. $item =& Files::get($id);
  74. // get the related anchor, if any
  75. $anchor = NULL;
  76. if(isset($item['anchor']) && $item['anchor'])
  77. $anchor =& Anchors::get($item['anchor']);
  78. // get related behaviors, if any
  79. $behaviors = NULL;
  80. include_once '../behaviors/behaviors.php';
  81. if(isset($item['id']))
  82. $behaviors = new Behaviors($item, $anchor);
  83. // public access is allowed
  84. if(isset($item['active']) && ($item['active'] == 'Y'))
  85. $permitted = TRUE;
  86. // access is restricted to authenticated member
  87. elseif(isset($item['active']) && ($item['active'] == 'R') && Surfer::is_logged())
  88. $permitted = TRUE;
  89. // the item is anchored to the profile of this member
  90. elseif(Surfer::is_member() && !strcmp($item['anchor'], 'user:'.Surfer::get_id()))
  91. $permitted = TRUE;
  92. // authenticated users may view their own posts
  93. elseif(isset($item['create_id']) && Surfer::is($item['create_id']))
  94. $permitted = TRUE;
  95. // associates and editors can do what they want
  96. elseif(Surfer::is_associate() || (is_object($anchor) && $anchor->is_assigned()))
  97. $permitted = TRUE;
  98. // the default is to disallow access
  99. else
  100. $permitted = FALSE;
  101. // load the skin, maybe with a variant
  102. load_skin('files', $anchor);
  103. // clear the tab we are in, if any
  104. if(is_object($anchor))
  105. $context['current_focus'] = $anchor->get_focus();
  106. // the path to this page
  107. if(is_object($anchor) && $anchor->is_viewable())
  108. $context['path_bar'] = $anchor->get_path_bar();
  109. else
  110. $context['path_bar'] = array( 'files/' => i18n::s('Files') );
  111. // the title of the page
  112. if($item['title'])
  113. $context['page_title'] = $item['title'];
  114. else
  115. $context['page_title'] = str_replace('_', ' ', $item['file_name']);
  116. // change default behavior
  117. if(isset($item['id']) && is_object($behaviors) && !$behaviors->allow('files/stream.php', 'file:'.$item['id']))
  118. $permitted = FALSE;
  119. // not found
  120. if(!$item['id']) {
  121. include '../error.php';
  122. // permission denied
  123. } elseif(!$permitted) {
  124. // give anonymous surfers a chance for HTTP authentication
  125. if(!Surfer::is_logged()) {
  126. Safe::header('WWW-Authenticate: Basic realm="'.utf8::to_iso8859($context['site_name']).'"');
  127. Safe::header('Status: 401 Unauthorized', TRUE, 401);
  128. }
  129. // permission denied to authenticated user
  130. Safe::header('Status: 401 Unauthorized', TRUE, 401);
  131. Logger::error(i18n::s('You are not allowed to perform this operation.'));
  132. // stream this file
  133. } else {
  134. // if we have an external reference, use it
  135. if(isset($item['file_href']) && $item['file_href']) {
  136. $target_href = $item['file_href'];
  137. // else redirect to ourself
  138. } else {
  139. // ensure a valid file name
  140. $file_name = utf8::to_ascii($item['file_name']);
  141. // where the file is
  142. $path = 'files/'.$context['virtual_path'].str_replace(':', '/', $item['anchor']).'/'.rawurlencode($item['file_name']);
  143. // redirect to the actual file
  144. $target_href = $context['url_to_home'].$context['url_to_root'].$path;
  145. }
  146. // determine attribute for this item
  147. $type = $mime = $text = '';
  148. // the default is to provide the file directly
  149. $fetched = FALSE;
  150. // embed the file depending on the file type
  151. $extension = strtolower(@array_pop(@explode('.', @basename($item['file_name']))));
  152. switch($extension) {
  153. case 'aif':
  154. case 'aiff':
  155. case 'au':
  156. case 'mka':
  157. case 'mp3':
  158. case 'snd':
  159. case 'wav':
  160. // we are returning a .m3u
  161. $type = '.m3u';
  162. $mime = 'audio/x-mpegurl';
  163. // protect file origin, and set winamp headers
  164. $text = $context['url_to_home'].$context['url_to_root'].Files::get_url($item['id'], 'fetch', $item['file_name']);
  165. // accounting is done through download
  166. $fetched = TRUE;
  167. break;
  168. case '3gp':
  169. case 'flv':
  170. case 'm4v':
  171. case 'mov':
  172. case 'mp4':
  173. // embed into a web page
  174. $type = '';
  175. $mime = 'text/html';
  176. // load the full library
  177. $script = 'included/browser/library.js';
  178. // page preamble
  179. $text = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'."\n"
  180. .'<html>'."\n"
  181. .'<head>'."\n"
  182. .'<title>'.$context['page_title'].'</title>'."\n"
  183. .'<script type="text/javascript" src="'.$context['url_to_root'].$script.'"></script>'."\n"
  184. .'</head>'."\n"
  185. .'<body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">'."\n";
  186. // embed the object
  187. $text .= Codes::render_embed($item['id']);
  188. // page postamble
  189. $text .= '</body>'."\n"
  190. .'</html>'."\n";
  191. break;
  192. case 'gan':
  193. // we are using the SIMILE Timeline viewer within a regular page
  194. $type = '';
  195. $mime = 'text/html';
  196. // page preamble
  197. $text = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'."\n"
  198. .'<html>'."\n"
  199. .'<head>'."\n"
  200. .'<title>'.$context['page_title'].'</title>'."\n"
  201. .'<script type="text/javascript" src="'.$context['url_to_root'].'included/browser/library.js"></script>'."\n"
  202. .'<script type="text/javascript" src="http://simile.mit.edu/timeline/api/timeline-api.js"></script>'."\n"
  203. .'</head>'."\n"
  204. .'<body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">'."\n";
  205. // embed the object into a regular page --100% does not work
  206. $text .= Codes::render_embed($item['id'].', 99%, 90%');
  207. // add a link to close the window
  208. $text .= '</div>'."\n"
  209. .'<p style="text-align: center; margin: 0.5em 0 1em 0;"><button type="button" onclick="self.close()">'.i18n::s('Close').'</button></p>'."\n";
  210. // page postamble
  211. $text .= '</body>'."\n"
  212. .'</html>'."\n";
  213. break;
  214. case 'mm':
  215. // we are invoking some freemind viewer
  216. $type = '';
  217. $mime = 'text/html';
  218. // page preamble
  219. $text = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'."\n"
  220. .'<html>'."\n"
  221. .'<head>'."\n"
  222. .'<title>'.$context['page_title'].'</title>'."\n"
  223. .'<script type="text/javascript" src="'.$context['url_to_root'].'included/browser/library.js"></script>'."\n"
  224. .'</head>'."\n"
  225. .'<body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">'."\n";
  226. // render object full size
  227. $text .= Codes::render_freemind($target_href.', 100%, 90%');
  228. // add a link to close the window
  229. $text .= '</div>'."\n"
  230. .'<p style="text-align: center; margin: 0.5em 0 1em 0;"><button type="button" onclick="self.close()">'.i18n::s('Close').'</button></p>'."\n";
  231. // page postamble
  232. $text .= '</body>'."\n"
  233. .'</html>'."\n";
  234. break;
  235. case 'swf':
  236. // display a large Flash file
  237. $type = '';
  238. $mime = 'text/html';
  239. // page preamble
  240. $text = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'."\n"
  241. .'<html>'."\n"
  242. .'<head>'."\n"
  243. .'<title>'.$context['page_title'].'</title>'."\n";
  244. // load the full library
  245. $script = 'included/browser/library.js';
  246. $text .= '<script type="text/javascript" src="'.$context['url_to_root'].$script.'"></script>'."\n";
  247. // load javascript files from the skin directory -- e.g., Global Crossing js extensions, etc.
  248. if(isset($context['skin'])) {
  249. foreach(Safe::glob($context['path_to_root'].$context['skin'].'/*.js') as $name)
  250. $text .= '<script type="text/javascript" src="'.$context['url_to_root'].$context['skin'].'/'.basename($name).'"></script>'."\n";
  251. }
  252. // load skin style sheet
  253. // if(isset($context['skin']))
  254. // $text .= '<link rel="stylesheet" href="'.$context['url_to_root'].$context['skin'].'/'.str_replace('skins/', '', $context['skin']).'.css" type="text/css" media="all" />'."\n";
  255. // full screen
  256. $text .= '</head>'."\n"
  257. .'<body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">'."\n"
  258. .'<div id="live_flash">'."\n";
  259. // render object full size
  260. $text .= Codes::render_embed($item['id'].', 100%, 90%');
  261. // add a link to close the window
  262. $text .= '</div>'."\n"
  263. .'<p style="text-align: center; margin: 0.5em 0 1em 0;"><button type="button" onclick="self.close()">'.i18n::s('Close').'</button></p>'."\n";
  264. // page postamble
  265. $text .= '</body>'."\n"
  266. .'</html>'."\n";
  267. break;
  268. case 'wma':
  269. // we are returning a .wax
  270. $type = '.wax';
  271. $mime = 'audio/x-ms-wax';
  272. // where the file actually is
  273. $text = '<ASX VERSION="3.0">'."\n"
  274. .' <ENTRY>'."\n"
  275. .' <REF HREF="'.$target_href.'" />'."\n"
  276. .' </ENTRY>'."\n"
  277. .'</ASX>';
  278. break;
  279. case 'wmv':
  280. // we are returning a .wvx
  281. $type = '.wvx';
  282. $mime = 'video/x-ms-wvx';
  283. // where the file actually is
  284. $text = '<ASX VERSION="3.0">'."\n"
  285. .' <ENTRY>'."\n"
  286. .' <REF HREF="'.$target_href.'" />'."\n"
  287. .' </ENTRY>'."\n"
  288. .'</ASX>';
  289. break;
  290. case 'ra':
  291. // we are returning a .ram
  292. $type = '.ram';
  293. $mime = 'audio/x-pn-realaudio';
  294. // where the file actually is
  295. $text = $target_href;
  296. break;
  297. // default
  298. default:
  299. Logger::error(i18n::s('Do not know how to stream this file type'));
  300. break;
  301. }
  302. //
  303. // transfer to the user agent
  304. //
  305. // if we have a valid redirector
  306. if($mime && $text) {
  307. // direct sourcing
  308. if(!$fetched) {
  309. // increment the count of downloads
  310. Files::increment_hits($item['id']);
  311. // record surfer activity
  312. Activities::post('file:'.$item['id'], 'fetch');
  313. }
  314. // no encoding, no compression and no yacs handler...
  315. if(!headers_sent()) {
  316. Safe::header('Content-Type: '.$mime);
  317. Safe::header('Content-Length: '.strlen($text));
  318. }
  319. // suggest a download
  320. if(!headers_sent()) {
  321. $file_name = utf8::to_ascii($item['file_name'].$type);
  322. Safe::header('Content-Disposition: inline; filename="'.$file_name.'"');
  323. }
  324. // enable 30-minute caching (30*60 = 1800), even through https, to help IE6 on download
  325. http::expire(1800);
  326. // strong validator
  327. $etag = '"'.md5($text).'"';
  328. // manage web cache
  329. if(http::validate(NULL, $etag))
  330. return;
  331. // actual transmission except on a HEAD request
  332. if(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] != 'HEAD'))
  333. echo $text;
  334. // the post-processing hook, then exit
  335. finalize_page(TRUE);
  336. }
  337. }
  338. // render the skin
  339. render_skin();
  340. ?>