- very early D6 version (Oct-01, 2007)
[project/gallery.git] / G2EmbedDiscoveryUtilities.class
CommitLineData
c15e9178 1<?php
2/*
c15e9178 3 * Gallery - a web based photo album viewer and editor
4 * Copyright (C) 2000-2006 Bharat Mediratta
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20/**
21 * @version $Revision$ $Date$
22 * @package GalleryCore
23 * @author Andy Staudacher <ast@gmx.ch>
24 */
25
26/**
27 * A collection of useful G2Embed related utilities to find the correct GalleryEmbed::init
28 * parameters
29 *
30 * @package GalleryCore
31 * @subpackage GalleryEmbed
32 * @static
33 */
34class G2EmbedDiscoveryUtilities {
35 /**
36 * Documentation:
37 * To use GalleryEmbed and its GalleryEmbed::init method to initialize G2, you need:
38 * a) the absolute filesystem path to embed.php
39 * b) embedUri, the URL of the entry point to your embedding application / embedded G2
40 * e.g. http://example.com/ or just / , or http://example.com/index.php?mod=gallery
41 * c) g2Uri, the URL path to G2, e.g. http://example.com/gallery2/ or just /gallery2/
42 *
43 * Methods to finding out the path to embed.php:
44 * ============================================
45 *
46 * - It's a good assumption that you can find out or define embedUri easily
47 * - g2Uri must be entered by the admin that configures the integration, just copy and paste
48 * the URL of G2
49 * - finding out embed.php is a little tricky.
50 *
51 * We offer two methods to get embed.php. Do NOT call them for each request. Call them once
52 * when configuring / installing your integration. Else you get a performance penalty.
53 *
54 * 1. If you ask the user to enter the g2Uri, you can call:
55 * list ($success, $embedPhpPath, $errorString) =
56 * G2EmbedDiscoveryUtilities::getG2EmbedPathByG2Uri($g2Uri);
57 * if (!$success) {
58 * print $errorString;
59 * /* Tell the admin to enter the correct input
60 * } else {
61 * /* embedPhpPath is correct and you can store it in your config for later use
62 * }
63 *
64 * 2. If you ask only for the g2Uri, you also need to provide the filesystem path to the
65 * entry point (the filesystem path to the file that embedUri points to)
66 * list ($success, $embedPhpPath, $errorString) =
67 * G2EmbedDiscoveryUtilities::getG2EmbedPathByG2UriEmbedUriAndLocation(
68 * $g2Uri, $embedUri, dirname(dirname(__FILE__)));
69 * if (!$success) {
70 * print $errorString;
71 * /* Tell the admin to enter the correct input
72 * } else {
73 * /* embedPhpPath is correct and you can store it in your config for later use
74 * }
75 * Disadvantage of this method: it's less reliable. It won't work with Apache Alias,
76 * or with subdomains, ...
77 *
78 *
79 * Method to normalize the g2Uri and embedUri before using them in GalleryEmbed::init:
80 * ==================================================================================
81 *
82 * Do NOT call them on each request. Call them once to verify / sanitize user input
83 * and then store them in your configuration.
84 * - These methods try their best to be tolerant to common user mistakes and return a
85 * string that GalleryEmbd::init accepts
86 * - You don't have to call these methods before calling the above methods to get
87 * embed.php, since it does that already internally
88 *
89 * 1. $g2Uri = G2EmbedDiscoveryUtilities::normalizeG2Uri($g2Uri);
90 * 2. $embedUri = G2EmbedDiscoveryUtilities::normalizeEmbedUri($embedUri);
91 */
92
93 /**
94 * The format for g2Uri accepted by GalleryEmbed::init is quite strict and well defined
95 * missing traling / leading slashes have a meaning.
96 * This function is more tolerant for incorrect user input and tries to normalize the
97 * given g2Uri to a value that is probably what the user meant to provide
98 *
99 * The returned URI is either a server-relative URI (e.g. /gallery2/) or an absolute URI
100 * including the schema (e.g. http://example.com/gallery/)
101 *
102 * The file / query string part is always removed)
103 *
104 * @param string g2Uri
105 * @return string normalized g2Uri
106 */
107 function normalizeG2Uri($g2Uri) {
108 list ($schemaAndHost, $path, $file, $queryString, $fragment) =
109 G2EmbedDiscoveryUtilities::_normalizeUri($g2Uri);
110
111 return $schemaAndHost . $path;
112 }
113
114 /**
115 * @see normalizeG2Uri
116 *
117 * Very similar, but file / query string is kept in the result
118 */
119 function normalizeEmbedUri($embedUri) {
120 list ($schemaAndHost, $path, $file, $queryString, $fragment) =
121 G2EmbedDiscoveryUtilities::_normalizeUri($embedUri);
122
123 return $schemaAndHost . $path . $file . $queryString . $fragment;
124 }
125
126 /**
127 * Find the absolute filesystem path to G2's embed.php when given the g2Uri
128 *
129 * Returns false if the g2Uri is wrong. Can also fail if G2 and emApp are
130 * on different (sub)domains / IPs
131 *
132 * @param string the g2Uri, a full URL or a server-relative URI
133 * @return array boolean success,
134 * string filesystem path of embed.php
135 * string error string
136 */
137 function getG2EmbedPathByG2Uri($g2Uri) {
138 $g2Uri = trim($g2Uri);
139 if (empty($g2Uri)) {
140 return array (false, null, "Bad parameter: the provided g2Uri is empty");
141 }
142
143 $g2Uri = G2EmbedDiscoveryUtilities::normalizeG2Uri($g2Uri);
144
145 /* Add a schema / host part to the g2Uri if necessary */
146 if (strpos($g2Uri, 'http') !== 0) {
147 $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
148 $host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '127.0.0.1';
149 $g2Uri = sprintf('%s://%s%s', $protocol, $host, $g2Uri);
150 }
151
152 $components = @parse_url($g2Uri);
153 if (!$components) {
154 return array(false, null, "Unable to parse normalized URL $g2Uri. Please enter the " .
155 "full address of your Gallery 2 installation.");
156 }
157 $port = empty($components['port']) ? 80 : $components['port'];
158 if (empty($components['path'])) {
159 $components['path'] = '/';
160 }
161
162 $fd = @fsockopen($components['host'], $port, $errno, $errstr, 1);
163 if (empty($fd)) {
dccc293a 164 return array(false, null, "Error $errno: '$errstr' retrieving $g2Uri");
c15e9178 165 }
166
167 $get = $components['path'] . 'embed.php?getEmbedPath=1';
168
169 /* Read the web page into a buffer */
170 $ok = fwrite($fd, sprintf("GET %s HTTP/1.0\r\n" .
171 "Host: %s\r\n" .
172 "\r\n",
173 $get,
174 $components['host']));
175 if (!$ok) {
176 /* Zero bytes written or false was returned */
177 $errorStr = "Verification of Gallery 2 location failed. fwrite call failed for $g2Uri";
178 if ($ok === false) {
179 $errorStr .= "\nreturn value was false";
180 }
181 return array(false, null, $errorStr);
182 }
183 $ok = fflush($fd);
184 if (!$ok) {
185 if (version_compare(phpversion(), '4.2.0', '>=')) {
186 /* Ignore false returned from fflush on PHP 4.1 */
187 return array(false, null, "Verification of Gallery 2 location failed. " .
188 "fflush call failed for $g2Uri");
189 }
190 }
191
192 /*
193 * Read the response code. fgets stops after newlines.
194 * The first line contains only the status code (200, 404, etc.).
195 */
196 $headers = array();
197 $response = trim(fgets($fd, 4096));
198
199 /* if the HTTP response code did not begin with a 2 this request was not successful */
200 if (!preg_match("/^HTTP\/\d+\.\d+\s2\d{2}/", $response)) {
201 return array(false, null, "URL derived from $g2Uri is invalid");
202 }
203
204 /* Read the headers. */
205 while (!feof($fd)) {
206 $line = trim(fgets($fd, 4096));
207 if (empty($line)) {
208 break;
209 }
210 /* Normalize the line endings */
211 $line = str_replace("\r", '', $line);
212
213 list ($key, $value) = explode(':', $line, 2);
214 $headers[$key] = trim($value);
215 }
216
217 $embedPhpPath = '';
218 if (isset($headers['X-G2-EMBED-PATH'])) {
219 $embedPhpPath = $headers['X-G2-EMBED-PATH'];
220 } else {
221 return array(false, null, "Either your server does not support the automated " .
222 "verification of the Gallery 2 location or the supplied g2Uri " .
223 "points to a incompatible Gallery 2 version (older than 2.1)");
224 }
225
226 if (empty($embedPhpPath)) {
227 return array(false, null, "Correct URL, but the returned " .
228 "embed.php path is empty (server error?!)");
229 }
230
231 /* Verify path */
232 list ($ok, $errorString) = @G2EmbedDiscoveryUtilities::isFileReadable($embedPhpPath);
233 if (!$ok) {
234 return array(false, null, $errorString);
235 } else {
236 return array(true, $embedPhpPath, null);
237 }
238 }
239
240 /**
241 * Get the absolute filesystem path to embed.php from the given g2Uri, embedUri and the
242 * absolute filesystem path of the entry point file of your embedding application
243 *
244 * Can be unreliable if short URLs are entered or if apache alias / symlinks are used
245 *
246 * @param string g2Uri
247 * @param string embedUri
248 * @param string the dirname of the location of the entry point of your embedding application
249 * e.g. dirname(__FILE__) if your embedUri points right to your wrapper file or if your
250 * wrapper file is in the same directory as the entry point to your emApp
251 * e.g. dirname(dirname(dirname(__FILE__))) if your wrapper is in a
252 * modules/g2integration/wrapper.inc.php file, which is 2 subdirectories deeper than
253 * the actual entry point that embedUri is pointing to
254 * @return array boolean success,
255 * string absolute filesystem path to embed.php,
256 * string errorString
257 */
258
259 function getG2EmbedPathByG2UriEmbedUriAndLocation($g2Uri, $embedUri, $dirnameOfEmApp) {
260 if (empty($dirnameOfEmApp)) {
261 return array(false, null, 'dirname of embedding application is empty');
262 }
263 /* Normalize g2Uri, embedUri */
264 list ($schemaAndHost, $path, $file, $queryString, $fragment) =
265 G2EmbedDiscoveryUtilities::_normalizeUri($g2Uri);
266 $g2Path = $path;
267 list ($schemaAndHost, $path, $file, $queryString, $fragment) =
268 G2EmbedDiscoveryUtilities::_normalizeUri($embedUri);
269 $embedPath = $path;
270
271 /* Normalize path separators */
272 $dirnameOfEmApp = str_replace(DIRECTORY_SEPARATOR, '/', $dirnameOfEmApp);
273 /* Remove trailing slash */
274 if (substr($dirnameOfEmApp, -1) == '/') {
275 $dirnameOfEmApp = substr($dirnameOfEmApp, 0, strlen($dirnameOfEmApp) - 1);
276 }
277
278 /*
279 * Do some directory traversal to translate g2Path + embedPath + dirnameOfEmApp
280 * to path to embed.php
281 * path
282 * Example: g2Path = /baz/bar/gallery2/ , embedPath = /baz/cms/foo/ ,
283 * dirnameOfEmApp = /home/john/www/cms/foo/
284 * 1. Remove as many dirs from the end of dirnameOfEmApp as embedPath has
285 * 2. append g2Path to dirnameOfEmApp
286 */
287 $numberOfSubDirs = count(explode('/', $embedPath));
288 /* Don't count the one before the leading and after the traling slash */
289 $numberOfSubDirs -= 2;
290
291 $pathElements = explode('/', $dirnameOfEmApp);
292 $max = 30; /* prevent infinite loop */
293 while ($numberOfSubDirs-- > 0 && $max-- > 0) {
294 array_pop($pathElements);
295 }
296
297 $embedPhpPath = join('/', $pathElements) . $g2Path . 'embed.php';
298
299 /* Convert / back to platform specific directory separator */
300 $embedPhpPath = str_replace('/', DIRECTORY_SEPARATOR, $embedPhpPath);
301
302 /* Verify path */
303 list ($ok, $errorString) = @G2EmbedDiscoveryUtilities::isFileReadable($embedPhpPath);
304 if (!$ok) {
305 return array(false, null, $errorString);
306 } else {
307 return array(true, $embedPhpPath, null);
308 }
309 }
310
311 /**
312 * Helper function for normalizeG2Uri and normalizeEmbedUri
313 *
314 * @access private
315 */
316 function _normalizeUri($uri) {
317 $uri = trim($uri);
318 if (empty($uri)) {
319 return array('', '/', '', '', '');
320 }
321 $schema = $host = $schemaAndHost = $path = $file = '';
322 $fragment = $queryString = '';
323
324 /* Normalize path separators */
325 $uri = str_replace("\\", '/', $uri);
326
327 /*
328 * With schema (http://) -> easy to identify host
329 * A single slash:
330 * www.example.com/
331 * www.example.com/gallery2
332 * www.example.com/index.php
333 * gallery2/
334 * /
335 * /gallery2
336 * /index.php
337 * gallery2/index.php
338 * Multiple slashes:
339 * www.example.com/gallery2/
340 * /gallery2/
341 * ....
342 * Problem: Differentiate between host, path and file
343 * @files: .php|.html? is recognized as file
344 * @host: (?:\w+:\w+@)[\w\.]*\w+\.\w{2-4}(?::\d+) is most likely a host string
345 * localhost or other host strings without a dot are impossible to
346 * differentiate from path names ->only http://localhost accepted
347 * @path: everything that is not a file or a host
348 */
349
350 /* Remove fragment / query string */
351 if (($pos = strpos($uri, '#')) !== false) {
352 $fragment = substr($uri, $pos);
353 $uri = substr($uri, 0, $pos);
354 }
355 if (($pos = strpos($uri, '?')) !== false) {
356 $queryString = substr($uri, $pos);
357 $uri = substr($uri, 0, $pos);
358 }
359
360 /* Handle and remove file part */
361 if (preg_match('{(.*/)?([\w\.]+\.(?:php|html?))$}i', $uri, $matches)) {
362 $uri = empty($matches[1]) ? '/' : $matches[1];
363 $file = $matches[2];
364 }
365
366 /* Get the schema and host for absolute URLs */
367 if (preg_match('{^(https?://)([^/]+)(.*)$}i', $uri, $matches)) {
368 $schema = strtolower($matches[1]);
369 $host = $matches[2];
370 $schemaAndHost = $schema . $host;
371 $uri = empty($matches[3]) ? '/' : $matches[3];
372 $uri = $uri{0} != '/' ? '/' . $uri : $uri;
373 } else {
374 /* Get the host string, e.g. from www.example.com/foo or www.example.com */
375 if (preg_match('{^((?:\w+:\w+@)?[\w\.]*\w+\.\w+(?::\d+)?)(.*)$}', $uri, $matches)) {
376 $host = $matches[1];
377 $schema = 'http://';
378 if ( !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
379 $schema = 'https://';
380 }
381 $schemaAndHost = $schema . $host;;
382 $uri = empty($matches[2]) ? '/' : $matches[2];
383 $uri = $uri{0} != '/' ? '/' . $uri : $uri;
384 }
385 }
386
387 /* Add leading / trailing slash to path */
388 $path = $uri{0} != '/' ? '/' . $uri : $uri;
389 $path .= substr($path, -1) != '/' ? '/' : '';
390
391 return array($schemaAndHost, $path, $file, $queryString, $fragment);
392 }
393
394 function isFileReadable($path) {
395 if (@file_exists($path) && @is_readable($path)) {
396 return array(true, null);
397 } else if (@G2EmbedDiscoveryUtilities::_isRestrictedByOpenBaseDir($path)) {
398 return array(false, "file $path is restricted by PHP open_basedir");
399 } else if (@file_exists($path) && !@is_readable($path)) {
400 return array(false, "file $path exists but is not readable");
401 } else {
402 return array(false, "file $path does not exist");
403 }
404 }
405
406 /**
407 * Return true if the path provided is not allowed by the current open_basedir configuration.
408 *
409 * Copied from GalleryPlatform and adjusted to be independent of the G2 framework
410 *
411 * @return true if the path is restricted
412 */
413 function _isRestrictedByOpenBaseDir($path) {
414 $slash = DIRECTORY_SEPARATOR;
415 if (!strncasecmp(PHP_OS, 'win', 3)) {
416 $separator = ';';
417 $caseSensitive = false;
418 } else {
419 $separator = ':';
420 $caseSensitive = true;
421 }
422
423 $openBasedir = @ini_get('open_basedir');
424 if (empty($openBasedir)) {
425 return false;
426 }
427
428 if (($realpath = realpath($path)) === false) {
429 /*
430 * PHP's open_basedir will actually take an invalid path, resolve relative
431 * paths, parse out .. and . and then check against the dir list..
432 * Here we do an ok job of doing the same, though it isn't perfect.
433 */
434 $s = '\\\/'; /* do this by hand because preg_quote() isn't reliable */
435 if (!preg_match("{^([a-z]+:)?[$s]}i", $path)) {
436 $path = getcwd() . $slash . $path;
437 }
438 for ($realpath = $path, $lastpath = ''; $realpath != $lastpath;) {
439 $realpath = preg_replace("#[$s]\.([$s]|\$)#", $slash, $lastpath = $realpath);
440 }
441
442 for ($lastpath = ''; $realpath != $lastpath;) {
443 $realpath = preg_replace("#[$s][^$s]+[$s]\.\.([$s]|\$)#",
444 $slash, $lastpath = $realpath);
445 }
446 }
447
448 $function = $caseSensitive ? 'strncmp' : 'strncasecmp';
449 foreach (explode($separator, $openBasedir) as $baseDir) {
450 if (($baseDirMatch = realpath($baseDir)) === false) {
451 $baseDirMatch = $baseDir;
452 } else if ($baseDir{strlen($baseDir)-1} == $slash) {
453 /* Realpath will remove a trailing slash.. add it back to avoid prefix match */
454 $baseDirMatch .= $slash;
455 }
456 /* Add slash on path so /dir is accepted if /dir/ is a valid basedir */
457 if (!$function($baseDirMatch, $realpath . $slash, strlen($baseDirMatch))) {
458 return false;
459 }
460 }
461
462 return true;
463 }
464}
e65698fe
TW
465
466?>