1<?php
2
3
4/**
5 * Frontend for retrieving images and files attached to a database record or
6 * available in an editor session.
7 *
8 * This file is part of DokuWiki plugin database2 and is available under
9 * GPL version 2. See the following URL for a copy of this license!
10 *
11 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
12 *
13 *
14 * @author Thomas Urban <soletan@nihilum.de>
15 * @version 0.2
16 * @copyright GPLv2
17 *
18 */
19
20
21/*
22 * initialize DokuWiki
23 */
24
25if ( !defined( 'DOKU_INC' ) )
26{
27
28	foreach ( array( dirname( __FILE__ ), $_SERVER['SCRIPT_FILENAME'] ) as $path )
29	{
30
31		while ( $path != '/' )
32			if ( is_file( $path . '/doku.php' ) && is_dir( $path . '/lib' ) )
33				break;
34			else
35				$path = dirname( $path );
36
37		if ( $path != '/' )
38			break;
39
40	}
41
42	if ( $path && ( $path != '/' ) )
43		define( 'DOKU_INC', $path . '/' );
44	else
45		die( "Failed to locate DokuWiki installation folder!");
46
47}
48
49
50ob_start();	// capture and flush all initial output of DokuWiki
51
52include_once( DOKU_INC . 'inc/init.php' );
53include_once( DOKU_INC . 'inc/common.php' );
54include_once( DOKU_INC . 'inc/events.php' );
55include_once( DOKU_INC . 'inc/pageutils.php' );
56include_once( DOKU_INC . 'inc/html.php' );
57include_once( DOKU_INC . 'inc/auth.php' );
58include_once( DOKU_INC . 'inc/actions.php' );
59include_once( DOKU_INC . 'lib/plugin.php' );
60
61ob_end_clean();
62
63
64
65
66try
67{
68
69	if ( $_SERVER['REQUEST_METHOD'] != 'GET' )
70		throw new Exception( 'invalid request method', 400 );
71
72
73	@session_start();
74
75
76	if ( $_GET['s'] )
77	{
78		// read media from session
79
80		$source = @unserialize( @base64_decode( $_GET['s'] ) );
81		if ( !$source )
82			throw new Exception( 'invalid request', 400 );
83
84
85		list( $pageID, $ioIndex, $column ) = $source;
86
87		$session = $_SESSION['database2'][$pageID]['tables'][$ioIndex]['editors'][$column];
88		if ( !is_array( $session ) )
89			throw new Exception( 'no such media', 404 );
90
91		$data = $session['file'];
92		$mime = $session['mime'];
93		$name = $session['name'];
94
95		if ( !$data )
96			throw new Exception( 'file is empty', 404 );
97
98		if ( !$name )
99			$_GET['d'] = false;
100
101		if ( !$mime )
102			throw new Exception( 'invalid media', 403 );
103
104	}
105	else
106	{
107		// read media from database
108
109		$source = @unserialize( @gzuncompress( @base64_decode( $_GET['a'] ) ) );
110		$hash   = trim( @base64_decode( $_GET['b'] ) );
111
112		if ( !is_array( $source ) || ( $hash === '' ) )
113			throw new Exception( 'invalid request parameter', 400 );
114
115		list( $dsn, $authSlot, $table, $column, $idColumn, $rowid, $pageID,
116			  $ioIndex, $user, $addr ) = $source;
117
118		if ( $addr !== $_SERVER['REMOTE_ADDR'] )
119			throw new Exception( 'invalid request context', 400 );
120
121
122
123		$t    = sha1( implode( '/', $source ) );
124		$salt = $_SESSION['database2'][$pageID]['tables'][$ioIndex]['linkedMediaSalts'][$t];
125
126		if ( !is_string( $salt ) || ( trim( $salt ) === '' ) )
127			throw new Exception( 'access denied', 403 );
128
129
130
131		// include Database2 ...
132		$libFile = '/database2.php';
133
134		// support working with development version if available and
135		// selected to enable development in a production wiki
136		// (as used on wiki.nihilum.de)
137		if ( is_file( dirname( __FILE__ ) . '/database2.dev.php' ) )
138			if ( $_SESSION['useDevIP'] )
139				if ( $_SESSION['useDevIP'] == $_SERVER['REMOTE_ADDR'] )
140					$libFile = '/database2.dev.php';
141
142		include_once( dirname( __FILE__ ) . $libFile );
143
144		// ... and derive it to gain access on internals to
145		class Database2_media extends Database2
146		{
147
148			public function __construct( $dsn, $authSlot, $table, $ioIndex,
149										 $pageId )
150			{
151
152				if ( !$this->connect( $dsn, $authSlot ) )
153					throw new Exception( 'database not available/not found', 404 );
154
155				$table = trim( $table );
156				if ( !self::isValidName( $table ) )
157					throw new Exception( 'invalid media selector (table)', 400 );
158
159				$this->table   = $table;
160				$this->ioIndex = $ioIndex;
161
162				$this->explicitPageID = $pageId;
163
164			}
165
166			final public function getMedia( $column, $idColumn, $rowid )
167			{
168
169				$column   = trim( $column );
170				$idColumn = trim( $idColumn );
171				$rowid    = intval( $rowid );
172
173				if ( !self::isValidName( $column ) ||
174					 !self::isValidName( $idColumn ) || !$rowid )
175					throw new Exception( 'invalid media selector', 400 );
176
177
178				$st = $this->getLink()->prepare( 'SELECT ' . $column .
179												 ' FROM ' . $this->table .
180												 ' WHERE ' . $idColumn . '=?' );
181				if ( !$st )
182					throw new Exception( 'failed to prepare retrieval', 500 );
183
184				if ( !$st->execute( array( $rowid ) ) )
185					throw new Exception( 'failed to retrieve', 500 );
186
187
188				$media = $st->fetch( PDO::FETCH_NUM );
189				if ( !is_array( $media ) )
190					throw new Exception( 'no such media', 404 );
191
192				$st->closeCursor();
193
194				return $media[0];
195
196			}
197
198			final public function printTable()
199			{
200				return $this->showTable( false, true, true, null, true );
201			}
202		}
203
204
205
206
207		// check salted hash provided in URL (used to proove authorization)
208		$providedHash = @base64_decode( $_GET['b'] );
209		if ( !$providedHash )
210			throw new Exception( 'access denied, missing valid hash', 403 );
211
212		$source = serialize( $source );
213		if ( Database2::ssha( $source, $salt ) !== $providedHash )
214			throw new Exception( 'access denied, invalid hash', 403 );
215
216
217
218		// query database for selected media file
219		$db = new Database2_media( $dsn, $authSlot, $table, $ioIndex, $pageID );
220
221		switch ( $_GET['m'] )
222		{
223
224			case 'print' :
225				$data = $db->printTable();
226
227				$title = sprintf( $db->getLang( 'printtitle' ), $table );
228
229				$data = <<<EOT
230<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
231<html xmlns="http://www.w3.org/1999/xhtml">
232<head>
233<title>$title</title>
234<link rel="stylesheet" type="text/css" href="./print.css" />
235</head>
236<body class="print" onload="print()">
237$data
238</body>
239</html>
240EOT;
241
242				// add meta information for proper download
243				$mime = 'text/html; charset=utf-8';
244				$name = $table . '_print_' . date( 'Y-m-d_H-i' ) . '.html';
245
246				break;
247
248			case 'xml' :
249			case 'csv' :
250
251				// get list of all columns not containing binary data
252				$meta = $db->getColumnsMeta();
253				$cols = array();
254				foreach ( $meta as $column => $def )
255					if ( $def['isColumn'] && ( $def['type'] != 'data' ) )
256						$cols[$column] = $column;
257
258				// retrieve records
259				$rows   = $db->__recordsList( $cols );
260
261				// extract separate header row
262				$header = array_shift( array_slice( $rows, 0, 1 ) );
263				foreach ( $header as $column => $data )
264					$header[$column] = $meta[$column]['label'] ? $meta[$column]['label'] : $column;
265
266				// write header row to CSV
267				$data = $db->__csvLine( $header );
268
269				// write all fetched rows to CSV
270				foreach ( $rows as $row )
271					$data .= $db->__csvLine( $row );
272
273
274				// add meta information for proper download
275				$mime = 'text/csv; charset=utf-8';
276				$name = $table . '_export_' . date( 'Y-m-d_H-i' ) . '.csv';
277
278				break;
279
280			case 'log' :
281
282				$st = $db->getLink()->prepare( 'SELECT rowid,action,username,ctime FROM __log WHERE tablename=? ORDER BY ctime DESC' );
283				if ( !$st )
284					throw new Exception( 'failed to prepare viewing log', 500 );
285
286				if ( !$st->execute( array( $table ) ) )
287					throw new Exception( 'failed to execute viewing log', 500 );
288
289
290				// retrieve records
291				$rows = $st->fetchAll( PDO::FETCH_ASSOC );
292				if ( empty( $rows ) )
293					$data = '';
294				else
295				{
296
297					// write header row to CSV
298					$data = $db->__csvLine( array_keys( array_shift( array_slice( $rows, 0, 1 ) ) ) );
299
300					// write all fetched rows to CSV
301					foreach ( $rows as $row )
302					{
303
304						$row['ctime'] = date( 'r', $row['ctime'] );
305
306						$data .= $db->__csvLine( $row );
307
308					}
309				}
310
311
312				// add meta information for proper download
313				$mime = 'text/csv; charset=utf-8';
314				$name = $table . '_log_' . date( 'Y-m-d_H-i' ) . '.csv';
315
316				break;
317
318			default :
319				$data = $db->getMedia( $column, $idColumn, $rowid );
320
321				// strip off MIME and filename embedded in retrieved file
322				$sepPos = strpos( $data, '|' );
323				if ( !$sepPos || ( $sepPos > 256 ) )
324					throw new Exception( 'untyped data record', 403 );
325
326				$mime = substr( $data, 0, $sepPos );
327				$data = substr( $data, $sepPos + 1 );
328
329
330				$sepPos = strpos( $data, '|' );
331				if ( !$sepPos || ( $sepPos > 256 ) )
332					// missing filename, thus reject to support download
333					$_GET['d'] = false;
334				else
335					$name = substr( $data, 0, $sepPos );
336
337				$data = substr( $data, $sepPos + 1 );
338
339		}
340	}
341
342
343
344
345	// provide file ...
346	if ( $_GET['d'] )
347		// ... for download otionally
348		header( 'Content-Disposition: attachment; filename=' . $name );
349
350
351	if ( isset( $_GET['thumb'] ) )
352	{
353		// derive thumbnail from file
354
355		list( $major, $minor ) = explode( '/', $mime );
356
357		// try to parse file as image first
358		$img = ( $major == 'image' ) ? @imagecreatefromstring( $data ) : false;
359		if ( $img )
360		{
361
362			// get desired size of thumbnail
363			$temp = ( trim( $_GET['thumb'] ) === '' ) ? '200x150' : $_GET['thumb'];
364
365			list( $width, $height ) = explode( 'x', $temp );
366
367			$width  = intval( $width )  ? intval( $width )  : null;
368			$height = intval( $height ) ? intval( $height ) : null;
369
370			if ( !$width || !$height )
371			{
372
373				if ( !$width && !$height )
374					$width = 200;
375
376				$aspect = ( imagesx( $img ) / imagesy( $img ) );
377
378				if ( $width )
379					$height = round( $width  / $aspect );
380				else
381					$width  = round( $height * $aspect );
382
383			}
384
385			if ( ( $width < imagesx( $img ) ) || ( $height < imagesy( $img ) ) )
386			{
387				// scale-down image to thumbnail size
388
389				$dest = imagecreatetruecolor( $width, $height );
390
391				imagesavealpha( $dest, ( $minor === 'png' ) );
392				imagealphablending( $dest, ( $minor !== 'png' ) );
393
394				if ( $minor !== 'png' )
395					imagecolorallocate( $dest, 255, 255, 255 );
396
397				imagecopyresampled( $dest, $img, 0, 0, 0, 0,
398									imagesx( $dest ), imagesy( $dest ),
399									imagesx( $img ), imagesy( $img ) );
400
401			}
402			else
403				// keep original, as it is smaller than requested thumbnail size
404				$dest = $img;
405
406
407			// render thumbnail image
408			ob_start();
409
410			switch ( $minor )
411			{
412
413				case 'gif' :
414					imagegif( $dest );
415					break;
416
417				case 'jpeg' :
418				case 'pjpeg' :
419				case 'jpg' :
420					imagejpeg( $dest );
421					break;
422
423				default :
424					$mime = 'image/png';
425				case 'png' :
426					imagepng( $dest );
427
428			}
429
430			$data = ob_get_clean();
431
432		}
433	}
434
435
436
437	header( 'Content-Type: ' . $mime );
438
439	echo $data;
440
441}
442catch ( Exception $e )
443{
444
445	header( 'HTTP/1.1 ' . $e->getCode() . ' ' . $e->getMessage() );
446
447	echo $e->getMessage();
448
449}
450
451
452?>