/** * This function can be used to safely write a file to disk. * * For writing the file, a swap file is used during the write phase. * Only if writing the swap file fully succeeded without running * into errors, the swap file is moved to its final location. * This way, we cannot get broken data because of scripts that crash * during writing or because of disks that are full. * * @param string $file * The name of the file to write to. * * @param string $data * The data to put in the file. * * @return boolean * TRUE in case the file was written successfully to disk. * FALSE if writing the file failed. The function * {@link phorum_api_strerror()} can be used to retrieve * information about the error which occurred. */ function phorum_api_write_file($file, $data) { // Reset error storage. $GLOBALS['PHORUM']['API']['errno'] = NULL; $GLOBALS['PHORUM']['API']['error'] = NULL; ini_set('track_errors', 1); // Generate the swap file name. $stamp = preg_replace('/\\D/', '', microtime()); $swpfile = $file . '.swp' . $stamp; // Open the swap file. $fp = @fopen($swpfile, 'w'); if (!$fp) { @unlink($swpfile); return phorum_api_error_set(PHORUM_ERRNO_ERROR, "Cannot create swap file \"{$swpfile}\": {$php_errormsg}"); } // Write file data to disk. @fputs($fp, $data); // Close the swap file. if (!@fclose($fp)) { @unlink($swpfile); return phorum_api_error_set(PHORUM_ERRNO_ERROR, "Error on closing swap file \"{$swpfile}\": disk full?"); } // A special check on the created outputfile. We have seen strange // things happen on Windows2000 where the webserver could not read // the file it just had written :-/ if (!($fp = @fopen($swpfile, 'r'))) { @unlink($swpfile); return phorum_api_error_set(PHORUM_ERRNO_ERROR, "Cannot read swap file \"{$swpfile}\", although it was just " . "written to disk. This is probably due to a problem with " . "the file permissions for the storage directory."); } @fclose($fp); // Move the swap file to its final location. if (!@rename($swpfile, $file)) { @unlink($swpfile); return phorum_api_error_set(PHORUM_ERRNO_ERROR, "Cannot move swap file \"{$swpfile}\": {$php_errormsg}"); } return TRUE; }
/** * Create an image thumbnail. * * This function can be used to create a scaled down thumbnail version of * an image. You can specifiy a width and/or a height to which to scale * down the image. The aspect ratio will always be kept. If both a width * and height are provided, then the one which requires the largest scale * down will be leading. * * @param string $image * The raw binary image data. * * @param integer $max_w * The maximum allowed width for the image in pixels. * Use NULL or 0 (zero) to indicate that any width will do. * * @param integer $max_h * The maximum allowed height for the image in pixels. * Use NULL or 0 (zero) to indicate that any height will do. * * @param string $method * The method to use for scaling the image. By default, this function * will try to autodetect a working method. Providing a $method parameter * is mostly useful for debugging purposes. Available methods (in the * order in which they are probed in the code) are: * - gd: using the GD library (requires extension "gd") * - imagick: using the ImageMagick library (requires extension "imagick") * - convert: using the ImageMagick "convert" tool (requires the * ImageMagick package to be installed on the server, does not work * in combination with some PHP safety restrictions). * * @return mixed * NULL is returned in case creating the thumbnail failed. The function * {@link phorum_api_strerror()} can be used to retrieve information * about the error which occurred. * * An array is returned in case creating the thumbnail did work. * This array contains the following fields: * - image: The scaled down image. NULL if no scaling was needed. * - method: The method that was used to create the thumbnail. * - cur_w: The width of the original $image. * - cur_h: The height of the original $image. * - cur_mime: The MIME type of the original $image. * - new_w: The width of the scaled down image. * - new_h: The height of the scaled down image. * - new_mime: The MIME type of the scaled down image, */ function phorum_api_image_thumbnail($image, $max_w = NULL, $max_h = NULL, $method = NULL) { // Reset error storage. $GLOBALS['PHORUM']['API']['errno'] = NULL; $GLOBALS['PHORUM']['API']['error'] = NULL; $error = NULL; if (empty($max_w)) { $max_w = NULL; } if (empty($max_h)) { $max_h = NULL; } // Initialize the return array. $img = array('image' => NULL, 'new_mime' => NULL); // Check if PHP supports the getimagesize() function. I think it // always should, but let's check it to be sure. if (!function_exists('getimagesize')) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, 'Your PHP installation lacks "getimagesize()" support'); } // Try to determine the image type and size using the getimagesize() // PHP function. Unfortunately, this function requires a file on disk // to process. Therefore we create a temporary file in the Phorum cache // for doing this. require_once './include/api/write_file.php'; $tmpdir = $GLOBALS['PHORUM']['cache']; $tmpfile = $tmpdir . '/scale_image_tmp_' . md5($image . microtime()); if (!phorum_api_write_file($tmpfile, $image)) { return NULL; } // Get the image information and clean up the temporary file. $file_info = getimagesize($tmpfile); @unlink($tmpfile); if ($file_info === FALSE) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, 'Running getimagesize() on the image data failed'); } // Add the image data to the return array. $img['cur_w'] = $img['new_w'] = $file_info[0]; $img['cur_h'] = $img['new_h'] = $file_info[1]; $img['cur_mime'] = $img['new_mime'] = $file_info['mime']; // We support only GIF, JPG and PNG images. if (substr($img['cur_mime'], 0, 6) == 'image/') { $type = substr($img['cur_mime'], 6); if ($type != 'jpeg' && $type != 'gif' && $type != 'png') { return phorum_api_error_set(PHORUM_ERRNO_ERROR, "Scaling image type \"{$img['cur_mime']}\" is not supported"); } } else { return phorum_api_error_set(PHORUM_ERRNO_ERROR, 'The file does not appear to be an image'); } // Check if scaling is required and if yes, what the new size should be. // First, determine width and height scale factors. $scale_w = NULL; $scale_h = NULL; // Need horizontal scaling? if ($max_w !== NULL && $max_w < $img['cur_w']) { $scale_w = $max_w / $img['cur_w']; } // Need vertical scaling? if ($max_h !== NULL && $max_h < $img['cur_h']) { $scale_h = $max_h / $img['cur_h']; } // No scaling needed, return. if ($scale_w === NULL && $scale_h === NULL) { return $img; } // The lowest scale factor wins. Compute the required image size. if ($scale_h === NULL || $scale_w !== NULL && $scale_w < $scale_h) { $img['new_w'] = $max_w; $img['new_h'] = floor($img['cur_h'] * $scale_w + 0.5); } else { $img['new_w'] = floor($img['cur_w'] * $scale_h + 0.5); $img['new_h'] = $max_h; } // ----------------------------------------------------------------- // Try to use the imagick library tools // ----------------------------------------------------------------- // First, try to load the imagick extension if it is not loaded yet. // This way we can make this work on systems where imagick is not built-in // or loaded from the PHP ini. if (($method === NULL || $method == 'imagick') && !extension_loaded('imagick')) { @dl('imagick.so'); } if (($method === NULL || $method == 'imagick') && extension_loaded('imagick') && class_exists('Imagick')) { $method = NULL; $imagick = new Imagick(); $imagick->readImageBlob($image); $imagick->thumbnailImage($img['new_w'], $img['new_h'], TRUE); $imagick->setFormat("jpg"); $img['image'] = $imagick->getimageblob(); $img['new_mime'] = 'image/' . $imagick->getFormat(); $img['method'] = 'imagick'; return $img; } // ----------------------------------------------------------------- // Try to use the GD library tools // ----------------------------------------------------------------- // First, try to load the GD extension if it is not loaded yet. // This way we can make this work on systems where gd is not built-in // or loaded from the PHP ini. if (($method === NULL || $method == 'gd') && !extension_loaded('gd')) { @dl('gd.so'); } if (($method === NULL || $method == 'gd') && extension_loaded('gd') && function_exists('gd_info')) { // We need gd_info() to check whether GD has the required // image support for the type of image that we are handling. $gd = gd_info(); // We need PNG support for the scaled down image. if (empty($gd['PNG Support'])) { $error = "GD: no PNG support available for creating thumbnail"; } elseif ($type == 'gif' && empty($gd['GIF Read Support']) || $type == 'jpeg' && empty($gd['JPG Support']) || $type == 'png' && empty($gd['PNG Support'])) { $error = "GD: no support available for image type \"{$type}\""; } else { // Create a GD image handler based on the image data. // imagecreatefromstring() spawns PHP warnings if the file cannot // be processed. We do not care to see that in the event logging, // so if event logging is loaded, we suspend it here. // Instead, we catch error output and try to mangle it into a // usable error message. if (defined('EVENT_LOGGING')) { phorum_mod_event_logging_suspend(); } ob_start(); $original = imagecreatefromstring($image); $error = ob_get_contents(); ob_end_clean(); $error = trim(preg_replace('!(^(?:\\w+:\\s*).*?:|in /.*$)!', '', trim($error))); if (defined('EVENT_LOGGING')) { phorum_mod_event_logging_resume(); } if (!$original) { if ($error == '') { $error = "GD: Cannot process the {$type} image using GD"; } else { $error = 'GD: ' . $error; } } else { // Create the scaled image. $scaled = imagecreatetruecolor($img['new_w'], $img['new_h']); //Retain transparency. $trans_idx = imagecolortransparent($original); if ($trans_idx >= 0) { $trans = imagecolorsforindex($original, $trans_idx); $idx = imagecolorallocate($scaled, $trans['red'], $trans['green'], $trans['blue']); imagefill($scaled, 0, 0, $idx); imagecolortransparent($scaled, $idx); } elseif ($type == 'png') { imagealphablending($scaled, FALSE); $trans = imagecolorallocatealpha($scaled, 0, 0, 0, 127); imagefill($scaled, 0, 0, $trans); imagesavealpha($scaled, TRUE); } // Scale the image. imagecopyresampled($scaled, $original, 0, 0, 0, 0, $img['new_w'], $img['new_h'], $img['cur_w'], $img['cur_h']); // Create the jpeg output data for the scaled image. ob_start(); imagejpeg($scaled); $image = ob_get_contents(); $size = ob_get_length(); ob_end_clean(); $img['image'] = $image; $img['new_mime'] = 'image/jpeg'; $img['method'] = 'gd'; return $img; } } } // ----------------------------------------------------------------- // Try to use the ImageMagick "convert" tool // ----------------------------------------------------------------- if ($method === NULL || $method == 'convert') { // Try to find the "convert" utility. // First, check if it is configured in the Phorum settings. $convert = NULL; if (isset($PHORUM['imagemagick_convert_path'])) { $path = $PHORUM['imagemagick_convert_path']; if (is_executable($path)) { $convert = $path; } } // Not found? Then simply use "convert" and hope that it // can be found in the OS' path. if ($convert === NULL) { $convert = 'convert'; } // Build the command line. $cmd = escapeshellcmd($convert) . ' ' . '- ' . '-thumbnail ' . $img['new_w'] . 'x' . $img['new_h'] . ' ' . '-write jpeg:- ' . '--'; // Otherwise I get: option requires an argument `-write' // Run the command. $descriptors = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $process = proc_open($cmd, $descriptors, $pipes); if ($process == FALSE) { $error = 'Failed to run "convert".'; } // Feed convert the image data on STDIN. fwrite($pipes[0], $image); fclose($pipes[0]); // Read the scaled image from STDOUT. $scaled = stream_get_contents($pipes[1]); fclose($pipes[1]); // Read errors. $errors = trim(stream_get_contents($pipes[2])); fclose($pipes[2]); $exit = proc_close($process); if ($exit == 0) { $img['image'] = $scaled; $img['new_mime'] = 'image/jpeg'; $img['method'] = 'convert'; return $img; } // Some error occurred. if ($errors == '') { $error = 'Got exit code ' . $exit . ' from "convert".'; } else { $error = $errors; } } // ----------------------------------------------------------------- // Safety nets. // ----------------------------------------------------------------- // Return error if one was set. if ($error) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, $error); } // Catch illegal methods if ($method !== NULL) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, 'Illegal scaling method: ' . $method); } // If we get here, then we were totally out of luck. return phorum_api_error_set(PHORUM_ERRNO_ERROR, 'No working image scaling method found'); }
/** * Create a Phorum user session. * * Before calling this function, the variable $PHORUM['use_cookies'] * should be set to one of {@link PHORUM_NO_COOKIES}, * {@link PHORUM_USE_COOKIES} or {@link PHORUM_REQUIRE_COOKIES}. * * Phorum does not use PHP sessions. Instead, it uses its own session * management system for remembering logged in users. There are * multiple reasons for that, amongst which are: * * - the lack of session support (on some PHP installs); * - missing out of the box load balancing support (sessions are normally * written to local session state files, so multiple machines would not * work well together); * - file I/O problems (both performance and file system permissions can * be a problem); * - the amount of unneeded overhead that is caused by the PHP session system; * - the fact that Phorum also supports URI based sessions (without cookie). * * This function can be used to create or maintain a login session for a * Phorum user. A prerequisite is that an active Phorum user is set through * the {@link phorum_api_user_set_active_user()} function, before calling * this function. * * There are two session types available: {@link PHORUM_FORUM_SESSION} * (used for the front end application) and {@link PHORUM_ADMIN_SESSION} * (used for the administrative back end). * * Admin sessions are used for the administrative back end system. For * security reasons, the back end does not share the front end session, * but uses a fully separate session instead. This session does not * have a timeout restriction, but it does not survive closing the * browser. It is always tracked using a cookie, never using URI * authentication (for security reasons). * * The forum sessions can be split up into long term and short term sessions: * * - Long term session: * The standard Phorum user session. This session is long lasting and will * survive after closing the browser (unless the long term session timeout * is set to zero). If tighter security is not enabled, then this session * is all a user needs to fully use all forum options. This session is * tracked using either a cookie or URI authentication. * * - Short term session: * This session has a limited life time and will not survive closing the * browser. If tighter security is enabled, then the user will not be able * to use all forum functions, unless there is a short term session active * (e.g. posting forum messages and reading/writing private messages are * restricted). This session is tracked using a cookie. If URI authentication * is in use (because of admin config or cookie-less browsers) Phorum will * only look at the long term session (even in tighter security mode), since * URI authentication can be considered to be short term by nature. * * @example user_login.php Handle a user forum login * * @param string $type * The type of session to initialize. This must be one of * {@link PHORUM_FORUM_SESSION} or {@link PHORUM_ADMIN_SESSION}. * * @param integer $reset * If it is set to 0 (zero, the default), then existing session_ids * will be reused if possible. * * If this parameter is set to PHORUM_SESSID_RESET_LOGIN, then a new * session id will be generated for short term forum sessions and if * cookies are disabled for some reason, for long term forum sessions * as well (to prevent accidental distribution of URLs with auth info * in them). This is the type of session id reset that is appropriate * after handling a login action. * * If this parameter is set to PHORUM_SESSID_RESET_ALL, then all session * ids will be reset to new values. This is for example * appropriate after a user changed the password (so active sessions on * other computers or browsers will be ended). * * @return boolean * TRUE in case the session was initialized successfully. * Otherwise, FALSE will be returned. The functions * {@link phorum_api_strerror()} and {@link phorum_api_errno()} can be * used to retrieve information about the error which occurred. */ function phorum_api_user_session_create($type, $reset = 0) { global $PHORUM; /** * [hook] * user_session_create * * [description] * Allow modules to override Phorum's session create management or * to even fully omit creating a session (for example useful * if the hook <hook>user_session_restore</hook> is used * to inherit an external session from some 3rd party application). * * [category] * User authentication and session handling * * [when] * Just before Phorum runs its own session initialization code * in the user API function * <literal>phorum_api_user_session_create()</literal>. * * [input] * The session type for which a session must be created. * This can be either <literal>PHORUM_FORUM_SESSION</literal> * or <literal>PHORUM_ADMIN_SESSION</literal>. * * [output] * Same as input if Phorum has to run its standard session * initialization code or NULL if that code should be fully skipped. * * [example] * <hookcode> * function phorum_mod_foo_user_session_create($type) * { * // Let Phorum handle admin sessions on its own. * if ($type == PHORUM_ADMIN_SESSION) return $type; * * // Override the session handling for front end forum sessions. * // We could for example put the session in a standard PHP * // session by first starting a PHP session if that was * // not done yet... * if (!session_id()) session_start(); * * // ...and then storing the user_id of the current user in the * // PHP session data. The user_id is really the only thing * // that needs to be remembered for a Phorum session, because * // all other data for the user is stored in the database. * $phorum_user_id = $GLOBALS["PHORUM"]["user"]["user_id"]; * $_SESSION['phorum_user_id'] = $phorum_user_id; * * // Tell Phorum not to run its own session initialization code. * return NULL; * } * </hookcode> * * See the <hook>user_session_restore</hook> hook for an example * of how to let Phorum pick up this PHP based session. */ if (isset($PHORUM['hooks']['user_session_create'])) { if (phorum_hook('user_session_create', $type) === NULL) { return TRUE; } } // Reset error storage. $PHORUM['API']['errno'] = NULL; $PHORUM['API']['error'] = NULL; // Check if we have a valid session type. if ($type != PHORUM_FORUM_SESSION && $type != PHORUM_ADMIN_SESSION) { trigger_error('phorum_api_user_session_create(): Illegal session type: ' . htmlspecialchars($type), E_USER_ERROR); return NULL; } // Check if the active Phorum user was set. if (empty($PHORUM['user']) || empty($PHORUM['user']['user_id'])) { trigger_error('phorum_api_user_session_create(): Missing user in environment', E_USER_ERROR); return NULL; } // Check if the user is activated. if ($PHORUM['user']['active'] != PHORUM_USER_ACTIVE) { return phorum_api_error_set(PHORUM_ERRNO_NOACCESS, 'The user is not (yet) activated (user id ' . $PHORUM['user']['user_id'] . ')'); } // For admin sessions, check if the user has administrator rights. // This is also checked from phorum_api_user_set_active_user(), but // one can never be too sure about this. if ($type == PHORUM_ADMIN_SESSION && empty($PHORUM['user']['admin'])) { return phorum_api_error_set(PHORUM_ERRNO_NOACCESS, 'The user is not an administrator (user id ' . $PHORUM['user']['user_id'] . ')'); } // Shortcut for checking if session ids are stored in cookies. // Note that the software that uses this function is responsible for // setting $PHORUM["use_cookies"] to PHORUM_NO_COOKIES if the client // does not support cookies. $use_cookies = isset($PHORUM['use_cookies']) && $PHORUM['use_cookies'] > PHORUM_NO_COOKIES; // ---------------------------------------------------------------------- // Retrieve or generate required session id(s). // ---------------------------------------------------------------------- $user = $PHORUM['user']; // Generate a long term session id. This one is used by all session types. // Create a new long term session id if no session id is available yet or // if a refresh was requested and cookies are disabled (with cookies // enabled, we always reuse the existing long term session, so the session // can be remembered and shared between multiple browsers / computers). $refresh_sessid_lt = empty($user['sessid_lt']) || !$use_cookies && $reset == PHORUM_SESSID_RESET_LOGIN || $reset == PHORUM_SESSID_RESET_ALL; if ($refresh_sessid_lt) { $sessid_lt = md5($user['username'] . microtime() . $user['password']); phorum_api_user_save_raw(array('user_id' => $user['user_id'], 'sessid_lt' => $sessid_lt)); $PHORUM['user']['sessid_lt'] = $sessid_lt; } else { $sessid_lt = $user['sessid_lt']; } // For forum sessions, generate a short term session id if tight // security is enabled in the configuration and cookies are enabled // (with URI authentication, the tight security system is bypassed // since the user will have to login on every visit already). $refresh_sessid_st = FALSE; if ($type == PHORUM_FORUM_SESSION && !empty($PHORUM['tight_security']) && $use_cookies) { // How much longer is the existing short term session id valid? $timeleft = empty($user['sessid_st_timeout']) ? 0 : $user['sessid_st_timeout'] - time(); // Create a new short term session id if .. if (empty($user['sessid_st']) || $reset) { // .. any type of reset was requested $sessid_st = md5($user['username'] . microtime() . $user['password']); $refresh_sessid_st = TRUE; } else { // Reuse the existing short term session id $sessid_st = $user['sessid_st']; // Have the session timeout reset if more than one third of the // session's life time has passed and if the session has not // yet expired. if ($timeleft > 0 && $timeleft < $PHORUM['short_session_timeout'] * 60 / 2) { $refresh_sessid_st = TRUE; } } // The session data needs updating. if ($refresh_sessid_st) { $timeout = time() + $PHORUM['short_session_timeout'] * 60; phorum_api_user_save_raw(array('user_id' => $user['user_id'], 'sessid_st' => $sessid_st, 'sessid_st_timeout' => $timeout)); $PHORUM['user']['sessid_st'] = $sessid_st; $PHORUM['user']['sessid_st_timeout'] = $timeout; } } // For admin sessions, the session id is computed using the long term // session id and some random data that was generated at install time. if ($type == PHORUM_ADMIN_SESSION) { $sessid_admin = md5($sessid_lt . $PHORUM['admin_session_salt']); } // ---------------------------------------------------------------------- // Route the required session id(s) to the user. // ---------------------------------------------------------------------- $user = $PHORUM['user']; if ($type == PHORUM_FORUM_SESSION) { // The long term session can be stored in either a cookie or // URL / form posting data. if ($use_cookies) { $timeout = empty($PHORUM['session_timeout']) ? 0 : time() + 86400 * $PHORUM['session_timeout']; setcookie(PHORUM_SESSION_LONG_TERM, $user['user_id'] . ':' . $sessid_lt, $timeout, $PHORUM['session_path'], $PHORUM['session_domain']); } else { // Add the session id to the URL building GET variables. $PHORUM['DATA']['GET_VARS'][PHORUM_SESSION_LONG_TERM] = PHORUM_SESSION_LONG_TERM . '=' . urlencode($user['user_id'] . ':' . $sessid_lt); // Add the session id to the form POST variables. $PHORUM['DATA']['POST_VARS'] .= '<input type="hidden" name="' . PHORUM_SESSION_LONG_TERM . '" ' . 'value="' . $user['user_id'] . ':' . $sessid_lt . '" />'; } // The short term session id is always put in a cookie. if ($refresh_sessid_st) { setcookie(PHORUM_SESSION_SHORT_TERM, $user['user_id'] . ':' . $user['sessid_st'], $user['sessid_st_timeout'], $PHORUM['session_path'], $PHORUM['session_domain']); } } elseif ($type == PHORUM_ADMIN_SESSION) { setcookie(PHORUM_SESSION_ADMIN, $user['user_id'] . ':' . $sessid_admin, 0, $PHORUM['session_path'], $PHORUM['session_domain']); } return TRUE; }
/** * Retrieve a Phorum file. * * This function can handle Phorum file retrieval in multiple ways: * either return the file to the caller or send it directly to the user's * browser (based on the $flags parameter). Sending it directly to the * browser allows for the implementation of modules that don't have to buffer * the full file data before sending it (a.k.a. streaming, which provides the * advantage of using less memory for sending files). * * @param mixed $file * This is either an array containing at least the fields "file_id" * and "filename" or a numerical file_id value. Note that you can * use the return value of the function * {@link phorum_api_file_check_read_access()} as input for this function. * * @param integer $flags * These are flags which influence aspects of the function call. It is * a bitflag value, so you can OR multiple flags together. Available * flags for this function are: {@link PHORUM_FLAG_IGNORE_PERMS}, * {@link PHORUM_FLAG_GET}, {@link PHORUM_FLAG_SEND} and * {@link PHORUM_FLAG_FORCE_DOWNLOAD}. The SEND flag has precedence * over the GET flag. * * @return mixed * On error, this function will return FALSE. * The functions {@link phorum_api_strerror()} and * {@link phorum_api_errno()} can be used to retrieve information about * the error which occurred. * * If the {@link PHORUM_FLAG_SEND} flag is used, then the function will * return NULL. * * If the {@link PHORUM_FLAG_GET} flag is used, then the function * will return a file description array, containing the fields "file_id", * "username", "file_data", "mime_type". * If the {@link $file} parameter was an array, then all fields from that * array will be included as well. */ function phorum_api_file_retrieve($file, $flags = PHORUM_FLAG_GET) { $PHORUM = $GLOBALS["PHORUM"]; // Reset error storage. $GLOBALS["PHORUM"]["API"]["errno"] = NULL; $GLOBALS["PHORUM"]["API"]["error"] = NULL; // If $file is not an array, we are handling a numerical file_id. // In that case, first retrieve the file data through the access check // function. All the function flags are passed on to that function, // so the PHORUM_FLAG_IGNORE_PERMS flag can be set for ignoring access // permissions. if (!is_array($file)) { $file_id = (int) $file; $file = phorum_api_file_check_read_access($file_id, $flags); // Return in case of errors. if ($file === FALSE) { return FALSE; } } // A small basic check to see if we have a proper $file array. if (!isset($file["file_id"])) { trigger_error("phorum_api_file_get(): \$file parameter needs a \"file_id\" field.", E_USER_ERROR); } if (!isset($file["filename"])) { trigger_error("phorum_api_file_get(): \$file parameter needs a \"filename\" field.", E_USER_ERROR); } settype($file["file_id"], "int"); // Allow modules to handle the file data retrieval. The hook can use // phorum_api_error_set() to return an error. Hooks should be aware // that their input might not be $file, but FALSE instead, in which // case they should immediately return FALSE themselves. $file["result"] = 0; $file["mime_type"] = NULL; $file["file_data"] = NULL; if (isset($PHORUM["hooks"]["file_retrieve"])) { list($file, $flags) = phorum_hook("file_retrieve", array($file, $flags)); if ($file === FALSE) { return FALSE; } // If a module sent the file data to the browser, then we are done. if ($file["result"] == PHORUM_FLAG_SEND) { return NULL; } } // If no module handled file retrieval, we will retrieve the // file from the Phorum database. if ($file["file_data"] === NULL) { $dbfile = phorum_db_file_get($file["file_id"], TRUE); if (empty($dbfile)) { return phorum_api_error_set(PHORUM_ERRNO_NOTFOUND, "Phorum file (id {$file["file_id"]}) could not be " . "retrieved from the database."); } // Phorum stores the files in base64 format in the database, to // prevent problems with dumping and restoring databases. $file["file_data"] = base64_decode($dbfile["file_data"]); } // Set the MIME type information if it was not set by a module. if ($file["mime_type"] === NULL) { $file["mime_type"] = phorum_api_file_get_mimetype($file["filename"]); } // Allow for post processing on the retrieved file. list($file, $flags) = phorum_hook("file_after_retrieve", array($file, $flags)); // In "send" mode, we directly send the file contents to the browser. if ($flags & PHORUM_FLAG_SEND) { // Avoid using any output compression or handling on the sent data. ini_set("zlib.output_compression", "0"); ini_set("output_handler", ""); // Get rid of any buffered output so far. phorum_ob_clean(); if ($flags & PHORUM_FLAG_FORCE_DOWNLOAD) { header("Content-Type: application/octet-stream"); } else { header("Content-Type: " . $file["mime_type"]); } header("Content-Disposition: filename=\"{$file["filename"]}\""); print $file["file_data"]; return NULL; } elseif ($flags & PHORUM_FLAG_GET) { return $file; } else { trigger_error("phorum_api_file_retrieve(): no retrieve mode specified in the " . "flags (either use PHORUM_FLAG_GET or PHORUM_FLAG_SEND).", E_USER_ERROR); } }
/** * This function can be used to retrieve data from a URL using * an HTTP GET request. * * The way in which data can be retrieved for URLs, depends a lot on * the features and the configuration for PHP on the system. This * function will try to autodetect a way that will work for the * running system automatically. * * @param string $url * The URL to retrieve. * * @param string $method * The method to use for retrieving the data. By default, this function * will try to autodetect a working method. Providing a $method parameter * is mostly useful for debugging purposes. Available methods (in the * order in which they are probed in the code) are: * - curl: using the curl library (requires extension "curl") * - socket: using PHP socket programming (requires extension "sockets") * - file: using fopen() (requires option "allow_url_fopen" to be enabled) * * @return string $data * The data that was loaded from the URL or NULL if an error occurred. * The function {@link phorum_api_strerror()} can be used to retrieve * information about the error which occurred. */ function phorum_api_http_get($url, $method = NULL) { // Reset error storage. $GLOBALS['PHORUM']['API']['errno'] = NULL; $GLOBALS['PHORUM']['API']['error'] = NULL; // For keeping track of errors in this function. $error = NULL; $fatal = FALSE; // ----------------------------------------------------------------- // Try to use the CURL library tools // ----------------------------------------------------------------- // First, try to load the curl extension if it is not loaded yet. // This way we can make this work on systems where curl is not built-in // or loaded from the PHP ini. if (($method === NULL || $method == 'curl') && !extension_loaded('curl')) { @dl('curl.so'); } if (($method === NULL || $method == 'curl') && extension_loaded('curl')) { $method = NULL; $curl = @curl_init(); if ($curl === FALSE) { $error = 'Failed to initialize curl request'; } else { // We don't care a lot about certificates for retrieving data. @curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); @curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); // Return the header and content when doing a request. @curl_setopt($curl, CURLOPT_HEADER, 1); @curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // We do not let the curl library follow Location: redirects. // Because (at the time of writing) PHP in safe mode disables // redirect following (to prevent redirection to file://), // we implement our own redirect handling here. @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 0); // The maximum number of redirects that we want to follow. // This has to be limited to prevent looping. $max_redirects = 10; // Also, track URLs that we have already seen as an extra // anti looping system. $seen = array(); $work_url = $url; for (;;) { // Only HTTP allowed. if (!preg_match('!^https?://!i', $work_url)) { $error = "Denying non-HTTP URL: {$work_url}"; $fatal = TRUE; break; } // Looping prevention. if ($max_redirects-- == 0) { $error = "Bailed out after too many page redirects."; $fatal = TRUE; break; } if (isset($seen[$work_url])) { $error = "Following the URL results in a loop."; $fatal = TRUE; break; } $seen[$work_url] = 1; // Retrieve the page. @curl_setopt($curl, CURLOPT_URL, $work_url); $data = @curl_exec($curl); if ($data == FALSE) { $error = "The curl HTTP request failed."; break; } list($header, $body) = explode("\r\n\r\n", $data, 2); $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); $requested_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); // If we got the data, we can return it. if ($code == 200) { return $body; } // Analyze the result data to see what we should to next. $res = phorum_api_http_get_analyze($requested_url, $code, $header); // An error was returned. Bail out. if (isset($res['error'])) { $fatal = empty($res['fatal']) ? 0 : 1; $error = $res['error']; break; } // A redirect was returned. if (isset($res['redirect'])) { $work_url = $res['redirect']; continue; } // Shouldn't get here. trigger_error('phorum_api_http_get_nalyze() returned an ' . 'unexpected result.', E_USER_ERROR); } } } // Return fatal errors. For non fatal errors, we fall through // to the next method. if ($error !== NULL && $fatal) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, $error); } // ----------------------------------------------------------------- // Try to use a direct fsockopen call. // ----------------------------------------------------------------- // First, try to load the socket extension if it is not loaded yet. // This way we can make this work on systems where sockets are not // built-in or loaded from the PHP ini. if (($method === NULL || $method == 'socket') && !extension_loaded('socket')) { @dl('socket.so'); } if (($method === NULL || $method == 'socket') && extension_loaded('sockets')) { $method = NULL; // The maximum number of redirects that we want to follow. // This has to be limited to prevent looping. $max_redirects = 10; // Also, track URLs that we have already seen as an extra // anti looping system. $seen = array(); $work_url = $url; for (;;) { // Only HTTP allowed. if (!preg_match('!^https?://!i', $work_url)) { $error = "Denying non-HTTP URL: {$work_url}"; $fatal = TRUE; break; } // Looping prevention. if ($max_redirects-- == 0) { $error = "Bailed out after too many page redirects."; $fatal = TRUE; break; } if (isset($seen[$work_url])) { $error = "Following the URL results in a loop."; $fatal = TRUE; break; } $seen[$work_url] = 1; // Format the HTTP request for retrieving the URL. $parsed = @parse_url(strtoupper($work_url)); if (!isset($parsed['host'])) { $error = "Cannot parse URL"; $fatal = TRUE; break; } $uri = preg_replace('!^\\w+://[^/]+!', '', $work_url); if ($uri == '') { $uri = '/'; } $req = "GET {$uri} HTTP/1.1\r\n" . "Connection: close\r\n" . "Host: " . strtolower($parsed['host']) . "\r\n" . "\r\n"; // Determine protocol and port for the request. $port = NULL; $proto = ''; if (!empty($parsed['port'])) { $port = (int) $parsed['port']; } if (!empty($parsed['scheme'])) { $s = strtolower($parsed['scheme']); if ($s == 'http') { $proto = ''; if ($port === NULL) { $port = 80; } } elseif ($s == 'https') { if (!extension_loaded('openssl')) { $error = "PHP lacks SSL support"; $fatal = TRUE; break; } $proto = 'ssl://'; if ($port === NULL) { $port = 443; } } } if ($port === NULL) { $port = 80; } // Connect to the webserver. $fp = @fsockopen($proto . $parsed['host'], $port, $errno, $errstr, 10); if (!$fp) { $error = "Connection to server failed ({$errstr})"; $fatal = TRUE; break; } // Send the HTTP request. fwrite($fp, $req); // Read the HTTP response. $response = ''; while (is_resource($fp) && !feof($fp)) { $response .= fread($fp, 1024); } fclose($fp); if ($response == '') { $error = "No data retrieved from server"; $fatal = TRUE; break; } // Parse the response. list($header, $body) = explode("\r\n\r\n", $response, 2); list($status, $header) = explode("\r\n", $header, 2); if (preg_match('!^HTTP/\\d+\\.\\d+\\s+(\\d+)\\s!', $status, $m)) { $code = $m[1]; } else { $error = "Unexpected status from server ({$status})"; $fatal = TRUE; break; } // If we got the data, we can return it. if ($code == 200) { // Check if we need to handle chunked transfers // (see RFC 2616, section 3.6.1: Chunked Transfer Coding). if (preg_match('/^Transfer-Encoding:\\s*chunked\\s*$/m', $header)) { $unchunked = ''; for (;;) { // Check if there is another chunk. // There should be, but let's protect against // bad chunked data. if (strstr($body, "\r\n") === FALSE) { break; } // Get the size of the next chunk. list($sz, $rest) = explode("\r\n", $body, 2); $sz = preg_replace('/;.*$/', '', $sz); $sz = hexdec($sz); // Size 0 indicates end of body data. if ($sz == 0) { break; } // Add the chunk to the unchunked body data. $unchunked .= substr($rest, 0, $sz); $body = substr($rest, $sz + 2); // +2 = skip \r\n } // Return the unchunked body content. return $unchunked; } // Return the body content. return $body; } // Analyze the result data to see what we should to next. $res = phorum_api_http_get_analyze($work_url, $code, $header); // An error was returned. Bail out. if (isset($res['error'])) { $fatal = empty($res['fatal']) ? 0 : 1; $error = $res['error']; break; } // A redirect was returned. if (isset($res['redirect'])) { $work_url = $res['redirect']; continue; } // Shouldn't get here. trigger_error('phorum_api_http_get_analyze() returned an ' . 'unexpected result.', E_USER_ERROR); } } // Return fatal errors. For non fatal errors, we fall through // to the next method. if ($error !== NULL && $fatal) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, $error); } // ----------------------------------------------------------------- // Try to use file_get_contents // ----------------------------------------------------------------- if (($method === NULL || $method == 'fopen') && ini_get('allow_url_fopen')) { $method = NULL; $track = ini_get('track_errors'); ini_set('track_errors', TRUE); $php_errormsg = ''; $contents = @file_get_contents($url); ini_set('track_errors', $track); if ($contents === FALSE || $php_errormsg != '') { $error = preg_replace('/(^.*?\\:\\s+|[\\r\\n])/', '', $php_errormsg); $error = "[{$error}]"; } else { return $contents; } } // Return errors. if ($error !== NULL) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, $error); } // Catch illegal methods if ($method !== NULL) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, 'Illegal method: ' . $method); } return phorum_api_error_set(PHORUM_ERRNO_ERROR, 'No working HTTP request method found'); }
/** * Retrieve a Phorum file. * * This function can handle Phorum file retrieval in multiple ways: * either return the file to the caller or send it directly to the user's * browser (based on the $flags parameter). Sending it directly to the * browser allows for the implementation of modules that don't have to buffer * the full file data before sending it (a.k.a. streaming, which provides the * advantage of using less memory for sending files). * * @param mixed $file * This is either an array containing at least the fields "file_id" * and "filename" or a numerical file_id value. Note that you can * use the return value of the function * {@link phorum_api_file_check_read_access()} as input for this function. * * @param integer $flags * These are flags which influence aspects of the function call. It is * a bitflag value, so you can OR multiple flags together. Available * flags for this function are: {@link PHORUM_FLAG_IGNORE_PERMS}, * {@link PHORUM_FLAG_GET}, {@link PHORUM_FLAG_SEND} and * {@link PHORUM_FLAG_FORCE_DOWNLOAD}. The SEND flag has precedence * over the GET flag. * * @return mixed * On error, this function will return FALSE. * The functions {@link phorum_api_strerror()} and * {@link phorum_api_errno()} can be used to retrieve information about * the error which occurred. * * If the {@link PHORUM_FLAG_SEND} flag is used, then the function will * return NULL. * * If the {@link PHORUM_FLAG_GET} flag is used, then the function * will return a file description array, containing the fields "file_id", * "username", "file_data", "mime_type". * If the {@link $file} parameter was an array, then all fields from that * array will be included as well. */ function phorum_api_file_retrieve($file, $flags = PHORUM_FLAG_GET) { $PHORUM = $GLOBALS["PHORUM"]; // Reset error storage. $GLOBALS["PHORUM"]["API"]["errno"] = NULL; $GLOBALS["PHORUM"]["API"]["error"] = NULL; // If $file is not an array, we are handling a numerical file_id. // In that case, first retrieve the file data through the access check // function. All the function flags are passed on to that function, // so the PHORUM_FLAG_IGNORE_PERMS flag can be set for ignoring access // permissions. if (!is_array($file)) { $file_id = (int) $file; $file = phorum_api_file_check_read_access($file_id, $flags); // Return in case of errors. if ($file === FALSE) { return FALSE; } } // A small basic check to see if we have a proper $file array. if (!isset($file["file_id"])) { trigger_error("phorum_api_file_get(): \$file parameter needs a \"file_id\" field.", E_USER_ERROR); } if (!isset($file["filename"])) { trigger_error("phorum_api_file_get(): \$file parameter needs a \"filename\" field.", E_USER_ERROR); } settype($file["file_id"], "int"); /* * [hook] * file_retrieve * * [description] * This hook allows modules to handle the file data retrieval. * The hook can use <literal>phorum_api_error_set()</literal> * to return an error. Hooks should be aware that their input might * not be <literal>$file</literal>, but <literal>FALSE</literal> * instead, in which case they should immediately return * <literal>FALSE</literal> themselves. * * [category] * File storage * * [when] * In <filename>include/api/file_storage.php</filename>, * right before a file attachment is retrieved from the database. * * [input] * Two part array where the first element is an empty file array * and the second element is the flags variable. * * [output] * Same as input with file_data filled in. */ $file["result"] = 0; $file["mime_type"] = NULL; $file["file_data"] = NULL; if (isset($PHORUM["hooks"]["file_retrieve"])) { list($file, $flags) = phorum_hook("file_retrieve", array($file, $flags)); if ($file === FALSE) { return FALSE; } // If a module sent the file data to the browser, then we are done. if ($file["result"] == PHORUM_FLAG_SEND) { return NULL; } } // If no module handled file retrieval, we will retrieve the // file from the Phorum database. if ($file["file_data"] === NULL) { $dbfile = phorum_db_file_get($file["file_id"], TRUE); if (empty($dbfile)) { return phorum_api_error_set(PHORUM_ERRNO_NOTFOUND, "Phorum file (id {$file["file_id"]}) could not be " . "retrieved from the database."); } // Phorum stores the files in base64 format in the database, to // prevent problems with dumping and restoring databases. $file["file_data"] = base64_decode($dbfile["file_data"]); } $mime_type_verified = FALSE; // Set the MIME type information if it was not set by a module. if ($file["mime_type"] === NULL) { $extension_mime_type = phorum_api_file_get_mimetype($file["filename"]); // mime magic file in case its needed if (!empty($PHORUM['mime_magic_file'])) { $mime_magic_file = $PHORUM['mime_magic_file']; } else { $mime_magic_file = NULL; } // retrieve the mime-type using the fileinfo extension if its available and enabled if (function_exists("finfo_open") && (!isset($PHORUM['file_fileinfo_ext']) || !empty($PHORUM['file_fileinfo_ext'])) && ($finfo = @finfo_open(FILEINFO_MIME, $mime_magic_file))) { $file["mime_type"] = finfo_buffer($finfo, $file['file_data']); finfo_close($finfo); if ($file["mime_type"] === FALSE) { return phorum_api_error_set(PHORUM_ERRNO_ERROR, "The mime-type of file {$file["file_id"]} couldn't be determined through the" . "fileinfo-extension"); } // extension mime-type doesn't fit the signature mime-type // make it a download then if ($extension_mime_type != $file["mime_type"]) { $flags = $flags | PHORUM_FLAG_FORCE_DOWNLOAD; } $mime_type_verified = TRUE; } else { $file["mime_type"] = $extension_mime_type; } } // If the file is not requested for downloading, then check if it is // safe for the browser to view this file. If it is not, then // enable the force download flag to make sure that the browser will // download the file. $safe_to_cache = TRUE; $safe_to_view = TRUE; if (!($flags & PHORUM_FLAG_FORCE_DOWNLOAD) && !$mime_type_verified) { list($safe_to_view, $safe_to_cache) = phorum_api_file_safe_to_view($file); if (!$safe_to_view) { $flags = $flags | PHORUM_FLAG_FORCE_DOWNLOAD; } } // Allow for post processing on the retrieved file. list($file, $flags) = phorum_hook("file_after_retrieve", array($file, $flags)); // In "send" mode, we directly send the file contents to the browser. if ($flags & PHORUM_FLAG_SEND) { // Get rid of any buffered output so far. phorum_ob_clean(); // Avoid using any output compression or handling on the sent data. ini_set("zlib.output_compression", "0"); ini_set("output_handler", ""); $time = (int) $file['add_datetime']; // Handle client side caching. if ($safe_to_cache) { if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { $header = preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']); $modified_since = strtotime($header); if ($modified_since >= $time) { $proto = empty($_SERVER['SERVER_PROTOCOL']) ? 'HTTP/1.0' : $_SERVER['SERVER_PROTOCOL']; header("{$proto} 304 Not Modified"); header('Status: 304'); exit(0); } } header("Last-Modified: " . gmdate('D, d M Y H:i:s \\G\\M\\T', $time)); header('Cache-Control: max-age=5184000'); // 60 days header('Expires: ' . gmdate('D, d M Y H:i:s \\G\\M\\T', time() + 5184000)); } else { // Expire in the past. header('Expires: ' . gmdate('D, d M Y H:i:s \\G\\M\\T', time() - 99999)); // Always modified. header('Last-Modified: ' . gmdate('D, d M Y H:i:s \\G\\M\\T', time())); // HTTP/1.1 header('cache-Control: no-store, no-cache, must-revalidate'); header('cache-Control: post-check=0, pre-check=0', FALSE); // HTTP/1.0 header('Pragma: no-cache'); } if ($flags & PHORUM_FLAG_FORCE_DOWNLOAD) { header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename=\"{$file["filename"]}\""); } else { header("Content-Type: " . $file["mime_type"]); header("Content-Disposition: filename=\"{$file["filename"]}\""); } header('Content-Length: ' . strlen($file['file_data'])); print $file["file_data"]; return NULL; } elseif ($flags & PHORUM_FLAG_GET) { return $file; } else { trigger_error("phorum_api_file_retrieve(): no retrieve mode specified in the " . "flags (either use PHORUM_FLAG_GET or PHORUM_FLAG_SEND).", E_USER_ERROR); } }
/** * Restore a previously deleted custom profile field. * * If a profile field is deleted, it's settings and data are not deleted. * The field is only flagged as deleted. This function can be used for * reverting the delete action. * * @param int $id * The id of the custom profile field to restore. * * @return bool * TRUE if the restore was successfull or FALSE if there was an error. * The functions {@link phorum_api_strerror()} and * {@link phorum_api_errno()} can be used to retrieve information about * the error which occurred. */ function phorum_api_custom_profile_field_restore($id) { settype($id, "int"); if (isset($GLOBALS["PHORUM"]["PROFILE_FIELDS"][$id])) { $f = $GLOBALS["PHORUM"]["PROFILE_FIELDS"][$id]; if (isset($f['deleted']) && $f['deleted']) { $f['deleted'] = 0; } $GLOBALS["PHORUM"]["PROFILE_FIELDS"][$id] = $f; phorum_db_update_settings(array('PROFILE_FIELDS' => $GLOBALS["PHORUM"]['PROFILE_FIELDS'])); } else { return phorum_api_error_set(PHORUM_ERRNO_NOTFOUND, "Unable to restore custom profile field {$id}: " . "no configuration found."); } return TRUE; }