Exemple #1
0
/**
 * 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;
}
Exemple #2
0
/**
 * 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');
}
Exemple #3
0
/**
 * 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;
}
Exemple #4
0
/**
 * 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);
    }
}
Exemple #5
0
/**
 * 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');
}
Exemple #6
0
/**
 * 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;
}